Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .github/workflows/pytest-builds.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: build

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
ubuntu:
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
python-version: [3.6, 3.7, 3.8]

steps:
- uses: actions/checkout@v2

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}

- name: Install package and dependencies
run: |
python -m pip install -U pip
python -m pip install .
python -m pip install pytest coverage pytest-cov
python -m pip install git+https://github.com/pydicom/pylibjpeg-data

- name: Run pytest
run: |
pytest --cov openjpeg --ignore=openjpeg/src/openjpeg

- name: Install pydicom release and rerun pytest
run: |
pip install pydicom
pytest --cov pylibjpeg

- name: Install -libjpeg plugin rerun pytest
run: |
pip install git+https://github.com/pydicom/pylibjpeg-libjpeg
pytest --cov pylibjpeg

- name: Install -openjpeg plugin rerun pytest
run: |
pip install git+https://github.com/pydicom/pylibjpeg-openjpeg
pytest --cov pylibjpeg

- name: Send coverage results
if: ${{ success() }}
run: |
bash <(curl --connect-timeout 10 --retry 10 --retry-max-time \
0 https://codecov.io/bash) || (sleep 30 && bash <(curl \
--connect-timeout 10 --retry 10 --retry-max-time \
0 https://codecov.io/bash))
93 changes: 0 additions & 93 deletions .travis.yml

This file was deleted.

29 changes: 19 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ plugins come in. To support a given JPEG format or DICOM Transfer Syntax
you first have to install the corresponding package:

#### JPEG Format
| Format | Decode? | Encode? | Plugin | Based on |
|---|------|---|---|---|
| JPEG, JPEG-LS and JPEG XT | Yes | No | [pylibjpeg-libjpeg][1] | [libjpeg][2] |
| Format | Decode? | Encode? | Plugin | Based on | Included? |
|---|------|---|---|---|---|
| JPEG, JPEG-LS and JPEG XT | Yes | No | [pylibjpeg-libjpeg][1] | [libjpeg][2] | No |
| JPEG 2000 | Yes | No | [pylibjpeg-openjpeg][3] | [openjpeg][4] | Yes |

#### Transfer Syntax
#### DICOM Transfer Syntax

| UID | Description | Plugin |
|---|---|----|
Expand All @@ -44,8 +45,9 @@ you first have to install the corresponding package:
| 1.2.840.10008.1.2.4.70 | JPEG Lossless, Non-Hierarchical, First-Order Prediction</br>(Process 14, Selection Value 1) | [pylibjpeg-libjpeg][1]|
| 1.2.840.10008.1.2.4.80 | JPEG-LS Lossless | [pylibjpeg-libjpeg][1]|
| 1.2.840.10008.1.2.4.81 | JPEG-LS Lossy (Near-Lossless) Image Compression | [pylibjpeg-libjpeg][1]|
| 1.2.840.10008.1.2.4.90 | JPEG 2000 Image Compression (Lossless Only) | Not yet supported |
| 1.2.840.10008.1.2.4.91 | JPEG 2000 Image Compression | Not yet supported |
| 1.2.840.10008.1.2.4.90 | JPEG 2000 Image Compression (Lossless Only) | [pylibjpeg-openjpeg][4] |
| 1.2.840.10008.1.2.4.91 | JPEG 2000 Image Compression | [pylibjpeg-openjpeg][4] |
| 1.2.840.10008.1.2.5 | RLE Lossless | Not yet supported |

If you're not sure what the dataset's *Transfer Syntax UID* is, it can be
determined with:
Expand All @@ -57,21 +59,28 @@ determined with:

[1]: https://github.com/pydicom/pylibjpeg-libjpeg
[2]: https://github.com/thorfdbg/libjpeg
[3]: https://github.com/pydicom/pylibjpeg-openjpeg
[4]: https://github.com/uclouvain/openjpeg


### Usage
#### With pydicom
Assuming you already have *pydicom* v1.4+ installed:
Assuming you already have *pydicom* v1.4+ and suitable plugins installed:

```python
from pydicom import dcmread
from pydicom.data import get_testdata_file

# With the pylibjpeg-libjpeg plugin installed
# Importing the package adds the pixel data handler to pydicom
import pylibjpeg

# With the pylibjpeg-libjpeg plugin
ds = dcmread(get_testdata_file('JPEG-LL.dcm'))
arr = ds.pixel_array
jpg_arr = ds.pixel_array

# With the pylibjpeg-openjpeg plugin
ds = dcmread(get_testdata_file('JPEG2000.dcm'))
j2k_arr = ds.pixel_array
```

For datasets with multiple frames you can reduce your memory usage by
Expand All @@ -93,7 +102,7 @@ You can also just use *pylibjpeg* to decode JPEG images to a [numpy ndarray](htt
```python
from pylibjpeg import decode

# Can decode using the path to a JPG file as str or pathlike
# Can decode using the path to a JPG file as str or path-like
arr = decode('filename.jpg')

# Or a file-like...
Expand Down
28 changes: 0 additions & 28 deletions build_tools/travis/install.sh

This file was deleted.

102 changes: 102 additions & 0 deletions docs/plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@

## Plugins

Plugins should register their entry points via the *entry_points* kwarg for [setuptools.setup()](https://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins) in their `setup.py` file.

### DICOM Pixel Data decoders
#### Decoder plugin registration

Plugins that decode DICOM *Pixel Data* should register their decoding functions using the corresponding *Transfer Syntax UID* as the entry point name. For example, if the `my_plugin` plugin supported *JPEG Baseline* (1.2.840.10008.1.2.4.50) with the decoding function `decode_jpeg_baseline()` and *JPEG-LS Lossless* (1.2.840.10008.1.2.4.80) with the decoding function `decode_jls_lossless()` then it should include the following in its `setup.py`:

```python
from setuptools import setup

setup(
...,
entry_points={
"pylibjpeg.pixel_data_decoders": [
"1.2.840.10008.1.2.4.50 = my_plugin:decode_jpeg_baseline",
"1.2.840.10008.1.2.4.80 = my_plugin:decode_jls_lossless",
],
}
)
```

#### Decoder function signature

The pixel data decoding function will be passed two arguments; a single encoded
image frame as [bytes](https://docs.python.org/3/library/stdtypes.html#bytes) and 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. 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 `'uint8'`:

```python
def my_pixel_data_decoder(data, ds):
"""Return the encoded `data` as an unshaped numpy ndarray of uint8.

Parameters
----------
data : bytes
A single frame of the encoded *Pixel Data*.
ds : pydicom.dataset.Dataset
A dataset containing the group ``0x0028`` elements corresponding to
the *Pixel Data*.

Returns
-------
numpy.ndarray
A 1-dimensional ndarray of 'uint8' containing the decoded pixel data.
"""
# Decoding happens here
```

### JPEG decoders
#### Decoder plugin registration

Plugins that decoder JPEG data should register their decoding functions uding
the name of the plugin as the entry point name. For example, if the `my_plugin`
plugin supports decoding JPEG images via the `decode_jpeg()` function then
it should include the following in its `setup.py`:

```python
from setuptools import setup

setup(
...,
entry_points={
"pylibjpeg.jpeg_decoders": "my_plugin = my_plugin:decode_jpeg",
}
)
```

Possible entry points for JPEG decoding are:

| JPEG Format | ISO/IEC Standard | Entry Point |
| --- | --- | --- |
| JPEG | [10918](https://www.iso.org/standard/18902.html) | `"pylibjpeg.jpeg_decoders"` |
| JPEG XT | [18477](https://www.iso.org/standard/62552.html) | `"pylibjpeg.jpeg_xt_decoders"` |
| JPEG-LS | [14495](https://www.iso.org/standard/22397.html) | `"pylibjpeg.jpeg_ls_decoders"` |
| JPEG 2000 | [15444](https://www.iso.org/standard/78321.html) | `"pylibjpeg.jpeg_2000_decoders"` |


#### Decoder function signature

The JPEG decoding function will be passed the encoded JPEG *data* as
[bytes](https://docs.python.org/3/library/stdtypes.html#bytes) and a
[dict](https://docs.python.org/3/library/stdtypes.html#dict) containing keyword arguments passed to the function. The function should return the decoded image data as a numpy [ndarray](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html) with a dtype and shape matching the image format and dimensions:

```python
def my_jpeg_decoder(data, **kwarg):
"""Return the encoded JPEG `data` as an numpy ndarray.

Parameters
----------
data : bytes
The encoded JPEG data.
kwarg
Keyword arguments passed to the decoder.

Returns
-------
numpy.ndarray
An ndarray containing the decoded pixel data.
"""
# Decoding happens here
```
2 changes: 2 additions & 0 deletions docs/release_notes/v1.1.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
* Removed the ``plugins`` and ``_config`` modules
* Added ``utils.get_decoders()`` function
* Added ``pydicom.utils.get_pixel_data_decoders()`` function
* Changed arguments passed to pixel data decoding functions to
``func(bytes, Dataset)``
10 changes: 6 additions & 4 deletions pylibjpeg/pydicom/pixel_data_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ def get_pixeldata(ds):
# Note: this does NOT include the trailing null byte for odd length data
expected_len = get_expected_length(ds)
if ds.PhotometricInterpretation == 'YBR_FULL_422':
# JPEG Transfer Syntaxes
# Plugin should have already resampled the pixel data
# see PS3.3 C.7.6.3.1.2
expected_len = expected_len // 2 * 3
Expand All @@ -158,12 +159,13 @@ def get_pixeldata(ds):
decoder = _DECODERS[tsyntax]
LOGGER.debug("Decoding {} Pixel Data using {}".format(tsyntax, decoder))

# Generators for the encoded JPG image frame(s) and insertion offsets
# Generators for the encoded JPEG image frame(s) and insertion offsets
generate_frames = generate_pixel_data_frame(ds.PixelData, nr_frames)
generate_offsets = range(0, expected_len, frame_len)
for frame, offset in zip(generate_frames, generate_offsets):
# Encoded JPG data to be sent to the decoder
frame = np.frombuffer(frame, np.uint8)
arr[offset:offset + frame_len] = decoder(frame, p_interp)
# Encoded JPEG data to be sent to the decoder
arr[offset:offset + frame_len] = decoder(
frame, ds.group_dataset(0x0028)
)

return arr.view(pixel_dtype(ds))
2 changes: 1 addition & 1 deletion pylibjpeg/pydicom/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def generate_frames(ds):
p_interp = ds.PhotometricInterpretation
nr_frames = getattr(ds, 'NumberOfFrames', 1)
for frame in generate_pixel_data_frame(ds.PixelData, nr_frames):
arr = decode(frame, p_interp).view(pixel_dtype(ds))
arr = decode(frame, ds.group_dataset(0x0028)).view(pixel_dtype(ds))
yield reshape_frame(ds, arr)


Expand Down
Loading