Skip to content

Commit

Permalink
Match with pylibjpeg encoding interface (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
scaramallion authored Apr 29, 2021
1 parent 778c5a1 commit 17f31d2
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 55 deletions.
4 changes: 3 additions & 1 deletion rle/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""Set package shortcuts."""

from rle._version import __version__
from rle.utils import pixel_array, generate_frames, decode_pixel_data
from rle.utils import (
pixel_array, generate_frames, decode_pixel_data, encode_pixel_data
)
48 changes: 30 additions & 18 deletions rle/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ def test_u8_1s_1f_by_kwargs(self):
kwargs = {}
kwargs['rows'] = ds.Rows
kwargs['columns'] = ds.Columns
kwargs['samples_per_px'] = ds.SamplesPerPixel
kwargs['bits_per_px'] = ds.BitsAllocated
kwargs['nr_frames'] = int(getattr(ds, "NumberOfFrames", 1))
kwargs['samples_per_pixel'] = ds.SamplesPerPixel
kwargs['bits_allocated'] = ds.BitsAllocated
kwargs['number_of_frames'] = int(getattr(ds, "NumberOfFrames", 1))

ref = ds.pixel_array
gen = encode_array(ref, **kwargs)
Expand Down Expand Up @@ -105,9 +105,9 @@ def test_u32_3s_2f_by_kwargs(self):
kwargs = {}
kwargs['rows'] = ds.Rows
kwargs['columns'] = ds.Columns
kwargs['samples_per_px'] = ds.SamplesPerPixel
kwargs['bits_per_px'] = ds.BitsAllocated
kwargs['nr_frames'] = int(getattr(ds, "NumberOfFrames", 1))
kwargs['samples_per_pixel'] = ds.SamplesPerPixel
kwargs['bits_allocated'] = ds.BitsAllocated
kwargs['number_of_frames'] = int(getattr(ds, "NumberOfFrames", 1))

ref = ds.pixel_array
gen = encode_array(ref, **kwargs)
Expand Down Expand Up @@ -137,8 +137,8 @@ def test_bad_byteorder_raises(self):
kwargs = {
'rows': 0,
'columns': 0,
'samples_per_px': 1,
'bits_per_px': 16,
'samples_per_pixel': 1,
'bits_allocated': 16,
'byteorder': '=',
}

Expand All @@ -153,17 +153,29 @@ def test_bad_byteorder_raises(self):
with pytest.raises(ValueError, match=msg):
encode_pixel_data(b'', **kwargs)

def test_no_byteorder_u8(self):
"""Test exception raised if invalid byteorder."""
kwargs = {
'rows': 1,
'columns': 1,
'samples_per_pixel': 1,
'bits_allocated': 8,
'byteorder': None,
}

assert b'\x00\x01' == encode_pixel_data(b'\x01', **kwargs)[64:]

def test_bad_samples_raises(self):
"""Test exception raised if invalid samples per pixel."""
kwargs = {
'rows': 0,
'columns': 0,
'samples_per_px': 0,
'bits_per_px': 0,
'samples_per_pixel': 0,
'bits_allocated': 0,
'byteorder': '<',
}

msg = r"'samples_per_px' must be 1 or 3"
msg = r"'samples_per_pixel' must be 1 or 3"
with pytest.raises(ValueError, match=msg):
encode_pixel_data(b'', **kwargs)

Expand All @@ -172,12 +184,12 @@ def test_bad_bits_allocated_raises(self):
kwargs = {
'rows': 0,
'columns': 0,
'samples_per_px': 1,
'bits_per_px': 2,
'samples_per_pixel': 1,
'bits_allocated': 2,
'byteorder': '<',
}

msg = r"'bits_per_px' must be 8, 16, 32 or 64"
msg = r"'bits_allocated' must be 8, 16, 32 or 64"
with pytest.raises(ValueError, match=msg):
encode_pixel_data(b'', **kwargs)

Expand All @@ -186,8 +198,8 @@ def test_bad_length_raises(self):
kwargs = {
'rows': 1,
'columns': 1,
'samples_per_px': 1,
'bits_per_px': 8,
'samples_per_pixel': 1,
'bits_allocated': 8,
'byteorder': '<',
}

Expand All @@ -200,8 +212,8 @@ def test_too_many_segments_raises(self):
kwargs = {
'rows': 1,
'columns': 1,
'samples_per_px': 3,
'bits_per_px': 64,
'samples_per_pixel': 3,
'bits_allocated': 64,
'byteorder': '<',
}

Expand Down
72 changes: 36 additions & 36 deletions rle/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ def decode_pixel_data(src: bytes, ds: "Dataset", **kwargs) -> "np.ndarray":
ds : pydicom.dataset.Dataset
A :class:`~pydicom.dataset.Dataset` containing the group ``0x0028``
elements corresponding to the image frame.
kwargs : dict, optional
A dictionary containing options for the decoder. Current options are:
**kwargs
Current decoding options are:
* ``{'byteorder': str}`` specify the byte ordering for the decoded data
when more than 8 bits per pixel are used, should be '<' for little
Expand Down Expand Up @@ -64,17 +64,16 @@ def encode_array(
The dataset corresponding to `arr` with matching values for *Rows*,
*Columns*, *Samples per Pixel* and *Bits Allocated*. Required if
the array properties aren't specified using `kwargs`.
kwargs : dict, optional
A dictionary containing keyword arguments. Required if `ds` isn't used,
keys are:
**kwargs
Required keyword parameters if `ds` isn't used are:
* ``{'rows': int, 'columns': int}`` the number of rows and columns
contained in `arr`.
* ``{samples_per_px': int}`` the number of samples per pixel, either
* ``'rows': int`` the number of rows contained in `src`
* ``'columns': int`` the number of columns contained in `src`
* ``samples_per_px': int`` the number of samples per pixel, either
1 for monochrome or 3 for RGB or similar data.
* ``{'bits_per_px': int}`` the number of bits needed to contain each
* ``'bits_per_px': int`` the number of bits needed to contain each
pixel, either 8, 16, 32 or 64.
* ``{'nr_frames': int}`` the number of frames in `arr`, required if
* ``'nr_frames': int`` the number of frames in `arr`, required if
more than one frame is present.
Yields
Expand All @@ -91,11 +90,11 @@ def encode_array(
if ds:
kwargs['rows'] = ds.Rows
kwargs['columns'] = ds.Columns
kwargs['samples_per_px'] = ds.SamplesPerPixel
kwargs['bits_per_px'] = ds.BitsAllocated
kwargs['nr_frames'] = int(getattr(ds, "NumberOfFrames", 1))
kwargs['samples_per_pixel'] = ds.SamplesPerPixel
kwargs['bits_allocated'] = ds.BitsAllocated
kwargs['number_of_frames'] = int(getattr(ds, "NumberOfFrames", 1) or 1)

if kwargs['nr_frames'] > 1:
if kwargs['number_of_frames'] > 1:
for frame in arr:
yield encode_pixel_data(frame.tobytes(), **kwargs)
else:
Expand Down Expand Up @@ -127,43 +126,42 @@ def encode_pixel_data(
*Columns*, *Samples per Pixel* and *Bits Allocated*. Required if
the frame properties aren't specified using `kwargs`.
byteorder : str, optional
Required if the samples per pixel is greater than 1 and the value is
not passed using `kwargs`. If `src` is in little-endian byte order
then ``'<'``, otherwise ``'>'`` for big-endian.
kwargs : dict
A dictionary containing keyword arguments. Required keys are:
* ``{'rows': int, 'columns': int}`` the number of rows and columns
contained in `src`
* ``{samples_per_px': int}`` the number of samples per pixel, either
Required if the samples per pixel is greater than 1. If `src` is in
little-endian byte order then ``'<'``, otherwise ``'>'`` for
big-endian.
**kwargs
If `ds` is not used then the following are required:
* ``'rows': int`` the number of rows contained in `src`
* ``'columns': int`` the number of columns contained in `src`
* ``samples_per_pixel': int`` the number of samples per pixel, either
1 for monochrome or 3 for RGB or similar data.
* ``{'bits_per_px': int}`` the number of bits needed to contain each
* ``'bits_allocated': int`` the number of bits needed to contain each
pixel, either 8, 16, 32 or 64.
* ``{'byteorder': str}``, required if the samples per pixel is greater
than 1. If `src` is in little-endian byte order then ``'<'``,
otherwise ``'>'`` for big-endian.
Returns
-------
bytes
The RLE encoded frame.
"""
if ds:
r, c = ds.Rows, ds.Columns
r = ds.Rows
c = ds.Columns
bpp = ds.BitsAllocated
spp = ds.SamplesPerPixel
else:
r, c = kwargs['rows'], kwargs['columns']
bpp = kwargs['bits_per_px']
spp = kwargs['samples_per_px']
r = kwargs['rows']
c = kwargs['columns']
bpp = kwargs['bits_allocated']
spp = kwargs['samples_per_pixel']

# Validate input
if spp not in [1, 3]:
src = "(0028,0002) 'Samples per Pixel'" if ds else "'samples_per_px'"
src = "(0028,0002) 'Samples per Pixel'" if ds else "'samples_per_pixel'"
raise ValueError(src + " must be 1 or 3")

if bpp not in [8, 16, 32, 64]:
src = "(0028,0100) 'Bits Allocated'" if ds else "'bits_per_px'"
src = "(0028,0100) 'Bits Allocated'" if ds else "'bits_allocated'"
raise ValueError(src + " must be 8, 16, 32 or 64")

if bpp / 8 * spp > 15:
Expand All @@ -172,7 +170,8 @@ def encode_pixel_data(
"Standard only allows a maximum of 15 segments"
)

if bpp > 8 and byteorder not in ('<', '>'):
byteorder = '<' if bpp == 8 else byteorder
if byteorder not in ('<', '>'):
raise ValueError(
"A valid 'byteorder' is required when the number of bits per "
"pixel is greater than 8"
Expand Down Expand Up @@ -236,8 +235,9 @@ def generate_frames(ds: "Dataset", reshape: bool = True) -> "np.ndarray":
"elements are missing from the dataset: " + ", ".join(missing)
)

nr_frames = getattr(ds, "NumberOfFrames", 1)
r, c = ds.Rows, ds.Columns
nr_frames = int(getattr(ds, "NumberOfFrames", 1) or 1)
r = ds.Rows
c = ds.Columns
bpp = ds.BitsAllocated

dtype = pixel_dtype(ds)
Expand Down

0 comments on commit 17f31d2

Please sign in to comment.