Skip to content

Commit c0766f1

Browse files
authored
Refactor, add tests and support for pixel data interface v2 (#82)
1 parent 1b4fab8 commit c0766f1

File tree

14 files changed

+517
-212
lines changed

14 files changed

+517
-212
lines changed

.coveragerc

-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
[run]
22
omit =
33
pylibjpeg/tests/*
4-
pylibjpeg/scripts/*
54
pylibjpeg/tools/*
6-
pylibjpeg-data/*
7-
pylibjpeg-libjpeg/*
8-
pydicom/*

.github/workflows/release-deploy.yml

+15-4
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,18 @@ jobs:
3535
path: ./dist
3636

3737
- name: Publish package to PyPi
38-
uses: pypa/gh-action-pypi-publish@master
39-
with:
40-
user: __token__
41-
password: ${{ secrets.PYPI_PASSWORD }}
38+
environment:
39+
name: pypi
40+
url: https://pypi.org/project/pylibjpeg/
41+
permissions:
42+
id-token: write
43+
44+
steps:
45+
- name: Download the wheels
46+
uses: actions/download-artifact@v4
47+
with:
48+
path: dist/
49+
merge-multiple: true
50+
51+
- name: Publish package to PyPi
52+
uses: pypa/gh-action-pypi-publish@release/v1

.gitignore

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ __pycache__
3131
.pytest_cache
3232
*.egg-info
3333
build
34+
env*/
3435

3536
# Docs build
3637
docs/_build/*
@@ -45,15 +46,14 @@ doc/reference/generated/*
4546
# PyCharm IDE files
4647
*.idea*
4748

48-
49-
5049
# jupyter notebooks
5150
*.ipynb
5251
.ipynb_checkpoints/*
5352
tests/test_pixel.py
5453

5554
# mypy
56-
pydicom/.mypy_cache/*
55+
.mypy_cache/
56+
.ruff_cache/
5757

5858
# vscode
5959
.vscode/*

README.md

+31-38
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
[![codecov](https://codecov.io/gh/pydicom/pylibjpeg/branch/master/graph/badge.svg)](https://codecov.io/gh/pydicom/pylibjpeg)
2-
[![Build Status](https://github.com/pydicom/pylibjpeg/workflows/build/badge.svg)](https://github.com/pydicom/pylibjpeg/actions?query=workflow%3Abuild)
3-
[![PyPI version](https://badge.fury.io/py/pylibjpeg.svg)](https://badge.fury.io/py/pylibjpeg)
4-
[![Python versions](https://img.shields.io/pypi/pyversions/pylibjpeg.svg)](https://img.shields.io/pypi/pyversions/pylibjpeg.svg)
1+
<p align="center">
2+
<a href="https://github.com/pydicom/pylibjpeg/actions?query=workflow%3Aunit-tests"><img alt="Build status" src="https://github.com/pydicom/pylibjpeg/workflows/unit-tests/badge.svg"></a>
3+
<a href="https://codecov.io/gh/pydicom/pylibjpeg"><img alt="Test coverage" src="https://codecov.io/gh/pydicom/pylibjpeg/branch/main/graph/badge.svg"></a>
4+
<a href="https://pypi.org/project/pylibjpeg/"><img alt="PyPI versions" src="https://img.shields.io/pypi/v/pylibjpeg"></a>
5+
<a href="https://www.python.org/"><img alt="Python versions" src="https://img.shields.io/pypi/pyversions/pylibjpeg.svg"></a>
6+
<a href="https://github.com/psf/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
7+
</p>
58

69
## pylibjpeg
710

8-
A Python 3.10+ framework for decoding JPEG images and decoding/encoding RLE datasets, with a focus on providing support for [pydicom](https://github.com/pydicom/pydicom).
11+
A Python 3.8+ framework for decoding JPEG images and decoding/encoding RLE datasets, with a focus on providing support for [pydicom](https://github.com/pydicom/pydicom).
912

1013

1114
### Installation
@@ -42,26 +45,29 @@ python -m pip install pylibjpeg
4245
One or more plugins are required before *pylibjpeg* is able to handle JPEG images or RLE datasets. To handle a given format or DICOM Transfer Syntax
4346
you first have to install the corresponding package:
4447

45-
#### Supported Formats
46-
|Format |Decode?|Encode?|Plugin |Based on |
47-
|--- |------ |--- |--- |--- |
48-
|JPEG, JPEG-LS and JPEG XT|Yes |No |[pylibjpeg-libjpeg][1] |[libjpeg][2] |
49-
|JPEG 2000 |Yes |No |[pylibjpeg-openjpeg][3]|[openjpeg][4]|
50-
|RLE Lossless (PackBits) |Yes |Yes |[pylibjpeg-rle][5] |- |
51-
52-
#### DICOM Transfer Syntax
53-
54-
|UID | Description | Plugin |
55-
|--- |--- |---- |
56-
|1.2.840.10008.1.2.4.50|JPEG Baseline (Process 1) |[pylibjpeg-libjpeg][1] |
57-
|1.2.840.10008.1.2.4.51|JPEG Extended (Process 2 and 4) |[pylibjpeg-libjpeg][1] |
58-
|1.2.840.10008.1.2.4.57|JPEG Lossless, Non-Hierarchical (Process 14) |[pylibjpeg-libjpeg][1] |
59-
|1.2.840.10008.1.2.4.70|JPEG Lossless, Non-Hierarchical, First-Order Prediction</br>(Process 14, Selection Value 1) | [pylibjpeg-libjpeg][1]|
60-
|1.2.840.10008.1.2.4.80|JPEG-LS Lossless |[pylibjpeg-libjpeg][1] |
61-
|1.2.840.10008.1.2.4.81|JPEG-LS Lossy (Near-Lossless) Image Compression |[pylibjpeg-libjpeg][1] |
62-
|1.2.840.10008.1.2.4.90|JPEG 2000 Image Compression (Lossless Only) |[pylibjpeg-openjpeg][4]|
63-
|1.2.840.10008.1.2.4.91|JPEG 2000 Image Compression |[pylibjpeg-openjpeg][4]|
64-
|1.2.840.10008.1.2.5 |RLE Lossless |[pylibjpeg-rle][5] |
48+
#### Supported Image Formats
49+
|Format |Decode?|Encode?|Plugin | License |Based on |
50+
|--- |------ |--- |--- |--- |--- |
51+
|JPEG, JPEG-LS and JPEG XT|Yes |No |[pylibjpeg-libjpeg][1] | GPLv3 |[libjpeg][2] |
52+
|JPEG 2000 |Yes |No |[pylibjpeg-openjpeg][3]| MIT |[openjpeg][4]|
53+
|RLE Lossless (PackBits) |Yes |Yes |[pylibjpeg-rle][5] | MIT |- |
54+
55+
#### Supported DICOM Transfer Syntaxes
56+
57+
|UID | Description | Plugin |
58+
|--- |--- |---- |
59+
|1.2.840.10008.1.2.4.50 |JPEG Baseline (Process 1) |[pylibjpeg-libjpeg][1] |
60+
|1.2.840.10008.1.2.4.51 |JPEG Extended (Process 2 and 4) |[pylibjpeg-libjpeg][1] |
61+
|1.2.840.10008.1.2.4.57 |JPEG Lossless, Non-Hierarchical (Process 14) |[pylibjpeg-libjpeg][1] |
62+
|1.2.840.10008.1.2.4.70 |JPEG Lossless, Non-Hierarchical, First-Order Prediction</br>(Process 14, Selection Value 1) | [pylibjpeg-libjpeg][1]|
63+
|1.2.840.10008.1.2.4.80 |JPEG-LS Lossless |[pylibjpeg-libjpeg][1] |
64+
|1.2.840.10008.1.2.4.81 |JPEG-LS Lossy (Near-Lossless) Image Compression |[pylibjpeg-libjpeg][1] |
65+
|1.2.840.10008.1.2.4.90 |JPEG 2000 Image Compression (Lossless Only) |[pylibjpeg-openjpeg][3]|
66+
|1.2.840.10008.1.2.4.91 |JPEG 2000 Image Compression |[pylibjpeg-openjpeg][3]|
67+
|1.2.840.10008.1.2.4.201|High-Throughput JPEG 2000 Image Compression (Lossless Only) |[pylibjpeg-openjpeg][3]|
68+
|1.2.840.10008.1.2.4.202|High-Throughput JPEG 2000 with RPCL Options Image Compression (Lossless Only) |[pylibjpeg-openjpeg][3]|
69+
|1.2.840.10008.1.2.4.203|High-Throughput JPEG 2000 Image Compression |[pylibjpeg-openjpeg][3]|
70+
|1.2.840.10008.1.2.5 |RLE Lossless |[pylibjpeg-rle][5] |
6571

6672
If you're not sure what the dataset's *Transfer Syntax UID* is, it can be
6773
determined with:
@@ -103,19 +109,6 @@ ds.decompress("pylibjpeg")
103109
rle_arr = ds.pixel_array
104110
```
105111

106-
For datasets with multiple frames you can reduce your memory usage by
107-
processing each frame separately using the ``generate_frames()`` generator
108-
function:
109-
```python
110-
from pydicom import dcmread
111-
from pydicom.data import get_testdata_file
112-
from pydicom.pixel_data_handlers.pylibjpeg_handler import generate_frames
113-
114-
ds = dcmread(get_testdata_file('color3d_jpeg_baseline.dcm'))
115-
frames = generate_frames(ds)
116-
arr = next(frames)
117-
```
118-
119112
##### Standalone JPEG decoding
120113
You can also just use *pylibjpeg* to decode JPEG images to a [numpy ndarray](https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html), provided you have a suitable plugin installed:
121114
```python

codecov.yml

-4
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,4 @@ coverage:
1313

1414
ignore:
1515
- "pylibjpeg/tests"
16-
- "pylibjpeg/scripts"
1716
- "pylibjpeg/tools"
18-
- "pylibjpeg-libjpeg"
19-
- "pylibjpeg-data"
20-
- "pydicom"

docs/plugins.md

+21-6
Original file line numberDiff line numberDiff line change
@@ -58,21 +58,34 @@ the requirements of the transfer syntax:
5858

5959
```python
6060
def my_pixel_data_decoder(
61-
src: bytes, ds: Optional[pydicom.dataset.Dataset] = None, **kwargs: Any
62-
) -> numpy.ndarray:
61+
src: bytes,
62+
ds: pydicom.dataset.Dataset | None = None,
63+
version: int = 1,
64+
**kwargs: Any,
65+
) -> numpy.ndarray | bytearray:
6366
"""Return the encoded *src* as an unshaped numpy ndarray of uint8.
6467
65-
.. versionchanged:: 1.3
68+
.. versionchanged: 1.3
6669
6770
Added requirement to return little-endian ordered data by default.
6871
72+
.. versionchanged: 2.0
73+
74+
Added `version` keyword argument and support for returning :class:`bytearray`
75+
6976
Parameters
7077
----------
7178
src : bytes
7279
A single frame of the encoded *Pixel Data*.
7380
ds : pydicom.dataset.Dataset, optional
7481
A dataset containing the group ``0x0028`` elements corresponding to
7582
the *Pixel Data*. If not used then *kwargs* must be supplied.
83+
version : int, optional
84+
85+
* If ``1`` (default) then either supplying either `ds` or `kwargs` is
86+
required and the return type is a :class:`~numpy.ndarray`
87+
* If ``2`` then `ds` will be ignored, `kwargs` is required and the return
88+
type is :class:`bytearray`
7689
kwargs : Dict[str, Any]
7790
A dict containing relevant image pixel module elements:
7891
@@ -94,8 +107,10 @@ def my_pixel_data_decoder(
94107
95108
Returns
96109
-------
97-
numpy.ndarray
98-
A 1-dimensional ndarray of 'uint8' containing the little-endian ordered decoded pixel data.
110+
numpy.ndarray | bytearray
111+
Either a 1-dimensional ndarray of 'uint8' or a bytearray containing the
112+
little-endian ordered decoded pixel data, depending on the value of
113+
`version`.
99114
"""
100115
# Decoding happens here
101116
```
@@ -206,7 +221,7 @@ The pixel data encoding function will be passed two required parameters:
206221
The function should return the encoded pixel data as `bytes`.
207222

208223
```python
209-
def my_pixel_data_encoder(src: bytes, **kwargs) -> bytes:
224+
def my_pixel_data_encoder(src: bytes, **kwargs: Any) -> bytes:
210225
"""Return `src` as encoded bytes.
211226
212227
Parameters

docs/release_notes/v2.0.0.rst

+2
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@
77
* Switched to a ``pyproject.toml`` based project
88
* Removed ``pydicom`` module
99
* Supported Python versions are 3.8, 3.9, 3.10, 3.11 and 3.12
10+
* Added type hints
11+
* Add support for version 2 of the pixel data interface

pylibjpeg/tests/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import logging
22
import sys
33

4-
_logger = logging.getLogger(__name__)
4+
LOGGER = logging.getLogger(__name__)
55

66
try:
77
import ljdata as _data
88

99
globals()["data"] = _data
1010
# Add to cache - needed for pytest
1111
sys.modules["pylibjpeg.data"] = _data
12-
_logger.debug("pylibjpeg-data module loaded")
12+
LOGGER.debug("pylibjpeg-data module loaded")
1313
except ImportError:
1414
pass

0 commit comments

Comments
 (0)