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
8 changes: 6 additions & 2 deletions .github/workflows/pytest-builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
architecture: ${{ matrix.arch }}
allow-prereleases: true

- name: Install package and dependencies
run: |
Expand Down Expand Up @@ -59,6 +60,7 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true

- name: Install package and dependencies
run: |
Expand All @@ -82,7 +84,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14']

steps:
- uses: actions/checkout@v4
Expand All @@ -93,6 +95,7 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true

- name: Install package and dependencies
run: |
Expand All @@ -106,13 +109,14 @@ jobs:
pytest --cov openjpeg openjpeg/tests

- name: Install pydicom dev and rerun pytest (3.10+)
if: ${{ contains('3.10 3.11 3.12 3.13', matrix.python-version) }}
if: ${{ contains('3.10 3.11 3.12 3.13 3.14', matrix.python-version) }}
run: |
pip install pylibjpeg
pip install git+https://github.com/pydicom/pydicom
pytest --cov openjpeg openjpeg/tests

- name: Switch to current pydicom release and rerun pytest
if: ${{ contains('3.10 3.11 3.12 3.13', matrix.python-version) }}
run: |
pip uninstall -y pydicom
pip install pydicom pylibjpeg
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/release-wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ jobs:
- os: windows-latest
python: 313
platform_id: win32
- os: windows-latest
python: 314
platform_id: win32

# Windows 64 bit
- os: windows-latest
Expand All @@ -74,6 +77,9 @@ jobs:
- os: windows-latest
python: 313
platform_id: win_amd64
- os: windows-latest
python: 314
platform_id: win_amd64

# Linux 64 bit manylinux2014
- os: ubuntu-latest
Expand All @@ -96,6 +102,10 @@ jobs:
python: 313
platform_id: manylinux_x86_64
manylinux_image: manylinux2014
- os: ubuntu-latest
python: 314
platform_id: manylinux_x86_64
manylinux_image: manylinux2014

# Linux aarch64
- os: ubuntu-latest
Expand All @@ -113,6 +123,9 @@ jobs:
- os: ubuntu-latest
python: 313
platform_id: manylinux_aarch64
- os: ubuntu-latest
python: 314
platform_id: manylinux_aarch64

# MacOS 13 x86_64
- os: macos-13
Expand All @@ -130,6 +143,9 @@ jobs:
- os: macos-13
python: 313
platform_id: macosx_x86_64
- os: macos-13
python: 314
platform_id: macosx_x86_64

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -194,6 +210,9 @@ jobs:
- os: macos-14
python: 313
platform_id: macosx_arm64
- os: macos-14
python: 314
platform_id: macosx_arm64

steps:
- uses: actions/checkout@v4
Expand Down
10 changes: 10 additions & 0 deletions docs/changes/v2.5.0.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.. _v2.5.0:

2.5.0
=====

Changes
.......

* Bits above the precision are now ignored when encoding (:issue:`104`)
* Supported Python versions are 3.9 to 3.14.
54 changes: 45 additions & 9 deletions lib/interface/encode.c
Original file line number Diff line number Diff line change
Expand Up @@ -781,20 +781,41 @@ extern int EncodeBuffer(
OPJ_UINT64 nr_pixels = rows * columns;
char *data = PyBytes_AsString(src);
if (bytes_per_pixel == 1) {
unsigned char value;
unsigned char unsigned_mask = 0xFF >> (8 - bits_stored);
unsigned char signed_mask = 0xFF << bits_stored;
unsigned char bit_flag = 1 << (bits_stored - 1);
unsigned short do_masking = bits_stored < 8;
for (OPJ_UINT64 ii = 0; ii < nr_pixels; ii++)
{
for (p = 0; p < samples_per_pixel; p++)
{
// comps[...].data[...] is OPJ_INT32 -> int32_t
image->comps[p].data[ii] = is_signed ? (signed char) *data : (unsigned char) *data;
value = (unsigned char) *data;
data++;

if (do_masking) {
// Unsigned: zero out bits above `precision`
// Signed: zero out bits above `precision` if value >= 0, otherwise
// set them to one
if (is_signed && (bit_flag & value)) {
value = value | signed_mask;
} else {
value = value & unsigned_mask;
}
}

image->comps[p].data[ii] = is_signed ? (signed char) value : value;
}
}
} else if (bytes_per_pixel == 2) {
unsigned short value;
unsigned char temp1;
unsigned char temp2;

unsigned short unsigned_mask = 0xFFFF >> (16 - bits_stored);
unsigned short signed_mask = 0xFFFF << bits_stored;
unsigned short bit_flag = 1 << (bits_stored - 1);
unsigned short do_masking = bits_stored < 16;
for (OPJ_UINT64 ii = 0; ii < nr_pixels; ii++)
{
for (p = 0; p < samples_per_pixel; p++)
Expand All @@ -803,7 +824,16 @@ extern int EncodeBuffer(
data++;
temp2 = (unsigned char) *data;
data++;

value = (unsigned short) ((temp2 << 8) + temp1);
if (do_masking) {
if (is_signed && (bit_flag & value)) {
value = value | signed_mask;
} else {
value = value & unsigned_mask;
}
}

image->comps[p].data[ii] = is_signed ? (signed short) value : value;
}
}
Expand All @@ -813,7 +843,10 @@ extern int EncodeBuffer(
unsigned char temp2;
unsigned char temp3;
unsigned char temp4;

unsigned long unsigned_mask = 0xFFFFFFFF >> (32 - bits_stored);
unsigned long signed_mask = 0xFFFFFFFF << bits_stored;
unsigned long bit_flag = 1 << (bits_stored - 1);
unsigned short do_masking = bits_stored < 32;
for (OPJ_UINT64 ii = 0; ii < nr_pixels; ii++)
{
for (p = 0; p < samples_per_pixel; p++)
Expand All @@ -827,13 +860,16 @@ extern int EncodeBuffer(
temp4 = (unsigned char) * data;
data++;

value = (unsigned long)((temp4 << 24) + (temp3 << 16) + (temp2 << 8) + temp1);

if (is_signed) {
image->comps[p].data[ii] = (long) value;
} else {
image->comps[p].data[ii] = value;
value = (unsigned long) ((temp4 << 24) + (temp3 << 16) + (temp2 << 8) + temp1);
if (do_masking) {
if (is_signed && (bit_flag & value)) {
value = value | signed_mask;
} else {
value = value & unsigned_mask;
}
}

image->comps[p].data[ii] = is_signed ? (long) value : value;
}
}
}
Expand Down
28 changes: 14 additions & 14 deletions openjpeg/tests/test_decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from io import BytesIO

try:
from pydicom.encaps import generate_pixel_data_frame
from pydicom.pixel_data_handlers.util import (
from pydicom.encaps import generate_frames
from pydicom.pixels.utils import (
reshape_pixel_array,
pixel_dtype,
)
Expand Down Expand Up @@ -72,10 +72,10 @@ def test_version():
assert 5 == version[1]


def generate_frames(ds):
def get_frame_generator(ds):
"""Return a frame generator for DICOM datasets."""
nr_frames = ds.get("NumberOfFrames", 1)
return generate_pixel_data_frame(ds.PixelData, nr_frames)
return generate_frames(ds.PixelData, number_of_frames=nr_frames)


def test_get_format_raises():
Expand All @@ -91,7 +91,7 @@ def test_bad_decode():
"""Test trying to decode bad data."""
index = get_indexed_datasets("1.2.840.10008.1.2.4.90")
ds = index["966.dcm"]["ds"]
frame = next(generate_frames(ds))
frame = next(get_frame_generator(ds))
msg = r"Error decoding the J2K data: failed to decode image"
with pytest.raises(RuntimeError, match=msg):
decode(frame)
Expand All @@ -108,7 +108,7 @@ def test_decode_bytes(self):
"""Test decoding using bytes."""
index = get_indexed_datasets("1.2.840.10008.1.2.4.90")
ds = index["MR_small_jp2klossless.dcm"]["ds"]
frame = next(generate_frames(ds))
frame = next(get_frame_generator(ds))
assert isinstance(frame, bytes)
arr = decode(frame)
assert arr.flags.writeable
Expand All @@ -126,7 +126,7 @@ def test_decode_filelike(self):
"""Test decoding using file-like."""
index = get_indexed_datasets("1.2.840.10008.1.2.4.90")
ds = index["MR_small_jp2klossless.dcm"]["ds"]
frame = BytesIO(next(generate_frames(ds)))
frame = BytesIO(next(get_frame_generator(ds)))
assert isinstance(frame, BytesIO)
arr = decode(frame)
assert arr.flags.writeable
Expand All @@ -144,7 +144,7 @@ def test_decode_bad_type_raises(self):
"""Test decoding using invalid type raises."""
index = get_indexed_datasets("1.2.840.10008.1.2.4.90")
ds = index["MR_small_jp2klossless.dcm"]["ds"]
frame = tuple(next(generate_frames(ds)))
frame = tuple(next(get_frame_generator(ds)))
assert not hasattr(frame, "tell") and not isinstance(frame, bytes)

msg = (
Expand All @@ -159,7 +159,7 @@ def test_decode_bad_format_raises(self):
"""Test decoding using invalid jpeg format raises."""
index = get_indexed_datasets("1.2.840.10008.1.2.4.90")
ds = index["MR_small_jp2klossless.dcm"]["ds"]
frame = next(generate_frames(ds))
frame = next(get_frame_generator(ds))

msg = r"Unsupported 'j2k_format' value: 3"
with pytest.raises(ValueError, match=msg):
Expand All @@ -170,7 +170,7 @@ def test_decode_reshape_true(self):
"""Test decoding using invalid jpeg format raises."""
index = get_indexed_datasets("1.2.840.10008.1.2.4.90")
ds = index["US1_J2KR.dcm"]["ds"]
frame = next(generate_frames(ds))
frame = next(get_frame_generator(ds))

arr = decode(frame)
assert arr.flags.writeable
Expand Down Expand Up @@ -205,7 +205,7 @@ def test_decode_reshape_false(self):
"""Test decoding using invalid jpeg format raises."""
index = get_indexed_datasets("1.2.840.10008.1.2.4.90")
ds = index["US1_J2KR.dcm"]["ds"]
frame = next(generate_frames(ds))
frame = next(get_frame_generator(ds))

arr = decode(frame, reshape=False)
assert arr.flags.writeable
Expand All @@ -216,7 +216,7 @@ def test_signed_error(self):
"""Regression test for #30."""
index = get_indexed_datasets("1.2.840.10008.1.2.4.90")
ds = index["693_J2KR.dcm"]["ds"]
frame = next(generate_frames(ds))
frame = next(get_frame_generator(ds))

arr = decode(frame)
assert -2000 == arr[0, 0]
Expand Down Expand Up @@ -372,7 +372,7 @@ def test_jpeg2000r(self, fname, info):
# info: (rows, columns, spp, bps)
index = get_indexed_datasets("1.2.840.10008.1.2.4.90")
ds = index[fname]["ds"]
frame = next(generate_frames(ds))
frame = next(get_frame_generator(ds))
arr = decode(BytesIO(frame), reshape=False)
assert arr.flags.writeable

Expand Down Expand Up @@ -406,7 +406,7 @@ def test_jpeg2000i(self, fname, info):
index = get_indexed_datasets("1.2.840.10008.1.2.4.91")
ds = index[fname]["ds"]

frame = next(generate_frames(ds))
frame = next(get_frame_generator(ds))
arr = decode(BytesIO(frame), reshape=False)
assert arr.flags.writeable

Expand Down
Loading
Loading