Skip to content

Commit 16dfb13

Browse files
authored
Add typing (#69)
1 parent 7624222 commit 16dfb13

File tree

8 files changed

+305
-211
lines changed

8 files changed

+305
-211
lines changed

.github/workflows/pytest-builds.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
python -m pip install -U pip
3232
python -m pip install -U pytest coverage pytest-cov
3333
python -m pip install git+https://github.com/pydicom/pylibjpeg-data
34-
python -m pip install .
34+
python -m pip install . -vv
3535
3636
- name: Run pytest
3737
run: |

docs/changes/v2.0.0.rst

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.. _v2.0.0:
2+
3+
2.0.0
4+
=====
5+
6+
Changes
7+
.......
8+
9+
* OpenJPEG version updated to v2.5.0
10+
* Supported Python versions are 3.8, 3.9, 3.10, 3.11 and 3.12
11+
* Added type hints

openjpeg/_openjpeg.c

+175-165
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

openjpeg/py.typed

Whitespace-only changes.

openjpeg/tests/test_decode.py

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ def test_version():
6464
assert isinstance(version[0], int)
6565
assert 3 == len(version)
6666
assert 2 == version[0]
67+
assert 5 == version[1]
6768

6869

6970
def generate_frames(ds):

openjpeg/utils.py

+71-40
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
11

22
from io import BytesIO
33
from math import ceil
4+
import os
45
from pathlib import Path
6+
from typing import BinaryIO, Tuple, Union, TYPE_CHECKING, Any, Dict, cast
57
import warnings
68

9+
import numpy as np
10+
711
import _openjpeg
812

913

10-
def _get_format(stream):
14+
if TYPE_CHECKING: # pragma: no cover
15+
from pydicom.dataset import Dataset
16+
17+
18+
def _get_format(stream: BinaryIO) -> int:
1119
"""Return the JPEG 2000 format for the encoded data in `stream`.
1220
1321
Parameters
1422
----------
15-
stream : bytes or file-like
23+
stream : file-like
1624
A Python object containing the encoded JPEG 2000 data. If not
1725
:class:`bytes` then the object must have ``tell()``, ``seek()`` and
1826
``read()`` methods.
@@ -56,13 +64,17 @@ def _get_format(stream):
5664
raise ValueError("No matching JPEG 2000 format found")
5765

5866

59-
def get_openjpeg_version():
67+
def get_openjpeg_version() -> Tuple[int, ...]:
6068
"""Return the openjpeg version as tuple of int."""
6169
version = _openjpeg.get_version().decode("ascii").split(".")
6270
return tuple([int(ii) for ii in version])
6371

6472

65-
def decode(stream, j2k_format=None, reshape=True):
73+
def decode(
74+
stream: Union[str, os.PathLike, bytes, bytearray, BinaryIO],
75+
j2k_format: Union[int, None] = None,
76+
reshape: bool = True,
77+
) -> np.ndarray:
6678
"""Return the decoded JPEG2000 data from `stream` as a
6779
:class:`numpy.ndarray`.
6880
@@ -98,42 +110,53 @@ def decode(stream, j2k_format=None, reshape=True):
98110
"""
99111
if isinstance(stream, (str, Path)):
100112
with open(stream, 'rb') as f:
101-
stream = f.read()
102-
103-
if isinstance(stream, (bytes, bytearray)):
104-
stream = BytesIO(stream)
105-
106-
required_methods = ["read", "tell", "seek"]
107-
if not all([hasattr(stream, meth) for meth in required_methods]):
108-
raise TypeError(
109-
"The Python object containing the encoded JPEG 2000 data must "
110-
"either be bytes or have read(), tell() and seek() methods."
111-
)
113+
buffer: BinaryIO = BytesIO(f.read())
114+
buffer.seek(0)
115+
elif isinstance(stream, (bytes, bytearray)):
116+
buffer = BytesIO(stream)
117+
else:
118+
# BinaryIO
119+
required_methods = ["read", "tell", "seek"]
120+
if not all([hasattr(stream, meth) for meth in required_methods]):
121+
raise TypeError(
122+
"The Python object containing the encoded JPEG 2000 data must "
123+
"either be bytes or have read(), tell() and seek() methods."
124+
)
125+
buffer = stream
112126

113127
if j2k_format is None:
114-
j2k_format = _get_format(stream)
128+
j2k_format = _get_format(buffer)
115129

116130
if j2k_format not in [0, 1, 2]:
117131
raise ValueError(f"Unsupported 'j2k_format' value: {j2k_format}")
118132

119-
arr = _openjpeg.decode(stream, j2k_format)
133+
arr = cast(np.ndarray, _openjpeg.decode(buffer, j2k_format))
120134
if not reshape:
121135
return arr
122136

123-
meta = get_parameters(stream, j2k_format)
124-
bpp = ceil(meta["precision"] / 8)
137+
meta = get_parameters(buffer, j2k_format)
138+
precision = cast(int, meta["precision"])
139+
rows = cast(int, meta["rows"])
140+
columns = cast(int, meta["columns"])
141+
pixels_per_sample = cast(int, meta["nr_components"])
142+
pixel_representation = cast(bool, meta["is_signed"])
143+
bpp = ceil(precision / 8)
125144

126-
dtype = f"uint{8 * bpp}" if not meta["is_signed"] else f"int{8 * bpp}"
145+
dtype = f"u{bpp}" if not pixel_representation else f"i{bpp}"
127146
arr = arr.view(dtype)
128147

129-
shape = [meta["rows"], meta["columns"]]
130-
if meta["nr_components"] > 1:
131-
shape.append(meta["nr_components"])
148+
shape = [rows, columns]
149+
if pixels_per_sample> 1:
150+
shape.append(pixels_per_sample)
132151

133152
return arr.reshape(*shape)
134153

135154

136-
def decode_pixel_data(stream, ds=None, **kwargs):
155+
def decode_pixel_data(
156+
stream: Union[bytes, bytearray, BinaryIO],
157+
ds: "Dataset" = None,
158+
**kwargs: Any
159+
) -> np.ndarray:
137160
"""Return the decoded JPEG 2000 data as a :class:`numpy.ndarray`.
138161
139162
Intended for use with *pydicom* ``Dataset`` objects.
@@ -189,7 +212,7 @@ def decode_pixel_data(stream, ds=None, **kwargs):
189212
)
190213

191214
if not ds and no_kwargs:
192-
return arr
215+
return cast(np.ndarray, arr)
193216

194217
samples_per_pixel = ds.get("SamplesPerPixel", samples_per_pixel)
195218
bits_stored = ds.get("BitsStored", bits_stored)
@@ -224,10 +247,13 @@ def decode_pixel_data(stream, ds=None, **kwargs):
224247
f"JPEG 2000 data '{val}'"
225248
)
226249

227-
return arr
250+
return cast(np.ndarray, arr)
228251

229252

230-
def get_parameters(stream, j2k_format=None):
253+
def get_parameters(
254+
stream: Union[str, os.PathLike, bytes, bytearray, BinaryIO],
255+
j2k_format: Union[int, None] = None,
256+
) -> Dict[str, Union[int, str, bool]]:
231257
"""Return a :class:`dict` containing the JPEG2000 image parameters.
232258
233259
.. versionchanged:: 1.1
@@ -263,22 +289,27 @@ def get_parameters(stream, j2k_format=None):
263289
"""
264290
if isinstance(stream, (str, Path)):
265291
with open(stream, 'rb') as f:
266-
stream = f.read()
267-
268-
if isinstance(stream, (bytes, bytearray)):
269-
stream = BytesIO(stream)
270-
271-
required_methods = ["read", "tell", "seek"]
272-
if not all([hasattr(stream, func) for func in required_methods]):
273-
raise TypeError(
274-
"The Python object containing the encoded JPEG 2000 data must "
275-
"either be bytes or have read(), tell() and seek() methods."
276-
)
292+
buffer: BinaryIO = BytesIO(f.read())
293+
buffer.seek(0)
294+
elif isinstance(stream, (bytes, bytearray)):
295+
buffer = BytesIO(stream)
296+
else:
297+
# BinaryIO
298+
required_methods = ["read", "tell", "seek"]
299+
if not all([hasattr(stream, meth) for meth in required_methods]):
300+
raise TypeError(
301+
"The Python object containing the encoded JPEG 2000 data must "
302+
"either be bytes or have read(), tell() and seek() methods."
303+
)
304+
buffer = stream
277305

278306
if j2k_format is None:
279-
j2k_format = _get_format(stream)
307+
j2k_format = _get_format(buffer)
280308

281309
if j2k_format not in [0, 1, 2]:
282310
raise ValueError(f"Unsupported 'j2k_format' value: {j2k_format}")
283311

284-
return _openjpeg.get_parameters(stream, j2k_format)
312+
return cast(
313+
Dict[str, Union[str, int, bool]],
314+
_openjpeg.get_parameters(buffer, j2k_format),
315+
)

poetry.lock

+42-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+4-2
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,13 @@ coverage = { version = "^7.3", optional = true }
6565
mypy = { version = "^1.7", optional = true }
6666
pytest = { version = "^7.4", optional = true }
6767
pytest-cov = { version = "^4.1", optional = true }
68-
pylibjpeg-data = { git = "https://github.com/pydicom/pylibjpeg-data.git"}
68+
pydicom = { version = "^2.4", optional = true }
69+
pylibjpeg = { git = "https://github.com/pydicom/pylibjpeg.git", optional = true}
70+
pylibjpeg-data = { git = "https://github.com/pydicom/pylibjpeg-data.git", optional = true}
6971

7072
[tool.poetry.extras]
7173
dev = ["black", "coverage", "mypy", "pytest", "pytest-cov"]
72-
tests = ["coverage", "pytest", "pytest-cov", "pylibjpeg-data"]
74+
tests = ["coverage", "pytest", "pytest-cov", "pylibjpeg-data", "pylibjpeg", "pydicom"]
7375

7476
[tool.poetry.plugins."pylibjpeg.jpeg_2000_decoders"]
7577
openjpeg = "openjpeg:decode"

0 commit comments

Comments
 (0)