Skip to content

Commit bc63702

Browse files
authored
Strip excess segment padding (#8)
* Strip excess segment padding during decoding * Add support for Python 3.10 * Remove support for Python 3.6
1 parent 17f31d2 commit bc63702

File tree

10 files changed

+85
-56
lines changed

10 files changed

+85
-56
lines changed

.github/workflows/pytest-builds.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
strategy:
1414
fail-fast: false
1515
matrix:
16-
python-version: [3.6, 3.7, 3.8, 3.9]
16+
python-version: [3.7, 3.8, 3.9, "3.10"]
1717
os: [ubuntu-latest, windows-latest, macos-latest]
1818

1919
steps:

.github/workflows/release-wheels.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ jobs:
99
name: Build wheels for ${{ matrix.os }}
1010
runs-on: ${{ matrix.os }}
1111
env:
12-
CIBW_SKIP: "cp35-*"
12+
CIBW_SKIP: "cp36-*"
1313
strategy:
1414
fail-fast: false
1515
matrix:
1616
os: [ubuntu-latest, windows-latest, macos-latest]
17-
python-version: [3.9]
17+
python-version: ["3.10"]
1818

1919
steps:
2020
- uses: actions/checkout@v2
@@ -35,7 +35,7 @@ jobs:
3535
- name: Install requirements
3636
run: |
3737
pip install -U pip
38-
pip install cibuildwheel==1.10.0
38+
pip install cibuildwheel==2.3.1
3939
pip install setuptools-rust
4040
4141
- name: Build sdist

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
## pylibjpeg-rle
33

4-
A fast DICOM ([PackBits](https://en.wikipedia.org/wiki/PackBits)) RLE plugin for [pylibjpeg](https://github.com/pydicom/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), written in Rust with a Python 3.7+ wrapper.
55

66
Linux, MacOS and Windows are all supported.
77

docs/release_notes/v1.2.0.rst

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.. _v1.2.0:
2+
3+
1.2.0
4+
=====
5+
6+
Enhancements
7+
............
8+
9+
* Support decoding segments with non-conformant padding (:issue:`7`)
10+
11+
Changes
12+
.......
13+
14+
* Support for Python 3.6 has been dropped, while Python 3.10 is now supported

rle/_version.py

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

55

6-
__version__ = '1.1.0'
6+
__version__ = '1.2.0'
77

88

99
VERSION_PATTERN = r"""

rle/tests/test_decode.py

+15-16
Original file line numberDiff line numberDiff line change
@@ -136,25 +136,15 @@ def test_invalid_samples_px_raises(self):
136136
with pytest.raises(ValueError, match=msg):
137137
decode_frame(d + b'\x00' * 8, 1, 8, '<')
138138

139-
def test_insufficient_frame_literal_raises(self):
140-
"""Test exception if frame not large enough to hold segment on lit."""
141-
msg = (
142-
r"The end of the frame was reached before the segment was "
143-
r"completely decoded"
144-
)
139+
def test_insufficient_frame_literal(self):
140+
"""Test segment with excess padding on lit."""
145141
d = self.as_bytes([64])
146-
with pytest.raises(ValueError, match=msg):
147-
decode_frame(d + b'\x00' * 8, 1, 8, '<')
142+
assert decode_frame(d + b'\x00' * 8, 1, 8, '<') == b"\x00"
148143

149-
def test_insufficient_frame_copy_raises(self):
150-
"""Test exception if frame not large enough to hold segment on copy."""
151-
msg = (
152-
r"The end of the frame was reached before the segment was "
153-
r"completely decoded"
154-
)
144+
def test_insufficient_frame_copy(self):
145+
"""Test segment withe excess padding on copy."""
155146
d = self.as_bytes([64])
156-
with pytest.raises(ValueError, match=msg):
157-
decode_frame(d + b'\xff\x00\x00', 1, 8, '<')
147+
assert decode_frame(d + b'\xff\x00\x00', 1, 8, '<') == b"\x00"
158148

159149
def test_insufficient_segment_copy_raises(self):
160150
"""Test exception if insufficient segment data on copy."""
@@ -844,3 +834,12 @@ def test_generator(self):
844834
assert (600, 800) == arr.shape
845835
with pytest.raises(StopIteration):
846836
next(gen)
837+
838+
def test_multi_sample(self):
839+
ds = deepcopy(INDEX["SC_rgb_rle_16bit.dcm"]['ds'])
840+
841+
gen = generate_frames(ds, reshape=True)
842+
arr = next(gen)
843+
assert (100, 100, 3) == arr.shape
844+
with pytest.raises(StopIteration):
845+
next(gen)

rle/tests/test_encode.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,7 @@ def test_cycle(self, _, ds, nr_frames):
245245
encoded = encode_frame(ds.PixelData, *params, '<')
246246
decoded = _rle_decode_frame(encoded, *params)
247247

248-
dtype = pixel_dtype(ds).newbyteorder('>')
249-
arr = np.frombuffer(decoded, dtype)
248+
arr = np.frombuffer(decoded, pixel_dtype(ds))
250249

251250
if ds.SamplesPerPixel == 1:
252251
arr = arr.reshape(ds.Rows, ds.Columns)

rle/tests/test_utils.py

+7
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,13 @@ def test_bad_byteorder_raises(self):
153153
with pytest.raises(ValueError, match=msg):
154154
encode_pixel_data(b'', **kwargs)
155155

156+
def test_encode_using_dataset(self):
157+
"""Test encoding using a dataset"""
158+
ds = INDEX_LEE["SC_rgb_32bit_2frame.dcm"]['ds']
159+
src = ds.pixel_array[0].tobytes()
160+
enc = encode_pixel_data(src, ds, "<")
161+
assert enc[:10] == b"\x0C\x00\x00\x00\x40\x00\x00\x00\x08\x01"
162+
156163
def test_no_byteorder_u8(self):
157164
"""Test exception raised if invalid byteorder."""
158165
kwargs = {

setup.py

+8-16
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,15 @@
11

2-
import os
3-
import sys
2+
from pathlib import Path
43
from setuptools import setup, find_packages
54
from setuptools_rust import Binding, RustExtension
65

7-
import subprocess
8-
from distutils.command.build import build as build_orig
9-
import distutils.sysconfig
106

7+
VERSION_FILE = Path(__file__).parent / "rle" / '_version.py'
8+
with open(VERSION_FILE) as f:
9+
exec(f.read())
1110

12-
13-
VERSION_FILE = os.path.join('rle', '_version.py')
14-
with open(VERSION_FILE) as fp:
15-
exec(fp.read())
16-
17-
with open('README.md', 'r') as fp:
18-
long_description = fp.read()
11+
with open('README.md', 'r') as f:
12+
long_description = f.read()
1913

2014
setup(
2115
name = 'pylibjpeg-rle',
@@ -39,15 +33,13 @@
3933
"Intended Audience :: Developers",
4034
"Intended Audience :: Healthcare Industry",
4135
"Intended Audience :: Science/Research",
42-
#"Development Status :: 3 - Alpha",
43-
#"Development Status :: 4 - Beta",
4436
"Development Status :: 5 - Production/Stable",
4537
"Natural Language :: English",
4638
"Programming Language :: Rust",
47-
"Programming Language :: Python :: 3.6",
4839
"Programming Language :: Python :: 3.7",
4940
"Programming Language :: Python :: 3.8",
5041
"Programming Language :: Python :: 3.9",
42+
"Programming Language :: Python :: 3.10",
5143
"Operating System :: MacOS :: MacOS X",
5244
"Operating System :: POSIX :: Linux",
5345
"Operating System :: Microsoft :: Windows",
@@ -58,7 +50,7 @@
5850
package_data = {'': ['*.txt', '*.rs', '*.pyx']},
5951
include_package_data = True,
6052
zip_safe = False,
61-
python_requires = ">=3.6",
53+
python_requires = ">=3.7",
6254
setup_requires = ['setuptools>=18.0', 'setuptools-rust'],
6355
install_requires = ["numpy"],
6456
extras_require = {

src/lib.rs

+34-16
Original file line numberDiff line numberDiff line change
@@ -312,12 +312,12 @@ fn _decode_segment_into_frame(
312312
completely decoded"
313313
).into()
314314
);
315-
let err_eof = Err(
316-
String::from(
317-
"The end of the frame was reached before the segment was \
318-
completely decoded"
319-
).into()
320-
);
315+
// let err_eof = Err(
316+
// String::from(
317+
// "The end of the frame was reached before the segment was \
318+
// completely decoded"
319+
// ).into()
320+
// );
321321

322322
loop {
323323
// `header_byte` is equivalent to N in the DICOM Standard
@@ -329,12 +329,21 @@ fn _decode_segment_into_frame(
329329
// however since using uint8 instead of int8 this will be
330330
// (256 - N + 1) times
331331
op_len = 257 - header_byte;
332-
// Check we have enough encoded data and remaining frame
333-
if (pos > max_offset) || (idx + op_len) > max_frame {
334-
match pos > max_offset {
335-
true => return err_eod,
336-
false => return err_eof
332+
333+
// Check we have enough encoded data
334+
if pos > max_offset {
335+
return err_eod
336+
}
337+
338+
// Check segment for excess padding
339+
if (idx + op_len) > max_frame {
340+
// Only copy until we reach the end of frame
341+
for _ in 0..(max_frame - idx) {
342+
dst[idx] = src[pos];
343+
idx += bpp;
337344
}
345+
346+
return Ok((idx - initial_offset) / bpp)
338347
}
339348

340349
for _ in 0..op_len {
@@ -345,12 +354,21 @@ fn _decode_segment_into_frame(
345354
} else if header_byte < 128 {
346355
// Extend by literally copying the next (N + 1) bytes
347356
op_len = header_byte + 1;
348-
// Check we have enough encoded data and remaining frame
349-
if ((pos + header_byte) > max_offset) || (idx + op_len > max_frame) {
350-
match (pos + header_byte) > max_offset {
351-
true => return err_eod,
352-
false => return err_eof
357+
358+
// Check we have enough encoded data
359+
if (pos + header_byte) > max_offset {
360+
return err_eod
361+
}
362+
363+
// Check segment for excess padding
364+
if (idx + op_len) > max_frame {
365+
// Only extend until the end of frame
366+
for ii in pos..pos + (max_frame - idx) {
367+
dst[idx] = src[ii];
368+
idx += bpp;
353369
}
370+
371+
return Ok((idx - initial_offset) / bpp)
354372
}
355373

356374
for ii in pos..pos + op_len {

0 commit comments

Comments
 (0)