Skip to content

Commit b288866

Browse files
authored
Pre-release fixes and updates (#3)
1 parent 8c7c5b3 commit b288866

File tree

9 files changed

+72
-73
lines changed

9 files changed

+72
-73
lines changed

.codecov.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
comment:
2+
layout: "diff, files"
3+
behavior: default
4+
require_changes: false # if true: only post the comment if coverage changes
5+
require_base: yes # [yes :: must have a base report to post]
6+
require_head: yes # [yes :: must have a head report to post]
7+
branches: # branch names that can post comment
8+
- "master"
9+
10+
coverage:
11+
status:
12+
project:
13+
default:
14+
target: auto
15+
threshold: 0.1%
16+
patch:
17+
default:
18+
target: auto
19+
threshold: 0.1%
20+
21+
ignore:
22+
- "rle/tests"
23+
- "rle/src"
24+
- "rle/benchmarks"

.github/workflows/pytest-builds.yml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,18 @@ on:
88

99
jobs:
1010
pytest:
11-
runs-on: ubuntu-latest
12-
timeout-minutes: 30
11+
runs-on: ${{ matrix.os }}
12+
timeout-minutes: 10
1313
strategy:
1414
fail-fast: false
1515
matrix:
1616
python-version: [3.6, 3.7, 3.8, 3.9]
17+
os: [ubuntu-latest, windows-latest, macos-latest]
1718

1819
steps:
1920
- uses: actions/checkout@v2
21+
with:
22+
fetch-depth: 2
2023

2124
- name: Set up Python ${{ matrix.python-version }}
2225
uses: actions/setup-python@v2
@@ -39,3 +42,11 @@ jobs:
3942
PYTHON_VERSION: ${{ matrix.python-version }}
4043
run: |
4144
pytest --cov rle
45+
46+
- name: Send coverage results
47+
if: ${{ success() && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.9' }}
48+
run: |
49+
bash <(curl --connect-timeout 10 --retry 10 --retry-max-time \
50+
0 https://codecov.io/bash) || (sleep 30 && bash <(curl \
51+
--connect-timeout 10 --retry 10 --retry-max-time \
52+
0 https://codecov.io/bash))

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2020 scaramallion
3+
Copyright (c) 2020-2021 scaramallion
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
## pylibjpeg-rle
33

4-
A fast DICOM RLE decoding plugin for pylibjpeg, written in Rust with a Python 3.6+ wrapper.
4+
A fast DICOM ([PackBits](https://en.wikipedia.org/wiki/PackBits)) RLE plugin for [pylibjpeg](https://github.com/pydicom/pylibjpeg) RLE decoding plugin for pylibjpeg, written in Rust with a Python 3.6+ wrapper.
55

66
Linux, OSX and Windows are all supported.
77

@@ -22,10 +22,10 @@ python -m setup.py develop
2222
```
2323

2424
### Supported Transfer Syntaxes
25-
#### Decoding
26-
| UID | Description |
27-
| --- | --- |
28-
| 1.2.840.10008.1.2.5 | RLE Lossless |
25+
26+
| UID | Description | Decoding | Encoding |
27+
| --- | --- | --- | --- |
28+
| 1.2.840.10008.1.2.5 | RLE Lossless | Yes | No |
2929

3030
### Benchmarks
3131
#### Decoding
@@ -48,10 +48,11 @@ Time per 1000 decodes, pydicom's NumPy RLE handler vs. pylibjpeg-rle
4848
| SC_rgb_rle_32bit_2frame.dcm | 20,000 | 240,000 | 1.03 s | 0.28 s |
4949

5050
### Usage
51-
#### With pylibjpeg and pydicom
51+
#### Decoding
52+
##### With pylibjpeg
5253

5354
Because pydicom defaults to the NumPy RLE decoder, you must specify the use
54-
of pylibjpeg when decompressing:
55+
of pylibjpeg when decompressing (**note: requires pydicom v2.2+**):
5556
*Pixel Data*:
5657
```python
5758
from pydicom import dcmread
@@ -62,6 +63,7 @@ ds.decompress("pylibjpeg")
6263
arr = ds.pixel_array
6364
```
6465

66+
#### Standalone with pydicom
6567
Alternatively you can use the included functions to decode a given dataset:
6668
```python
6769
from rle import pixel_array, generate_frames

docs/release_notes/v1.0.0.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.. _v1.0.0:
2+
3+
1.0.0
4+
=====
5+
6+
Enhancements
7+
............
8+
9+
* Initial release
10+
* Added support for decoding *RLE Lossless* encoded *Pixel Data*
11+
* Added support for use of a plugin with pylibjpeg

rle/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import re
44

55

6-
__version__ = '1.0.0.dev0'
6+
__version__ = '1.0.0'
77

88

99
VERSION_PATTERN = r"""

rle/tests/test_handler.py

Lines changed: 5 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
try:
66
from pydicom import dcmread
7+
from pydicom.encaps import generate_pixel_data_frame
78
from pydicom.uid import RLELossless
89
HAVE_PYDICOM = True
910
except ImportError:
@@ -29,27 +30,12 @@ def test_u8_1s_1f(self):
2930
assert 800 == ds.Columns
3031
assert 1 == getattr(ds, 'NumberOfFrames', 1)
3132

32-
arr = decode_pixel_data(ds.PixelData, ds)
33+
frame = next(generate_pixel_data_frame(ds.PixelData))
34+
arr = decode_pixel_data(frame, ds)
3335
assert (480000, ) == arr.shape
3436
assert arr.flags.writeable
3537
assert 'uint8' == arr.dtype
3638

37-
def test_u8_1s_2f(self):
38-
"""Test plugin decoder for 8 bit, 1 sample, 2 frame data."""
39-
ds = INDEX["OBXXXX1A_rle_2frame.dcm"]['ds']
40-
assert ds.file_meta.TransferSyntaxUID == RLELossless
41-
assert 8 == ds.BitsAllocated
42-
assert 1 == ds.SamplesPerPixel
43-
assert 0 == ds.PixelRepresentation
44-
assert 600 == ds.Rows
45-
assert 800 == ds.Columns
46-
assert 2 == getattr(ds, 'NumberOfFrames', 1)
47-
48-
arr = decode_pixel_data(ds.PixelData, ds)
49-
assert (960000, ) == arr.shape
50-
assert arr.flags.writeable
51-
assert 'uint8' == arr.dtype
52-
5339
def test_u32_3s_1f(self):
5440
"""Test plugin decoder for 32 bit, 3 sample, 1 frame data."""
5541
ds = INDEX["SC_rgb_rle_32bit.dcm"]['ds']
@@ -61,23 +47,8 @@ def test_u32_3s_1f(self):
6147
assert 100 == ds.Columns
6248
assert 1 == getattr(ds, 'NumberOfFrames', 1)
6349

64-
arr = decode_pixel_data(ds.PixelData, ds)
50+
frame = next(generate_pixel_data_frame(ds.PixelData))
51+
arr = decode_pixel_data(frame, ds)
6552
assert (120000, ) == arr.shape
6653
assert arr.flags.writeable
6754
assert 'uint8' == arr.dtype
68-
69-
def test_u32_3s_2f(self):
70-
"""Test plugin decoder for 32 bit, 3 sample, 2 frame data."""
71-
ds = INDEX["SC_rgb_rle_32bit_2frame.dcm"]['ds']
72-
assert ds.file_meta.TransferSyntaxUID == RLELossless
73-
assert 32 == ds.BitsAllocated
74-
assert 3 == ds.SamplesPerPixel
75-
assert 0 == ds.PixelRepresentation
76-
assert 100 == ds.Rows
77-
assert 100 == ds.Columns
78-
assert 2 == getattr(ds, 'NumberOfFrames', 1)
79-
80-
arr = decode_pixel_data(ds.PixelData, ds)
81-
assert (240000, ) == arr.shape
82-
assert arr.flags.writeable
83-
assert 'uint8' == arr.dtype

rle/utils.py

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,43 +14,26 @@ def decode_pixel_data(stream: bytes, ds: "Dataset") -> "np.ndarray":
1414
Parameters
1515
----------
1616
stream : bytes
17-
The contents of the dataset's *Pixel Data*.
17+
The image frame to be decoded.
1818
ds : pydicom.dataset.Dataset
1919
A :class:`~pydicom.dataset.Dataset` containing the group ``0x0028``
2020
elements corresponding to the *Pixel Data*.
2121
2222
Returns
2323
-------
2424
numpy.ndarray
25-
A 1D array of ``numpy.uint8`` containing the decoded image data,
25+
A 1D array of ``numpy.uint8`` containing the decoded frame data,
2626
with big-endian encoding and planar configuration 1.
2727
2828
Raises
2929
------
3030
ValueError
3131
If the decoding failed.
3232
"""
33-
from pydicom.encaps import generate_pixel_data_frame
34-
from pydicom.pixel_data_handlers.util import get_expected_length
35-
from pydicom.uid import RLELossless
36-
37-
nr_frames = getattr(ds, "NumberOfFrames", 1)
38-
r, c = ds.Rows, ds.Columns
39-
bpp = ds.BitsAllocated
40-
41-
expected_len = get_expected_length(ds, 'bytes')
42-
frame_len = expected_len // getattr(ds, "NumberOfFrames", 1)
43-
# Empty destination array for our decoded pixel data
44-
arr = np.empty(expected_len, dtype='uint8')
45-
46-
generate_frames = generate_pixel_data_frame(ds.PixelData, nr_frames)
47-
generate_offsets = range(0, expected_len, frame_len)
48-
for frame, offset in zip(generate_frames, generate_offsets):
49-
arr[offset:offset + frame_len] = np.frombuffer(
50-
decode_frame(frame, r * c, bpp), dtype='uint8'
51-
)
52-
53-
return arr
33+
return np.frombuffer(
34+
decode_frame(stream, ds.Rows * ds.Columns, ds.BitsAllocated),
35+
dtype='uint8'
36+
)
5437

5538

5639
def generate_frames(

src/lib.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
// https://pyo3.rs/v0.13.2/conversions/tables.html
2-
// bytes -> &[u8] or Vec<u8>
3-
// bytearray -> Vec<u8>
4-
// list[T] -> Vec<T>
51

62
use std::error::Error;
73
use std::convert::TryFrom;
@@ -105,7 +101,8 @@ fn _decode_frame(
105101
px_per_sample
106102
The number of pixels per sample (rows x columns), maximum (2^32 - 1).
107103
bits_per_px
108-
The number of bits per pixel, should be a multiple of 8 and no larger than 64.
104+
The number of bits per pixel, should be a multiple of 8 and no larger
105+
than 64.
109106
*/
110107

111108
// Pre-define our errors for neatness

0 commit comments

Comments
 (0)