Glymur 0.13.7__py3-none-any.whl → 0.14.0.post1__py3-none-any.whl

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/jp2box.py CHANGED
@@ -27,11 +27,13 @@ import warnings
27
27
  # Third party library imports ...
28
28
  try:
29
29
  from osgeo import gdal
30
+
30
31
  _HAVE_GDAL = True
31
32
  except (ImportError, ModuleNotFoundError): # pragma: no cover
32
33
  _HAVE_GDAL = False
33
34
  else:
34
35
  gdal.UseExceptions()
36
+ import lxml.objectify
35
37
  import lxml.etree as ET
36
38
  import numpy as np
37
39
 
@@ -39,10 +41,15 @@ import numpy as np
39
41
  # Local imports ...
40
42
  from .codestream import Codestream
41
43
  from .core import (
42
- _COLORSPACE_MAP_DISPLAY, _COLOR_TYPE_MAP_DISPLAY,
43
- SRGB, GREYSCALE, YCC,
44
- ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE,
45
- ANY_ICC_PROFILE, VENDOR_COLOR_METHOD
44
+ _COLORSPACE_MAP_DISPLAY,
45
+ _COLOR_TYPE_MAP_DISPLAY,
46
+ SRGB,
47
+ GREYSCALE,
48
+ YCC,
49
+ ENUMERATED_COLORSPACE,
50
+ RESTRICTED_ICC_PROFILE,
51
+ ANY_ICC_PROFILE,
52
+ VENDOR_COLOR_METHOD,
46
53
  )
47
54
  from .lib.tiff import tiff_header, BadTiffTagDatatype
48
55
  from . import get_option
@@ -50,37 +57,38 @@ from ._iccprofile import _ICCProfile
50
57
 
51
58
 
52
59
  _COLORSPACE_METHODS = {
53
- ENUMERATED_COLORSPACE: 'enumerated colorspace',
54
- RESTRICTED_ICC_PROFILE: 'restricted ICC profile',
55
- ANY_ICC_PROFILE: 'any ICC profile',
56
- VENDOR_COLOR_METHOD: 'vendor color method',
60
+ ENUMERATED_COLORSPACE: "enumerated colorspace",
61
+ RESTRICTED_ICC_PROFILE: "restricted ICC profile",
62
+ ANY_ICC_PROFILE: "any ICC profile",
63
+ VENDOR_COLOR_METHOD: "vendor color method",
57
64
  }
58
65
 
59
66
 
60
67
  _APPROXIMATION_MEASURES = {
61
- 0: 'JP2 only',
62
- 1: 'accurately represents correct colorspace definition',
63
- 2: 'approximates correct colorspace definition, exceptional quality',
64
- 3: 'approximates correct colorspace definition, reasonable quality',
65
- 4: 'approximates correct colorspace definition, poor quality',
68
+ 0: "JP2 only",
69
+ 1: "accurately represents correct colorspace definition",
70
+ 2: "approximates correct colorspace definition, exceptional quality",
71
+ 3: "approximates correct colorspace definition, reasonable quality",
72
+ 4: "approximates correct colorspace definition, poor quality",
66
73
  }
67
74
 
68
75
  # Three different UUIDs are given special treatment.
69
- _GEOTIFF_UUID = UUID('b14bf8bd-083d-4b43-a5ae-8cd7d5a6ce03')
70
- _EXIF_UUID = UUID(bytes=b'JpgTiffExif->JP2')
71
- _XMP_UUID = UUID('be7acfcb-97a9-42e8-9c71-999491e3afac')
76
+ _GEOTIFF_UUID = UUID("b14bf8bd-083d-4b43-a5ae-8cd7d5a6ce03")
77
+ _EXIF_UUID = UUID(bytes=b"JpgTiffExif->JP2")
78
+ _XMP_UUID = UUID("be7acfcb-97a9-42e8-9c71-999491e3afac")
72
79
 
73
80
 
74
81
  class InvalidJp2kWarning(UserWarning):
75
82
  """Issue this warning in case the file is technically invalid but we can
76
83
  still read the image.
77
84
  """
85
+
78
86
  pass
79
87
 
80
88
 
81
89
  class InvalidJp2kError(RuntimeError):
82
- """Raise this exception in case we cannot parse a valid JP2 file.
83
- """
90
+ """Raise this exception in case we cannot parse a valid JP2 file."""
91
+
84
92
  pass
85
93
 
86
94
 
@@ -123,8 +131,7 @@ class Jp2kBox(object):
123
131
  warnings.warn(msg)
124
132
 
125
133
  def write(self, _):
126
- """Must be implemented in a subclass.
127
- """
134
+ """Must be implemented in a subclass."""
128
135
  msg = f"Writing not supported for {self.longname} box."
129
136
  raise NotImplementedError(msg)
130
137
 
@@ -134,7 +141,7 @@ class Jp2kBox(object):
134
141
  for box in self.box:
135
142
  boxstr = str(box)
136
143
  # Indent the child boxes to make the association clear.
137
- msg += '\n' + textwrap.indent(boxstr, ' ' * 4)
144
+ msg += "\n" + textwrap.indent(boxstr, " " * 4)
138
145
  return msg
139
146
 
140
147
  def _write_superbox(self, fptr, box_id):
@@ -148,7 +155,7 @@ class Jp2kBox(object):
148
155
  4-byte sequence that identifies the superbox.
149
156
  """
150
157
  b = io.BytesIO()
151
- b.write(struct.pack('>I4s', 0, box_id))
158
+ b.write(struct.pack(">I4s", 0, box_id))
152
159
  for box in self.box:
153
160
  box.write(b)
154
161
 
@@ -156,7 +163,7 @@ class Jp2kBox(object):
156
163
 
157
164
  # come back and write the length.
158
165
  b.seek(0)
159
- buffer = struct.pack('>I', box_length)
166
+ buffer = struct.pack(">I", box_length)
160
167
  b.write(buffer)
161
168
 
162
169
  fptr.write(b.getvalue())
@@ -186,12 +193,15 @@ class Jp2kBox(object):
186
193
  # We don't recognize the box ID, so create an UnknownBox and be
187
194
  # done with it.
188
195
  msg = (
189
- f'Unrecognized box ({box_id}) '
190
- f'encountered at byte offset {fptr.tell() - 8}.'
196
+ f"Unrecognized box ({box_id}) "
197
+ f"encountered at byte offset {fptr.tell() - 8}."
191
198
  )
192
199
  warnings.warn(msg, UserWarning)
193
200
  box = UnknownBox(
194
- box_id, offset=start, length=num_bytes, longname='Unknown'
201
+ box_id,
202
+ offset=start,
203
+ length=num_bytes,
204
+ longname="Unknown"
195
205
  )
196
206
 
197
207
  return box
@@ -200,16 +210,16 @@ class Jp2kBox(object):
200
210
  box = parser(fptr, start, num_bytes)
201
211
  except Exception as err:
202
212
  msg = (
203
- f'Encountered an error while parsing a '
204
- f'{_BOX_WITH_ID[box_id]} box at byte offset {start}. '
213
+ f"Encountered an error while parsing a "
214
+ f"{_BOX_WITH_ID[box_id]} box at byte offset {start}. "
205
215
  f'The original error message was "{str(err)}".'
206
216
  )
207
217
  warnings.warn(msg, UserWarning)
208
218
  box = UnknownBox(
209
- box_id.decode('utf-8'),
219
+ box_id.decode("utf-8"),
210
220
  length=num_bytes,
211
221
  offset=start,
212
- longname='Unknown'
222
+ longname="Unknown",
213
223
  )
214
224
 
215
225
  return box
@@ -245,7 +255,7 @@ class Jp2kBox(object):
245
255
  warnings.warn(msg, UserWarning)
246
256
  return superbox
247
257
 
248
- (box_length, box_id) = struct.unpack('>I4s', read_buffer)
258
+ (box_length, box_id) = struct.unpack(">I4s", read_buffer)
249
259
  if box_length == 0:
250
260
  # The length of the box is presumed to last until the end of
251
261
  # the file. Compute the effective length of the box.
@@ -254,7 +264,7 @@ class Jp2kBox(object):
254
264
  elif box_length == 1:
255
265
  # The length of the box is in the XL field, a 64-bit value.
256
266
  read_buffer = fptr.read(8)
257
- num_bytes, = struct.unpack('>Q', read_buffer)
267
+ (num_bytes,) = struct.unpack(">Q", read_buffer)
258
268
 
259
269
  else:
260
270
  # The box_length value really is the length of the box!
@@ -275,9 +285,9 @@ class Jp2kBox(object):
275
285
  # The box must be invalid somehow, as the file pointer is
276
286
  # positioned past the end of the box.
277
287
  msg = (
278
- f'{box_id} box may be invalid, the file pointer is '
279
- f'positioned {fptr.tell() - (start + num_bytes)} bytes '
280
- 'past the end of the box.'
288
+ f"{box_id} box may be invalid, the file pointer is "
289
+ f"positioned {fptr.tell() - (start + num_bytes)} bytes "
290
+ "past the end of the box."
281
291
  )
282
292
  warnings.warn(msg, UserWarning)
283
293
  fptr.seek(start + num_bytes)
@@ -317,13 +327,19 @@ class ColourSpecificationBox(Jp2kBox):
317
327
  ICC profile header according to ICC profile specification. If
318
328
  colorspace is not None, then icc_profile must be empty.
319
329
  """
320
- longname = 'Colour Specification'
321
- box_id = 'colr'
330
+
331
+ longname = "Colour Specification"
332
+ box_id = "colr"
322
333
 
323
334
  def __init__(
324
- self, method=ENUMERATED_COLORSPACE, precedence=0,
325
- approximation=0, colorspace=None, icc_profile=None,
326
- length=0, offset=-1
335
+ self,
336
+ method=ENUMERATED_COLORSPACE,
337
+ precedence=0,
338
+ approximation=0,
339
+ colorspace=None,
340
+ icc_profile=None,
341
+ length=0,
342
+ offset=-1,
327
343
  ):
328
344
  super().__init__()
329
345
 
@@ -345,8 +361,10 @@ class ColourSpecificationBox(Jp2kBox):
345
361
  def _validate(self, writing=False):
346
362
  """Verify that the box obeys the specifications."""
347
363
  if self.colorspace is not None and self.icc_profile is not None:
348
- msg = ("Colorspace and icc_profile cannot both be set when "
349
- "creating a ColourSpecificationBox.")
364
+ msg = (
365
+ "Colorspace and icc_profile cannot both be set when "
366
+ "creating a ColourSpecificationBox."
367
+ )
350
368
  self._dispatch_validation_error(msg, writing=writing)
351
369
 
352
370
  if self.method not in _COLORSPACE_METHODS:
@@ -411,7 +429,7 @@ class ColourSpecificationBox(Jp2kBox):
411
429
 
412
430
  def __str__(self):
413
431
  title = Jp2kBox.__str__(self)
414
- if get_option('print.short') is True:
432
+ if get_option("print.short") is True:
415
433
  return title
416
434
 
417
435
  lst = []
@@ -419,66 +437,65 @@ class ColourSpecificationBox(Jp2kBox):
419
437
  try:
420
438
  item = _COLORSPACE_METHODS[self.method]
421
439
  except KeyError:
422
- item = f'unrecognized value ({self.method})'
423
- text = f'Method: {item}'
440
+ item = f"unrecognized value ({self.method})"
441
+ text = f"Method: {item}"
424
442
 
425
443
  lst.append(text)
426
- text = f'Precedence: {self.precedence}'
444
+ text = f"Precedence: {self.precedence}"
427
445
  lst.append(text)
428
446
 
429
447
  if self.approximation != 0:
430
448
  try:
431
449
  dispvalue = _APPROXIMATION_MEASURES[self.approximation]
432
450
  except KeyError:
433
- dispvalue = f'invalid ({self.approximation})'
434
- text = f'Approximation: {dispvalue}'
451
+ dispvalue = f"invalid ({self.approximation})"
452
+ text = f"Approximation: {dispvalue}"
435
453
  lst.append(text)
436
454
 
437
455
  if self.colorspace is not None:
438
456
  try:
439
457
  dispvalue = _COLORSPACE_MAP_DISPLAY[self.colorspace]
440
458
  except KeyError:
441
- dispvalue = f'{self.colorspace} (unrecognized)'
442
- text = f'Colorspace: {dispvalue}'
459
+ dispvalue = f"{self.colorspace} (unrecognized)"
460
+ text = f"Colorspace: {dispvalue}"
443
461
  else:
444
462
  if self.icc_profile is None:
445
- text = 'ICC Profile: None'
463
+ text = "ICC Profile: None"
446
464
  else:
447
465
  text = pprint.pformat(self.icc_profile_header)
448
- text = textwrap.indent(text, ' ' * 4)
449
- text = '\n'.join(['ICC Profile:', text])
466
+ text = textwrap.indent(text, " " * 4)
467
+ text = "\n".join(["ICC Profile:", text])
450
468
 
451
469
  lst.append(text)
452
470
 
453
- text = '\n'.join(lst)
471
+ text = "\n".join(lst)
454
472
 
455
- text = '\n'.join([title, textwrap.indent(text, ' ' * 4)])
473
+ text = "\n".join([title, textwrap.indent(text, " " * 4)])
456
474
 
457
475
  return text
458
476
 
459
477
  def write(self, fptr):
460
- """Write an Colour Specification box to file.
461
- """
478
+ """Write an Colour Specification box to file."""
462
479
 
463
480
  self._write_validate()
464
481
  length = 15 if self.icc_profile is None else 11 + len(self.icc_profile)
465
- fptr.write(struct.pack('>I4s', length, b'colr'))
482
+ fptr.write(struct.pack(">I4s", length, b"colr"))
466
483
 
467
484
  if self.icc_profile is None:
468
485
 
469
486
  buffer = struct.pack(
470
- '>BBBI',
487
+ ">BBBI",
471
488
  self.method,
472
489
  self.precedence,
473
490
  self.approximation,
474
- self.colorspace
491
+ self.colorspace,
475
492
  )
476
493
  fptr.write(buffer)
477
494
 
478
495
  else:
479
496
 
480
497
  buffer = struct.pack(
481
- '>BBB',
498
+ ">BBB",
482
499
  self.method,
483
500
  self.precedence,
484
501
  self.approximation,
@@ -507,12 +524,12 @@ class ColourSpecificationBox(Jp2kBox):
507
524
  num_bytes = offset + length - fptr.tell()
508
525
  read_buffer = fptr.read(num_bytes)
509
526
 
510
- lst = struct.unpack_from('>BBB', read_buffer, offset=0)
527
+ lst = struct.unpack_from(">BBB", read_buffer, offset=0)
511
528
  method, precedence, approximation = lst
512
529
 
513
530
  if method == 1:
514
531
  # enumerated colour space
515
- colorspace, = struct.unpack_from('>I', read_buffer, offset=3)
532
+ (colorspace,) = struct.unpack_from(">I", read_buffer, offset=3)
516
533
  icc_profile = None
517
534
 
518
535
  else:
@@ -536,7 +553,7 @@ class ColourSpecificationBox(Jp2kBox):
536
553
  colorspace=colorspace,
537
554
  icc_profile=icc_profile,
538
555
  length=length,
539
- offset=offset
556
+ offset=offset,
540
557
  )
541
558
 
542
559
 
@@ -561,8 +578,9 @@ class ChannelDefinitionBox(Jp2kBox):
561
578
  association : list
562
579
  index of the associated color
563
580
  """
564
- box_id = 'cdef'
565
- longname = 'Channel Definition'
581
+
582
+ box_id = "cdef"
583
+ longname = "Channel Definition"
566
584
 
567
585
  def __init__(self, channel_type, association, index=None, **kwargs):
568
586
  super().__init__()
@@ -580,8 +598,10 @@ class ChannelDefinitionBox(Jp2kBox):
580
598
  def _validate(self, writing=False):
581
599
  """Verify that the box obeys the specifications."""
582
600
  # channel type and association must be specified.
583
- if not ((len(self.index) == len(self.channel_type))
584
- and (len(self.channel_type) == len(self.association))):
601
+ if not (
602
+ (len(self.index) == len(self.channel_type))
603
+ and (len(self.channel_type) == len(self.association))
604
+ ):
585
605
  msg = (
586
606
  f"The length of the index ({len(self.index)}), "
587
607
  f"channel_type ({len(self.channel_type)}), "
@@ -604,7 +624,7 @@ class ChannelDefinitionBox(Jp2kBox):
604
624
 
605
625
  def __str__(self):
606
626
  title = Jp2kBox.__str__(self)
607
- if get_option('print.short') is True:
627
+ if get_option("print.short") is True:
608
628
  return title
609
629
 
610
630
  lst = []
@@ -616,13 +636,13 @@ class ChannelDefinitionBox(Jp2kBox):
616
636
  except KeyError:
617
637
  color_type_string = f"invalid ({channel_type})"
618
638
 
619
- association = str(association) if association else 'whole image'
620
- text = f'Channel {index} ({color_type_string}) ==> ({association})'
639
+ association = str(association) if association else "whole image"
640
+ text = f"Channel {index} ({color_type_string}) ==> ({association})"
621
641
  lst.append(text)
622
642
 
623
- text = '\n'.join(lst)
624
- text = textwrap.indent(text, ' ' * 4)
625
- text = '\n'.join([title, text])
643
+ text = "\n".join(lst)
644
+ text = textwrap.indent(text, " " * 4)
645
+ text = "\n".join([title, text])
626
646
  return text
627
647
 
628
648
  def __repr__(self):
@@ -637,14 +657,16 @@ class ChannelDefinitionBox(Jp2kBox):
637
657
  """Write a channel definition box to file."""
638
658
  self._validate(writing=True)
639
659
  num_components = len(self.association)
640
- fptr.write(struct.pack('>I4s', 8 + 2 + num_components * 6, b'cdef'))
641
- fptr.write(struct.pack('>H', num_components))
660
+ fptr.write(struct.pack(">I4s", 8 + 2 + num_components * 6, b"cdef"))
661
+ fptr.write(struct.pack(">H", num_components))
642
662
  for j in range(num_components):
643
- fptr.write(struct.pack(
644
- '>' + 'H' * 3,
645
- self.index[j],
646
- self.channel_type[j],
647
- self.association[j])
663
+ fptr.write(
664
+ struct.pack(
665
+ ">" + "H" * 3,
666
+ self.index[j],
667
+ self.channel_type[j],
668
+ self.association[j],
669
+ )
648
670
  )
649
671
 
650
672
  @classmethod
@@ -669,11 +691,11 @@ class ChannelDefinitionBox(Jp2kBox):
669
691
  read_buffer = fptr.read(num_bytes)
670
692
 
671
693
  # Read the number of components.
672
- num_components, = struct.unpack_from('>H', read_buffer)
694
+ (num_components,) = struct.unpack_from(">H", read_buffer)
695
+
696
+ fmt = ">" + "HHH" * num_components
697
+ data = struct.unpack_from(fmt, read_buffer, offset=2)
673
698
 
674
- data = struct.unpack_from(
675
- '>' + 'HHH' * num_components, read_buffer, offset=2
676
- )
677
699
  index = data[0:num_components * 6:3]
678
700
  channel_type = data[1:num_components * 6:3]
679
701
  association = data[2:num_components * 6:3]
@@ -682,7 +704,8 @@ class ChannelDefinitionBox(Jp2kBox):
682
704
  index=tuple(index),
683
705
  channel_type=tuple(channel_type),
684
706
  association=tuple(association),
685
- length=length, offset=offset
707
+ length=length,
708
+ offset=offset,
686
709
  )
687
710
 
688
711
 
@@ -702,8 +725,9 @@ class CodestreamHeaderBox(Jp2kBox):
702
725
  box : list
703
726
  List of boxes contained in this superbox.
704
727
  """
705
- box_id = 'jpch'
706
- longname = 'Codestream Header'
728
+
729
+ box_id = "jpch"
730
+ longname = "Codestream Header"
707
731
 
708
732
  def __init__(self, box=None, length=0, offset=-1):
709
733
  super().__init__()
@@ -721,7 +745,7 @@ class CodestreamHeaderBox(Jp2kBox):
721
745
 
722
746
  def write(self, fptr):
723
747
  """Write a codestream header box to file."""
724
- self._write_superbox(fptr, b'jpch')
748
+ self._write_superbox(fptr, b"jpch")
725
749
 
726
750
  @classmethod
727
751
  def parse(cls, fptr, offset, length):
@@ -766,8 +790,9 @@ class ColourGroupBox(Jp2kBox):
766
790
  box : list
767
791
  List of boxes contained in this superbox.
768
792
  """
769
- box_id = 'cgrp'
770
- longname = 'Colour Group'
793
+
794
+ box_id = "cgrp"
795
+ longname = "Colour Group"
771
796
 
772
797
  def __init__(self, box=None, length=0, offset=-1):
773
798
  super().__init__()
@@ -785,15 +810,17 @@ class ColourGroupBox(Jp2kBox):
785
810
 
786
811
  def _validate(self, writing=True):
787
812
  """Verify that the box obeys the specifications."""
788
- if any([box.box_id != 'colr' for box in self.box]):
789
- msg = ("Colour group boxes can only contain colour specification "
790
- "boxes.")
813
+ if any([box.box_id != "colr" for box in self.box]):
814
+ msg = (
815
+ "Colour group boxes can only contain colour specification "
816
+ "boxes."
817
+ )
791
818
  self._dispatch_validation_error(msg, writing=writing)
792
819
 
793
820
  def write(self, fptr):
794
821
  """Write a colour group box to file."""
795
822
  self._validate(writing=True)
796
- self._write_superbox(fptr, b'cgrp')
823
+ self._write_superbox(fptr, b"cgrp")
797
824
 
798
825
  @classmethod
799
826
  def parse(cls, fptr, offset, length):
@@ -838,8 +865,9 @@ class CompositingLayerHeaderBox(Jp2kBox):
838
865
  box : list
839
866
  List of boxes contained in this superbox.
840
867
  """
841
- box_id = 'jplh'
842
- longname = 'Compositing Layer Header'
868
+
869
+ box_id = "jplh"
870
+ longname = "Compositing Layer Header"
843
871
 
844
872
  def __init__(self, box=None, length=0, offset=-1):
845
873
  super().__init__()
@@ -857,7 +885,7 @@ class CompositingLayerHeaderBox(Jp2kBox):
857
885
 
858
886
  def write(self, fptr):
859
887
  """Write a compositing layer header box to file."""
860
- self._write_superbox(fptr, b'jplh')
888
+ self._write_superbox(fptr, b"jplh")
861
889
 
862
890
  @classmethod
863
891
  def parse(cls, fptr, offset, length):
@@ -905,11 +933,13 @@ class ComponentMappingBox(Jp2kBox):
905
933
  palette_index : tuple
906
934
  Index component from palette
907
935
  """
908
- box_id = 'cmap'
909
- longname = 'Component Mapping'
910
936
 
911
- def __init__(self, component_index, mapping_type, palette_index,
912
- length=0, offset=-1):
937
+ box_id = "cmap"
938
+ longname = "Component Mapping"
939
+
940
+ def __init__(
941
+ self, component_index, mapping_type, palette_index, length=0, offset=-1
942
+ ):
913
943
  super().__init__()
914
944
  self.component_index = component_index
915
945
  self.mapping_type = mapping_type
@@ -928,7 +958,7 @@ class ComponentMappingBox(Jp2kBox):
928
958
 
929
959
  def __str__(self):
930
960
  title = Jp2kBox.__str__(self)
931
- if get_option('print.short') is True:
961
+ if get_option("print.short") is True:
932
962
  return title
933
963
 
934
964
  lst = []
@@ -938,32 +968,32 @@ class ComponentMappingBox(Jp2kBox):
938
968
  if mapping_type == 1:
939
969
  # palette mapping
940
970
  text = (
941
- f'Component {component_idx} ==> '
942
- f'palette column {palette_idx}'
971
+ f"Component {component_idx} ==> "
972
+ f"palette column {palette_idx}"
943
973
  )
944
974
  else:
945
975
  # Direct use
946
- text = f'Component {component_idx} ==> {k}'
976
+ text = f"Component {component_idx} ==> {k}"
947
977
  lst.append(text)
948
978
 
949
- text = '\n'.join(lst)
950
- text = textwrap.indent(text, ' ' * 4)
951
- text = '\n'.join([title, text])
979
+ text = "\n".join(lst)
980
+ text = textwrap.indent(text, " " * 4)
981
+ text = "\n".join([title, text])
952
982
 
953
983
  return text
954
984
 
955
985
  def write(self, fptr):
956
986
  """Write a Component Mapping box to file."""
957
987
  length = 8 + 4 * len(self.component_index)
958
- write_buffer = struct.pack('>I4s', length, b'cmap')
988
+ write_buffer = struct.pack(">I4s", length, b"cmap")
959
989
  fptr.write(write_buffer)
960
990
 
961
991
  for j in range(len(self.component_index)):
962
992
  write_buffer = struct.pack(
963
- '>HBB',
993
+ ">HBB",
964
994
  self.component_index[j],
965
995
  self.mapping_type[j],
966
- self.palette_index[j]
996
+ self.palette_index[j],
967
997
  )
968
998
  fptr.write(write_buffer)
969
999
 
@@ -989,14 +1019,19 @@ class ComponentMappingBox(Jp2kBox):
989
1019
  num_components = int(num_bytes / 4)
990
1020
 
991
1021
  read_buffer = fptr.read(num_bytes)
992
- data = struct.unpack('>' + 'HBB' * num_components, read_buffer)
1022
+ data = struct.unpack(">" + "HBB" * num_components, read_buffer)
993
1023
 
994
1024
  component_index = data[0:num_bytes:3]
995
1025
  mapping_type = data[1:num_bytes:3]
996
1026
  palette_index = data[2:num_bytes:3]
997
1027
 
998
- return cls(component_index, mapping_type, palette_index,
999
- length=length, offset=offset)
1028
+ return cls(
1029
+ component_index,
1030
+ mapping_type,
1031
+ palette_index,
1032
+ length=length,
1033
+ offset=offset
1034
+ )
1000
1035
 
1001
1036
 
1002
1037
  class ContiguousCodestreamBox(Jp2kBox):
@@ -1018,11 +1053,17 @@ class ContiguousCodestreamBox(Jp2kBox):
1018
1053
  main_header_offset : int
1019
1054
  offset of main header from start of file
1020
1055
  """
1021
- box_id = 'jp2c'
1022
- longname = 'Contiguous Codestream'
1023
1056
 
1024
- def __init__(self, codestream=None, main_header_offset=None, length=0,
1025
- offset=-1):
1057
+ box_id = "jp2c"
1058
+ longname = "Contiguous Codestream"
1059
+
1060
+ def __init__(
1061
+ self,
1062
+ codestream=None,
1063
+ main_header_offset=None,
1064
+ length=0,
1065
+ offset=-1
1066
+ ):
1026
1067
  super().__init__()
1027
1068
  self._codestream = codestream
1028
1069
  self.length = length
@@ -1034,18 +1075,20 @@ class ContiguousCodestreamBox(Jp2kBox):
1034
1075
 
1035
1076
  @property
1036
1077
  def codestream(self):
1037
- if get_option('parse.full_codestream') is True:
1078
+ if get_option("parse.full_codestream") is True:
1038
1079
  header_only = False
1039
1080
  else:
1040
1081
  header_only = True
1041
1082
  if self._codestream is None:
1042
1083
  if self._filename is not None:
1043
- with open(self._filename, 'rb') as fptr:
1084
+ with open(self._filename, "rb") as fptr:
1044
1085
  fptr.seek(self.main_header_offset)
1045
- codestream = Codestream(
1046
- fptr, self.length, header_only=header_only
1086
+ self._codestream = Codestream(
1087
+ fptr,
1088
+ self.length,
1089
+ header_only=header_only
1047
1090
  )
1048
- self._codestream = codestream
1091
+
1049
1092
  return self._codestream
1050
1093
 
1051
1094
  def __repr__(self):
@@ -1055,18 +1098,18 @@ class ContiguousCodestreamBox(Jp2kBox):
1055
1098
 
1056
1099
  def __str__(self):
1057
1100
  title = Jp2kBox.__str__(self)
1058
- if get_option('print.short') is True:
1101
+ if get_option("print.short") is True:
1059
1102
  return title
1060
- if get_option('print.codestream') is False:
1103
+ if get_option("print.codestream") is False:
1061
1104
  return title
1062
1105
 
1063
1106
  lst = []
1064
1107
  for segment in self.codestream.segment:
1065
1108
  lst.append(str(segment))
1066
1109
 
1067
- text = '\n'.join(lst)
1068
- text = textwrap.indent(text, ' ' * 4)
1069
- text = '\n'.join([title, text])
1110
+ text = "\n".join(lst)
1111
+ text = textwrap.indent(text, " " * 4)
1112
+ text = "\n".join([title, text])
1070
1113
  return text
1071
1114
 
1072
1115
  @classmethod
@@ -1088,7 +1131,7 @@ class ContiguousCodestreamBox(Jp2kBox):
1088
1131
  Instance of the current contiguous codestream box.
1089
1132
  """
1090
1133
  main_header_offset = fptr.tell()
1091
- if get_option('parse.full_codestream'):
1134
+ if get_option("parse.full_codestream"):
1092
1135
  codestream = Codestream(fptr, length, header_only=False)
1093
1136
  else:
1094
1137
  codestream = None
@@ -1096,7 +1139,7 @@ class ContiguousCodestreamBox(Jp2kBox):
1096
1139
  codestream,
1097
1140
  main_header_offset=main_header_offset,
1098
1141
  length=length,
1099
- offset=offset
1142
+ offset=offset,
1100
1143
  )
1101
1144
  box._filename = fptr.name
1102
1145
  return box
@@ -1118,8 +1161,9 @@ class DataReferenceBox(Jp2kBox):
1118
1161
  DR : list
1119
1162
  Data Entry URL boxes.
1120
1163
  """
1121
- box_id = 'dtbl'
1122
- longname = 'Data Reference'
1164
+
1165
+ box_id = "dtbl"
1166
+ longname = "Data Reference"
1123
1167
 
1124
1168
  def __init__(self, data_entry_url_boxes=None, length=0, offset=-1):
1125
1169
  super().__init__()
@@ -1134,9 +1178,11 @@ class DataReferenceBox(Jp2kBox):
1134
1178
  def _validate(self, writing=False):
1135
1179
  """Verify that the box obeys the specifications."""
1136
1180
  for box in self.DR:
1137
- if box.box_id != 'url ':
1138
- msg = ('Child boxes of a data reference box can only be data '
1139
- 'entry URL boxes.')
1181
+ if box.box_id != "url ":
1182
+ msg = (
1183
+ "Child boxes of a data reference box can only be data "
1184
+ "entry URL boxes."
1185
+ )
1140
1186
  self._dispatch_validation_error(msg, writing=writing)
1141
1187
 
1142
1188
  def _write_validate(self):
@@ -1153,10 +1199,10 @@ class DataReferenceBox(Jp2kBox):
1153
1199
 
1154
1200
  # Very similar to the way a superbox is written.
1155
1201
  orig_pos = fptr.tell()
1156
- fptr.write(struct.pack('>I4s', 0, b'dtbl'))
1202
+ fptr.write(struct.pack(">I4s", 0, b"dtbl"))
1157
1203
 
1158
1204
  # Write the number of data entry url boxes.
1159
- write_buffer = struct.pack('>H', len(self.DR))
1205
+ write_buffer = struct.pack(">H", len(self.DR))
1160
1206
  fptr.write(write_buffer)
1161
1207
 
1162
1208
  for box in self.DR:
@@ -1164,12 +1210,12 @@ class DataReferenceBox(Jp2kBox):
1164
1210
 
1165
1211
  end_pos = fptr.tell()
1166
1212
  fptr.seek(orig_pos)
1167
- fptr.write(struct.pack('>I', end_pos - orig_pos))
1213
+ fptr.write(struct.pack(">I", end_pos - orig_pos))
1168
1214
  fptr.seek(end_pos)
1169
1215
 
1170
1216
  def __str__(self):
1171
1217
  title = Jp2kBox.__str__(self)
1172
- if get_option('print.short') is True:
1218
+ if get_option("print.short") is True:
1173
1219
  return title
1174
1220
 
1175
1221
  if len(self.DR) == 0:
@@ -1178,14 +1224,14 @@ class DataReferenceBox(Jp2kBox):
1178
1224
  lst = []
1179
1225
  for box in self.DR:
1180
1226
  lst.append(str(box))
1181
- text = '\n'.join(lst)
1182
- text = textwrap.indent(text, ' ' * 4)
1227
+ text = "\n".join(lst)
1228
+ text = textwrap.indent(text, " " * 4)
1183
1229
 
1184
- text = '\n'.join([title, text])
1230
+ text = "\n".join([title, text])
1185
1231
  return text
1186
1232
 
1187
1233
  def __repr__(self):
1188
- msg = 'glymur.jp2box.DataReferenceBox()'
1234
+ msg = "glymur.jp2box.DataReferenceBox()"
1189
1235
  return msg
1190
1236
 
1191
1237
  @classmethod
@@ -1210,7 +1256,7 @@ class DataReferenceBox(Jp2kBox):
1210
1256
  read_buffer = fptr.read(num_bytes)
1211
1257
 
1212
1258
  # Read the number of data references
1213
- ndr, = struct.unpack_from('>H', read_buffer, offset=0)
1259
+ (ndr,) = struct.unpack_from(">H", read_buffer, offset=0)
1214
1260
 
1215
1261
  # Need to keep track of where the next url box starts.
1216
1262
  box_offset = 2
@@ -1220,10 +1266,8 @@ class DataReferenceBox(Jp2kBox):
1220
1266
 
1221
1267
  # Create an in-memory binary stream for each URL box.
1222
1268
  box_fptr = io.BytesIO(read_buffer[box_offset:])
1223
- box_buffer = box_fptr.read(8)
1224
- (box_length, box_id) = struct.unpack_from(
1225
- '>I4s', box_buffer, offset=0
1226
- )
1269
+ buffer = box_fptr.read(8)
1270
+ box_length, box_id = struct.unpack_from(">I4s", buffer, offset=0)
1227
1271
  box = DataEntryURLBox.parse(box_fptr, 0, box_length)
1228
1272
 
1229
1273
  # Need to adjust the box start to that of the "real" file.
@@ -1257,19 +1301,24 @@ class FileTypeBox(Jp2kBox):
1257
1301
  compatibility_list: list
1258
1302
  List of file conformance profiles.
1259
1303
  """
1260
- box_id = 'ftyp'
1261
- longname = 'File Type'
1262
- _valid_cls = ['jp2 ', 'jph ', 'jpx ', 'jpxb']
1304
+
1305
+ box_id = "ftyp"
1306
+ longname = "File Type"
1307
+ _valid_cls = ["jp2 ", "jph ", "jpx ", "jpxb"]
1263
1308
 
1264
1309
  def __init__(
1265
- self, brand='jp2 ', minor_version=0, compatibility_list=None,
1266
- length=0, offset=-1
1310
+ self,
1311
+ brand="jp2 ",
1312
+ minor_version=0,
1313
+ compatibility_list=None,
1314
+ length=0,
1315
+ offset=-1,
1267
1316
  ):
1268
1317
  super().__init__()
1269
1318
  self.brand = brand
1270
1319
  self.minor_version = minor_version
1271
1320
  if compatibility_list is None:
1272
- self.compatibility_list = ['jp2 ']
1321
+ self.compatibility_list = ["jp2 "]
1273
1322
  else:
1274
1323
  self.compatibility_list = compatibility_list
1275
1324
  self.length = length
@@ -1287,19 +1336,19 @@ class FileTypeBox(Jp2kBox):
1287
1336
 
1288
1337
  def __str__(self):
1289
1338
  title = Jp2kBox.__str__(self)
1290
- if get_option('print.short') is True:
1339
+ if get_option("print.short") is True:
1291
1340
  return title
1292
1341
 
1293
1342
  lst = []
1294
- text = f'Brand: {self.brand}'
1343
+ text = f"Brand: {self.brand}"
1295
1344
  lst.append(text)
1296
- text = f'Compatibility: {self.compatibility_list}'
1345
+ text = f"Compatibility: {self.compatibility_list}"
1297
1346
  lst.append(text)
1298
1347
 
1299
- text = '\n'.join(lst)
1300
- text = textwrap.indent(text, ' ' * 4)
1348
+ text = "\n".join(lst)
1349
+ text = textwrap.indent(text, " " * 4)
1301
1350
 
1302
- text = '\n'.join([title, text])
1351
+ text = "\n".join([title, text])
1303
1352
 
1304
1353
  return text
1305
1354
 
@@ -1307,7 +1356,7 @@ class FileTypeBox(Jp2kBox):
1307
1356
  """
1308
1357
  Validate the box before writing to file.
1309
1358
  """
1310
- if self.brand not in ['jp2 ', 'jpx ', 'jph ']:
1359
+ if self.brand not in ["jp2 ", "jpx ", "jph "]:
1311
1360
  msg = (
1312
1361
  f"The file type brand was '{self.brand}'. "
1313
1362
  f"It should be either 'jp2 ', 'jpx ', or 'jph '."
@@ -1333,9 +1382,9 @@ class FileTypeBox(Jp2kBox):
1333
1382
  """Write a File Type box to file."""
1334
1383
  self._validate(writing=True)
1335
1384
  length = 16 + 4 * len(self.compatibility_list)
1336
- fptr.write(struct.pack('>I4s', length, b'ftyp'))
1385
+ fptr.write(struct.pack(">I4s", length, b"ftyp"))
1337
1386
  fptr.write(self.brand.encode())
1338
- fptr.write(struct.pack('>I', self.minor_version))
1387
+ fptr.write(struct.pack(">I", self.minor_version))
1339
1388
 
1340
1389
  for item in self.compatibility_list:
1341
1390
  fptr.write(item.encode())
@@ -1361,16 +1410,16 @@ class FileTypeBox(Jp2kBox):
1361
1410
  num_bytes = offset + length - fptr.tell()
1362
1411
  read_buffer = fptr.read(num_bytes)
1363
1412
  # Extract the brand, minor version.
1364
- (brand, minor_version) = struct.unpack_from('>4sI', read_buffer, 0)
1365
- brand = brand.decode('utf-8')
1413
+ (brand, minor_version) = struct.unpack_from(">4sI", read_buffer, 0)
1414
+ brand = brand.decode("utf-8")
1366
1415
 
1367
1416
  # Extract the compatibility list. Each entry has 4 bytes.
1368
1417
  num_entries = int((length - 16) / 4)
1369
1418
  compatibility_list = []
1370
1419
  for j in range(int(num_entries)):
1371
- entry, = struct.unpack_from('>4s', read_buffer, 8 + j * 4)
1420
+ (entry,) = struct.unpack_from(">4s", read_buffer, 8 + j * 4)
1372
1421
  try:
1373
- entry = entry.decode('utf-8')
1422
+ entry = entry.decode("utf-8")
1374
1423
  except UnicodeDecodeError:
1375
1424
  # The entry is invalid, but we've got code to catch this
1376
1425
  # later on.
@@ -1383,7 +1432,7 @@ class FileTypeBox(Jp2kBox):
1383
1432
  minor_version=minor_version,
1384
1433
  compatibility_list=compatibility_list,
1385
1434
  length=length,
1386
- offset=offset
1435
+ offset=offset,
1387
1436
  )
1388
1437
 
1389
1438
 
@@ -1401,11 +1450,16 @@ class FragmentListBox(Jp2kBox):
1401
1450
  longname : str
1402
1451
  more verbose description of the box.
1403
1452
  """
1404
- box_id = 'flst'
1405
- longname = 'Fragment List'
1453
+
1454
+ box_id = "flst"
1455
+ longname = "Fragment List"
1406
1456
 
1407
1457
  def __init__(
1408
- self, fragment_offset, fragment_length, data_reference, length=0,
1458
+ self,
1459
+ fragment_offset,
1460
+ fragment_length,
1461
+ data_reference,
1462
+ length=0,
1409
1463
  offset=-1
1410
1464
  ):
1411
1465
  super().__init__()
@@ -1418,8 +1472,9 @@ class FragmentListBox(Jp2kBox):
1418
1472
 
1419
1473
  def _validate(self, writing=False):
1420
1474
  """Validate internal correctness."""
1421
- if ((((len(self.fragment_offset) != len(self.fragment_length))
1422
- or (len(self.fragment_length) != len(self.data_reference))))):
1475
+ if (len(self.fragment_offset) != len(self.fragment_length)) or (
1476
+ len(self.fragment_length) != len(self.data_reference)
1477
+ ):
1423
1478
  msg = (
1424
1479
  f"A FragmentListBox at byte offset {self.offset} has invalid "
1425
1480
  f"parameters. The lengths of the fragment offsets, fragment "
@@ -1440,15 +1495,13 @@ class FragmentListBox(Jp2kBox):
1440
1495
  def __repr__(self):
1441
1496
  msg = "glymur.jp2box.FragmentListBox({0}, {1}, {2})"
1442
1497
  msg = msg.format(
1443
- self.fragment_offset,
1444
- self.fragment_length,
1445
- self.data_reference
1498
+ self.fragment_offset, self.fragment_length, self.data_reference
1446
1499
  )
1447
1500
  return msg
1448
1501
 
1449
1502
  def __str__(self):
1450
1503
  title = Jp2kBox.__str__(self)
1451
- if get_option('print.short') is True:
1504
+ if get_option("print.short") is True:
1452
1505
  return title
1453
1506
 
1454
1507
  lst = []
@@ -1460,9 +1513,9 @@ class FragmentListBox(Jp2kBox):
1460
1513
  text = f"Data Reference {j}: {self.data_reference[j]}"
1461
1514
  lst.append(text)
1462
1515
 
1463
- text = '\n'.join(lst)
1464
- text = textwrap.indent(text, ' ' * 4)
1465
- text = '\n'.join([title, text])
1516
+ text = "\n".join(lst)
1517
+ text = textwrap.indent(text, " " * 4)
1518
+ text = "\n".join([title, text])
1466
1519
  return text
1467
1520
 
1468
1521
  def write(self, fptr):
@@ -1470,14 +1523,14 @@ class FragmentListBox(Jp2kBox):
1470
1523
  self._validate(writing=True)
1471
1524
  num_items = len(self.fragment_offset)
1472
1525
  length = 8 + 2 + num_items * 14
1473
- fptr.write(struct.pack('>I4s', length, b'flst'))
1474
- fptr.write(struct.pack('>H', num_items))
1526
+ fptr.write(struct.pack(">I4s", length, b"flst"))
1527
+ fptr.write(struct.pack(">H", num_items))
1475
1528
  for j in range(num_items):
1476
1529
  write_buffer = struct.pack(
1477
- '>QIH',
1530
+ ">QIH",
1478
1531
  self.fragment_offset[j],
1479
1532
  self.fragment_length[j],
1480
- self.data_reference[j]
1533
+ self.data_reference[j],
1481
1534
  )
1482
1535
  fptr.write(write_buffer)
1483
1536
 
@@ -1501,16 +1554,22 @@ class FragmentListBox(Jp2kBox):
1501
1554
  """
1502
1555
  num_bytes = offset + length - fptr.tell()
1503
1556
  read_buffer = fptr.read(num_bytes)
1504
- num_fragments, = struct.unpack_from('>H', read_buffer, offset=0)
1557
+ (num_fragments,) = struct.unpack_from(">H", read_buffer, offset=0)
1558
+
1559
+ fmt = ">" + "QIH" * num_fragments
1560
+ lst = struct.unpack_from(fmt, read_buffer, offset=2)
1505
1561
 
1506
- lst = struct.unpack_from('>' + 'QIH' * num_fragments,
1507
- read_buffer,
1508
- offset=2)
1509
1562
  frag_offset = lst[0::3]
1510
1563
  frag_len = lst[1::3]
1511
1564
  data_reference = lst[2::3]
1512
- return cls(frag_offset, frag_len, data_reference,
1513
- length=length, offset=offset)
1565
+
1566
+ return cls(
1567
+ frag_offset,
1568
+ frag_len,
1569
+ data_reference,
1570
+ length=length,
1571
+ offset=offset
1572
+ )
1514
1573
 
1515
1574
 
1516
1575
  class FragmentTableBox(Jp2kBox):
@@ -1529,8 +1588,9 @@ class FragmentTableBox(Jp2kBox):
1529
1588
  box : list
1530
1589
  List containing exactly one FragmentListBox
1531
1590
  """
1532
- box_id = 'ftbl'
1533
- longname = 'Fragment Table'
1591
+
1592
+ box_id = "ftbl"
1593
+ longname = "Fragment Table"
1534
1594
 
1535
1595
  def __init__(self, box=None, length=0, offset=-1):
1536
1596
  super().__init__()
@@ -1575,15 +1635,17 @@ class FragmentTableBox(Jp2kBox):
1575
1635
  def _validate(self, writing=False):
1576
1636
  """Self-validate the box before writing."""
1577
1637
  box_ids = [box.box_id for box in self.box]
1578
- if len(box_ids) != 1 or box_ids[0] != 'flst':
1579
- msg = ("Fragment table boxes must have a single fragment list "
1580
- "box as a child box.")
1638
+ if len(box_ids) != 1 or box_ids[0] != "flst":
1639
+ msg = (
1640
+ "Fragment table boxes must have a single fragment list "
1641
+ "box as a child box."
1642
+ )
1581
1643
  self._dispatch_validation_error(msg, writing=writing)
1582
1644
 
1583
1645
  def write(self, fptr):
1584
1646
  """Write a fragment table box to file."""
1585
1647
  self._validate(writing=True)
1586
- self._write_superbox(fptr, b'ftbl')
1648
+ self._write_superbox(fptr, b"ftbl")
1587
1649
 
1588
1650
 
1589
1651
  class FreeBox(Jp2kBox):
@@ -1600,8 +1662,9 @@ class FreeBox(Jp2kBox):
1600
1662
  longname : str
1601
1663
  more verbose description of the box.
1602
1664
  """
1603
- box_id = 'free'
1604
- longname = 'Free'
1665
+
1666
+ box_id = "free"
1667
+ longname = "Free"
1605
1668
 
1606
1669
  def __init__(self, length=0, offset=-1):
1607
1670
  super().__init__()
@@ -1668,13 +1731,22 @@ class ImageHeaderBox(Jp2kBox):
1668
1731
  False if the file does not contain intellectual propery rights
1669
1732
  information.
1670
1733
  """
1671
- box_id = 'ihdr'
1672
- longname = 'Image Header'
1734
+
1735
+ box_id = "ihdr"
1736
+ longname = "Image Header"
1673
1737
 
1674
1738
  def __init__(
1675
- self, height, width, num_components=1, signed=False,
1676
- bits_per_component=8, compression=7, colorspace_unknown=False,
1677
- ip_provided=False, length=0, offset=-1
1739
+ self,
1740
+ height,
1741
+ width,
1742
+ num_components=1,
1743
+ signed=False,
1744
+ bits_per_component=8,
1745
+ compression=7,
1746
+ colorspace_unknown=False,
1747
+ ip_provided=False,
1748
+ length=0,
1749
+ offset=-1,
1678
1750
  ):
1679
1751
  """Examples
1680
1752
  --------
@@ -1707,18 +1779,18 @@ class ImageHeaderBox(Jp2kBox):
1707
1779
 
1708
1780
  def __str__(self):
1709
1781
  title = Jp2kBox.__str__(self)
1710
- if get_option('print.short') is True:
1782
+ if get_option("print.short") is True:
1711
1783
  return title
1712
1784
 
1713
1785
  lst = []
1714
1786
 
1715
- text = f'Size: [{self.height} {self.width} {self.num_components}]'
1787
+ text = f"Size: [{self.height} {self.width} {self.num_components}]"
1716
1788
  lst.append(text)
1717
1789
 
1718
- text = f'Bitdepth: {self.bits_per_component}'
1790
+ text = f"Bitdepth: {self.bits_per_component}"
1719
1791
  lst.append(text)
1720
1792
 
1721
- text = f'Signed: {self.signed}'
1793
+ text = f"Signed: {self.signed}"
1722
1794
  lst.append(text)
1723
1795
 
1724
1796
  text = (
@@ -1727,30 +1799,31 @@ class ImageHeaderBox(Jp2kBox):
1727
1799
  )
1728
1800
  lst.append(text)
1729
1801
 
1730
- text = f'Colorspace Unknown: {self.colorspace_unknown}'
1802
+ text = f"Colorspace Unknown: {self.colorspace_unknown}"
1731
1803
  lst.append(text)
1732
1804
 
1733
- text = '\n'.join(lst)
1734
- text = textwrap.indent(text, ' ' * 4)
1735
- text = '\n'.join([title, text])
1805
+ text = "\n".join(lst)
1806
+ text = textwrap.indent(text, " " * 4)
1807
+ text = "\n".join([title, text])
1736
1808
 
1737
1809
  return text
1738
1810
 
1739
1811
  def write(self, fptr):
1740
1812
  """Write an Image Header box to file."""
1741
- fptr.write(struct.pack('>I4s', 22, b'ihdr'))
1813
+ fptr.write(struct.pack(">I4s", 22, b"ihdr"))
1742
1814
 
1743
1815
  # signedness and bps are stored together in a single byte
1744
1816
  bit_depth_signedness = 0x80 if self.signed else 0x00
1745
1817
  bit_depth_signedness |= self.bits_per_component - 1
1746
1818
  read_buffer = struct.pack(
1747
- '>IIHBBBB',
1748
- self.height, self.width,
1819
+ ">IIHBBBB",
1820
+ self.height,
1821
+ self.width,
1749
1822
  self.num_components,
1750
1823
  bit_depth_signedness,
1751
1824
  self.compression,
1752
1825
  1 if self.colorspace_unknown else 0,
1753
- 1 if self.ip_provided else 0
1826
+ 1 if self.ip_provided else 0,
1754
1827
  )
1755
1828
  fptr.write(read_buffer)
1756
1829
 
@@ -1774,23 +1847,28 @@ class ImageHeaderBox(Jp2kBox):
1774
1847
  """
1775
1848
  # Read the box information
1776
1849
  read_buffer = fptr.read(14)
1777
- params = struct.unpack('>IIHBBBB', read_buffer)
1850
+ params = struct.unpack(">IIHBBBB", read_buffer)
1778
1851
  height = params[0]
1779
1852
  width = params[1]
1780
1853
  num_components = params[2]
1781
- bits_per_component = (params[3] & 0x7f) + 1
1854
+ bits_per_component = (params[3] & 0x7F) + 1
1782
1855
  signed = (params[3] & 0x80) > 1
1783
1856
  compression = params[4]
1784
1857
  colorspace_unknown = True if params[5] else False
1785
1858
  ip_provided = True if params[6] else False
1786
1859
 
1787
- return cls(height, width, num_components=num_components,
1788
- bits_per_component=bits_per_component,
1789
- signed=signed,
1790
- compression=compression,
1791
- colorspace_unknown=colorspace_unknown,
1792
- ip_provided=ip_provided,
1793
- length=length, offset=offset)
1860
+ return cls(
1861
+ height,
1862
+ width,
1863
+ num_components=num_components,
1864
+ bits_per_component=bits_per_component,
1865
+ signed=signed,
1866
+ compression=compression,
1867
+ colorspace_unknown=colorspace_unknown,
1868
+ ip_provided=ip_provided,
1869
+ length=length,
1870
+ offset=offset,
1871
+ )
1794
1872
 
1795
1873
 
1796
1874
  class AssociationBox(Jp2kBox):
@@ -1809,8 +1887,9 @@ class AssociationBox(Jp2kBox):
1809
1887
  box : list
1810
1888
  List of boxes contained in this superbox.
1811
1889
  """
1812
- box_id = 'asoc'
1813
- longname = 'Association'
1890
+
1891
+ box_id = "asoc"
1892
+ longname = "Association"
1814
1893
 
1815
1894
  def __init__(self, box=None, length=0, offset=-1):
1816
1895
  super().__init__()
@@ -1828,17 +1907,17 @@ class AssociationBox(Jp2kBox):
1828
1907
  # Is it a GML JP2 box? If so, add the gdal info.
1829
1908
  if (
1830
1909
  len(self.box) == 2
1831
- and self.box[0].box_id == 'lbl '
1832
- and self.box[0].label == 'gml.data'
1833
- and self.box[1].box_id == 'asoc'
1910
+ and self.box[0].box_id == "lbl "
1911
+ and self.box[0].label == "gml.data"
1912
+ and self.box[1].box_id == "asoc"
1834
1913
  and len(self.box[1].box) == 2
1835
- and self.box[1].box[0].box_id == 'lbl '
1836
- and self.box[1].box[0].label == 'gml.root-instance'
1837
- and self.box[1].box[1].box_id == 'xml '
1914
+ and self.box[1].box[0].box_id == "lbl "
1915
+ and self.box[1].box[0].label == "gml.root-instance"
1916
+ and self.box[1].box[1].box_id == "xml "
1838
1917
  ):
1839
1918
  options = gdal.InfoOptions(showColorTable=False)
1840
1919
  txt = gdal.Info(self._filename, options=options)
1841
- txt = textwrap.indent(txt, ' ' * 4)
1920
+ txt = textwrap.indent(txt, " " * 4)
1842
1921
  msg = f"{msg}\n{txt}"
1843
1922
 
1844
1923
  return msg
@@ -1871,7 +1950,7 @@ class AssociationBox(Jp2kBox):
1871
1950
 
1872
1951
  def write(self, fptr):
1873
1952
  """Write an association box to file."""
1874
- self._write_superbox(fptr, b'asoc')
1953
+ self._write_superbox(fptr, b"asoc")
1875
1954
 
1876
1955
 
1877
1956
  class BitsPerComponentBox(Jp2kBox):
@@ -1892,8 +1971,9 @@ class BitsPerComponentBox(Jp2kBox):
1892
1971
  signed : list
1893
1972
  True if signed, false if not, for each component
1894
1973
  """
1895
- box_id = 'bpcc'
1896
- longname = 'Bits Per Component'
1974
+
1975
+ box_id = "bpcc"
1976
+ longname = "Bits Per Component"
1897
1977
 
1898
1978
  def __init__(self, bpc, signed, length=0, offset=-1):
1899
1979
  super().__init__()
@@ -1908,13 +1988,13 @@ class BitsPerComponentBox(Jp2kBox):
1908
1988
 
1909
1989
  def __str__(self):
1910
1990
  title = Jp2kBox.__str__(self)
1911
- if get_option('print.short') is True:
1991
+ if get_option("print.short") is True:
1912
1992
  return title
1913
1993
 
1914
- body = f'Bits per component: {self.bpc}\nSigned: {self.signed}'
1915
- body = textwrap.indent(body, ' ' * 4)
1994
+ body = f"Bits per component: {self.bpc}\nSigned: {self.signed}"
1995
+ body = textwrap.indent(body, " " * 4)
1916
1996
 
1917
- text = '\n'.join([title, body])
1997
+ text = "\n".join([title, body])
1918
1998
  return text
1919
1999
 
1920
2000
  @classmethod
@@ -1937,7 +2017,7 @@ class BitsPerComponentBox(Jp2kBox):
1937
2017
  """
1938
2018
  nbytes = length - 8
1939
2019
  data = fptr.read(nbytes)
1940
- bpc = tuple(((x & 0x7f) + 1) for x in bytearray(data))
2020
+ bpc = tuple(((x & 0x7F) + 1) for x in bytearray(data))
1941
2021
  signed = tuple(((x & 0x80) > 0) for x in bytearray(data))
1942
2022
 
1943
2023
  return cls(bpc, signed, length=length, offset=offset)
@@ -1959,8 +2039,9 @@ class JP2HeaderBox(Jp2kBox):
1959
2039
  box : list
1960
2040
  List of boxes contained in this superbox.
1961
2041
  """
1962
- box_id = 'jp2h'
1963
- longname = 'JP2 Header'
2042
+
2043
+ box_id = "jp2h"
2044
+ longname = "JP2 Header"
1964
2045
 
1965
2046
  def __init__(self, box=None, length=0, offset=-1):
1966
2047
  super().__init__()
@@ -1978,7 +2059,7 @@ class JP2HeaderBox(Jp2kBox):
1978
2059
 
1979
2060
  def write(self, fptr):
1980
2061
  """Write a JP2 Header box to file."""
1981
- self._write_superbox(fptr, b'jp2h')
2062
+ self._write_superbox(fptr, b"jp2h")
1982
2063
 
1983
2064
  @classmethod
1984
2065
  def parse(cls, fptr, offset, length):
@@ -2023,8 +2104,9 @@ class JPEG2000SignatureBox(Jp2kBox):
2023
2104
  signature : tuple
2024
2105
  Four-byte tuple identifying the file as JPEG 2000.
2025
2106
  """
2026
- box_id = 'jP '
2027
- longname = 'JPEG 2000 Signature'
2107
+
2108
+ box_id = "jP "
2109
+ longname = "JPEG 2000 Signature"
2028
2110
 
2029
2111
  def __init__(self, signature=(13, 10, 135, 10), length=0, offset=-1):
2030
2112
  super().__init__()
@@ -2033,23 +2115,23 @@ class JPEG2000SignatureBox(Jp2kBox):
2033
2115
  self.offset = offset
2034
2116
 
2035
2117
  def __repr__(self):
2036
- return 'glymur.jp2box.JPEG2000SignatureBox()'
2118
+ return "glymur.jp2box.JPEG2000SignatureBox()"
2037
2119
 
2038
2120
  def __str__(self):
2039
2121
  title = Jp2kBox.__str__(self)
2040
- if get_option('print.short') is True:
2122
+ if get_option("print.short") is True:
2041
2123
  return title
2042
2124
 
2043
- body = 'Signature: {0:02x}{1:02x}{2:02x}{3:02x}'
2125
+ body = "Signature: {0:02x}{1:02x}{2:02x}{3:02x}"
2044
2126
  body = body.format(*self.signature)
2045
- body = textwrap.indent(body, ' ' * 4)
2046
- text = '\n'.join([title, body])
2127
+ body = textwrap.indent(body, " " * 4)
2128
+ text = "\n".join([title, body])
2047
2129
  return text
2048
2130
 
2049
2131
  def write(self, fptr):
2050
2132
  """Write a JPEG 2000 Signature box to file."""
2051
- fptr.write(struct.pack('>I4s', 12, b'jP '))
2052
- fptr.write(struct.pack('>BBBB', *self.signature))
2133
+ fptr.write(struct.pack(">I4s", 12, b"jP "))
2134
+ fptr.write(struct.pack(">BBBB", *self.signature))
2053
2135
 
2054
2136
  @classmethod
2055
2137
  def parse(cls, fptr, offset, length):
@@ -2070,7 +2152,7 @@ class JPEG2000SignatureBox(Jp2kBox):
2070
2152
  Instance of the current JPEG2000 signature box.
2071
2153
  """
2072
2154
  read_buffer = fptr.read(4)
2073
- signature = struct.unpack('>BBBB', read_buffer)
2155
+ signature = struct.unpack(">BBBB", read_buffer)
2074
2156
 
2075
2157
  return cls(signature=signature, length=length, offset=offset)
2076
2158
 
@@ -2091,11 +2173,18 @@ class PaletteBox(Jp2kBox):
2091
2173
  palette : ndarray
2092
2174
  Colormap array.
2093
2175
  """
2094
- longname = 'Palette'
2095
- box_id = 'pclr'
2096
2176
 
2097
- def __init__(self, palette, bits_per_component, signed, length=0,
2098
- offset=-1):
2177
+ longname = "Palette"
2178
+ box_id = "pclr"
2179
+
2180
+ def __init__(
2181
+ self,
2182
+ palette,
2183
+ bits_per_component,
2184
+ signed,
2185
+ length=0,
2186
+ offset=-1
2187
+ ):
2099
2188
  super().__init__()
2100
2189
  self.palette = palette
2101
2190
  self.bits_per_component = bits_per_component
@@ -2106,8 +2195,9 @@ class PaletteBox(Jp2kBox):
2106
2195
 
2107
2196
  def _validate(self, writing=False):
2108
2197
  """Verify that the box obeys the specifications."""
2109
- if (((len(self.bits_per_component) != len(self.signed))
2110
- or (len(self.signed) != self.palette.shape[1]))):
2198
+ if (len(self.bits_per_component) != len(self.signed)) or (
2199
+ len(self.signed) != self.palette.shape[1]
2200
+ ):
2111
2201
  msg = (
2112
2202
  "The length of the 'bits_per_component' and the 'signed' "
2113
2203
  "members must equal the number of columns of the palette."
@@ -2131,13 +2221,13 @@ class PaletteBox(Jp2kBox):
2131
2221
 
2132
2222
  def __str__(self):
2133
2223
  title = Jp2kBox.__str__(self)
2134
- if get_option('print.short') is True:
2224
+ if get_option("print.short") is True:
2135
2225
  return title
2136
2226
 
2137
- body = f'Size: ({self.palette.shape[0]} x {self.palette.shape[1]})'
2138
- body = textwrap.indent(body, ' ' * 4)
2227
+ body = f"Size: ({self.palette.shape[0]} x {self.palette.shape[1]})"
2228
+ body = textwrap.indent(body, " " * 4)
2139
2229
 
2140
- text = '\n'.join([title, body])
2230
+ text = "\n".join([title, body])
2141
2231
  return text
2142
2232
 
2143
2233
  def write(self, fptr):
@@ -2153,20 +2243,22 @@ class PaletteBox(Jp2kBox):
2153
2243
  box_length = 8 + 3 + self.palette.shape[1] + bytes_per_palette
2154
2244
 
2155
2245
  # Write the usual (L, T) header.
2156
- write_buffer = struct.pack('>I4s', int(box_length), b'pclr')
2246
+ write_buffer = struct.pack(">I4s", int(box_length), b"pclr")
2157
2247
  fptr.write(write_buffer)
2158
2248
 
2159
2249
  # NE, NPC
2160
2250
  write_buffer = struct.pack(
2161
- '>HB', self.palette.shape[0], self.palette.shape[1]
2251
+ ">HB",
2252
+ self.palette.shape[0],
2253
+ self.palette.shape[1]
2162
2254
  )
2163
2255
  fptr.write(write_buffer)
2164
2256
 
2165
2257
  # Bits Per Sample. Signed components aren't supported.
2166
2258
  bps_signed = [x - 1 for x in self.bits_per_component]
2167
- write_buffer = struct.pack(
2168
- '>' + 'B' * self.palette.shape[1], *bps_signed
2169
- )
2259
+
2260
+ fmt = ">" + "B" * self.palette.shape[1]
2261
+ write_buffer = struct.pack(fmt, *bps_signed)
2170
2262
  fptr.write(write_buffer)
2171
2263
 
2172
2264
  # C(i,j)
@@ -2192,11 +2284,12 @@ class PaletteBox(Jp2kBox):
2192
2284
  """
2193
2285
  num_bytes = offset + length - fptr.tell()
2194
2286
  read_buffer = fptr.read(num_bytes)
2195
- nrows, ncols = struct.unpack_from('>HB', read_buffer, offset=0)
2287
+ nrows, ncols = struct.unpack_from(">HB", read_buffer, offset=0)
2288
+
2289
+ fmt = ">" + "B" * ncols
2290
+ bps_signed = struct.unpack_from(fmt, read_buffer, offset=3)
2196
2291
 
2197
- bps_signed = struct.unpack_from('>' + 'B' * ncols, read_buffer,
2198
- offset=3)
2199
- bps = [((x & 0x7f) + 1) for x in bps_signed]
2292
+ bps = [((x & 0x7F) + 1) for x in bps_signed]
2200
2293
  signed = [((x & 0x80) > 1) for x in bps_signed]
2201
2294
 
2202
2295
  # Are any components signed or differently sized? We don't handle
@@ -2221,93 +2314,116 @@ class PaletteBox(Jp2kBox):
2221
2314
 
2222
2315
  # Map rreq codes to display text.
2223
2316
  _READER_REQUIREMENTS_DISPLAY = {
2224
- 0: 'File not completely understood',
2225
- 1: 'Deprecated - contains no extensions',
2226
- 2: 'Contains multiple composition layers',
2227
- 3: ('Deprecated - codestream is compressed using JPEG 2000 and requires '
2228
- 'at least a Profile 0 decoder as defind in ITU-T Rec. T.800 '
2229
- '| ISO/IEC 15444-1, A.10 Table A.45'),
2230
- 4: 'JPEG 2000 Part 1 Profile 1 codestream',
2231
- 5: ('Unrestricted JPEG 2000 Part 1 codestream, ITU-T Rec. T.800 '
2232
- '| ISO/IEC 15444-1'),
2233
- 6: 'Unrestricted JPEG 2000 Part 2 codestream',
2234
- 7: 'JPEG codestream as defined in ISO/IEC 10918-1',
2235
- 8: 'Deprecated - does not contain opacity',
2236
- 9: 'Non-premultiplied opacity channel',
2237
- 10: 'Premultiplied opacity channel',
2238
- 11: 'Chroma-key based opacity',
2239
- 12: 'Deprecated - codestream is contiguous',
2240
- 13: 'Fragmented codestream where all fragments are in file and in order',
2241
- 14: ('Fragmented codestream where all fragments are in file '
2242
- 'but are out of order'),
2243
- 15: ('Fragmented codestream where not all fragments are within the file '
2244
- 'but are all in locally accessible files'),
2245
- 16: ('Fragmented codestream where some fragments may be accessible '
2246
- 'only through a URL specified network connection'),
2247
- 17: ('Compositing required to produce rendered result from multiple '
2248
- 'compositing layers'),
2249
- 18: 'Deprecated - support for compositing is not required',
2250
- 19: ('Deprecated - contains multiple, discrete layers that should not '
2251
- 'be combined through either animation or compositing'),
2252
- 20: ('Deprecated - compositing layers each contain only a single '
2253
- 'codestream'),
2254
- 21: 'At least one compositing layer consists of multiple codestreams',
2255
- 22: 'Deprecated - all compositing layers are in the same colourspace',
2256
- 23: ('Colourspace transformations are required to combine compositing '
2257
- 'layers; not all compositing layers are in the same colourspace'),
2258
- 24: 'Deprecated - rendered result created without using animation',
2259
- 25: ('Deprecated - animated, but first layer covers entire area and is '
2260
- 'opaque'),
2261
- 26: 'First animation layer does not cover entire rendered result',
2262
- 27: 'Deprecated - animated, and no layer is reused',
2263
- 28: 'Reuse of animation layers',
2264
- 29: 'Deprecated - animated, but layers are reused',
2265
- 30: 'Some animated frames are non-persistent',
2266
- 31: 'Deprecated - rendered result created without using scaling',
2267
- 32: 'Rendered result involves scaling within a layer',
2268
- 33: 'Rendered result involves scaling between layers',
2269
- 34: 'ROI metadata',
2270
- 35: 'IPR metadata',
2271
- 36: 'Content metadata',
2272
- 37: 'History metadata',
2273
- 38: 'Creation metadata',
2274
- 39: 'JPX digital signatures',
2275
- 40: 'JPX checksums',
2276
- 41: 'Desires Graphics Arts Reproduction specified',
2277
- 42: 'Deprecated - compositing layer uses palettized colour',
2278
- 43: 'Deprecated - compositing layer uses restricted ICC profile',
2279
- 44: 'Compositing layer uses Any ICC profile',
2280
- 45: 'Deprecated - compositing layer uses sRGB enumerated colourspace',
2281
- 46: 'Deprecated - compositing layer uses sRGB-grey enumerated colourspace',
2282
- 47: 'BiLevel 1 enumerated colourspace',
2283
- 48: 'BiLevel 2 enumerated colourspace',
2284
- 49: 'YCbCr 1 enumerated colourspace',
2285
- 50: 'YCbCr 2 enumerated colourspace',
2286
- 51: 'YCbCr 3 enumerated colourspace',
2287
- 52: 'PhotoYCC enumerated colourspace',
2288
- 53: 'YCCK enumerated colourspace',
2289
- 54: 'CMY enumerated colourspace',
2290
- 55: 'CMYK enumerated colorspace',
2291
- 56: 'CIELab enumerated colourspace with default parameters',
2292
- 57: 'CIELab enumerated colourspace with non-default parameters',
2293
- 58: 'CIEJab enumerated colourspace with default parameters',
2294
- 59: 'CIEJab enumerated colourspace with non-default parameters',
2295
- 60: 'e-sRGB enumerated colorspace',
2296
- 61: 'ROMM_RGB enumerated colorspace',
2297
- 62: 'Non-square samples',
2298
- 63: 'Deprecated - compositing layers have labels',
2299
- 64: 'Deprecated - codestreams have labels',
2300
- 65: 'Deprecated - compositing layers have different colour spaces',
2301
- 66: 'Deprecated - compositing layers have different metadata',
2302
- 67: 'GIS metadata XML box',
2303
- 68: 'JPSEC extensions in codestream as specified by ISO/IEC 15444-8',
2304
- 69: 'JP3D extensions in codestream as specified by ISO/IEC 15444-10',
2305
- 70: 'Deprecated - compositing layer uses sYCC enumerated colour space',
2306
- 71: 'e-sYCC enumerated colourspace',
2307
- 72: ('JPEG 2000 Part 2 codestream as restricted by baseline conformance '
2308
- 'requirements in M.9.2.3'),
2309
- 73: 'YPbPr(1125/60) enumerated colourspace',
2310
- 74: 'YPbPr(1250/50) enumerated colourspace'}
2317
+ 0: "File not completely understood",
2318
+ 1: "Deprecated - contains no extensions",
2319
+ 2: "Contains multiple composition layers",
2320
+ 3: (
2321
+ "Deprecated - codestream is compressed using JPEG 2000 and requires "
2322
+ "at least a Profile 0 decoder as defind in ITU-T Rec. T.800 "
2323
+ "| ISO/IEC 15444-1, A.10 Table A.45"
2324
+ ),
2325
+ 4: "JPEG 2000 Part 1 Profile 1 codestream",
2326
+ 5: (
2327
+ "Unrestricted JPEG 2000 Part 1 codestream, ITU-T Rec. T.800 "
2328
+ "| ISO/IEC 15444-1"
2329
+ ),
2330
+ 6: "Unrestricted JPEG 2000 Part 2 codestream",
2331
+ 7: "JPEG codestream as defined in ISO/IEC 10918-1",
2332
+ 8: "Deprecated - does not contain opacity",
2333
+ 9: "Non-premultiplied opacity channel",
2334
+ 10: "Premultiplied opacity channel",
2335
+ 11: "Chroma-key based opacity",
2336
+ 12: "Deprecated - codestream is contiguous",
2337
+ 13: "Fragmented codestream where all fragments are in file and in order",
2338
+ 14: (
2339
+ "Fragmented codestream where all fragments are in file "
2340
+ "but are out of order"
2341
+ ),
2342
+ 15: (
2343
+ "Fragmented codestream where not all fragments are within the file "
2344
+ "but are all in locally accessible files"
2345
+ ),
2346
+ 16: (
2347
+ "Fragmented codestream where some fragments may be accessible "
2348
+ "only through a URL specified network connection"
2349
+ ),
2350
+ 17: (
2351
+ "Compositing required to produce rendered result from multiple "
2352
+ "compositing layers"
2353
+ ),
2354
+ 18: "Deprecated - support for compositing is not required",
2355
+ 19: (
2356
+ "Deprecated - contains multiple, discrete layers that should not "
2357
+ "be combined through either animation or compositing"
2358
+ ),
2359
+ 20: (
2360
+ "Deprecated - compositing layers each contain only a single "
2361
+ "codestream"
2362
+ ),
2363
+ 21: "At least one compositing layer consists of multiple codestreams",
2364
+ 22: "Deprecated - all compositing layers are in the same colourspace",
2365
+ 23: (
2366
+ "Colourspace transformations are required to combine compositing "
2367
+ "layers; not all compositing layers are in the same colourspace"
2368
+ ),
2369
+ 24: "Deprecated - rendered result created without using animation",
2370
+ 25: (
2371
+ "Deprecated - animated, but first layer covers entire area and is "
2372
+ "opaque"
2373
+ ),
2374
+ 26: "First animation layer does not cover entire rendered result",
2375
+ 27: "Deprecated - animated, and no layer is reused",
2376
+ 28: "Reuse of animation layers",
2377
+ 29: "Deprecated - animated, but layers are reused",
2378
+ 30: "Some animated frames are non-persistent",
2379
+ 31: "Deprecated - rendered result created without using scaling",
2380
+ 32: "Rendered result involves scaling within a layer",
2381
+ 33: "Rendered result involves scaling between layers",
2382
+ 34: "ROI metadata",
2383
+ 35: "IPR metadata",
2384
+ 36: "Content metadata",
2385
+ 37: "History metadata",
2386
+ 38: "Creation metadata",
2387
+ 39: "JPX digital signatures",
2388
+ 40: "JPX checksums",
2389
+ 41: "Desires Graphics Arts Reproduction specified",
2390
+ 42: "Deprecated - compositing layer uses palettized colour",
2391
+ 43: "Deprecated - compositing layer uses restricted ICC profile",
2392
+ 44: "Compositing layer uses Any ICC profile",
2393
+ 45: "Deprecated - compositing layer uses sRGB enumerated colourspace",
2394
+ 46: "Deprecated - compositing layer uses sRGB-grey enumerated colourspace",
2395
+ 47: "BiLevel 1 enumerated colourspace",
2396
+ 48: "BiLevel 2 enumerated colourspace",
2397
+ 49: "YCbCr 1 enumerated colourspace",
2398
+ 50: "YCbCr 2 enumerated colourspace",
2399
+ 51: "YCbCr 3 enumerated colourspace",
2400
+ 52: "PhotoYCC enumerated colourspace",
2401
+ 53: "YCCK enumerated colourspace",
2402
+ 54: "CMY enumerated colourspace",
2403
+ 55: "CMYK enumerated colorspace",
2404
+ 56: "CIELab enumerated colourspace with default parameters",
2405
+ 57: "CIELab enumerated colourspace with non-default parameters",
2406
+ 58: "CIEJab enumerated colourspace with default parameters",
2407
+ 59: "CIEJab enumerated colourspace with non-default parameters",
2408
+ 60: "e-sRGB enumerated colorspace",
2409
+ 61: "ROMM_RGB enumerated colorspace",
2410
+ 62: "Non-square samples",
2411
+ 63: "Deprecated - compositing layers have labels",
2412
+ 64: "Deprecated - codestreams have labels",
2413
+ 65: "Deprecated - compositing layers have different colour spaces",
2414
+ 66: "Deprecated - compositing layers have different metadata",
2415
+ 67: "GIS metadata XML box",
2416
+ 68: "JPSEC extensions in codestream as specified by ISO/IEC 15444-8",
2417
+ 69: "JP3D extensions in codestream as specified by ISO/IEC 15444-10",
2418
+ 70: "Deprecated - compositing layer uses sYCC enumerated colour space",
2419
+ 71: "e-sYCC enumerated colourspace",
2420
+ 72: (
2421
+ "JPEG 2000 Part 2 codestream as restricted by baseline conformance "
2422
+ "requirements in M.9.2.3"
2423
+ ),
2424
+ 73: "YPbPr(1125/60) enumerated colourspace",
2425
+ 74: "YPbPr(1250/50) enumerated colourspace",
2426
+ }
2311
2427
 
2312
2428
 
2313
2429
  class ReaderRequirementsBox(Jp2kBox):
@@ -2338,12 +2454,20 @@ class ReaderRequirementsBox(Jp2kBox):
2338
2454
  Specifies the compatibility mask for each corresponding vendor
2339
2455
  feature.
2340
2456
  """
2341
- box_id = 'rreq'
2342
- longname = 'Reader Requirements'
2457
+
2458
+ box_id = "rreq"
2459
+ longname = "Reader Requirements"
2343
2460
 
2344
2461
  def __init__(
2345
- self, fuam, dcm, standard_flag, standard_mask, vendor_feature,
2346
- vendor_mask, length=0, offset=-1
2462
+ self,
2463
+ fuam,
2464
+ dcm,
2465
+ standard_flag,
2466
+ standard_mask,
2467
+ vendor_feature,
2468
+ vendor_mask,
2469
+ length=0,
2470
+ offset=-1,
2347
2471
  ):
2348
2472
  super().__init__()
2349
2473
  self.fuam = fuam
@@ -2367,53 +2491,53 @@ class ReaderRequirementsBox(Jp2kBox):
2367
2491
  standard_flag=self.standard_flag,
2368
2492
  standard_mask=self.standard_mask,
2369
2493
  vendor_feature=self.vendor_feature,
2370
- vendor_mask=self.vendor_mask
2494
+ vendor_mask=self.vendor_mask,
2371
2495
  )
2372
2496
  return msg
2373
2497
 
2374
2498
  def __str__(self):
2375
2499
  title = Jp2kBox.__str__(self)
2376
- if get_option('print.short') is True:
2500
+ if get_option("print.short") is True:
2377
2501
  return title
2378
2502
 
2379
2503
  lst = []
2380
2504
 
2381
- text = f'Fully Understands Aspect Mask: 0x{self.fuam:x}'
2505
+ text = f"Fully Understands Aspect Mask: 0x{self.fuam:x}"
2382
2506
  lst.append(text)
2383
2507
 
2384
- text = f'Display Completely Mask: 0x{self.dcm:x}'
2508
+ text = f"Display Completely Mask: 0x{self.dcm:x}"
2385
2509
  lst.append(text)
2386
2510
 
2387
- text = 'Standard Features and Masks:'
2511
+ text = "Standard Features and Masks:"
2388
2512
  lst.append(text)
2389
2513
 
2390
2514
  lst2 = []
2391
- text = 'Feature {flag:03d}: 0x{mask:x} {decoded}'
2515
+ text = "Feature {flag:03d}: 0x{mask:x} {decoded}"
2392
2516
  for j in range(len(self.standard_flag)):
2393
2517
  kwargs = {
2394
- 'flag': self.standard_flag[j],
2395
- 'mask': self.standard_mask[j],
2396
- 'decoded': _READER_REQUIREMENTS_DISPLAY[self.standard_flag[j]],
2518
+ "flag": self.standard_flag[j],
2519
+ "mask": self.standard_mask[j],
2520
+ "decoded": _READER_REQUIREMENTS_DISPLAY[self.standard_flag[j]],
2397
2521
  }
2398
2522
  lst2.append(text.format(**kwargs))
2399
- text = '\n'.join(lst2)
2400
- text = textwrap.indent(text, ' ' * 4)
2523
+ text = "\n".join(lst2)
2524
+ text = textwrap.indent(text, " " * 4)
2401
2525
  lst.append(text)
2402
2526
 
2403
- text = 'Vendor Features:'
2527
+ text = "Vendor Features:"
2404
2528
  lst.append(text)
2405
2529
 
2406
2530
  lst2 = []
2407
2531
  for j in range(len(self.vendor_feature)):
2408
- text = f'UUID {self.vendor_feature[j]}'
2532
+ text = f"UUID {self.vendor_feature[j]}"
2409
2533
  lst2.append(text)
2410
- text = '\n'.join(lst2)
2411
- text = textwrap.indent(text, ' ' * 4)
2534
+ text = "\n".join(lst2)
2535
+ text = textwrap.indent(text, " " * 4)
2412
2536
  lst.append(text)
2413
2537
 
2414
- text = '\n'.join(lst)
2415
- text = textwrap.indent(text, ' ' * 4)
2416
- text = '\n'.join([title, text])
2538
+ text = "\n".join(lst)
2539
+ text = textwrap.indent(text, " " * 4)
2540
+ text = "\n".join([title, text])
2417
2541
 
2418
2542
  return text
2419
2543
 
@@ -2437,7 +2561,7 @@ class ReaderRequirementsBox(Jp2kBox):
2437
2561
  """
2438
2562
  num_bytes = offset + length - fptr.tell()
2439
2563
  read_buffer = fptr.read(num_bytes)
2440
- mask_length, = struct.unpack_from('>B', read_buffer, offset=0)
2564
+ (mask_length,) = struct.unpack_from(">B", read_buffer, offset=0)
2441
2565
 
2442
2566
  # Fully Understands Aspect Mask
2443
2567
  # Decodes Completely Mask
@@ -2447,31 +2571,45 @@ class ReaderRequirementsBox(Jp2kBox):
2447
2571
  # The mask length tells us the format string to use when unpacking
2448
2572
  # from the buffer read from file.
2449
2573
  try:
2450
- mask_format = {1: 'B', 2: 'H', 4: 'I', 8: 'Q'}[mask_length]
2451
- fuam, dcm = struct.unpack_from('>' + mask_format * 2, read_buffer,
2452
- offset=1)
2574
+ mask_format = {1: "B", 2: "H", 4: "I", 8: "Q"}[mask_length]
2575
+ fuam, dcm = struct.unpack_from(
2576
+ ">" + mask_format * 2,
2577
+ read_buffer,
2578
+ offset=1
2579
+ )
2453
2580
  std_flg_offset = 1 + 2 * mask_length
2454
- data = _parse_standard_flag(read_buffer[std_flg_offset:],
2455
- mask_length)
2581
+ data = _parse_standard_flag(
2582
+ read_buffer[std_flg_offset:],
2583
+ mask_length
2584
+ )
2456
2585
  standard_flag, standard_mask = data
2457
2586
 
2458
2587
  nflags = len(standard_flag)
2459
2588
  vndr_offset = 1 + 2 * mask_length + 2 + (2 + mask_length) * nflags
2460
- data = _parse_vendor_features(read_buffer[vndr_offset:],
2461
- mask_length)
2589
+ data = _parse_vendor_features(
2590
+ read_buffer[vndr_offset:],
2591
+ mask_length
2592
+ )
2462
2593
  vendor_feature, vendor_mask = data
2463
2594
 
2464
2595
  except KeyError:
2465
2596
  msg = (
2466
- f'The ReaderRequirements box (rreq) has a mask length of '
2467
- f'{mask_length} bytes, but only values of 1, 2, 4, or 8 are '
2468
- f'supported. The box contents will not be interpreted.'
2597
+ f"The ReaderRequirements box (rreq) has a mask length of "
2598
+ f"{mask_length} bytes, but only values of 1, 2, 4, or 8 are "
2599
+ f"supported. The box contents will not be interpreted."
2469
2600
  )
2470
2601
  warnings.warn(msg, UserWarning)
2471
2602
 
2472
- return cls(fuam, dcm, standard_flag, standard_mask,
2473
- vendor_feature, vendor_mask,
2474
- length=length, offset=offset)
2603
+ return cls(
2604
+ fuam,
2605
+ dcm,
2606
+ standard_flag,
2607
+ standard_mask,
2608
+ vendor_feature,
2609
+ vendor_mask,
2610
+ length=length,
2611
+ offset=offset,
2612
+ )
2475
2613
 
2476
2614
 
2477
2615
  def _parse_standard_flag(read_buffer, mask_length):
@@ -2488,14 +2626,14 @@ def _parse_standard_flag(read_buffer, mask_length):
2488
2626
  """
2489
2627
  # The mask length tells us the format string to use when unpacking
2490
2628
  # from the buffer read from file.
2491
- mask_format = {1: 'B', 2: 'H', 4: 'I'}[mask_length]
2629
+ mask_format = {1: "B", 2: "H", 4: "I"}[mask_length]
2492
2630
 
2493
- num_standard_flags, = struct.unpack_from('>H', read_buffer, offset=0)
2631
+ (num_standard_flags,) = struct.unpack_from(">H", read_buffer, offset=0)
2494
2632
 
2495
2633
  # Read in standard flags and standard masks. Each standard flag should
2496
2634
  # be two bytes, but the standard mask flag is as long as specified by
2497
2635
  # the mask length.
2498
- fmt = '>' + ('H' + mask_format) * num_standard_flags
2636
+ fmt = ">" + ("H" + mask_format) * num_standard_flags
2499
2637
  data = struct.unpack_from(fmt, read_buffer, offset=2)
2500
2638
 
2501
2639
  standard_flag = data[0:num_standard_flags * 2:2]
@@ -2518,9 +2656,9 @@ def _parse_vendor_features(read_buffer, mask_length):
2518
2656
  """
2519
2657
  # The mask length tells us the format string to use when unpacking
2520
2658
  # from the buffer read from file.
2521
- mask_format = {1: 'B', 2: 'H', 4: 'I'}[mask_length]
2659
+ mask_format = {1: "B", 2: "H", 4: "I"}[mask_length]
2522
2660
 
2523
- num_vendor_features, = struct.unpack_from('>H', read_buffer)
2661
+ (num_vendor_features,) = struct.unpack_from(">H", read_buffer)
2524
2662
 
2525
2663
  # Each vendor feature consists of a 16-byte UUID plus a mask whose
2526
2664
  # length is specified by, you guessed it, "mask_length".
@@ -2532,7 +2670,7 @@ def _parse_vendor_features(read_buffer, mask_length):
2532
2670
  ubuffer = read_buffer[uslice]
2533
2671
  vendor_feature.append(UUID(bytes=ubuffer[0:16]))
2534
2672
 
2535
- vmask = struct.unpack('>' + mask_format, ubuffer[16:])
2673
+ vmask = struct.unpack(">" + mask_format, ubuffer[16:])
2536
2674
  vendor_mask.append(vmask)
2537
2675
 
2538
2676
  return vendor_feature, vendor_mask
@@ -2554,8 +2692,9 @@ class ResolutionBox(Jp2kBox):
2554
2692
  box : list
2555
2693
  List of boxes contained in this superbox.
2556
2694
  """
2557
- box_id = 'res '
2558
- longname = 'Resolution'
2695
+
2696
+ box_id = "res "
2697
+ longname = "Resolution"
2559
2698
 
2560
2699
  def __init__(self, box=None, length=0, offset=-1):
2561
2700
  super().__init__()
@@ -2573,7 +2712,7 @@ class ResolutionBox(Jp2kBox):
2573
2712
 
2574
2713
  def write(self, fptr):
2575
2714
  """Write a Resolution super box to file."""
2576
- self._write_superbox(fptr, b'res ')
2715
+ self._write_superbox(fptr, b"res ")
2577
2716
 
2578
2717
  @classmethod
2579
2718
  def parse(cls, fptr, offset, length):
@@ -2618,11 +2757,16 @@ class CaptureResolutionBox(Jp2kBox):
2618
2757
  vertical_resolution, horizontal_resolution : float
2619
2758
  Vertical, horizontal resolution.
2620
2759
  """
2621
- box_id = 'resc'
2622
- longname = 'Capture Resolution'
2760
+
2761
+ box_id = "resc"
2762
+ longname = "Capture Resolution"
2623
2763
 
2624
2764
  def __init__(
2625
- self, vertical_resolution, horizontal_resolution, length=0, offset=-1
2765
+ self,
2766
+ vertical_resolution,
2767
+ horizontal_resolution,
2768
+ length=0,
2769
+ offset=-1
2626
2770
  ):
2627
2771
  super().__init__()
2628
2772
  self.vertical_resolution = vertical_resolution
@@ -2639,19 +2783,19 @@ class CaptureResolutionBox(Jp2kBox):
2639
2783
 
2640
2784
  def __str__(self):
2641
2785
  title = Jp2kBox.__str__(self)
2642
- if get_option('print.short') is True:
2786
+ if get_option("print.short") is True:
2643
2787
  return title
2644
2788
 
2645
2789
  lst = []
2646
- text = f'VCR: {self.vertical_resolution}'
2790
+ text = f"VCR: {self.vertical_resolution}"
2647
2791
  lst.append(text)
2648
- text = f'HCR: {self.horizontal_resolution}'
2792
+ text = f"HCR: {self.horizontal_resolution}"
2649
2793
  lst.append(text)
2650
2794
 
2651
- text = '\n'.join(lst)
2652
- text = textwrap.indent(text, ' ' * 4)
2795
+ text = "\n".join(lst)
2796
+ text = textwrap.indent(text, " " * 4)
2653
2797
 
2654
- text = '\n'.join([title, text])
2798
+ text = "\n".join([title, text])
2655
2799
  return text
2656
2800
 
2657
2801
  @classmethod
@@ -2673,9 +2817,9 @@ class CaptureResolutionBox(Jp2kBox):
2673
2817
  Instance of the current capture resolution box.
2674
2818
  """
2675
2819
  read_buffer = fptr.read(10)
2676
- (rn1, rd1, rn2, rd2, re1, re2) = struct.unpack('>HHHHbb', read_buffer)
2677
- vres = rn1 / rd1 * 10 ** re1
2678
- hres = rn2 / rd2 * 10 ** re2
2820
+ (rn1, rd1, rn2, rd2, re1, re2) = struct.unpack(">HHHHbb", read_buffer)
2821
+ vres = rn1 / rd1 * 10**re1
2822
+ hres = rn2 / rd2 * 10**re2
2679
2823
 
2680
2824
  return cls(vres, hres, length=length, offset=offset)
2681
2825
 
@@ -2685,12 +2829,12 @@ class CaptureResolutionBox(Jp2kBox):
2685
2829
  # 4 bytes for length, 4 for the ID, always 10 bytes for the payload
2686
2830
  length = 18
2687
2831
 
2688
- fptr.write(struct.pack('>I4s', length, b'resc'))
2832
+ fptr.write(struct.pack(">I4s", length, b"resc"))
2689
2833
 
2690
2834
  re1, rn1, rd1 = decompose_resolution(self.vertical_resolution)
2691
2835
  re2, rn2, rd2 = decompose_resolution(self.horizontal_resolution)
2692
2836
 
2693
- buffer = struct.pack('>HHHHbb', rn1, rd1, rn2, rd2, re1, re2)
2837
+ buffer = struct.pack(">HHHHbb", rn1, rd1, rn2, rd2, re1, re2)
2694
2838
  fptr.write(buffer)
2695
2839
 
2696
2840
 
@@ -2710,11 +2854,16 @@ class DisplayResolutionBox(Jp2kBox):
2710
2854
  vertical_resolution, horizontal_resolution : float
2711
2855
  Vertical, horizontal resolution.
2712
2856
  """
2713
- box_id = 'resd'
2714
- longname = 'Display Resolution'
2857
+
2858
+ box_id = "resd"
2859
+ longname = "Display Resolution"
2715
2860
 
2716
2861
  def __init__(
2717
- self, vertical_resolution, horizontal_resolution, length=0, offset=-1
2862
+ self,
2863
+ vertical_resolution,
2864
+ horizontal_resolution,
2865
+ length=0,
2866
+ offset=-1
2718
2867
  ):
2719
2868
  super().__init__()
2720
2869
  self.vertical_resolution = vertical_resolution
@@ -2729,19 +2878,19 @@ class DisplayResolutionBox(Jp2kBox):
2729
2878
 
2730
2879
  def __str__(self):
2731
2880
  title = Jp2kBox.__str__(self)
2732
- if get_option('print.short') is True:
2881
+ if get_option("print.short") is True:
2733
2882
  return title
2734
2883
 
2735
2884
  lst = []
2736
- text = f'VDR: {self.vertical_resolution}'
2885
+ text = f"VDR: {self.vertical_resolution}"
2737
2886
  lst.append(text)
2738
- text = f'HDR: {self.horizontal_resolution}'
2887
+ text = f"HDR: {self.horizontal_resolution}"
2739
2888
  lst.append(text)
2740
2889
 
2741
- text = '\n'.join(lst)
2742
- text = textwrap.indent(text, ' ' * 4)
2890
+ text = "\n".join(lst)
2891
+ text = textwrap.indent(text, " " * 4)
2743
2892
 
2744
- text = '\n'.join([title, text])
2893
+ text = "\n".join([title, text])
2745
2894
  return text
2746
2895
 
2747
2896
  @classmethod
@@ -2764,9 +2913,9 @@ class DisplayResolutionBox(Jp2kBox):
2764
2913
  """
2765
2914
 
2766
2915
  read_buffer = fptr.read(10)
2767
- (rn1, rd1, rn2, rd2, re1, re2) = struct.unpack('>HHHHbb', read_buffer)
2768
- vres = rn1 / rd1 * 10 ** re1
2769
- hres = rn2 / rd2 * 10 ** re2
2916
+ (rn1, rd1, rn2, rd2, re1, re2) = struct.unpack(">HHHHbb", read_buffer)
2917
+ vres = rn1 / rd1 * 10**re1
2918
+ hres = rn2 / rd2 * 10**re2
2770
2919
 
2771
2920
  return cls(vres, hres, length=length, offset=offset)
2772
2921
 
@@ -2776,12 +2925,12 @@ class DisplayResolutionBox(Jp2kBox):
2776
2925
  # 4 bytes for length, 4 for the ID, always 10 bytes for the payload
2777
2926
  length = 18
2778
2927
 
2779
- fptr.write(struct.pack('>I4s', length, b'resd'))
2928
+ fptr.write(struct.pack(">I4s", length, b"resd"))
2780
2929
 
2781
2930
  re1, rn1, rd1 = decompose_resolution(self.vertical_resolution)
2782
2931
  re2, rn2, rd2 = decompose_resolution(self.horizontal_resolution)
2783
2932
 
2784
- buffer = struct.pack('>HHHHbb', rn1, rd1, rn2, rd2, re1, re2)
2933
+ buffer = struct.pack(">HHHHbb", rn1, rd1, rn2, rd2, re1, re2)
2785
2934
  fptr.write(buffer)
2786
2935
 
2787
2936
 
@@ -2801,8 +2950,9 @@ class LabelBox(Jp2kBox):
2801
2950
  label : str
2802
2951
  Textual label.
2803
2952
  """
2804
- box_id = 'lbl '
2805
- longname = 'Label'
2953
+
2954
+ box_id = "lbl "
2955
+ longname = "Label"
2806
2956
 
2807
2957
  def __init__(self, label, length=0, offset=-1):
2808
2958
  super().__init__()
@@ -2812,13 +2962,13 @@ class LabelBox(Jp2kBox):
2812
2962
 
2813
2963
  def __str__(self):
2814
2964
  title = Jp2kBox.__str__(self)
2815
- if get_option('print.short') is True:
2965
+ if get_option("print.short") is True:
2816
2966
  return title
2817
2967
 
2818
- text = f'Label: {self.label}'
2819
- text = textwrap.indent(text, ' ' * 4)
2968
+ text = f"Label: {self.label}"
2969
+ text = textwrap.indent(text, " " * 4)
2820
2970
 
2821
- text = '\n'.join([title, text])
2971
+ text = "\n".join([title, text])
2822
2972
  return text
2823
2973
 
2824
2974
  def __repr__(self):
@@ -2828,7 +2978,7 @@ class LabelBox(Jp2kBox):
2828
2978
  def write(self, fptr):
2829
2979
  """Write a Label box to file."""
2830
2980
  length = 8 + len(self.label.encode())
2831
- fptr.write(struct.pack('>I4s', length, b'lbl '))
2981
+ fptr.write(struct.pack(">I4s", length, b"lbl "))
2832
2982
  fptr.write(self.label.encode())
2833
2983
 
2834
2984
  @classmethod
@@ -2851,7 +3001,7 @@ class LabelBox(Jp2kBox):
2851
3001
  """
2852
3002
  num_bytes = offset + length - fptr.tell()
2853
3003
  read_buffer = fptr.read(num_bytes)
2854
- label = read_buffer.decode('utf-8')
3004
+ label = read_buffer.decode("utf-8")
2855
3005
  return cls(label, length=length, offset=offset)
2856
3006
 
2857
3007
 
@@ -2872,8 +3022,9 @@ class NumberListBox(Jp2kBox):
2872
3022
  Descriptors of an entity with which the data contained within the same
2873
3023
  Association box is associated.
2874
3024
  """
2875
- box_id = 'nlst'
2876
- longname = 'Number List'
3025
+
3026
+ box_id = "nlst"
3027
+ longname = "Number List"
2877
3028
 
2878
3029
  def __init__(self, associations, length=0, offset=-1):
2879
3030
  super().__init__()
@@ -2883,32 +3034,32 @@ class NumberListBox(Jp2kBox):
2883
3034
 
2884
3035
  def __str__(self):
2885
3036
  title = Jp2kBox.__str__(self)
2886
- if get_option('print.short') is True:
3037
+ if get_option("print.short") is True:
2887
3038
  return title
2888
3039
 
2889
3040
  lst = []
2890
3041
  for j, association in enumerate(self.associations):
2891
- text = f'Association[{j}]: '
3042
+ text = f"Association[{j}]: "
2892
3043
  if association == 0:
2893
- text += 'the rendered result'
3044
+ text += "the rendered result"
2894
3045
  elif (association >> 24) == 1:
2895
3046
  idx = association & 0x00FFFFFF
2896
- text += f'codestream {idx}'
3047
+ text += f"codestream {idx}"
2897
3048
  elif (association >> 24) == 2:
2898
3049
  idx = association & 0x00FFFFFF
2899
- text += f'compositing layer {idx}'
3050
+ text += f"compositing layer {idx}"
2900
3051
  else:
2901
- text += 'unrecognized'
3052
+ text += "unrecognized"
2902
3053
  lst.append(text)
2903
3054
 
2904
- body = '\n'.join(lst)
2905
- body = textwrap.indent(body, ' ' * 4)
3055
+ body = "\n".join(lst)
3056
+ body = textwrap.indent(body, " " * 4)
2906
3057
 
2907
- text = '\n'.join([title, body])
3058
+ text = "\n".join([title, body])
2908
3059
  return text
2909
3060
 
2910
3061
  def __repr__(self):
2911
- msg = f'glymur.jp2box.NumberListBox(associations={self.associations})'
3062
+ msg = f"glymur.jp2box.NumberListBox(associations={self.associations})"
2912
3063
  return msg
2913
3064
 
2914
3065
  @classmethod
@@ -2932,15 +3083,14 @@ class NumberListBox(Jp2kBox):
2932
3083
  num_bytes = offset + length - fptr.tell()
2933
3084
  raw_data = fptr.read(num_bytes)
2934
3085
  num_associations = int(len(raw_data) / 4)
2935
- lst = struct.unpack('>' + 'I' * num_associations, raw_data)
3086
+ lst = struct.unpack(">" + "I" * num_associations, raw_data)
2936
3087
  return cls(lst, length=length, offset=offset)
2937
3088
 
2938
3089
  def write(self, fptr):
2939
3090
  """Write a NumberList box to file."""
2940
- fptr.write(struct.pack('>I4s',
2941
- len(self.associations) * 4 + 8, b'nlst'))
3091
+ fptr.write(struct.pack(">I4s", len(self.associations) * 4 + 8, b"nlst"))
2942
3092
 
2943
- fmt = '>' + 'I' * len(self.associations)
3093
+ fmt = ">" + "I" * len(self.associations)
2944
3094
  write_buffer = struct.pack(fmt, *self.associations)
2945
3095
  fptr.write(write_buffer)
2946
3096
 
@@ -2961,8 +3111,9 @@ class XMLBox(Jp2kBox):
2961
3111
  xml : ElementTree object
2962
3112
  XML section.
2963
3113
  """
2964
- box_id = 'xml '
2965
- longname = 'XML'
3114
+
3115
+ box_id = "xml "
3116
+ longname = "XML"
2966
3117
 
2967
3118
  def __init__(self, xml=None, filename=None, length=0, offset=-1):
2968
3119
  """Parameters
@@ -2992,26 +3143,28 @@ class XMLBox(Jp2kBox):
2992
3143
 
2993
3144
  def __str__(self):
2994
3145
  title = Jp2kBox.__str__(self)
2995
- if get_option('print.short') is True:
3146
+ if get_option("print.short") is True:
2996
3147
  return title
2997
- if get_option('print.xml') is False:
3148
+ if get_option("print.xml") is False:
2998
3149
  return title
2999
3150
 
3000
3151
  if self.xml is not None:
3001
- body = ET.tostring(self.xml,
3002
- encoding='utf-8',
3003
- pretty_print=True).decode('utf-8').rstrip()
3152
+ body = (
3153
+ ET.tostring(self.xml, encoding="utf-8", pretty_print=True)
3154
+ .decode("utf-8")
3155
+ .rstrip()
3156
+ )
3004
3157
  else:
3005
- body = 'None'
3006
- body = textwrap.indent(body, ' ' * 4)
3158
+ body = "None"
3159
+ body = textwrap.indent(body, " " * 4)
3007
3160
 
3008
- text = '\n'.join([title, body])
3161
+ text = "\n".join([title, body])
3009
3162
  return text
3010
3163
 
3011
3164
  def write(self, fptr):
3012
3165
  """Write an XML box to file."""
3013
- read_buffer = ET.tostring(self.xml.getroot(), encoding='utf-8')
3014
- fptr.write(struct.pack('>I4s', len(read_buffer) + 8, b'xml '))
3166
+ read_buffer = ET.tostring(self.xml.getroot(), encoding="utf-8")
3167
+ fptr.write(struct.pack(">I4s", len(read_buffer) + 8, b"xml "))
3015
3168
  fptr.write(read_buffer)
3016
3169
 
3017
3170
  @classmethod
@@ -3036,48 +3189,48 @@ class XMLBox(Jp2kBox):
3036
3189
  read_buffer = fptr.read(num_bytes)
3037
3190
 
3038
3191
  try:
3039
- text = read_buffer.decode('utf-8')
3192
+ text = read_buffer.decode("utf-8")
3040
3193
  except UnicodeDecodeError as err:
3041
3194
  # Possibly bad string of bytes to begin with.
3042
3195
  # Try to search for <?xml and go from there.
3043
- decl_start = read_buffer.find(b'<?xml')
3196
+ decl_start = read_buffer.find(b"<?xml")
3044
3197
  if decl_start <= -1:
3045
3198
  # Nope, that's not it. All is lost.
3046
3199
  msg = (
3047
- f'A problem was encountered while parsing an XML box:'
3048
- f'\n\n\t'
3200
+ f"A problem was encountered while parsing an XML box:"
3201
+ f"\n\n\t"
3049
3202
  f'"{str(err)}"'
3050
- f'\n\n'
3051
- f'No XML was retrieved.'
3203
+ f"\n\n"
3204
+ f"No XML was retrieved."
3052
3205
  )
3053
3206
  warnings.warn(msg, UserWarning)
3054
3207
  return XMLBox(xml=None, length=length, offset=offset)
3055
3208
 
3056
- text = read_buffer[decl_start:].decode('utf-8')
3209
+ text = read_buffer[decl_start:].decode("utf-8")
3057
3210
 
3058
3211
  # Let the user know that the XML box was problematic.
3059
3212
  msg = (
3060
- f'A UnicodeDecodeError was encountered parsing an XML box '
3061
- f'at byte position {offset:d} ({err.reason}), but the XML was '
3062
- f'still recovered.'
3213
+ f"A UnicodeDecodeError was encountered parsing an XML box "
3214
+ f"at byte position {offset:d} ({err.reason}), but the XML was "
3215
+ f"still recovered."
3063
3216
  )
3064
3217
  warnings.warn(msg, UserWarning)
3065
3218
 
3066
3219
  # Strip out any trailing nulls, as they can foul up XML parsing.
3067
3220
  text = text.rstrip(chr(0))
3068
- f = io.BytesIO(text.encode('utf-8'))
3221
+ f = io.BytesIO(text.encode("utf-8"))
3069
3222
 
3070
3223
  try:
3071
3224
  xml = ET.parse(f)
3072
3225
  except ET.ParseError as err:
3073
3226
  exc_type, _, _ = sys.exc_info()
3074
3227
  msg = (
3075
- f'{exc_type.__name__} encountered while parsing an XML '
3076
- f'box at byte offset {offset:d}:'
3077
- f'\n\n\t'
3228
+ f"{exc_type.__name__} encountered while parsing an XML "
3229
+ f"box at byte offset {offset:d}:"
3230
+ f"\n\n\t"
3078
3231
  f'"{err}"'
3079
- f'\n\n'
3080
- f'No XML was retrieved.'
3232
+ f"\n\n"
3233
+ f"No XML was retrieved."
3081
3234
  )
3082
3235
  warnings.warn(msg, UserWarning)
3083
3236
  xml = None
@@ -3101,8 +3254,9 @@ class UUIDListBox(Jp2kBox):
3101
3254
  ulst : list
3102
3255
  List of UUIDs.
3103
3256
  """
3104
- box_id = 'ulst'
3105
- longname = 'UUID List'
3257
+
3258
+ box_id = "ulst"
3259
+ longname = "UUID List"
3106
3260
 
3107
3261
  def __init__(self, ulst, length=0, offset=-1):
3108
3262
  super().__init__()
@@ -3116,24 +3270,24 @@ class UUIDListBox(Jp2kBox):
3116
3270
 
3117
3271
  def __str__(self):
3118
3272
  title = Jp2kBox.__str__(self)
3119
- if get_option('print.short') is True:
3273
+ if get_option("print.short") is True:
3120
3274
  return title
3121
3275
 
3122
3276
  lst = []
3123
3277
  for j, uuid_item in enumerate(self.ulst):
3124
- text = f'UUID[{j}]: {uuid_item}'
3278
+ text = f"UUID[{j}]: {uuid_item}"
3125
3279
  lst.append(text)
3126
- body = '\n'.join(lst)
3127
- body = textwrap.indent(body, ' ' * 4)
3280
+ body = "\n".join(lst)
3281
+ body = textwrap.indent(body, " " * 4)
3128
3282
 
3129
- text = '\n'.join([title, body])
3283
+ text = "\n".join([title, body])
3130
3284
  return text
3131
3285
 
3132
3286
  def write(self, fptr):
3133
3287
  """Write a UUID list box to file."""
3134
3288
  num_uuids = len(self.ulst)
3135
3289
  length = 4 + 4 + 2 + num_uuids * 16
3136
- write_buffer = struct.pack('>I4sH', length, b'ulst', num_uuids)
3290
+ write_buffer = struct.pack(">I4sH", length, b"ulst", num_uuids)
3137
3291
  fptr.write(write_buffer)
3138
3292
 
3139
3293
  for j in range(num_uuids):
@@ -3160,7 +3314,7 @@ class UUIDListBox(Jp2kBox):
3160
3314
  num_bytes = offset + length - fptr.tell()
3161
3315
  read_buffer = fptr.read(num_bytes)
3162
3316
 
3163
- num_uuids, = struct.unpack_from('>H', read_buffer)
3317
+ (num_uuids,) = struct.unpack_from(">H", read_buffer)
3164
3318
 
3165
3319
  ulst = []
3166
3320
  for j in range(num_uuids):
@@ -3186,8 +3340,9 @@ class UUIDInfoBox(Jp2kBox):
3186
3340
  box : list
3187
3341
  List of boxes contained in this superbox.
3188
3342
  """
3189
- box_id = 'uinf'
3190
- longname = 'UUIDInfo'
3343
+
3344
+ box_id = "uinf"
3345
+ longname = "UUIDInfo"
3191
3346
 
3192
3347
  def __init__(self, box=None, length=0, offset=-1):
3193
3348
  super().__init__()
@@ -3205,7 +3360,7 @@ class UUIDInfoBox(Jp2kBox):
3205
3360
 
3206
3361
  def write(self, fptr):
3207
3362
  """Write a UUIDInfo box to file."""
3208
- self._write_superbox(fptr, b'uinf')
3363
+ self._write_superbox(fptr, b"uinf")
3209
3364
 
3210
3365
  @classmethod
3211
3366
  def parse(cls, fptr, offset, length):
@@ -3255,8 +3410,9 @@ class DataEntryURLBox(Jp2kBox):
3255
3410
  URL : str
3256
3411
  Associated URL.
3257
3412
  """
3258
- box_id = 'url '
3259
- longname = 'Data Entry URL'
3413
+
3414
+ box_id = "url "
3415
+ longname = "Data Entry URL"
3260
3416
 
3261
3417
  def __init__(self, version, flag, url, length=0, offset=-1):
3262
3418
  super().__init__()
@@ -3276,10 +3432,13 @@ class DataEntryURLBox(Jp2kBox):
3276
3432
 
3277
3433
  length = 8 + 1 + 3 + len(url)
3278
3434
  write_buffer = struct.pack(
3279
- '>I4sBBBB',
3280
- length, b'url ',
3435
+ ">I4sBBBB",
3436
+ length,
3437
+ b"url ",
3281
3438
  self.version,
3282
- self.flag[0], self.flag[1], self.flag[2]
3439
+ self.flag[0],
3440
+ self.flag[1],
3441
+ self.flag[2],
3283
3442
  )
3284
3443
  fptr.write(write_buffer)
3285
3444
  fptr.write(url)
@@ -3291,17 +3450,17 @@ class DataEntryURLBox(Jp2kBox):
3291
3450
 
3292
3451
  def __str__(self):
3293
3452
  title = Jp2kBox.__str__(self)
3294
- if get_option('print.short') is True:
3453
+ if get_option("print.short") is True:
3295
3454
  return title
3296
3455
 
3297
3456
  body = (
3298
- f'Version: {self.version}\n'
3299
- f'Flag: {self.flag[0]} {self.flag[1]} {self.flag[2]}\n'
3457
+ f"Version: {self.version}\n"
3458
+ f"Flag: {self.flag[0]} {self.flag[1]} {self.flag[2]}\n"
3300
3459
  f'URL: "{self.url}"'
3301
3460
  )
3302
- body = textwrap.indent(body, ' ' * 4)
3461
+ body = textwrap.indent(body, " " * 4)
3303
3462
 
3304
- text = '\n'.join([title, body])
3463
+ text = "\n".join([title, body])
3305
3464
  return text
3306
3465
 
3307
3466
  @classmethod
@@ -3324,11 +3483,11 @@ class DataEntryURLBox(Jp2kBox):
3324
3483
  """
3325
3484
  num_bytes = offset + length - fptr.tell()
3326
3485
  read_buffer = fptr.read(num_bytes)
3327
- data = struct.unpack_from('>BBBB', read_buffer)
3486
+ data = struct.unpack_from(">BBBB", read_buffer)
3328
3487
  version = data[0]
3329
3488
  flag = data[1:4]
3330
3489
 
3331
- url = read_buffer[4:].decode('utf-8').rstrip(chr(0))
3490
+ url = read_buffer[4:].decode("utf-8").rstrip(chr(0))
3332
3491
  return cls(version, flag, url, length=length, offset=offset)
3333
3492
 
3334
3493
 
@@ -3350,10 +3509,11 @@ class UnknownBox(Jp2kBox):
3350
3509
  longname : str
3351
3510
  more verbose description of the box.
3352
3511
  """
3353
- def __init__(self, claimed_box_id, length=0, offset=-1, longname=''):
3512
+
3513
+ def __init__(self, claimed_box_id, length=0, offset=-1, longname=""):
3354
3514
  super().__init__()
3355
3515
  self.longname = longname
3356
- self.box_id = 'xxxx'
3516
+ self.box_id = "xxxx"
3357
3517
  self.claimed_box_id = claimed_box_id
3358
3518
  self.length = length
3359
3519
  self.offset = offset
@@ -3364,8 +3524,8 @@ class UnknownBox(Jp2kBox):
3364
3524
 
3365
3525
  def __str__(self):
3366
3526
  title = Jp2kBox.__str__(self)
3367
- body = f' Claimed ID: {self.claimed_box_id}'
3368
- text = '\n'.join([title, body])
3527
+ body = f" Claimed ID: {self.claimed_box_id}"
3528
+ text = "\n".join([title, body])
3369
3529
  return text
3370
3530
 
3371
3531
 
@@ -3397,8 +3557,9 @@ class UUIDBox(Jp2kBox):
3397
3557
  16684-1:2012 - Graphic technology -- Extensible metadata platform (XMP)
3398
3558
  specification -- Part 1: Data model, serialization and core properties
3399
3559
  """
3400
- box_id = 'uuid'
3401
- longname = 'UUID'
3560
+
3561
+ box_id = "uuid"
3562
+ longname = "UUID"
3402
3563
 
3403
3564
  def __init__(self, the_uuid, raw_data, length=0, offset=-1):
3404
3565
  """Parameters
@@ -3436,29 +3597,29 @@ class UUIDBox(Jp2kBox):
3436
3597
  """Private function for parsing UUID payloads if possible."""
3437
3598
  if self.uuid == _XMP_UUID:
3438
3599
 
3439
- txt = self.raw_data.decode('utf-8')
3600
+ txt = self.raw_data.decode("utf-8")
3440
3601
 
3441
3602
  # If XMP comes from a TIFF tag, then it should be terminated
3442
3603
  # by a null byte. libxml2 now requires that null byte to be
3443
3604
  # stripped off before being fed into lxml.
3444
- elt = ET.fromstring(txt.strip('\x00'))
3605
+ elt = ET.fromstring(txt.strip("\x00"))
3445
3606
  self.data = ET.ElementTree(elt)
3446
3607
 
3447
3608
  elif self.uuid == _GEOTIFF_UUID:
3448
3609
  self.data = tiff_header(self.raw_data)
3449
3610
  elif self.uuid == _EXIF_UUID:
3450
- if self.raw_data[0:4].decode('utf-8').lower() == 'exif':
3611
+ if self.raw_data[0:4].decode("utf-8").lower() == "exif":
3451
3612
  # Cut off 'EXIF\0\0' part.
3452
3613
  payload = self.raw_data[6:]
3453
3614
  self.data = tiff_header(payload)
3454
- elif self.raw_data[:2].decode('utf-8').lower() in ['ii', 'mm']:
3615
+ elif self.raw_data[:2].decode("utf-8").lower() in ["ii", "mm"]:
3455
3616
  # Missing the exif lead-in, take the data as-is.
3456
3617
  payload = self.raw_data
3457
3618
  self.data = tiff_header(payload)
3458
3619
  else:
3459
3620
  msg = (
3460
- 'A UUID that identified itself as an EXIF UUID could not '
3461
- 'be parsed.'
3621
+ "A UUID that identified itself as an EXIF UUID could not "
3622
+ "be parsed."
3462
3623
  )
3463
3624
  warnings.warn(msg)
3464
3625
  self.data = None
@@ -3466,72 +3627,92 @@ class UUIDBox(Jp2kBox):
3466
3627
  self.data = self.raw_data
3467
3628
 
3468
3629
  def __repr__(self):
3469
- msg = (
3470
- "glymur.jp2box.UUIDBox({0}, raw_data=<byte array {1} elements>)"
3471
- )
3630
+ msg = "glymur.jp2box.UUIDBox({0}, raw_data=<byte array {1} elements>)"
3472
3631
  return msg.format(repr(self.uuid), len(self.raw_data))
3473
3632
 
3474
3633
  def __str__(self):
3475
3634
  title = Jp2kBox.__str__(self)
3476
- if get_option('print.short') is True:
3635
+ if get_option("print.short") is True:
3477
3636
  return title
3478
3637
 
3479
- text = f'UUID: {self.uuid}'
3638
+ text = f"UUID: {self.uuid}"
3480
3639
  if self.uuid == _XMP_UUID:
3481
- text += ' (XMP)'
3640
+ text += " (XMP)"
3482
3641
  elif self.uuid == _GEOTIFF_UUID:
3483
- text += ' (GeoTIFF)'
3642
+ text += " (GeoTIFF)"
3484
3643
  elif self.uuid == _EXIF_UUID:
3485
- text += ' (EXIF)'
3644
+ text += " (EXIF)"
3486
3645
  else:
3487
- text += ' (unknown)'
3646
+ text += " (unknown)"
3488
3647
 
3489
3648
  lst = [text]
3490
3649
 
3491
- if not get_option('print.xml') and self.uuid == _XMP_UUID:
3650
+ if not get_option("print.xml") and self.uuid == _XMP_UUID:
3651
+
3492
3652
  # If it's an XMP UUID, don't print the XML contents.
3493
3653
  pass
3494
3654
 
3495
3655
  elif self.uuid == _XMP_UUID:
3496
- b = ET.tostring(self.data, encoding='utf-8', pretty_print=True)
3497
- s = b.decode('utf-8').strip()
3498
- text = f'UUID Data:\n{s}'
3656
+
3657
+ s = self.raw_data.decode('utf-8').rstrip('\0')
3658
+ e = lxml.objectify.fromstring(s)
3659
+ xml = ET.tostring(e, pretty_print=True).decode('utf-8').strip()
3660
+ text = f"UUID Data:\n{xml}"
3499
3661
  lst.append(text)
3662
+
3500
3663
  elif self.uuid == _EXIF_UUID:
3664
+
3501
3665
  s = io.StringIO()
3502
3666
 
3503
3667
  if self.data is None:
3504
3668
  # If the UUID was malformed, just say so and go on. This
3505
3669
  # should not be a showstopper.
3506
- text = 'UUID Data: Invalid Exif UUID'
3507
- lst.append(text)
3670
+ text = "UUID Data: Invalid Exif UUID"
3508
3671
  else:
3509
3672
  with np.printoptions(threshold=4):
3510
3673
  pprint.pprint(self.data, stream=s, indent=4)
3511
- text = f'UUID Data: {s.getvalue().rstrip()}'
3512
- lst.append(text)
3674
+ text = f"UUID Data: {s.getvalue().rstrip()}"
3675
+
3676
+ lst.append(text)
3677
+
3513
3678
  elif self.uuid == _GEOTIFF_UUID:
3514
3679
 
3515
3680
  options = gdal.InfoOptions(showColorTable=False)
3516
- txt = gdal.Info(self._fptr.name, options=options)
3517
- txt = textwrap.indent(txt, ' ' * 4).rstrip()
3681
+ gdal_txt = gdal.Info(self._fptr.name, options=options)
3682
+ gdal_txt = textwrap.indent(gdal_txt, " " * 4).rstrip()
3683
+
3684
+ # now append the raw IFD
3685
+ s = io.StringIO()
3686
+ with np.printoptions(threshold=4):
3687
+ pprint.pprint(self.data, stream=s, indent=4)
3688
+
3689
+ ifd_txt = s.getvalue().rstrip()
3690
+
3691
+ txt = (
3692
+ f'UUID Data:'
3693
+ f'\n\n'
3694
+ f'Geo Metadata:\n{gdal_txt}'
3695
+ f'\n\n'
3696
+ f'Raw IFD Metadata:\n{ifd_txt}'
3697
+ )
3518
3698
 
3519
- txt = f'UUID Data:\n{txt}'
3520
3699
  lst.append(txt)
3700
+
3521
3701
  else:
3522
- text = f'UUID Data: {len(self.raw_data)} bytes'
3702
+
3703
+ text = f"UUID Data: {len(self.raw_data)} bytes"
3523
3704
  lst.append(text)
3524
3705
 
3525
- body = '\n'.join(lst)
3526
- body = textwrap.indent(body, ' ' * 4)
3706
+ body = "\n".join(lst)
3707
+ body = textwrap.indent(body, " " * 4)
3527
3708
 
3528
- text = '\n'.join([title, body])
3709
+ text = "\n".join([title, body])
3529
3710
  return text
3530
3711
 
3531
3712
  def write(self, fptr):
3532
3713
  """Write a UUID box to file."""
3533
3714
  length = 4 + 4 + 16 + len(self.raw_data)
3534
- write_buffer = struct.pack('>I4s', length, b'uuid')
3715
+ write_buffer = struct.pack(">I4s", length, b"uuid")
3535
3716
  fptr.write(write_buffer)
3536
3717
  fptr.write(self.uuid.bytes)
3537
3718
  fptr.write(self.raw_data)
@@ -3565,35 +3746,36 @@ class UUIDBox(Jp2kBox):
3565
3746
 
3566
3747
  # Map each box ID to the corresponding class.
3567
3748
  _BOX_WITH_ID = {
3568
- b'asoc': AssociationBox,
3569
- b'bpcc': BitsPerComponentBox,
3570
- b'cdef': ChannelDefinitionBox,
3571
- b'cgrp': ColourGroupBox,
3572
- b'cmap': ComponentMappingBox,
3573
- b'colr': ColourSpecificationBox,
3574
- b'dtbl': DataReferenceBox,
3575
- b'ftyp': FileTypeBox,
3576
- b'ihdr': ImageHeaderBox,
3577
- b'jP ': JPEG2000SignatureBox,
3578
- b'jpch': CodestreamHeaderBox,
3579
- b'jplh': CompositingLayerHeaderBox,
3580
- b'jp2c': ContiguousCodestreamBox,
3581
- b'free': FreeBox,
3582
- b'flst': FragmentListBox,
3583
- b'ftbl': FragmentTableBox,
3584
- b'jp2h': JP2HeaderBox,
3585
- b'lbl ': LabelBox,
3586
- b'nlst': NumberListBox,
3587
- b'pclr': PaletteBox,
3588
- b'res ': ResolutionBox,
3589
- b'resc': CaptureResolutionBox,
3590
- b'resd': DisplayResolutionBox,
3591
- b'rreq': ReaderRequirementsBox,
3592
- b'uinf': UUIDInfoBox,
3593
- b'ulst': UUIDListBox,
3594
- b'url ': DataEntryURLBox,
3595
- b'uuid': UUIDBox,
3596
- b'xml ': XMLBox}
3749
+ b"asoc": AssociationBox,
3750
+ b"bpcc": BitsPerComponentBox,
3751
+ b"cdef": ChannelDefinitionBox,
3752
+ b"cgrp": ColourGroupBox,
3753
+ b"cmap": ComponentMappingBox,
3754
+ b"colr": ColourSpecificationBox,
3755
+ b"dtbl": DataReferenceBox,
3756
+ b"ftyp": FileTypeBox,
3757
+ b"ihdr": ImageHeaderBox,
3758
+ b"jP ": JPEG2000SignatureBox,
3759
+ b"jpch": CodestreamHeaderBox,
3760
+ b"jplh": CompositingLayerHeaderBox,
3761
+ b"jp2c": ContiguousCodestreamBox,
3762
+ b"free": FreeBox,
3763
+ b"flst": FragmentListBox,
3764
+ b"ftbl": FragmentTableBox,
3765
+ b"jp2h": JP2HeaderBox,
3766
+ b"lbl ": LabelBox,
3767
+ b"nlst": NumberListBox,
3768
+ b"pclr": PaletteBox,
3769
+ b"res ": ResolutionBox,
3770
+ b"resc": CaptureResolutionBox,
3771
+ b"resd": DisplayResolutionBox,
3772
+ b"rreq": ReaderRequirementsBox,
3773
+ b"uinf": UUIDInfoBox,
3774
+ b"ulst": UUIDListBox,
3775
+ b"url ": DataEntryURLBox,
3776
+ b"uuid": UUIDBox,
3777
+ b"xml ": XMLBox,
3778
+ }
3597
3779
 
3598
3780
 
3599
3781
  def decompose_resolution(value: Number) -> Tuple[int, int, int]: