Skip to content

Commit 91ec45e

Browse files
authored
Improve interface for DICOM decoding (#6)
1 parent 2903c86 commit 91ec45e

7 files changed

Lines changed: 115 additions & 39 deletions

File tree

README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ plugin for [pylibjpeg](http://github.com/pydicom/pylibjpeg).
1010
Linux, OSX and Windows are all supported.
1111

1212
### Installation
13+
#### Dependencies
14+
[NumPy](http://numpy.org)
15+
16+
#### Installing the current release
17+
```bash
18+
pip install pylibjpeg-libjpeg
19+
```
1320
#### Installing the development version
1421

1522
Make sure [Python](https://www.python.org/) and [Git](https://git-scm.com/) are installed. For Windows, you also need to install
@@ -20,15 +27,19 @@ python -m pip install pylibjpeg-libjpeg
2027
```
2128

2229
### Supported JPEG Formats
30+
#### Decoding
2331

2432
| ISO/IEC Standard | ITU Equivalent | JPEG Format |
2533
| --- | --- | --- |
2634
| [10918](https://www.iso.org/standard/18902.html) | [T.81](https://www.itu.int/rec/T-REC-T.81/en) | [JPEG](https://jpeg.org/jpeg/index.html) |
2735
| [14495](https://www.iso.org/standard/22397.html) | [T.87](https://www.itu.int/rec/T-REC-T.87/en) | [JPEG-LS](https://jpeg.org/jpegls/index.html) |
2836
| [18477](https://www.iso.org/standard/62552.html) | | [JPEG XT](https://jpeg.org/jpegxt/) |
2937

30-
### Supported Transfer Syntaxes
38+
#### Encoding
39+
Encoding of JPEG images is not currently supported
3140

41+
### Supported Transfer Syntaxes
42+
#### Decoding
3243
| UID | Description |
3344
| --- | --- |
3445
| 1.2.840.10008.1.2.4.50 | JPEG Baseline (Process 1) |
@@ -40,7 +51,6 @@ python -m pip install pylibjpeg-libjpeg
4051

4152
### Usage
4253
#### With pylibjpeg and pydicom
43-
Assuming you already have *pylibjpeg* and *pydicom* installed:
4454

4555
```python
4656
from pydicom import dcmread

libjpeg/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
import sys
44

5+
from ._config import DICOM_ENCODERS, DICOM_DECODERS
56
from ._version import __version__
6-
from .utils import decode, get_parameters
7+
from .utils import decode, decode_pixel_data, get_parameters
78

89

910
# Add the testing data to libjpeg (if available)

libjpeg/_config.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
from libjpeg.utils import decode_pixel_data
3+
4+
5+
DICOM_DECODERS = {
6+
# JPEG Baseline (Process 1)
7+
'1.2.840.10008.1.2.4.50' : decode_pixel_data,
8+
# JPEG Extended (Processes 2 and 4)
9+
'1.2.840.10008.1.2.4.51' : decode_pixel_data,
10+
# JPEG Lossless (Process 14)
11+
'1.2.840.10008.1.2.4.57' : decode_pixel_data,
12+
# JPEG Lossless (Process 14, Selection Value 1)
13+
'1.2.840.10008.1.2.4.70' : decode_pixel_data,
14+
# JPEG-LS Lossless
15+
'1.2.840.10008.1.2.4.80' : decode_pixel_data,
16+
# JPEG-LS Near Lossless
17+
'1.2.840.10008.1.2.4.81' : decode_pixel_data,
18+
}
19+
"""A :class:`dict` of the DICOM (0002,0010) *Transfer Syntax UID* values
20+
that specify the encoding of the (7FE0,0010) *Pixel Data* that can be decoded
21+
using this library. The format of the ``dict`` is ``{UID: callable}``, where
22+
`callable` the function to use for decoding as
23+
``callable(arr, photometric_interpretation)``.
24+
"""
25+
26+
27+
DICOM_ENCODERS = {}
28+
"""A :class:`dict` of the DICOM (0002,0010) *Transfer Syntax UID* values
29+
that specify the encoding of the (7FEO,0010) *Pixel Data* to be encoded using
30+
this library. The format of the ``dict`` is ``{UID: callable}``, where
31+
`callable` the function to use for encoding as ``callable(arr)``.
32+
"""

libjpeg/_version.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Version information for pynetdicom based on PEP396 and 440."""
1+
"""Version information based on PEP396 and 440."""
22

33
import re
44

@@ -41,11 +41,15 @@
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+
(
45+
r'^([1-9]\d*!)?(0|[1-9]\d*)'
46+
r'(\.(0|[1-9]\d*))'
47+
r'*((a|b|rc)(0|[1-9]\d*))'
48+
r'?(\.post(0|[1-9]\d*))'
49+
r'?(\.dev(0|[1-9]\d*))?$'
50+
),
51+
version
52+
)
4953

5054
return match is not None
5155

libjpeg/tests/pydicom_handler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
JPEGLSLossy,
2626
)
2727

28-
from libjpeg import decode
28+
from libjpeg import decode_pixel_data
2929

3030

3131
HANDLER_NAME = 'libjpeg-test'
@@ -138,6 +138,6 @@ def get_pixeldata(ds):
138138
for frame, offset in zip(generate_frames, generate_offsets):
139139
# Encoded JPG data to be sent to the decoder
140140
frame = np.frombuffer(frame, np.uint8)
141-
arr[offset:offset + frame_len] = decode(frame, p_interp, reshape=False)
141+
arr[offset:offset + frame_len] = decode_pixel_data(frame, p_interp)
142142

143143
return arr.view(pixel_dtype(ds))

libjpeg/tests/test_decode.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010
try:
1111
import pydicom
1212
from pydicom.encaps import generate_pixel_data_frame
13+
from pydicom.pixel_data_handlers.util import reshape_pixel_array
1314
from . import handler
1415
HAS_PYDICOM = True
1516
except ImportError:
1617
HAS_PYDICOM = False
1718

1819
from . import add_handler, remove_handler
19-
from libjpeg import decode
20+
from libjpeg import decode, decode_pixel_data
2021
from libjpeg.data import get_indexed_datasets, JPEG_DIRECTORY
2122

2223

@@ -100,11 +101,11 @@ def test_invalid_colourspace_warns():
100101
ds = index['JPEGBaseline_1s_1f_u_08_08.dcm']['ds']
101102
nr_frames = ds.get('NumberOfFrames', 1)
102103
frame = next(generate_pixel_data_frame(ds.PixelData, nr_frames))
103-
msg = (
104-
r""
105-
)
104+
msg = r"no colour transformation will be applied"
106105
with pytest.warns(UserWarning, match=msg):
107-
arr = decode(np.frombuffer(frame, 'uint8'), colourspace='ANY')
106+
arr = decode_pixel_data(np.frombuffer(frame, 'uint8'), 'ANY')
107+
108+
arr = reshape_pixel_array(ds, arr)
108109

109110
assert arr.flags.writeable
110111
assert 'uint8' == arr.dtype

libjpeg/utils.py

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,20 @@
5252
}
5353

5454

55-
def decode(arr, colourspace='YBR_FULL', reshape=True):
55+
def decode(arr, colour_transform=0, reshape=True):
5656
"""Return the decoded JPEG data from `arr` as a :class:`numpy.ndarray`.
5757
5858
Parameters
5959
----------
6060
arr : numpy.ndarray or bytes
6161
A 1D array of ``np.uint8``, or a Python :class:`bytes` object
6262
containing the encoded JPEG image.
63-
colourspace : str, optional
64-
One of ``'MONOCHROME1'``, ``'MONOCHROME2'``, ``'RGB'``, ``'YBR_FULL'``,
65-
``'YBR_FULL_422'``.
63+
colour_transform : int, optional
64+
The colour transform used, one of:
65+
| ``0`` : No transform applied (default)
66+
| ``1`` : RGB to YCbCr
67+
| ``2`` : JPEG-LS pseudo RCT or RCT
68+
| ``3`` : Freeform
6669
reshape : bool, optional
6770
Reshape and review the output array so it matches the image data
6871
(default), otherwise return a 1D array of ``np.uint8``.
@@ -77,28 +80,10 @@ def decode(arr, colourspace='YBR_FULL', reshape=True):
7780
RuntimeError
7881
If the decoding failed.
7982
"""
80-
colours = {
81-
'MONOCHROME1': 0,
82-
'MONOCHROME2' : 0,
83-
'RGB' : 1,
84-
'YBR_FULL' : 0,
85-
'YBR_FULL_422' : 0,
86-
-1 : -1, # For unit testing only
87-
}
88-
89-
try:
90-
transform = colours[colourspace]
91-
except KeyError:
92-
warnings.warn(
93-
"Unsupported colour space '{}', no colour transform will "
94-
"be applied".format(colourspace)
95-
)
96-
transform = 0
97-
9883
if isinstance(arr, bytes):
9984
arr = np.frombuffer(arr, 'uint8')
10085

101-
status, out, params = _libjpeg.decode(arr, transform)
86+
status, out, params = _libjpeg.decode(arr, colour_transform)
10287
status = status.decode("utf-8")
10388
code, msg = status.split("::::")
10489
code = int(code)
@@ -128,6 +113,49 @@ def decode(arr, colourspace='YBR_FULL', reshape=True):
128113
)
129114

130115

116+
def decode_pixel_data(arr, photometric_interp):
117+
"""Return the decoded JPEG data from `arr` as a :class:`numpy.ndarray`.
118+
119+
Parameters
120+
----------
121+
arr : numpy.ndarray or bytes
122+
A 1D array of ``np.uint8``, or a Python :class:`bytes` object
123+
containing the encoded JPEG image.
124+
photometric_interp : str
125+
The (0028,0004) *Photometric Interpretation* of the pixel data, one of
126+
``'MONOCHROME1'``, ``'MONOCHROME2'``, ``'RGB'``, ``'YBR_FULL'``,
127+
``'YBR_FULL_422'``.
128+
129+
Returns
130+
-------
131+
numpy.ndarray
132+
A 1D array of ``numpy.uint8`` containing the decoded image data.
133+
134+
Raises
135+
------
136+
RuntimeError
137+
If the decoding failed.
138+
"""
139+
colours = {
140+
'MONOCHROME1': 0,
141+
'MONOCHROME2' : 0,
142+
'RGB' : 1,
143+
'YBR_FULL' : 0,
144+
'YBR_FULL_422' : 0,
145+
}
146+
147+
try:
148+
transform = colours[photometric_interp]
149+
except KeyError:
150+
warnings.warn(
151+
"Unsupported (0028,0004) Photometric Interpretation '{}', no "
152+
"colour transformation will be applied".format(photometric_interp)
153+
)
154+
transform = 0
155+
156+
return decode(arr, transform, reshape=False)
157+
158+
131159
def get_parameters(arr):
132160
"""Return a :class:`dict` containing JPEG image parameters.
133161

0 commit comments

Comments
 (0)