Glymur 0.13.2.post1__tar.gz → 0.13.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {glymur-0.13.2.post1 → glymur-0.13.3}/CHANGES.txt +6 -2
- {glymur-0.13.2.post1 → glymur-0.13.3}/Glymur.egg-info/PKG-INFO +1 -1
- {glymur-0.13.2.post1 → glymur-0.13.3}/PKG-INFO +1 -1
- {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/codestream.py +3 -4
- {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/jp2box.py +1 -1
- {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/jp2k.py +17 -19
- {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/jp2kr.py +67 -38
- {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/version.py +1 -1
- {glymur-0.13.2.post1 → glymur-0.13.3}/setup.cfg +1 -1
- {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_codestream.py +111 -5
- {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_jp2box.py +37 -26
- {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_jp2k.py +0 -39
- {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_jp2k_writes.py +2 -3
- {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_jp2kr.py +0 -25
- {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_set_decoded_components.py +0 -1
- {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_warnings.py +0 -57
- {glymur-0.13.2.post1 → glymur-0.13.3}/Glymur.egg-info/SOURCES.txt +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/Glymur.egg-info/dependency_links.txt +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/Glymur.egg-info/entry_points.txt +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/Glymur.egg-info/not-zip-safe +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/Glymur.egg-info/requires.txt +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/Glymur.egg-info/top_level.txt +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/LICENSE.txt +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/MANIFEST.in +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/README.md +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/__init__.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/_iccprofile.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/command_line.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/config.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/core.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/data/__init__.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/data/goodstuff.j2k +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/data/heliov.jpx +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/data/nemo.jp2 +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/lib/__init__.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/lib/openjp2.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/lib/tiff.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/options.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/tiff.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/pyproject.toml +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_callbacks.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_cinema.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_colour_specification_box.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_config.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_geo.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_jp2box_jpx.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_jp2box_uuid.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_jp2box_xml.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_libtiff.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_openjp2.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_printing.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_slicing.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_threading.py +0 -0
- {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_tiff2jp2.py +0 -0
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
June 30, 2024 - v0.13.3
|
|
2
|
+
Refactor parsing errors and warnings.
|
|
3
|
+
Update CI configuration for numpy 2.0.
|
|
4
|
+
Skip psnr doctest for numpy 2.0.
|
|
5
|
+
Fix test issue on s390x.
|
|
6
|
+
Refactor code pattern for finding first element.
|
|
3
7
|
|
|
4
8
|
May 07, 2024 - v0.13.2
|
|
5
9
|
Improve doctesting, fix broken libtiff doctest
|
|
@@ -767,11 +767,10 @@ class Codestream(object):
|
|
|
767
767
|
num_tiles_y = (xysiz[1] - xyosiz[1]) / (xytsiz[1] - xytosiz[1])
|
|
768
768
|
except ZeroDivisionError:
|
|
769
769
|
msg = (
|
|
770
|
-
f"Invalid tile specification
|
|
771
|
-
f"size of {xytsiz[1]} x {xytsiz[0]}
|
|
772
|
-
f"offset of {xytosiz[1]} x {xytsiz[0]}."
|
|
770
|
+
f"Invalid tile specification in SIZ segment at byte offset "
|
|
771
|
+
f"{offset}: tile size of {xytsiz[1]} x {xytsiz[0]}."
|
|
773
772
|
)
|
|
774
|
-
|
|
773
|
+
raise ZeroDivisionError(msg)
|
|
775
774
|
else:
|
|
776
775
|
numtiles = np.ceil(num_tiles_x) * np.ceil(num_tiles_y)
|
|
777
776
|
if numtiles > 65535:
|
|
@@ -71,7 +71,7 @@ _EXIF_UUID = UUID(bytes=b'JpgTiffExif->JP2')
|
|
|
71
71
|
_XMP_UUID = UUID('be7acfcb-97a9-42e8-9c71-999491e3afac')
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
class InvalidJp2kWarning(
|
|
74
|
+
class InvalidJp2kWarning(UserWarning):
|
|
75
75
|
"""Issue this warning in case the file is technically invalid but we can
|
|
76
76
|
still read the image.
|
|
77
77
|
"""
|
|
@@ -258,7 +258,7 @@ class Jp2k(Jp2kr):
|
|
|
258
258
|
header box if we were so instructed. This requires a wrapping
|
|
259
259
|
operation.
|
|
260
260
|
"""
|
|
261
|
-
jp2h =
|
|
261
|
+
jp2h = next(filter(lambda x: x.box_id == 'jp2h', self.box), None)
|
|
262
262
|
|
|
263
263
|
extra_boxes = []
|
|
264
264
|
if self._capture_resolution is not None:
|
|
@@ -1125,8 +1125,7 @@ class Jp2k(Jp2kr):
|
|
|
1125
1125
|
|
|
1126
1126
|
def _validate_jp2_colr(self, boxes):
|
|
1127
1127
|
"""Validate JP2 requirements on colour specification boxes."""
|
|
1128
|
-
|
|
1129
|
-
jp2h = lst[0]
|
|
1128
|
+
jp2h = next(filter(lambda x: x.box_id == 'jp2h', boxes), None)
|
|
1130
1129
|
for colr in [box for box in jp2h.box if box.box_id == 'colr']:
|
|
1131
1130
|
if colr.approximation != 0:
|
|
1132
1131
|
msg = (
|
|
@@ -1161,19 +1160,21 @@ class Jp2k(Jp2kr):
|
|
|
1161
1160
|
def _validate_jp2c(self, boxes):
|
|
1162
1161
|
"""Validate the codestream box in relation to other boxes."""
|
|
1163
1162
|
# jp2c must be preceeded by jp2h
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1163
|
+
jp2h_idx, _ = next(
|
|
1164
|
+
filter(lambda x: x[1].box_id == 'jp2h', enumerate(boxes)),
|
|
1165
|
+
(None, None)
|
|
1166
|
+
)
|
|
1167
|
+
jp2c_idx, _ = next(
|
|
1168
|
+
filter(lambda x: x[1].box_id == 'jp2c', enumerate(boxes)),
|
|
1169
|
+
(None, None)
|
|
1170
|
+
)
|
|
1171
|
+
if jp2c_idx is None:
|
|
1170
1172
|
msg = (
|
|
1171
1173
|
"A codestream box must be defined in the outermost list of "
|
|
1172
1174
|
"boxes."
|
|
1173
1175
|
)
|
|
1174
1176
|
raise InvalidJp2kError(msg)
|
|
1175
1177
|
|
|
1176
|
-
jp2c_idx = jp2c_lst[0]
|
|
1177
1178
|
if jp2h_idx >= jp2c_idx:
|
|
1178
1179
|
msg = "The codestream box must be preceeded by a jp2 header box."
|
|
1179
1180
|
raise InvalidJp2kError(msg)
|
|
@@ -1182,8 +1183,7 @@ class Jp2k(Jp2kr):
|
|
|
1182
1183
|
"""Validate the JP2 Header box."""
|
|
1183
1184
|
self._check_jp2h_child_boxes(boxes, 'top-level')
|
|
1184
1185
|
|
|
1185
|
-
|
|
1186
|
-
jp2h = jp2h_lst[0]
|
|
1186
|
+
jp2h = next(filter(lambda x: x.box_id == 'jp2h', boxes), None)
|
|
1187
1187
|
|
|
1188
1188
|
# 1st jp2 header box cannot be empty.
|
|
1189
1189
|
if len(jp2h.box) == 0:
|
|
@@ -1199,20 +1199,18 @@ class Jp2k(Jp2kr):
|
|
|
1199
1199
|
raise InvalidJp2kError(msg)
|
|
1200
1200
|
|
|
1201
1201
|
# colr must be present in jp2 header box.
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
]
|
|
1205
|
-
if len(colr_lst) == 0:
|
|
1202
|
+
colr = next(filter(lambda x: x.box_id == 'colr', jp2h.box), None)
|
|
1203
|
+
if colr is None:
|
|
1206
1204
|
msg = "The jp2 header box must contain a color definition box."
|
|
1207
1205
|
raise InvalidJp2kError(msg)
|
|
1208
|
-
colr = jp2h.box[colr_lst[0]]
|
|
1209
1206
|
|
|
1210
1207
|
self._validate_channel_definition(jp2h, colr)
|
|
1211
1208
|
|
|
1212
1209
|
def _validate_channel_definition(self, jp2h, colr):
|
|
1213
1210
|
"""Validate the channel definition box."""
|
|
1214
|
-
cdef_lst = [
|
|
1215
|
-
|
|
1211
|
+
cdef_lst = [
|
|
1212
|
+
idx for (idx, box) in enumerate(jp2h.box) if box.box_id == 'cdef'
|
|
1213
|
+
]
|
|
1216
1214
|
if len(cdef_lst) > 1:
|
|
1217
1215
|
msg = ("Only one channel definition box is allowed in the "
|
|
1218
1216
|
"JP2 header.")
|
|
@@ -9,11 +9,11 @@ License: MIT
|
|
|
9
9
|
# Standard library imports...
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
from contextlib import ExitStack
|
|
12
|
-
from itertools import filterfalse
|
|
13
12
|
import ctypes
|
|
14
13
|
import pathlib
|
|
15
14
|
import re
|
|
16
15
|
import struct
|
|
16
|
+
import sys
|
|
17
17
|
import warnings
|
|
18
18
|
|
|
19
19
|
# Third party library imports
|
|
@@ -22,7 +22,7 @@ import numpy as np
|
|
|
22
22
|
# Local imports...
|
|
23
23
|
from .codestream import Codestream
|
|
24
24
|
from . import core, version, get_option
|
|
25
|
-
from .jp2box import Jp2kBox, FileTypeBox, InvalidJp2kError
|
|
25
|
+
from .jp2box import Jp2kBox, FileTypeBox, InvalidJp2kError, InvalidJp2kWarning
|
|
26
26
|
from .lib import openjp2 as opj2
|
|
27
27
|
|
|
28
28
|
|
|
@@ -108,8 +108,8 @@ class Jp2kr(Jp2kBox):
|
|
|
108
108
|
num_components = len(cstr.segment[1].xrsiz)
|
|
109
109
|
else:
|
|
110
110
|
# try to get the image size from the IHDR box
|
|
111
|
-
jp2h =
|
|
112
|
-
ihdr =
|
|
111
|
+
jp2h = next(filter(lambda x: x.box_id == 'jp2h', self.box), None)
|
|
112
|
+
ihdr = next(filter(lambda x: x.box_id == 'ihdr', jp2h.box), None)
|
|
113
113
|
|
|
114
114
|
height, width = ihdr.height, ihdr.width
|
|
115
115
|
num_components = ihdr.num_components
|
|
@@ -189,10 +189,10 @@ class Jp2kr(Jp2kBox):
|
|
|
189
189
|
@layer.setter
|
|
190
190
|
def layer(self, layer):
|
|
191
191
|
# Set to the indicated value so long as it is valid.
|
|
192
|
-
cod =
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
192
|
+
cod = next(
|
|
193
|
+
filter(lambda x: x.marker_id == 'COD', self.codestream.segment),
|
|
194
|
+
None
|
|
195
|
+
)
|
|
196
196
|
if layer < 0 or layer >= cod.layers:
|
|
197
197
|
msg = f"Invalid layer number, must be in range [0, {cod.layers})."
|
|
198
198
|
raise ValueError(msg)
|
|
@@ -355,7 +355,13 @@ class Jp2kr(Jp2kBox):
|
|
|
355
355
|
# Don't bother trying to validate JPX.
|
|
356
356
|
return
|
|
357
357
|
|
|
358
|
-
jp2h =
|
|
358
|
+
jp2h = next(filter(lambda x: x.box_id == 'jp2h', self.box), None)
|
|
359
|
+
if jp2h is None:
|
|
360
|
+
msg = (
|
|
361
|
+
"No JP2 header box was located in the outermost jacket of "
|
|
362
|
+
"boxes."
|
|
363
|
+
)
|
|
364
|
+
raise InvalidJp2kError(msg)
|
|
359
365
|
|
|
360
366
|
# An IHDR box is required as the first child box of the JP2H box.
|
|
361
367
|
if jp2h.box[0].box_id != 'ihdr':
|
|
@@ -373,7 +379,7 @@ class Jp2kr(Jp2kBox):
|
|
|
373
379
|
"enumerated colorspace or a restricted ICC profile if the "
|
|
374
380
|
"file type box brand is 'jp2 '."
|
|
375
381
|
)
|
|
376
|
-
warnings.warn(msg,
|
|
382
|
+
warnings.warn(msg, InvalidJp2kWarning)
|
|
377
383
|
|
|
378
384
|
# We need to have one and only one JP2H box if we have a JP2 file.
|
|
379
385
|
num_jp2h_boxes = len([box for box in self.box if box.box_id == 'jp2h'])
|
|
@@ -382,7 +388,7 @@ class Jp2kr(Jp2kBox):
|
|
|
382
388
|
f"This file has {num_jp2h_boxes} JP2H boxes in the outermost "
|
|
383
389
|
"layer of boxes. There should only be one."
|
|
384
390
|
)
|
|
385
|
-
warnings.warn(msg)
|
|
391
|
+
warnings.warn(msg, InvalidJp2kWarning)
|
|
386
392
|
|
|
387
393
|
# We should have one and only one JP2C box if we have a JP2 file.
|
|
388
394
|
num_jp2c_boxes = len([box for box in self.box if box.box_id == 'jp2c'])
|
|
@@ -404,10 +410,10 @@ class Jp2kr(Jp2kBox):
|
|
|
404
410
|
ihdr = jp2h.box[0]
|
|
405
411
|
ihdr_dims = ihdr.height, ihdr.width, ihdr.num_components
|
|
406
412
|
|
|
407
|
-
siz =
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
413
|
+
siz = next(
|
|
414
|
+
filter(lambda x: x.marker_id == 'SIZ', self.codestream.segment),
|
|
415
|
+
None
|
|
416
|
+
)
|
|
411
417
|
|
|
412
418
|
siz_dims = (siz.ysiz, siz.xsiz, len(siz.bitdepth))
|
|
413
419
|
if ihdr_dims != siz_dims:
|
|
@@ -460,9 +466,9 @@ class Jp2kr(Jp2kBox):
|
|
|
460
466
|
if isinstance(pargs, tuple) and any(isinstance(x, int) for x in pargs):
|
|
461
467
|
# Replace the first such integer argument, replace it with a slice.
|
|
462
468
|
lst = list(pargs)
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
469
|
+
idx, _ = next(
|
|
470
|
+
filter(lambda x: isinstance(x[1], int), enumerate(lst)), None
|
|
471
|
+
)
|
|
466
472
|
lst[idx] = slice(pargs[idx], pargs[idx] + 1)
|
|
467
473
|
newindex = tuple(lst)
|
|
468
474
|
|
|
@@ -676,10 +682,13 @@ class Jp2kr(Jp2kBox):
|
|
|
676
682
|
# Must check the specified rlevel against the maximum.
|
|
677
683
|
if rlevel != 0:
|
|
678
684
|
# Must check the specified rlevel against the maximum.
|
|
679
|
-
cod_seg =
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
685
|
+
cod_seg = next(
|
|
686
|
+
filter(
|
|
687
|
+
lambda x: x.marker_id == 'COD',
|
|
688
|
+
self.codestream.segment
|
|
689
|
+
),
|
|
690
|
+
None
|
|
691
|
+
)
|
|
683
692
|
max_rlevel = cod_seg.num_res
|
|
684
693
|
if rlevel == -1:
|
|
685
694
|
# -1 is shorthand for the largest rlevel
|
|
@@ -889,25 +898,45 @@ class Jp2kr(Jp2kBox):
|
|
|
889
898
|
Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1))
|
|
890
899
|
"""
|
|
891
900
|
with self.path.open('rb') as fptr:
|
|
901
|
+
|
|
902
|
+
# if it's just a raw codestream file, it's easy
|
|
892
903
|
if self._codec_format == opj2.CODEC_J2K:
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
904
|
+
return self._get_codestream(fptr, self.length, header_only)
|
|
905
|
+
|
|
906
|
+
# continue assuming JP2, must seek to the JP2C box and past its
|
|
907
|
+
# header
|
|
908
|
+
box = next(filter(lambda x: x.box_id == 'jp2c', self.box), None)
|
|
909
|
+
|
|
910
|
+
fptr.seek(box.offset)
|
|
911
|
+
read_buffer = fptr.read(8)
|
|
912
|
+
(box_length, _) = struct.unpack('>I4s', read_buffer)
|
|
913
|
+
if box_length == 0:
|
|
914
|
+
# The length of the box is presumed to last until the end
|
|
915
|
+
# of the file. Compute the effective length of the box.
|
|
916
|
+
box_length = self.path.stat().st_size - fptr.tell() + 8
|
|
917
|
+
elif box_length == 1:
|
|
918
|
+
# Seek past the XL field.
|
|
898
919
|
read_buffer = fptr.read(8)
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
codestream = Codestream(fptr, box_length - 8,
|
|
909
|
-
header_only=header_only)
|
|
920
|
+
box_length, = struct.unpack('>Q', read_buffer)
|
|
921
|
+
|
|
922
|
+
return self._get_codestream(fptr, box_length - 8, header_only)
|
|
923
|
+
|
|
924
|
+
def _get_codestream(self, fptr, length, header_only):
|
|
925
|
+
"""
|
|
926
|
+
Parsing errors can make for confusing errors sometimes, so catch any
|
|
927
|
+
such error and add context to it.
|
|
928
|
+
"""
|
|
910
929
|
|
|
930
|
+
try:
|
|
931
|
+
codestream = Codestream(fptr, length, header_only=header_only)
|
|
932
|
+
except Exception:
|
|
933
|
+
_, value, traceback = sys.exc_info()
|
|
934
|
+
msg = (
|
|
935
|
+
f'The file is invalid '
|
|
936
|
+
f'because the codestream could not be parsed: "{value}"'
|
|
937
|
+
)
|
|
938
|
+
raise InvalidJp2kError(msg).with_traceback(traceback)
|
|
939
|
+
else:
|
|
911
940
|
return codestream
|
|
912
941
|
|
|
913
942
|
def _validate_nonzero_image_size(self, nrows, ncols, component_index):
|
|
@@ -6,18 +6,21 @@ Test suite for codestream oddities
|
|
|
6
6
|
# Standard library imports ...
|
|
7
7
|
import importlib.resources as ir
|
|
8
8
|
from io import BytesIO
|
|
9
|
+
import pathlib
|
|
9
10
|
import struct
|
|
11
|
+
import tempfile
|
|
10
12
|
import unittest
|
|
11
13
|
import warnings
|
|
12
14
|
|
|
13
15
|
# Local imports ...
|
|
14
16
|
import glymur
|
|
15
|
-
from glymur import Jp2k
|
|
17
|
+
from glymur import Jp2k, Jp2kr
|
|
18
|
+
from glymur.jp2box import InvalidJp2kError
|
|
16
19
|
from . import fixtures
|
|
17
20
|
|
|
18
21
|
|
|
19
22
|
class TestSuite(fixtures.TestCommon):
|
|
20
|
-
"""Test suite for
|
|
23
|
+
"""Test suite for codestreams."""
|
|
21
24
|
|
|
22
25
|
def setUp(self):
|
|
23
26
|
super().setUp()
|
|
@@ -28,6 +31,107 @@ class TestSuite(fixtures.TestCommon):
|
|
|
28
31
|
self.issue142 = ir.files('tests.data').joinpath('issue142.j2k')
|
|
29
32
|
self.edf_c2_1178956 = ir.files('tests.data').joinpath('edf_c2_1178956.jp2') # noqa : E501
|
|
30
33
|
|
|
34
|
+
def test_unrecognized_marker(self):
|
|
35
|
+
"""
|
|
36
|
+
SCENARIO: There is an unrecognized marker just after an SOT marker but
|
|
37
|
+
before the EOC marker. All markers must have a leading byte value of
|
|
38
|
+
0xff.
|
|
39
|
+
|
|
40
|
+
EXPECTED RESULT: InvalidJp2kError
|
|
41
|
+
"""
|
|
42
|
+
with open(self.temp_j2k_filename, mode='wb') as tfile:
|
|
43
|
+
with open(self.j2kfile, 'rb') as ifile:
|
|
44
|
+
# Everything up until the SOT marker.
|
|
45
|
+
read_buffer = ifile.read(98)
|
|
46
|
+
tfile.write(read_buffer)
|
|
47
|
+
|
|
48
|
+
# Write the bad marker 0xd900
|
|
49
|
+
read_buffer = struct.pack('>H', 0xd900)
|
|
50
|
+
tfile.write(read_buffer)
|
|
51
|
+
|
|
52
|
+
# Get the rest of the input file.
|
|
53
|
+
read_buffer = ifile.read()
|
|
54
|
+
tfile.write(read_buffer)
|
|
55
|
+
tfile.flush()
|
|
56
|
+
|
|
57
|
+
with self.assertRaises(InvalidJp2kError):
|
|
58
|
+
Jp2k(tfile.name).get_codestream(header_only=False)
|
|
59
|
+
|
|
60
|
+
def test_bad_tile_part_pointer(self):
|
|
61
|
+
"""
|
|
62
|
+
SCENARIO: A bad SOT marker segment is encountered (Psot value pointing
|
|
63
|
+
far beyond the end of the EOC marker) when requesting a fully parsed
|
|
64
|
+
codestream.
|
|
65
|
+
|
|
66
|
+
EXPECTED RESULT: InvalidJp2kError
|
|
67
|
+
"""
|
|
68
|
+
with open(self.temp_jp2_filename, 'wb') as ofile:
|
|
69
|
+
with open(self.jp2file, 'rb') as ifile:
|
|
70
|
+
# Copy up until Psot field.
|
|
71
|
+
ofile.write(ifile.read(204))
|
|
72
|
+
|
|
73
|
+
# Write a bad Psot value.
|
|
74
|
+
ofile.write(struct.pack('>I', 2000000))
|
|
75
|
+
|
|
76
|
+
# copy the rest of the file as-is.
|
|
77
|
+
ifile.seek(208)
|
|
78
|
+
ofile.write(ifile.read())
|
|
79
|
+
ofile.flush()
|
|
80
|
+
|
|
81
|
+
j = Jp2kr(self.temp_jp2_filename)
|
|
82
|
+
with self.assertRaises(InvalidJp2kError):
|
|
83
|
+
j.get_codestream(header_only=False)
|
|
84
|
+
|
|
85
|
+
def test_tile_height_is_zero(self):
|
|
86
|
+
"""
|
|
87
|
+
Scenario: A tile has height of zero.
|
|
88
|
+
|
|
89
|
+
Expected result: ZeroDivisionError
|
|
90
|
+
|
|
91
|
+
Original test file was input/nonregression/2539.pdf.SIGFPE.706.1712.jp2
|
|
92
|
+
"""
|
|
93
|
+
fp = BytesIO()
|
|
94
|
+
|
|
95
|
+
buffer = struct.pack('>H', 47) # length
|
|
96
|
+
|
|
97
|
+
# kwargs = {'rsiz': 1,
|
|
98
|
+
# 'xysiz': (1000, 1000),
|
|
99
|
+
# 'xyosiz': (0, 0),
|
|
100
|
+
# 'xytsiz': (0, 1000),
|
|
101
|
+
# 'xytosiz': (0, 0),
|
|
102
|
+
# 'Csiz': 3,
|
|
103
|
+
# 'bitdepth': (8, 8, 8),
|
|
104
|
+
# 'signed': (False, False, False),
|
|
105
|
+
# 'xyrsiz': ((1, 1, 1), (1, 1, 1)),
|
|
106
|
+
# 'length': 47,
|
|
107
|
+
# 'offset': 2}
|
|
108
|
+
buffer += struct.pack('>HIIIIIIIIH', 1, 1000, 1000, 0, 0, 0, 1000,
|
|
109
|
+
0, 0, 3)
|
|
110
|
+
buffer += struct.pack('>BBBBBBBBB', 7, 1, 1, 7, 1, 1, 7, 1, 1)
|
|
111
|
+
fp.write(buffer)
|
|
112
|
+
fp.seek(0)
|
|
113
|
+
|
|
114
|
+
with self.assertRaises(ZeroDivisionError):
|
|
115
|
+
glymur.codestream.Codestream._parse_siz_segment(fp)
|
|
116
|
+
|
|
117
|
+
def test_invalid_codestream_past_header(self):
|
|
118
|
+
"""
|
|
119
|
+
Scenario: the codestream is ok thru the header, but invalid after
|
|
120
|
+
that. The codestream header for the complete test file ends at byte
|
|
121
|
+
|
|
122
|
+
Expected result: InvalidJp2kError
|
|
123
|
+
"""
|
|
124
|
+
path = ir.files('tests.data').joinpath('p1_06.j2k')
|
|
125
|
+
|
|
126
|
+
with tempfile.TemporaryDirectory() as tdir:
|
|
127
|
+
with open(path, mode='rb') as ifile:
|
|
128
|
+
with open(pathlib.Path(tdir) / 'tmp.j2k', mode='wb') as ofile:
|
|
129
|
+
ofile.write(ifile.read(555))
|
|
130
|
+
|
|
131
|
+
with self.assertRaises(InvalidJp2kError):
|
|
132
|
+
j = Jp2k(pathlib.Path(tdir) / 'tmp.j2k')
|
|
133
|
+
j.get_codestream(header_only=False)
|
|
134
|
+
|
|
31
135
|
def test_tlm_segment(self):
|
|
32
136
|
"""
|
|
33
137
|
Verify parsing of the TLM segment.
|
|
@@ -141,12 +245,14 @@ class TestSuite(fixtures.TestCommon):
|
|
|
141
245
|
|
|
142
246
|
def test_626(self):
|
|
143
247
|
"""
|
|
144
|
-
Scenario:
|
|
248
|
+
Scenario: After parsing the SOC and SIZ segments, an unknown segment
|
|
249
|
+
(probably invalid) is hit, and then the file ends, leaving us trying
|
|
250
|
+
to interpret EOF as another marker segment.
|
|
145
251
|
|
|
146
|
-
Expected result:
|
|
252
|
+
Expected result: InvalidJp2kError
|
|
147
253
|
"""
|
|
148
254
|
path = ir.files('tests.data').joinpath('issue626.j2k')
|
|
149
|
-
with self.assertRaises(
|
|
255
|
+
with self.assertRaises(InvalidJp2kError):
|
|
150
256
|
Jp2k(path)
|
|
151
257
|
|
|
152
258
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Test suite specifically targeting JP2 box layout.
|
|
2
2
|
"""
|
|
3
3
|
# Standard library imports ...
|
|
4
|
-
import doctest
|
|
5
4
|
import importlib.resources as ir
|
|
6
5
|
from io import BytesIO
|
|
7
6
|
import os
|
|
@@ -33,20 +32,6 @@ from . import fixtures
|
|
|
33
32
|
from .fixtures import OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG
|
|
34
33
|
|
|
35
34
|
|
|
36
|
-
def docTearDown(doctest_obj): # pragma: no cover
|
|
37
|
-
glymur.set_option('parse.full_codestream', False)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def load_tests(loader, tests, ignore): # pragma: no cover
|
|
41
|
-
"""Run doc tests as well."""
|
|
42
|
-
if os.name == "nt":
|
|
43
|
-
# Can't do it on windows, temporary file issue.
|
|
44
|
-
return tests
|
|
45
|
-
tests.addTests(doctest.DocTestSuite('glymur.jp2box',
|
|
46
|
-
tearDown=docTearDown))
|
|
47
|
-
return tests
|
|
48
|
-
|
|
49
|
-
|
|
50
35
|
@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG)
|
|
51
36
|
class TestDataEntryURL(fixtures.TestCommon):
|
|
52
37
|
"""Test suite for DataEntryURL boxes."""
|
|
@@ -1055,11 +1040,40 @@ class TestJp2Boxes(fixtures.TestCommon):
|
|
|
1055
1040
|
box._filename = str(self.jp2file)
|
|
1056
1041
|
box.codestream
|
|
1057
1042
|
|
|
1043
|
+
def test_no_jp2h_box(self):
|
|
1044
|
+
"""
|
|
1045
|
+
SCENARIO: The JP2/JP2H box is missing
|
|
1046
|
+
|
|
1047
|
+
EXPECTED RESULT: InvalidJp2kError
|
|
1048
|
+
"""
|
|
1049
|
+
# Write a new JP2 file that omits the IHDR box.
|
|
1050
|
+
j = Jp2k(self.jp2file)
|
|
1051
|
+
jp2h = [box for box in j.box if box.box_id == 'jp2h'][0]
|
|
1052
|
+
with open(self.temp_jp2_filename, mode='wb') as tfile:
|
|
1053
|
+
numbytes = jp2h.offset
|
|
1054
|
+
with open(self.jp2file, 'rb') as ifile:
|
|
1055
|
+
# Write all the way up to the ihdr box
|
|
1056
|
+
tfile.write(ifile.read(numbytes))
|
|
1057
|
+
|
|
1058
|
+
# Seek past the ihdr box
|
|
1059
|
+
ifile.seek(jp2h.length, os.SEEK_CUR)
|
|
1060
|
+
|
|
1061
|
+
# Write the rest of the JP2 file
|
|
1062
|
+
tfile.write(ifile.read(numbytes))
|
|
1063
|
+
|
|
1064
|
+
tfile.flush()
|
|
1065
|
+
|
|
1066
|
+
with self.assertRaises(InvalidJp2kError):
|
|
1067
|
+
with warnings.catch_warnings():
|
|
1068
|
+
# Lots of things wrong with this file.
|
|
1069
|
+
warnings.simplefilter('ignore')
|
|
1070
|
+
Jp2k(tfile.name)
|
|
1071
|
+
|
|
1058
1072
|
def test_no_ihdr_box(self):
|
|
1059
1073
|
"""
|
|
1060
1074
|
SCENARIO: The JP2/IHDR box cannot be parsed.
|
|
1061
1075
|
|
|
1062
|
-
EXPECTED RESULT:
|
|
1076
|
+
EXPECTED RESULT: InvalidJp2kError
|
|
1063
1077
|
"""
|
|
1064
1078
|
# Write a new JP2 file that omits the IHDR box.
|
|
1065
1079
|
j = Jp2k(self.jp2file)
|
|
@@ -1089,19 +1103,16 @@ class TestJp2Boxes(fixtures.TestCommon):
|
|
|
1089
1103
|
"""
|
|
1090
1104
|
SCENARIO: The JP2 file has no JP2C box.
|
|
1091
1105
|
|
|
1092
|
-
EXPECTED RESULT: An InvalidJp2kError is issued
|
|
1106
|
+
EXPECTED RESULT: An InvalidJp2kError is issued when the file is
|
|
1107
|
+
parsed.
|
|
1093
1108
|
"""
|
|
1094
|
-
|
|
1095
|
-
j = Jp2k(self.jp2file)
|
|
1096
|
-
jp2c = [box for box in j.box if box.box_id == 'jp2c'][0]
|
|
1097
|
-
with open(self.temp_jp2_filename, mode='wb') as tfile:
|
|
1098
|
-
numbytes = jp2c.offset
|
|
1099
|
-
with open(self.jp2file, 'rb') as ifile:
|
|
1100
|
-
tfile.write(ifile.read(numbytes))
|
|
1101
|
-
tfile.flush()
|
|
1109
|
+
testfile = ir.files('tests.data').joinpath('no_jp2c.jp2')
|
|
1102
1110
|
|
|
1111
|
+
with warnings.catch_warnings():
|
|
1112
|
+
# Lots of things wrong with this file.
|
|
1113
|
+
warnings.simplefilter('ignore')
|
|
1103
1114
|
with self.assertRaises(InvalidJp2kError):
|
|
1104
|
-
Jp2k(
|
|
1115
|
+
Jp2k(testfile)
|
|
1105
1116
|
|
|
1106
1117
|
def test_two_jp2c_boxes(self):
|
|
1107
1118
|
"""
|
|
@@ -262,31 +262,6 @@ class TestJp2k(fixtures.TestCommon):
|
|
|
262
262
|
|
|
263
263
|
np.testing.assert_array_equal(rgb, bgr[:, :, [2, 1, 0]])
|
|
264
264
|
|
|
265
|
-
def test_bad_tile_part_pointer(self):
|
|
266
|
-
"""
|
|
267
|
-
SCENARIO: A bad SOT marker segment is encountered (Psot value pointing
|
|
268
|
-
far beyond the end of the EOC marker) when requesting a fully parsed
|
|
269
|
-
codestream.
|
|
270
|
-
|
|
271
|
-
EXPECTED RESULT: struct.error
|
|
272
|
-
"""
|
|
273
|
-
with open(self.temp_jp2_filename, 'wb') as ofile:
|
|
274
|
-
with open(self.jp2file, 'rb') as ifile:
|
|
275
|
-
# Copy up until Psot field.
|
|
276
|
-
ofile.write(ifile.read(204))
|
|
277
|
-
|
|
278
|
-
# Write a bad Psot value.
|
|
279
|
-
ofile.write(struct.pack('>I', 2000000))
|
|
280
|
-
|
|
281
|
-
# copy the rest of the file as-is.
|
|
282
|
-
ifile.seek(208)
|
|
283
|
-
ofile.write(ifile.read())
|
|
284
|
-
ofile.flush()
|
|
285
|
-
|
|
286
|
-
j = Jp2k(self.temp_jp2_filename)
|
|
287
|
-
with self.assertRaises(struct.error):
|
|
288
|
-
j.get_codestream(header_only=False)
|
|
289
|
-
|
|
290
265
|
def test_read_differing_subsamples(self):
|
|
291
266
|
"""
|
|
292
267
|
SCENARIO: Attempt to read a file where the components have differing
|
|
@@ -393,20 +368,6 @@ class TestJp2k(fixtures.TestCommon):
|
|
|
393
368
|
with self.assertRaises(InvalidJp2kError):
|
|
394
369
|
Jp2k(path)
|
|
395
370
|
|
|
396
|
-
@unittest.skip("This test may not be appropriate")
|
|
397
|
-
def test_file_does_not_exist(self):
|
|
398
|
-
"""
|
|
399
|
-
Scenario: The Jp2k construtor is passed a file that does not exist
|
|
400
|
-
and the intent is reading.
|
|
401
|
-
|
|
402
|
-
Expected Result: FileNotFoundError
|
|
403
|
-
"""
|
|
404
|
-
# Verify that we error out appropriately if not given an existing file
|
|
405
|
-
# at all.
|
|
406
|
-
filename = 'this file does not actually exist on the file system.'
|
|
407
|
-
with self.assertRaises(FileNotFoundError):
|
|
408
|
-
Jp2k(filename)
|
|
409
|
-
|
|
410
371
|
def test_codestream(self):
|
|
411
372
|
"""
|
|
412
373
|
Verify the markers and segments of a JP2 file codestream.
|
|
@@ -1842,7 +1842,7 @@ class TestSuite(fixtures.TestCommon):
|
|
|
1842
1842
|
|
|
1843
1843
|
def test_1x1_tile(self):
|
|
1844
1844
|
"""
|
|
1845
|
-
SCENARIO: Write an image that is tiled 1x1.
|
|
1845
|
+
SCENARIO: Write by tiles an image that is tiled 1x1.
|
|
1846
1846
|
|
|
1847
1847
|
EXPECTED RESULT: RuntimeError, as this triggers an unresolved
|
|
1848
1848
|
bug, issue586.
|
|
@@ -1858,8 +1858,7 @@ class TestSuite(fixtures.TestCommon):
|
|
|
1858
1858
|
self.temp_j2k_filename, shape=shape, tilesize=tilesize,
|
|
1859
1859
|
)
|
|
1860
1860
|
with self.assertRaises(RuntimeError):
|
|
1861
|
-
|
|
1862
|
-
tw[:] = j2k_data
|
|
1861
|
+
j.get_tilewriters()
|
|
1863
1862
|
|
|
1864
1863
|
def test_openjpeg_library_too_old_for_tile_writing(self):
|
|
1865
1864
|
"""
|
|
@@ -205,31 +205,6 @@ class TestJp2kr(fixtures.TestCommon):
|
|
|
205
205
|
rgb_from_idx[r, c] = palette[idx[r, c]]
|
|
206
206
|
np.testing.assert_array_equal(rgb, rgb_from_idx)
|
|
207
207
|
|
|
208
|
-
def test_bad_tile_part_pointer(self):
|
|
209
|
-
"""
|
|
210
|
-
SCENARIO: A bad SOT marker segment is encountered (Psot value pointing
|
|
211
|
-
far beyond the end of the EOC marker) when requesting a fully parsed
|
|
212
|
-
codestream.
|
|
213
|
-
|
|
214
|
-
EXPECTED RESULT: struct.error
|
|
215
|
-
"""
|
|
216
|
-
with open(self.temp_jp2_filename, 'wb') as ofile:
|
|
217
|
-
with open(self.jp2file, 'rb') as ifile:
|
|
218
|
-
# Copy up until Psot field.
|
|
219
|
-
ofile.write(ifile.read(204))
|
|
220
|
-
|
|
221
|
-
# Write a bad Psot value.
|
|
222
|
-
ofile.write(struct.pack('>I', 2000000))
|
|
223
|
-
|
|
224
|
-
# copy the rest of the file as-is.
|
|
225
|
-
ifile.seek(208)
|
|
226
|
-
ofile.write(ifile.read())
|
|
227
|
-
ofile.flush()
|
|
228
|
-
|
|
229
|
-
j = Jp2kr(self.temp_jp2_filename)
|
|
230
|
-
with self.assertRaises(struct.error):
|
|
231
|
-
j.get_codestream(header_only=False)
|
|
232
|
-
|
|
233
208
|
def test_read_differing_subsamples(self):
|
|
234
209
|
"""
|
|
235
210
|
SCENARIO: Attempt to read a file where the components have differing
|
|
@@ -78,33 +78,6 @@ class TestSuite(fixtures.TestCommon):
|
|
|
78
78
|
# c = Jp2k(tfile.name).get_codestream(header_only=False)
|
|
79
79
|
Jp2k(tfile.name)
|
|
80
80
|
|
|
81
|
-
def test_unrecognized_marker(self):
|
|
82
|
-
"""
|
|
83
|
-
SCENARIO: There is an unrecognized marker just after an SOT marker but
|
|
84
|
-
before the EOC marker. All markers must have a leading byte value of
|
|
85
|
-
0xff.
|
|
86
|
-
|
|
87
|
-
EXPECTED RESULT: The SOT marker is the last one retrieved from the
|
|
88
|
-
codestream.
|
|
89
|
-
"""
|
|
90
|
-
with open(self.temp_j2k_filename, mode='wb') as tfile:
|
|
91
|
-
with open(self.j2kfile, 'rb') as ifile:
|
|
92
|
-
# Everything up until the SOT marker.
|
|
93
|
-
read_buffer = ifile.read(98)
|
|
94
|
-
tfile.write(read_buffer)
|
|
95
|
-
|
|
96
|
-
# Write the bad marker 0xd900
|
|
97
|
-
read_buffer = struct.pack('>H', 0xd900)
|
|
98
|
-
tfile.write(read_buffer)
|
|
99
|
-
|
|
100
|
-
# Get the rest of the input file.
|
|
101
|
-
read_buffer = ifile.read()
|
|
102
|
-
tfile.write(read_buffer)
|
|
103
|
-
tfile.flush()
|
|
104
|
-
|
|
105
|
-
with self.assertRaises(ValueError):
|
|
106
|
-
Jp2k(tfile.name).get_codestream(header_only=False)
|
|
107
|
-
|
|
108
81
|
def test_unrecoverable_xml(self):
|
|
109
82
|
"""
|
|
110
83
|
Bad byte sequence in XML that cannot be parsed.
|
|
@@ -123,36 +96,6 @@ class TestSuite(fixtures.TestCommon):
|
|
|
123
96
|
|
|
124
97
|
self.assertIsNone(box.xml)
|
|
125
98
|
|
|
126
|
-
def test_tile_height_is_zero(self):
|
|
127
|
-
"""
|
|
128
|
-
Zero tile height should not cause an exception.
|
|
129
|
-
|
|
130
|
-
Original test file was input/nonregression/2539.pdf.SIGFPE.706.1712.jp2
|
|
131
|
-
"""
|
|
132
|
-
fp = BytesIO()
|
|
133
|
-
|
|
134
|
-
buffer = struct.pack('>H', 47) # length
|
|
135
|
-
|
|
136
|
-
# kwargs = {'rsiz': 1,
|
|
137
|
-
# 'xysiz': (1000, 1000),
|
|
138
|
-
# 'xyosiz': (0, 0),
|
|
139
|
-
# 'xytsiz': (0, 1000),
|
|
140
|
-
# 'xytosiz': (0, 0),
|
|
141
|
-
# 'Csiz': 3,
|
|
142
|
-
# 'bitdepth': (8, 8, 8),
|
|
143
|
-
# 'signed': (False, False, False),
|
|
144
|
-
# 'xyrsiz': ((1, 1, 1), (1, 1, 1)),
|
|
145
|
-
# 'length': 47,
|
|
146
|
-
# 'offset': 2}
|
|
147
|
-
buffer += struct.pack('>HIIIIIIIIH', 1, 1000, 1000, 0, 0, 0, 1000,
|
|
148
|
-
0, 0, 3)
|
|
149
|
-
buffer += struct.pack('>BBBBBBBBB', 7, 1, 1, 7, 1, 1, 7, 1, 1)
|
|
150
|
-
fp.write(buffer)
|
|
151
|
-
fp.seek(0)
|
|
152
|
-
|
|
153
|
-
with self.assertWarns(UserWarning):
|
|
154
|
-
glymur.codestream.Codestream._parse_siz_segment(fp)
|
|
155
|
-
|
|
156
99
|
def test_invalid_progression_order(self):
|
|
157
100
|
"""
|
|
158
101
|
Should still be able to parse even if prog order is invalid.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|