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.
Files changed (54) hide show
  1. {glymur-0.13.2.post1 → glymur-0.13.3}/CHANGES.txt +6 -2
  2. {glymur-0.13.2.post1 → glymur-0.13.3}/Glymur.egg-info/PKG-INFO +1 -1
  3. {glymur-0.13.2.post1 → glymur-0.13.3}/PKG-INFO +1 -1
  4. {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/codestream.py +3 -4
  5. {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/jp2box.py +1 -1
  6. {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/jp2k.py +17 -19
  7. {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/jp2kr.py +67 -38
  8. {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/version.py +1 -1
  9. {glymur-0.13.2.post1 → glymur-0.13.3}/setup.cfg +1 -1
  10. {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_codestream.py +111 -5
  11. {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_jp2box.py +37 -26
  12. {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_jp2k.py +0 -39
  13. {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_jp2k_writes.py +2 -3
  14. {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_jp2kr.py +0 -25
  15. {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_set_decoded_components.py +0 -1
  16. {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_warnings.py +0 -57
  17. {glymur-0.13.2.post1 → glymur-0.13.3}/Glymur.egg-info/SOURCES.txt +0 -0
  18. {glymur-0.13.2.post1 → glymur-0.13.3}/Glymur.egg-info/dependency_links.txt +0 -0
  19. {glymur-0.13.2.post1 → glymur-0.13.3}/Glymur.egg-info/entry_points.txt +0 -0
  20. {glymur-0.13.2.post1 → glymur-0.13.3}/Glymur.egg-info/not-zip-safe +0 -0
  21. {glymur-0.13.2.post1 → glymur-0.13.3}/Glymur.egg-info/requires.txt +0 -0
  22. {glymur-0.13.2.post1 → glymur-0.13.3}/Glymur.egg-info/top_level.txt +0 -0
  23. {glymur-0.13.2.post1 → glymur-0.13.3}/LICENSE.txt +0 -0
  24. {glymur-0.13.2.post1 → glymur-0.13.3}/MANIFEST.in +0 -0
  25. {glymur-0.13.2.post1 → glymur-0.13.3}/README.md +0 -0
  26. {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/__init__.py +0 -0
  27. {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/_iccprofile.py +0 -0
  28. {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/command_line.py +0 -0
  29. {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/config.py +0 -0
  30. {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/core.py +0 -0
  31. {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/data/__init__.py +0 -0
  32. {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/data/goodstuff.j2k +0 -0
  33. {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/data/heliov.jpx +0 -0
  34. {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/data/nemo.jp2 +0 -0
  35. {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/lib/__init__.py +0 -0
  36. {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/lib/openjp2.py +0 -0
  37. {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/lib/tiff.py +0 -0
  38. {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/options.py +0 -0
  39. {glymur-0.13.2.post1 → glymur-0.13.3}/glymur/tiff.py +0 -0
  40. {glymur-0.13.2.post1 → glymur-0.13.3}/pyproject.toml +0 -0
  41. {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_callbacks.py +0 -0
  42. {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_cinema.py +0 -0
  43. {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_colour_specification_box.py +0 -0
  44. {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_config.py +0 -0
  45. {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_geo.py +0 -0
  46. {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_jp2box_jpx.py +0 -0
  47. {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_jp2box_uuid.py +0 -0
  48. {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_jp2box_xml.py +0 -0
  49. {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_libtiff.py +0 -0
  50. {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_openjp2.py +0 -0
  51. {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_printing.py +0 -0
  52. {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_slicing.py +0 -0
  53. {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_threading.py +0 -0
  54. {glymur-0.13.2.post1 → glymur-0.13.3}/tests/test_tiff2jp2.py +0 -0
@@ -1,5 +1,9 @@
1
- May 07, 2024 - v0.13.2.post1
2
- Fix big endian test issue
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: Glymur
3
- Version: 0.13.2.post1
3
+ Version: 0.13.3
4
4
  Home-page: https://github.com/quintusdias/glymur
5
5
  Author: 'John Evans'
6
6
  Author-email: "John Evans" <jevans667cc@proton.me>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: Glymur
3
- Version: 0.13.2.post1
3
+ Version: 0.13.3
4
4
  Home-page: https://github.com/quintusdias/glymur
5
5
  Author: 'John Evans'
6
6
  Author-email: "John Evans" <jevans667cc@proton.me>
@@ -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
- warnings.warn(msg, UserWarning)
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(RuntimeError):
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 = [box for box in self.box if box.box_id == 'jp2h'][0]
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
- lst = [box for box in boxes if box.box_id == 'jp2h']
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
- jp2h_lst = [idx for (idx, box) in enumerate(boxes)
1165
- if box.box_id == 'jp2h']
1166
- jp2h_idx = jp2h_lst[0]
1167
- jp2c_lst = [idx for (idx, box) in enumerate(boxes)
1168
- if box.box_id == 'jp2c']
1169
- if len(jp2c_lst) == 0:
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
- jp2h_lst = [box for box in boxes if box.box_id == 'jp2h']
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
- colr_lst = [
1203
- j for (j, box) in enumerate(jp2h.box) if box.box_id == 'colr'
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 = [j for (j, box) in enumerate(jp2h.box)
1215
- if box.box_id == 'cdef']
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 = [box for box in self.box if box.box_id == 'jp2h'][0]
112
- ihdr = [box for box in jp2h.box if box.box_id == 'ihdr'][0]
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
- segment for segment in self.codestream.segment
194
- if segment.marker_id == 'COD'
195
- ][0]
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 = [box for box in self.box if box.box_id == 'jp2h'][0]
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, UserWarning)
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
- segment for segment in self.codestream.segment
409
- if segment.marker_id == 'SIZ'
410
- ][0]
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
- g = filterfalse(lambda x: not isinstance(x[1], int),
464
- enumerate(pargs))
465
- idx = next(g)[0]
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
- segment for segment in self.codestream.segment
681
- if segment.marker_id == 'COD'
682
- ][0]
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
- codestream = Codestream(fptr, self.length,
894
- header_only=header_only)
895
- else:
896
- box = [x for x in self.box if x.box_id == 'jp2c']
897
- fptr.seek(box[0].offset)
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
- (box_length, _) = struct.unpack('>I4s', read_buffer)
900
- if box_length == 0:
901
- # The length of the box is presumed to last until the end
902
- # of the file. Compute the effective length of the box.
903
- box_length = self.path.stat().st_size - fptr.tell() + 8
904
- elif box_length == 1:
905
- # Seek past the XL field.
906
- read_buffer = fptr.read(8)
907
- box_length, = struct.unpack('>Q', read_buffer)
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):
@@ -20,7 +20,7 @@ from .lib import tiff
20
20
 
21
21
  # Do not change the format of this next line! Doing so risks breaking
22
22
  # setup.py
23
- version = "0.13.2.post1"
23
+ version = "0.13.3"
24
24
 
25
25
  version_tuple = parse(version).release
26
26
 
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = Glymur
3
- version = 0.13.2.post1
3
+ version = 0.13.3
4
4
  author = 'John Evans'
5
5
  author_email = "John Evans" <jevans667cc@proton.me>
6
6
  license = 'MIT'
@@ -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 ICC Profile code."""
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: J2KParseError
252
+ Expected result: InvalidJp2kError
147
253
  """
148
254
  path = ir.files('tests.data').joinpath('issue626.j2k')
149
- with self.assertRaises(glymur.codestream.J2KParseError):
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: An RuntimeError is issued.
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
- # Write a new JP2 file that omits the JP2C box.
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(tfile.name)
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
- for tw in j.get_tilewriters():
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
@@ -150,7 +150,6 @@ class TestSuite(unittest.TestCase):
150
150
  with warnings.catch_warnings():
151
151
  warnings.simplefilter('error')
152
152
  j2k.decoded_components = -1
153
- j2k[:]
154
153
 
155
154
  def test_same_component_several_times(self):
156
155
  """
@@ -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