Skip to content

Commit bc25035

Browse files
authored
Update interface, workflows (#68)
1 parent 68455e3 commit bc25035

20 files changed

+754
-628
lines changed

.github/workflows/pytest-builds.yml

+13-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: build
1+
name: unit-tests
22

33
on:
44
push:
@@ -12,7 +12,7 @@ jobs:
1212
strategy:
1313
fail-fast: false
1414
matrix:
15-
python-version: [3.6, 3.7, 3.8, 3.9]
15+
python-version: ['3.7', '3.8', '3.9', '3.10']
1616

1717
steps:
1818
- uses: actions/checkout@v2
@@ -25,11 +25,9 @@ jobs:
2525
- name: Install package and dependencies
2626
run: |
2727
python -m pip install -U pip
28-
python -m pip install wheel
29-
python -m pip install .
30-
python -m pip uninstall -y pylibjpeg-openjpeg
28+
python -m pip install -U wheel pytest coverage pytest-cov
3129
python -m pip install git+https://github.com/pydicom/pylibjpeg-data
32-
python -m pip install pytest coverage pytest-cov
30+
python -m pip install .
3331
3432
- name: Run pytest with no plugins
3533
run: |
@@ -44,7 +42,6 @@ jobs:
4442
run: |
4543
4644
pip install git+https://github.com/pydicom/pylibjpeg-libjpeg
47-
pip install git+https://github.com/pydicom/pylibjpeg-openjpeg
4845
pytest --cov=pylibjpeg --cov-append
4946
5047
- name: Rerun pytest with -oj plugin
@@ -53,15 +50,17 @@ jobs:
5350
pip install git+https://github.com/pydicom/pylibjpeg-openjpeg
5451
pytest --cov=pylibjpeg --cov-append
5552
56-
- name: Rerun pytest with -oj and -lj plugins
53+
- name: Rerun pytest with -rle plugin
5754
run: |
58-
pip install git+https://github.com/pydicom/pylibjpeg-libjpeg
55+
pip uninstall -y pylibjpeg-libjpeg pylibjpeg-openjpeg
56+
pip install git+https://github.com/pydicom/pylibjpeg-rle
57+
pytest --cov=pylibjpeg --cov-append
58+
59+
- name: Rerun pytest with all plugins
60+
run: |
61+
pip install .[all]
5962
pytest --cov=pylibjpeg --cov-append
6063
6164
- name: Send coverage results
6265
if: ${{ success() }}
63-
run: |
64-
bash <(curl --connect-timeout 10 --retry 10 --retry-max-time \
65-
0 https://codecov.io/bash) || (sleep 30 && bash <(curl \
66-
--connect-timeout 10 --retry 10 --retry-max-time \
67-
0 https://codecov.io/bash))
66+
uses: codecov/codecov-action@v1

.github/workflows/release-deploy.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Build package and deploy to PyPI
1+
name: release-deploy
22

33
on:
44
release:

README.md

+9-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
## pylibjpeg
77

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

1010

1111
### Installation
@@ -17,10 +17,16 @@ pip install pylibjpeg
1717

1818
##### Installing extra requirements
1919

20-
The package can be installed with extra requirements `openjpeg` or `rle` to enable support for JPEG-2000 and Run-Length Encoding (RLE), respectively:
20+
The package can be installed with extra requirements to enable support for JPEG (with `libjpeg`), JPEG 2000 (with `openjpeg`) and Run-Length Encoding (RLE) (with `rle`), respectively:
2121

2222
```
23-
pip install pylibjpeg[openjpeg,rle]
23+
pip install pylibjpeg[libjpeg,openjpeg,rle]
24+
```
25+
26+
Or alternatively with just `all`:
27+
28+
```
29+
pip install pylibjpeg[all]
2430
```
2531

2632
#### Installing the development version

docs/plugins.md

+47-7
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,43 @@ setup(
2424

2525
#### Decoder function signature
2626

27-
The pixel data decoding function will be passed two required parameters:
27+
The pixel data decoding function will be passed one required parameter:
2828

2929
* *src*: a single encoded image frame as [bytes](https://docs.python.org/3/library/stdtypes.html#bytes)
30+
31+
And at least one of:
3032
* *ds*: a *pydicom* [Dataset](https://pydicom.github.io/pydicom/stable/reference/generated/pydicom.dataset.Dataset.html) object containing the (0028,eeee) elements corresponding to the pixel data
33+
* *kwargs*: a dict with at least the following keys:
34+
* `"transfer_syntax_uid": pydicom.uid.UID` - the *Transfer Syntax UID* of
35+
the encoded data.
36+
* `'rows': int` - the number of rows of pixels in the *src*.
37+
* `'columns': int` - the number of columns of pixels in the
38+
*src*.
39+
* `'samples_per_pixel': int` - the number of samples used per
40+
pixel, e.g. `1` for grayscale images or `3` for RGB.
41+
* `'bits_allocated': int` - the number of bits used to contain
42+
each pixel in *src*, should be 8, 16, 32 or 64.
43+
* `'bits_stored': int` - the number of bits actually used by
44+
each pixel in *src*.
45+
* `'bits_stored': int` - the number of bits actually used by
46+
each pixel in *src*, e.g. 12-bit pixel data (range 0 to 4095) will be
47+
contained by 16-bits (range 0 to 65535).
48+
* `'pixel_representation': int` - the type of data in *src*,
49+
`0` for unsigned integers, `1` for 2's complement (signed)
50+
integers.
51+
* `'photometric_interpretation': str` - the color space
52+
of the encoded data, such as `'YBR_FULL'`.
53+
54+
Other decoder-specific optional keyword parameters may also be present.
3155

3256
The function should return the decoded pixel data as a one-dimensional numpy [ndarray](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html) of little-endian ordered `'uint8'`, with the data ordered from left-to-right, top-to-bottom (i.e. the first byte corresponds to the upper left pixel and the last byte corresponds to the lower-right pixel) and a planar configuration that matches
3357
the requirements of the transfer syntax:
3458

3559
```python
3660
def my_pixel_data_decoder(
37-
src: bytes, ds: pydicom.dataset.Dataset, **kwargs
61+
src: bytes, ds: Optional[pydicom.dataset.Dataset] = None, **kwargs: Any
3862
) -> numpy.ndarray:
39-
"""Return the encoded `src` as an unshaped numpy ndarray of uint8.
63+
"""Return the encoded *src* as an unshaped numpy ndarray of uint8.
4064
4165
.. versionchanged:: 1.3
4266
@@ -46,11 +70,27 @@ def my_pixel_data_decoder(
4670
----------
4771
src : bytes
4872
A single frame of the encoded *Pixel Data*.
49-
ds : pydicom.dataset.Dataset
73+
ds : pydicom.dataset.Dataset, optional
5074
A dataset containing the group ``0x0028`` elements corresponding to
51-
the *Pixel Data*.
52-
kwargs
53-
Optional keyword parameters for the decoder.
75+
the *Pixel Data*. If not used then *kwargs* must be supplied.
76+
kwargs : Dict[str, Any]
77+
A dict containing relevant image pixel module elements:
78+
79+
* "rows": int - the number of rows of pixels in *src*, maximum 65535.
80+
* "columns": int - the number of columns of pixels in *src*, maximum
81+
65535.
82+
* "number_of_frames": int - the number of frames in *src*.
83+
* "samples_per_pixel": int - the number of samples per pixel in *src*,
84+
should be 1 or 3.
85+
* "bits_allocated": int - the number of bits used to contain each
86+
pixel, should be a multiple of 8.
87+
* "bits_stored": int - the number of bits actually used per pixel.
88+
* "pixel_representation": int - the type of data being decoded, 0 for
89+
unsigned, 1 for 2's complement (signed)
90+
* "photometric_interpretation": the color space of the *encoded* pixel
91+
data, such as "YBR_FULL".
92+
93+
And optional keyword parameters for the decoder.
5494
5595
Returns
5696
-------

pylibjpeg/__init__.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@
88

99

1010
# Setup default logging
11-
_logger = logging.getLogger('pylibjpeg')
11+
_logger = logging.getLogger("pylibjpeg")
1212
_logger.addHandler(logging.NullHandler())
1313
_logger.debug("pylibjpeg v{}".format(__version__))
1414

1515

1616
def debug_logger():
1717
"""Setup the logging for debugging."""
18-
logger = logging.getLogger('pylibjpeg')
18+
logger = logging.getLogger("pylibjpeg")
1919
logger.handlers = []
2020
handler = logging.StreamHandler()
2121
logger.setLevel(logging.DEBUG)
22-
formatter = logging.Formatter('%(levelname).1s: %(message)s')
22+
formatter = logging.Formatter("%(levelname).1s: %(message)s")
2323
handler.setFormatter(formatter)
2424
logger.addHandler(handler)

pylibjpeg/_version.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import re
44

55

6-
__version__ = '1.4.0'
6+
__version__ = "1.4.0"
77

88

99
VERSION_PATTERN = r"""
@@ -41,11 +41,13 @@
4141
def is_canonical(version):
4242
"""Return True if `version` is a PEP440 conformant version."""
4343
match = re.match(
44-
r'^([1-9]\d*!)?(0|[1-9]\d*)'
45-
r'(\.(0|[1-9]\d*))'
46-
r'*((a|b|rc)(0|[1-9]\d*))'
47-
r'?(\.post(0|[1-9]\d*))'
48-
r'?(\.dev(0|[1-9]\d*))?$', version)
44+
r"^([1-9]\d*!)?(0|[1-9]\d*)"
45+
r"(\.(0|[1-9]\d*))"
46+
r"*((a|b|rc)(0|[1-9]\d*))"
47+
r"?(\.post(0|[1-9]\d*))"
48+
r"?(\.dev(0|[1-9]\d*))?$",
49+
version,
50+
)
4951

5052
return match is not None
5153

pylibjpeg/pydicom/utils.py

+18-19
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@ def generate_frames(ds):
3030
try:
3131
import pydicom
3232
except ImportError:
33-
raise RuntimeError(
34-
"'generate_frames' requires the pydicom package"
35-
)
33+
raise RuntimeError("'generate_frames' requires the pydicom package")
3634

3735
from pydicom.encaps import generate_pixel_data_frame
3836
from pydicom.pixel_data_handlers.util import pixel_dtype
@@ -41,7 +39,7 @@ def generate_frames(ds):
4139
decode = decoders[ds.file_meta.TransferSyntaxUID]
4240

4341
p_interp = ds.PhotometricInterpretation
44-
nr_frames = getattr(ds, 'NumberOfFrames', 1)
42+
nr_frames = getattr(ds, "NumberOfFrames", 1)
4543
for frame in generate_pixel_data_frame(ds.PixelData, nr_frames):
4644
arr = decode(frame, ds.group_dataset(0x0028)).view(pixel_dtype(ds))
4745
yield reshape_frame(ds, arr)
@@ -121,16 +119,16 @@ def reshape_frame(ds, arr):
121119
"""
122120
# Transfer Syntax UIDs that are always Planar Configuration 0
123121
conf_zero = [
124-
'1.2.840.10008.1.2.4.50',
125-
'1.2.840.10008.1.2.4.57',
126-
'1.2.840.10008.1.2.4.70',
127-
'1.2.840.10008.1.2.4.90',
128-
'1.2.840.10008.1.2.4.91'
122+
"1.2.840.10008.1.2.4.50",
123+
"1.2.840.10008.1.2.4.57",
124+
"1.2.840.10008.1.2.4.70",
125+
"1.2.840.10008.1.2.4.90",
126+
"1.2.840.10008.1.2.4.91",
129127
]
130128
# Transfer Syntax UIDs that are always Planar Configuration 1
131129
conf_one = [
132-
'1.2.840.10008.1.2.4.80',
133-
'1.2.840.10008.1.2.4.81',
130+
"1.2.840.10008.1.2.4.80",
131+
"1.2.840.10008.1.2.4.81",
134132
]
135133

136134
# Valid values for Planar Configuration are dependent on transfer syntax
@@ -147,8 +145,9 @@ def reshape_frame(ds, arr):
147145
if planar_configuration not in [0, 1]:
148146
raise ValueError(
149147
"Unable to reshape the pixel array as a value of {} for "
150-
"(0028,0006) 'Planar Configuration' is invalid."
151-
.format(planar_configuration)
148+
"(0028,0006) 'Planar Configuration' is invalid.".format(
149+
planar_configuration
150+
)
152151
)
153152

154153
if nr_samples == 1:
@@ -187,22 +186,22 @@ def get_j2k_parameters(codestream):
187186
"""
188187
try:
189188
# First 2 bytes must be the SOC marker - if not then wrong format
190-
if codestream[0:2] != b'\xff\x4f':
189+
if codestream[0:2] != b"\xff\x4f":
191190
return {}
192191

193192
# SIZ is required to be the second marker - Figure A-3 in 15444-1
194-
if codestream[2:4] != b'\xff\x51':
193+
if codestream[2:4] != b"\xff\x51":
195194
return {}
196195

197196
# See 15444-1 A.5.1 for format of the SIZ box and contents
198197
ssiz = ord(codestream[42:43])
199198
parameters = {}
200199
if ssiz & 0x80:
201-
parameters['precision'] = (ssiz & 0x7F) + 1
202-
parameters['is_signed'] = True
200+
parameters["precision"] = (ssiz & 0x7F) + 1
201+
parameters["is_signed"] = True
203202
else:
204-
parameters['precision'] = ssiz + 1
205-
parameters['is_signed'] = False
203+
parameters["precision"] = ssiz + 1
204+
parameters["is_signed"] = False
206205

207206
return parameters
208207

pylibjpeg/tests/__init__.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
21
import logging
32
import sys
43

54
_logger = logging.getLogger(__name__)
65

76
try:
87
import ljdata as _data
9-
globals()['data'] = _data
8+
9+
globals()["data"] = _data
1010
# Add to cache - needed for pytest
11-
sys.modules['pylibjpeg.data'] = _data
12-
_logger.debug('pylibjpeg-data module loaded')
11+
sys.modules["pylibjpeg.data"] = _data
12+
_logger.debug("pylibjpeg-data module loaded")
1313
except ImportError:
1414
pass

0 commit comments

Comments
 (0)