Skip to content
Draft
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
26 changes: 25 additions & 1 deletion lib/interface/encode.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ extern int EncodeArray(
int use_mct,
PyObject *compression_ratios,
PyObject *signal_noise_ratios,
int codec_format
int codec_format,
int add_tlm,
int add_plt
)
{
/* Encode a numpy ndarray using JPEG 2000.
Expand All @@ -64,6 +66,10 @@ extern int EncodeArray(
The format of the encoded JPEG 2000 data, one of:
* ``0`` - OPJ_CODEC_J2K : JPEG-2000 codestream
* ``1`` - OPJ_CODEC_JP2 : JP2 file format
add_tlm : int
Add tile-part data length markers (TLM). Supported values 0-1.
add_plt : int
Add packet length tile-part header markers (PLT). Supported values 0-1.

Returns
-------
Expand Down Expand Up @@ -411,6 +417,24 @@ extern int EncodeArray(
goto failure;
}

const char* extra_options[3] = { NULL, NULL, NULL };
int extra_option_index = 0;
if (add_plt) {
extra_options[extra_option_index] = "PLT=YES";
extra_option_index += 1;
}
if (add_tlm) {
extra_options[extra_option_index] = "TLM=YES";
extra_option_index += 1;
}
if (extra_option_index > 0) {
if (! opj_encoder_set_extra_options(codec, extra_options)) {
py_error("Failed to set extra options on the encoder");
return_code = 28;
goto failure;
}
}

/* Send info, warning, error message to Python logging */
opj_set_info_handler(codec, info_callback, NULL);
opj_set_warning_handler(codec, warning_callback, NULL);
Expand Down
10 changes: 10 additions & 0 deletions openjpeg/_openjpeg.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ cdef extern int EncodeArray(
PyObject* compression_ratios,
PyObject* signal_noise_ratios,
int codec_format,
bint add_tlm,
bint add_plt,
)
cdef extern int EncodeBuffer(
PyObject* src,
Expand Down Expand Up @@ -213,6 +215,8 @@ def encode_array(
List[float] compression_ratios,
List[float] signal_noise_ratios,
int codec_format,
bint add_tlm,
bint add_plt,
) -> Tuple[int, bytes]:
"""Return the JPEG 2000 compressed `arr`.

Expand All @@ -239,6 +243,10 @@ def encode_array(

* ``0``: JPEG 2000 codestream only (default) (J2K/J2C format)
* ``1``: A boxed JPEG 2000 codestream (JP2 format)
add_tlm : bool
If ``True`` then add tile-part length markers (TLM) to the codestream.
add_plt : bool
If ``True`` then add packet length tile-part header markers (PLT) to the codestream.

Returns
-------
Expand Down Expand Up @@ -318,6 +326,8 @@ def encode_array(
<PyObject *> compression_ratios,
<PyObject *> signal_noise_ratios,
codec_format,
add_tlm,
add_plt,
)
return return_code, dst.getvalue()

Expand Down
128 changes: 128 additions & 0 deletions openjpeg/tests/test_encode.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,57 @@ def parse_j2k(buffer):

return param

def parse_codestream_markers(buffer):
offset = 0
markers = []
while offset < len(buffer):
symbol_code_bytes = buffer[offset: offset + 2]
marker = unpack(">H", symbol_code_bytes)[0]
offset += 2
if marker == 0xFF4F:
markers.append("SOC")
elif marker == 0xFF51:
markers.append("SIZ")
length_bytes = buffer[offset: offset + 2]
length = unpack(">H", length_bytes)[0]
offset += length
elif marker == 0xFF52:
markers.append("COD")
length_bytes = buffer[offset: offset + 2]
length = unpack(">H", length_bytes)[0]
offset += length
elif marker == 0xFF55:
markers.append("TLM")
length_bytes = buffer[offset: offset + 2]
length = unpack(">H", length_bytes)[0]
offset += length
elif marker == 0xFF58:
markers.append("PLT")
length_bytes = buffer[offset: offset + 2]
length = unpack(">H", length_bytes)[0]
offset += length
elif marker == 0xFF5C:
markers.append("QCD")
length_bytes = buffer[offset: offset + 2]
length = unpack(">H", length_bytes)[0]
offset += length
elif marker == 0xFF64:
markers.append("COM")
length_bytes = buffer[offset: offset + 2]
length = unpack(">H", length_bytes)[0]
offset += length
elif marker == 0xFF90:
markers.append("SOT")
length_bytes = buffer[offset: offset + 2]
length = unpack(">H", length_bytes)[0]
offset += length
elif marker == 0xFF93:
markers.append("SOD")
# If we get to here, we have the marker info we need
break
else:
raise Exception(f"unexpected marker: 0x{marker:04X}")
return markers

class TestEncode:
"""Tests for encode_array()"""
Expand Down Expand Up @@ -700,7 +751,84 @@ def test_jp2(self):

buffer = encode_array(arr, codec_format=1)
assert buffer.startswith(b"\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a")

def test_no_tlm_or_plt_explicit(self):
"""Test encoding with no TLM or PLT, explicitly disabled"""
rows = 123
cols = 234
bit_depth = 8
maximum = 2**bit_depth - 1
dtype = f"u{math.ceil(bit_depth / 8)}"
arr = np.random.randint(0, high=maximum + 1, size=(rows, cols), dtype=dtype)
buffer = encode_array(arr, compression_ratios=[4, 2, 1], add_tlm=False, add_plt=False)
out = decode(buffer)
markers = parse_codestream_markers(buffer)
assert "TLM" not in markers
assert "PLT" not in markers
assert np.allclose(arr, out, atol=5)

def test_no_tlm_or_plt_default(self):
"""Test encoding with no TLM or PLT, default options"""
rows = 123
cols = 234
bit_depth = 8
maximum = 2**bit_depth - 1
dtype = f"u{math.ceil(bit_depth / 8)}"
arr = np.random.randint(0, high=maximum + 1, size=(rows, cols), dtype=dtype)
buffer = encode_array(arr, compression_ratios=[4, 2, 1])
out = decode(buffer)
markers = parse_codestream_markers(buffer)
assert "TLM" not in markers
assert "PLT" not in markers
assert np.allclose(arr, out, atol=5)


def test_tlm(self):
"""Test encoding with TLM"""
rows = 123
cols = 234
bit_depth = 8
maximum = 2**bit_depth - 1
dtype = f"u{math.ceil(bit_depth / 8)}"
arr = np.random.randint(0, high=maximum + 1, size=(rows, cols), dtype=dtype)
buffer = encode_array(arr, compression_ratios=[4, 2, 1], add_tlm=True)
out = decode(buffer)
markers = parse_codestream_markers(buffer)
assert "TLM" in markers
assert "PLT" not in markers
assert np.allclose(arr, out, atol=5)

def test_plt(self):
"""Test encoding with PLT"""
rows = 123
cols = 234
bit_depth = 8
maximum = 2**bit_depth - 1
dtype = f"u{math.ceil(bit_depth / 8)}"
arr = np.random.randint(0, high=maximum + 1, size=(rows, cols), dtype=dtype)
buffer = encode_array(arr, compression_ratios=[4, 2, 1], add_plt=True)
out = decode(buffer)
markers = parse_codestream_markers(buffer)
assert "TLM" not in markers
assert "PLT" in markers
assert np.allclose(arr, out, atol=5)

def test_tlm_and_plt(self):
"""Test encoding with both TLM and PLT"""
rows = 123
cols = 234
bit_depth = 8
maximum = 2**bit_depth - 1
dtype = f"u{math.ceil(bit_depth / 8)}"
arr = np.random.randint(0, high=maximum + 1, size=(rows, cols), dtype=dtype)
buffer = encode_array(arr, compression_ratios=[4, 2, 1], add_plt=True, add_tlm=True)
out = decode(buffer)
param = parse_j2k(buffer)
assert param["precision"] == bit_depth
markers = parse_codestream_markers(buffer)
assert "TLM" in markers
assert "PLT" in markers
assert np.allclose(arr, out, atol=5)

class TestEncodeBuffer:
"""Tests for _openjpeg.encode_buffer"""
Expand Down
11 changes: 11 additions & 0 deletions openjpeg/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,8 @@ def encode_array(
compression_ratios: Union[List[float], None] = None,
signal_noise_ratios: Union[List[float], None] = None,
codec_format: int = 0,
add_tlm: bool = False,
add_plt: bool = False,
**kwargs: Any,
) -> bytes:
"""Return the JPEG 2000 compressed `arr`.
Expand Down Expand Up @@ -523,6 +525,13 @@ def encode_array(

* ``0``: JPEG 2000 codestream only (default) (J2K/J2C format)
* ``1``: A boxed JPEG 2000 codestream (JP2 format)
add_tlm : bool, optional
Add tile-part length markers (TLM) to the codestream. This can help
to speed up decoding of parts of very large images for some decoders.
add_plt : bool, optional
Add packet length, tile-part length markers (PLT) to the codestream. This
can help to speed up decoding of parts of very large images for some
decoders.

Returns
-------
Expand Down Expand Up @@ -553,6 +562,8 @@ def encode_array(
compression_ratios,
signal_noise_ratios,
codec_format,
add_tlm,
add_plt,
)

if return_code != 0:
Expand Down