Glymur 0.12.9.post2__py3-none-any.whl → 0.13.1__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/jp2k.py CHANGED
@@ -10,10 +10,8 @@ License: MIT
10
10
  from __future__ import annotations
11
11
  from collections import Counter
12
12
  from contextlib import ExitStack
13
- from itertools import filterfalse
14
13
  import ctypes
15
14
  import pathlib
16
- import re
17
15
  import shutil
18
16
  import struct
19
17
  from typing import List, Tuple
@@ -25,69 +23,56 @@ import numpy as np
25
23
 
26
24
  # Local imports...
27
25
  import glymur
28
- from .codestream import Codestream
29
26
  from . import core, version, get_option
27
+ from .jp2kr import Jp2kr
30
28
  from .jp2box import (
31
- Jp2kBox, JPEG2000SignatureBox, FileTypeBox, JP2HeaderBox,
29
+ JPEG2000SignatureBox, FileTypeBox, JP2HeaderBox,
32
30
  ColourSpecificationBox, ContiguousCodestreamBox, ImageHeaderBox,
33
31
  InvalidJp2kError
34
32
  )
35
33
  from .lib import openjp2 as opj2
36
34
 
37
35
 
38
- class Jp2k(Jp2kBox):
39
- """Access JPEG 2000 files.
36
+ class Jp2k(Jp2kr):
37
+ """Write JPEG 2000 files.
40
38
 
41
39
  Parameters
42
40
  ----------
43
- filename : str or Path
44
- If you are reading JPEG 2000 files instead of writing, this is the only
45
- argument you need to supply.
41
+ filename : str or path
42
+ The path to JPEG 2000 file. If you are only reading JPEG 2000 files,
43
+ this is the only argument you need to supply.
46
44
  data : np.ndarray, optional
47
- Image data to be written. This should not be specified when writing by
48
- tile.
45
+ Image data to be written to file.
49
46
  shape : Tuple[int, int, ...], optional
50
- Size of the image to be written. This should only be specified when
51
- the JPEG 2000 file is to be written by tiles.
47
+ Size of image data, only required when image_data is not provided.
52
48
  capture_resolution : Tuple[int, int], optional
53
- Capture solution (VRES, HRES). Specifying this option will append a
54
- capture resolution box onto the end of the JP2 file.
49
+ Capture solution (VRES, HRES). This appends a capture resolution
50
+ box onto the end of the JP2 file when it is created.
55
51
  cbsize : Tuple[int, int], optional
56
- Code block size (NROWS, NCOLS). This parameter is for advanced use
57
- cases.
52
+ Code block size (NROWS, NCOLS)
58
53
  cinema2k : int, optional
59
- Frames per second, either 24 or 48. This option is required to
60
- generate a codestream compliant to the digital cinema specifications
61
- for 2K resolution content.
54
+ Frames per second, either 24 or 48.
62
55
  cinema4k : bool, optional
63
- Set to True to specify Cinema4K mode, defaults to false. This option
64
- is required to generate a codestream compliant to the digital cinema
65
- specifications for 4K resolution content.
56
+ Set to True to specify Cinema4K mode, defaults to false.
66
57
  colorspace : {'rgb', 'gray'}, optional
67
- The image color space. If not supplied, it will be interpreted from
68
- other parameters.
58
+ The image color space. If not supplied, it will be inferred.
69
59
  cratios : Tuple[int, ...], optional
70
- Each value is a factor of compression and each value represents a
71
- quality layer. If a lossless layer is intended, the last value should
72
- be 1. The default action is to write a single lossless quality layer.
60
+ Compression ratios for successive layers.
73
61
  display_resolution : Tuple[int, int], optional
74
- Display solution (VRES, HRES). Specifying this option will append a
75
- display resolution box onto the end of the JP2.
62
+ Display solution (VRES, HRES). This appends a display resolution
63
+ box onto the end of the JP2 file when it is created.
76
64
  eph : bool, optional
77
- If true, write an EPH marker after each packet header.
65
+ If true, write EPH marker after each header packet.
78
66
  grid_offset : Tuple[int, int], optional
79
- Offset (dY, dX) of the origin of the image in the reference grid.
67
+ Offset (DY, DX) of the origin of the image in the reference grid.
80
68
  irreversible : bool, optional
81
- If true, use the irreversible DWT 9-7 transform in place of the
82
- reversible 5-3 transform.
69
+ If true, use the irreversible DWT 9-7 transform.
83
70
  mct : bool, optional
84
- If true, use the multi component transform to write an image. If not
71
+ Usage of the multi component transform to write an image. If not
85
72
  specified, defaults to True if the color space is RGB, false if the
86
73
  color space is grayscale.
87
74
  modesw : int, optional
88
- This is an advanced option, it allows the possibility to use a mode
89
- switch during the encoding process. There are the following
90
- possibilities:
75
+ mode switch
91
76
  1 = BYPASS(LAZY)
92
77
  2 = RESET
93
78
  4 = RESTART(TERMALL)
@@ -98,61 +83,28 @@ class Jp2k(Jp2kBox):
98
83
  Number of resolutions, defaults to 6. This number will be equal to
99
84
  the number of thumbnails plus the original image.
100
85
  plt : bool, optional
101
- Write PLT markers in the tile-part header.
86
+ Generate PLT markers.
102
87
  prog : {'LRCP', 'RLCP', 'RPCL', 'PCRL', 'CPRL'}, optional
103
- Progression order. The default is 'LRCP'. If cinema2k or cinema4k is
104
- specified, the choice must be 'CPRL'.
88
+ Progression order. If not specified, the chosen progression order
89
+ will be 'CPRL' if either cinema2k or cinema4k is specified,
105
90
  otherwise defaulting to 'LRCP'.
106
91
  psizes : List[Tuple[int, int]], optional
107
92
  Precinct sizes, each precinct size tuple is defined as
108
93
  (height, width).
109
94
  psnr : Tuple[int, ...] or None
110
95
  Different PSNR for successive layers. If the last layer is desired
111
- to be lossless, specify 0 for the last value. Do not specify psnr with
112
- cratios.
96
+ to be lossless, specify 0 for the last value.
113
97
  sop : bool, optional
114
- If true, write a SOP marker before each packet.
98
+ If true, write SOP marker before each packet.
115
99
  subsam : Tuple[int, int], optional
116
100
  Subsampling factors (dy, dx).
117
101
  tilesize : Tuple[int, int], optional
118
102
  Tile size in terms of (numrows, numcols), not (X, Y).
119
103
  tlm : bool, optional
120
- Write TLM markers in the main header.
104
+ Generate TLM markers.
121
105
  verbose : bool, optional
122
106
  Print informational messages produced by the OpenJPEG library.
123
107
 
124
- Attributes
125
- ----------
126
- codestream : glymur.codestream.Codestream
127
- Contains header information for the codestream.
128
- decoded_components : bool
129
- If set, decode only these components. The MCT will not be used.
130
- dtype : numpy dtype object
131
- Describes the format of the elements in the image data.
132
- ignore_pclr_cmap_cdef : bool
133
- Specifies whether or not to ignore the pclr, cmap, or cdef
134
- boxes during any color transformation, defaults to False.
135
- layer : int
136
- Zero-based number of quality layer to decode. Defaults to 0, the
137
- highest quality layer.
138
- ndim : ndim
139
- The image data array's number of dimensions.
140
- shape : tuple of ints
141
- Shape of the image data array.
142
- tilesize : 2-tuple of ints
143
- Height and width of the tiles that make up the image.
144
- verbose : bool
145
- Specifies whether or not to print informational messages
146
- produced by the OpenJPEG library.
147
- filename : str
148
- The path to the JPEG 2000 file.
149
- box : sequence
150
- List of top-level boxes in the file. Each box may in turn contain
151
- its own list of boxes. Will be empty if the file consists only of a
152
- raw codestream.
153
- shape : tuple
154
- Size of the image.
155
-
156
108
  Examples
157
109
  --------
158
110
  >>> import glymur
@@ -214,17 +166,16 @@ class Jp2k(Jp2kBox):
214
166
  tlm: bool = False,
215
167
  verbose: bool = False,
216
168
  ):
217
- super().__init__()
169
+ try:
170
+ super().__init__(filename, verbose=verbose)
171
+ except FileNotFoundError:
172
+ # must assume we are writing
173
+ pass
218
174
 
219
175
  # In case of pathlib.Paths...
220
176
  self.filename = str(filename)
221
177
  self.path = pathlib.Path(self.filename)
222
178
 
223
- self.box = []
224
- self._layer = 0
225
- self._codestream = None
226
- self._decoded_components = None
227
-
228
179
  self._capture_resolution = capture_resolution
229
180
  self._cbsize = cbsize
230
181
  self._cinema2k = cinema2k
@@ -237,10 +188,7 @@ class Jp2k(Jp2kBox):
237
188
  self._irreversible = irreversible
238
189
  self._mct = mct
239
190
  self._modesw = modesw
240
-
241
- # The default used to be 6 if numres came in as None.
242
191
  self._numres = numres if numres is not None else 6
243
-
244
192
  self._plt = plt
245
193
  self._prog = prog
246
194
  self._psizes = psizes
@@ -249,61 +197,41 @@ class Jp2k(Jp2kBox):
249
197
  self._subsam = subsam
250
198
  self._tilesize = tilesize
251
199
  self._tlm = tlm
252
-
253
- self._shape = shape
254
- self._ndim = None
255
- self._dtype = None
256
- self._ignore_pclr_cmap_cdef = False
257
200
  self._verbose = verbose
258
201
 
259
- if self.filename[-4:].endswith(('.jp2', '.JP2')):
260
- self._codec_format = opj2.CODEC_JP2
261
- else:
262
- self._codec_format = opj2.CODEC_J2K
263
-
264
- if data is None and shape is None and self.path.exists():
265
- self._readonly = True
266
- else:
267
- self._readonly = False
268
-
269
- if data is None and tilesize is not None and shape is not None:
270
- self._writing_by_tiles = True
271
- else:
272
- self._writing_by_tiles = False
202
+ if shape is not None:
203
+ self._shape = shape
204
+ elif data is not None:
205
+ self._shape = data.shape
206
+ elif not hasattr(self, 'shape'):
207
+ # We must be writing via slicing.
208
+ # Must be determined when writing.
209
+ self._shape = None
273
210
 
274
- if data is None and tilesize is None:
275
- # case of
276
- # j = Jp2k(filename)
277
- # j[:] = data
278
- self._expecting_to_write_by_setitem = True
279
- else:
280
- self._expecting_to_write_by_setitem = False
211
+ # If there already was a shape attribute, then don't mess with it,
212
+ # it was set by the reader superclass.
281
213
 
282
- if data is not None:
283
- self._have_data = True
284
- self._shape = data.shape
214
+ if self.filename[-4:].endswith(('.jp2', '.JP2', '.jpx', 'JPX')):
215
+ self._codec_format = opj2.CODEC_JP2
285
216
  else:
286
- self._have_data = False
217
+ self._codec_format = opj2.CODEC_J2K
287
218
 
288
219
  self._validate_kwargs()
289
220
 
290
- if self._readonly:
291
- # We must be just reading a JP2/J2K/JPX file. Parse its
292
- # contents, then determine the shape. We are then done.
293
- self.parse()
294
- self._initialize_shape()
221
+ if data is None:
222
+ # Expecting to write either by tiles or by setitem. Do not
223
+ # parse just yet, as there is nothing to parse. We are done for
224
+ # now.
295
225
  return
296
226
 
297
- if data is not None:
227
+ else:
298
228
  # We are writing a JP2/J2K/JPX file where the image is
299
229
  # contained in memory.
300
- self._write(data)
301
-
302
- self.finalize()
230
+ self[:] = data
303
231
 
304
232
  def finalize(self, force_parse=False):
305
- """For now, the only task remaining is to possibly write out a
306
- ResolutionBox if we were so instructed. There could be other
233
+ """For now, the only remaining tasks are to possibly parse the file
234
+ and to possibly write out a ResolutionBox. There could be other
307
235
  possibilities in the future.
308
236
 
309
237
  Parameters
@@ -311,28 +239,16 @@ class Jp2k(Jp2kBox):
311
239
  force : bool
312
240
  If true, then run finalize operations
313
241
  """
314
- # Cases where we do NOT want to parse.
315
- if (
316
- (self._writing_by_tiles or self._expecting_to_write_by_setitem)
317
- and not force_parse
318
- ):
319
- # We are writing by tiles but we are not finished doing that.
320
- # or
321
- # we are writing by __setitem__ but aren't finished doing that
322
- # either
323
- return
242
+ self.parse(force=force_parse)
324
243
 
325
- # So now we are basically done writing a JP2/Jp2k file ...
326
244
  if (
327
245
  self._capture_resolution is None
328
246
  and self._display_resolution is None
329
247
  ):
330
- # ... and we don't have any extra boxes, so go ahead and parse.
331
- self.parse()
248
+ # ... and we don't have any extra boxes, so we are done
332
249
  return
333
250
 
334
- # So we DO have extra boxes. Handle them, and THEN parse.
335
- self.parse()
251
+ # So we DO have extra boxes.
336
252
  self._insert_resolution_superbox()
337
253
 
338
254
  def _insert_resolution_superbox(self):
@@ -361,7 +277,7 @@ class Jp2k(Jp2kBox):
361
277
  temp_filename = self.filename + '.tmp'
362
278
  self.wrap(temp_filename, boxes=self.box)
363
279
  shutil.move(temp_filename, self.filename)
364
- self.parse()
280
+ self.parse(force=True)
365
281
 
366
282
  def _validate_kwargs(self):
367
283
  """Validate keyword parameters passed to the constructor."""
@@ -403,7 +319,10 @@ class Jp2k(Jp2kBox):
403
319
  )
404
320
  raise InvalidJp2kError(msg)
405
321
 
406
- if self._codec_format == opj2.CODEC_J2K and self._colorspace is not None: # noqa : E501
322
+ if (
323
+ self._codec_format == opj2.CODEC_J2K
324
+ and self._colorspace is not None
325
+ ):
407
326
  msg = 'Do not specify a colorspace when writing a raw codestream.'
408
327
  raise InvalidJp2kError(msg)
409
328
 
@@ -418,13 +337,6 @@ class Jp2k(Jp2kBox):
418
337
  )
419
338
  raise InvalidJp2kError(msg)
420
339
 
421
- if self._readonly and self._capture_resolution is not None:
422
- msg = (
423
- 'Capture/Display resolution keyword parameters cannot be '
424
- 'supplied when the intent seems to be to read an image.'
425
- )
426
- raise RuntimeError(msg)
427
-
428
340
  if (
429
341
  self._shape is not None
430
342
  and self.tilesize is not None
@@ -439,198 +351,10 @@ class Jp2k(Jp2kBox):
439
351
  )
440
352
  raise RuntimeError(msg)
441
353
 
442
- def _initialize_shape(self):
443
- """If there was no image data provided and if no shape was
444
- initially provisioned, then shape must be computed AFTER we
445
- have parsed the input file.
446
- """
447
- if self._codec_format == opj2.CODEC_J2K:
448
- # get the image size from the codestream
449
- cstr = self.codestream
450
- height = cstr.segment[1].ysiz
451
- width = cstr.segment[1].xsiz
452
- num_components = len(cstr.segment[1].xrsiz)
453
- else:
454
- # try to get the image size from the IHDR box
455
- jp2h = [box for box in self.box if box.box_id == 'jp2h'][0]
456
- ihdr = [box for box in jp2h.box if box.box_id == 'ihdr'][0]
457
-
458
- height, width = ihdr.height, ihdr.width
459
- num_components = ihdr.num_components
460
-
461
- if num_components == 1:
462
- # but if there is a PCLR box, then we need to check
463
- # that as well, as that turns a single-channel image
464
- # into a multi-channel image
465
- pclr = [box for box in jp2h.box if box.box_id == 'pclr']
466
- if len(pclr) > 0:
467
- num_components = len(pclr[0].signed)
468
-
469
- if num_components == 1:
470
- self.shape = (height, width)
471
- else:
472
- self.shape = (height, width, num_components)
473
-
474
- return self._shape
475
-
476
- @property
477
- def ignore_pclr_cmap_cdef(self):
478
- """Whether or not to ignore the pclr, cmap, or cdef boxes during any
479
- color transformation, defaults to False.
480
- """
481
- return self._ignore_pclr_cmap_cdef
482
-
483
- @ignore_pclr_cmap_cdef.setter
484
- def ignore_pclr_cmap_cdef(self, ignore_pclr_cmap_cdef):
485
- self._ignore_pclr_cmap_cdef = ignore_pclr_cmap_cdef
486
-
487
- @property
488
- def decoded_components(self):
489
- """If set, decode only these components. The MCT will not be used.
490
- List or scalar or None (default).
491
- """
492
- return self._decoded_components
493
-
494
- @decoded_components.setter
495
- def decoded_components(self, components):
496
-
497
- if components is None:
498
- # This is ok. It is a special case where we are restoring the
499
- # original behavior of reading all bands.
500
- self._decoded_components = components
501
- return
502
-
503
- if np.isscalar(components):
504
- # turn it into a list to be general
505
- components = [components]
506
-
507
- if any(x > len(self.codestream.segment[1].xrsiz) for x in components):
508
-
509
- msg = (
510
- f"{components} has at least one invalid component, "
511
- f"cannot be greater than "
512
- f"{len(self.codestream.segment[1].xrsiz)}."
513
- )
514
- raise ValueError(msg)
515
-
516
- elif any(x < 0 for x in components):
517
-
518
- msg = (
519
- f"{components} has at least one invalid component, "
520
- f"components cannot be negative."
521
- )
522
- raise ValueError(msg)
523
-
524
- self._decoded_components = components
525
-
526
- @property
527
- def layer(self):
528
- """Zero-based number of quality layer to decode. Defaults to 0, the
529
- highest quality layer.
530
- """
531
- return self._layer
532
-
533
- @layer.setter
534
- def layer(self, layer):
535
- # Set to the indicated value so long as it is valid.
536
- cod = [
537
- segment for segment in self.codestream.segment
538
- if segment.marker_id == 'COD'
539
- ][0]
540
- if layer < 0 or layer >= cod.layers:
541
- msg = f"Invalid layer number, must be in range [0, {cod.layers})."
542
- raise ValueError(msg)
543
-
544
- self._layer = layer
545
-
546
- @property
547
- def dtype(self):
548
- """Datatype of each image component."""
549
- if self._dtype is None:
550
- c = self.get_codestream()
551
- bps0 = c.segment[1].bitdepth[0]
552
- sgnd0 = c.segment[1].signed[0]
553
-
554
- if (
555
- all(bitdepth == bps0 for bitdepth in c.segment[1].bitdepth)
556
- and all(signed == sgnd0 for signed in c.segment[1].signed)
557
- ):
558
- if bps0 <= 8:
559
- self._dtype = np.int8 if sgnd0 else np.uint8
560
- else:
561
- self._dtype = np.int16 if sgnd0 else np.uint16
562
- else:
563
- msg = (
564
- "The dtype property is only valid when all components "
565
- "have the same bitdepth and sign. "
566
- "\n\n"
567
- f"{c.segment[1]}"
568
- )
569
- raise TypeError(msg)
570
-
571
- return self._dtype
572
-
573
- @property
574
- def ndim(self):
575
- """Number of image dimensions."""
576
- if self._ndim is None:
577
- self._ndim = len(self.shape)
578
-
579
- return self._ndim
580
-
581
- @property
582
- def codestream(self):
583
- """JP2 or J2K codestream object (header only)."""
584
- if self._codestream is None:
585
- self._codestream = self.get_codestream(header_only=True)
586
- return self._codestream
587
-
588
- @property
589
- def tilesize(self):
590
- """Dimensions of JP2 tiling."""
591
- return self._tilesize
592
-
593
- @property
594
- def verbose(self):
595
- """Whether or not to print informational messages produced by the
596
- OpenJPEG library, defaults to false.
597
-
598
- Parameters
599
- ----------
600
- verbose : {True, False}
601
- Set to verbose or not.
602
- """
603
- return self._verbose
604
-
605
- @verbose.setter
606
- def verbose(self, verbose):
607
- self._verbose = verbose
608
-
609
- @property
610
- def shape(self):
611
- """Dimensions of full resolution image."""
612
- return self._shape
613
-
614
- @shape.setter
615
- def shape(self, shape):
616
- self._shape = shape
617
-
618
354
  def __repr__(self):
619
355
  msg = f"glymur.Jp2k('{self.path}')"
620
356
  return msg
621
357
 
622
- def __str__(self):
623
- metadata = [f'File: {self.path.name}']
624
- if len(self.box) > 0:
625
- for box in self.box:
626
- metadata.append(str(box))
627
- elif self._codestream is None and not self.path.exists():
628
- # No codestream either. Empty file? We are done.
629
- return metadata[0]
630
- else:
631
- metadata.append(str(self.codestream))
632
- return '\n'.join(metadata)
633
-
634
358
  def get_tilewriters(self):
635
359
  """Return an object that facilitates writing tile by tile."""
636
360
 
@@ -644,132 +368,6 @@ class Jp2k(Jp2kBox):
644
368
 
645
369
  return _TileWriter(self)
646
370
 
647
- def parse(self):
648
- """Parses the JPEG 2000 file.
649
-
650
- Raises
651
- ------
652
- RuntimeError
653
- The file was not JPEG 2000.
654
- """
655
- self.length = self.path.stat().st_size
656
-
657
- with self.path.open('rb') as fptr:
658
-
659
- # Make sure we have a JPEG2000 file. It could be either JP2 or
660
- # J2C. Check for J2C first, single box in that case.
661
- read_buffer = fptr.read(2)
662
- signature, = struct.unpack('>H', read_buffer)
663
- if signature == 0xff4f:
664
- self._codec_format = opj2.CODEC_J2K
665
- # That's it, we're done. The codestream object is only
666
- # produced upon explicit request.
667
- return
668
-
669
- self._codec_format = opj2.CODEC_JP2
670
-
671
- # Should be JP2.
672
- # First 4 bytes should be 12, the length of the 'jP ' box.
673
- # 2nd 4 bytes should be the box ID ('jP ').
674
- # 3rd 4 bytes should be the box signature (13, 10, 135, 10).
675
- fptr.seek(0)
676
- read_buffer = fptr.read(12)
677
- values = struct.unpack('>I4s4B', read_buffer)
678
- box_length = values[0]
679
- box_id = values[1]
680
- signature = values[2:]
681
-
682
- if (
683
- box_length != 12
684
- or box_id != b'jP '
685
- or signature != (13, 10, 135, 10)
686
- ):
687
- msg = f'{self.filename} is not a JPEG 2000 file.'
688
- raise InvalidJp2kError(msg)
689
-
690
- # Back up and start again, we know we have a superbox (box of
691
- # boxes) here.
692
- fptr.seek(0)
693
- self.box = self.parse_superbox(fptr)
694
- self._validate()
695
-
696
- def _validate(self):
697
- """Validate the JPEG 2000 outermost superbox. These checks must be
698
- done at a file level.
699
- """
700
- # A JP2 file must contain certain boxes. The 2nd box must be a file
701
- # type box.
702
- if not isinstance(self.box[1], FileTypeBox):
703
- msg = f"{self.filename} does not contain a valid File Type box."
704
- raise InvalidJp2kError(msg)
705
-
706
- ftyp = self.box[1]
707
- if ftyp.brand != 'jp2 ':
708
- # Don't bother trying to validate JPX.
709
- return
710
-
711
- jp2h = [box for box in self.box if box.box_id == 'jp2h'][0]
712
-
713
- # An IHDR box is required as the first child box of the JP2H box.
714
- if jp2h.box[0].box_id != 'ihdr':
715
- msg = "A valid IHDR box was not found. The JP2 file is invalid."
716
- raise InvalidJp2kError(msg)
717
-
718
- # A jp2-branded file cannot contain an "any ICC profile
719
- colrs = [box for box in jp2h.box if box.box_id == 'colr']
720
- for colr in colrs:
721
- if colr.method not in (
722
- core.ENUMERATED_COLORSPACE, core.RESTRICTED_ICC_PROFILE
723
- ):
724
- msg = (
725
- "Color Specification box method must specify either an "
726
- "enumerated colorspace or a restricted ICC profile if the "
727
- "file type box brand is 'jp2 '."
728
- )
729
- warnings.warn(msg, UserWarning)
730
-
731
- # We need to have one and only one JP2H box if we have a JP2 file.
732
- num_jp2h_boxes = len([box for box in self.box if box.box_id == 'jp2h'])
733
- if num_jp2h_boxes > 1:
734
- msg = (
735
- f"This file has {num_jp2h_boxes} JP2H boxes in the outermost "
736
- "layer of boxes. There should only be one."
737
- )
738
- warnings.warn(msg)
739
-
740
- # We should have one and only one JP2C box if we have a JP2 file.
741
- num_jp2c_boxes = len([box for box in self.box if box.box_id == 'jp2c'])
742
- if num_jp2c_boxes > 1 and self.box[1].brand == 'jp2 ':
743
- msg = (
744
- f"This file has {num_jp2c_boxes} JP2C boxes (images) in the "
745
- "outermost layer of boxes. All JP2C boxes after the first "
746
- "will be ignored."
747
- )
748
- warnings.warn(msg)
749
- elif num_jp2c_boxes == 0:
750
- msg = (
751
- "A valid JP2C box was not found in the outermost level of JP2 "
752
- "boxes. The JP2 file is invalid."
753
- )
754
- raise InvalidJp2kError(msg)
755
-
756
- # Make sure that IHDR and SIZ conform on the dimensions.
757
- ihdr = jp2h.box[0]
758
- ihdr_dims = ihdr.height, ihdr.width, ihdr.num_components
759
-
760
- siz = [
761
- segment for segment in self.codestream.segment
762
- if segment.marker_id == 'SIZ'
763
- ][0]
764
-
765
- siz_dims = (siz.ysiz, siz.xsiz, len(siz.bitdepth))
766
- if ihdr_dims != siz_dims:
767
- msg = (
768
- f"The IHDR dimensions {ihdr_dims} do not match the codestream "
769
- f"dimensions {siz_dims}."
770
- )
771
- warnings.warn(msg, UserWarning)
772
-
773
371
  def _set_cinema_params(self, cinema_mode, fps):
774
372
  """Populate compression parameters structure for cinema2K.
775
373
 
@@ -952,6 +550,10 @@ class Jp2k(Jp2kBox):
952
550
 
953
551
  self._write_openjp2(img_array)
954
552
 
553
+ # if writing the entire image, we need to parse ourselves in case
554
+ # further operations are needed
555
+ self.finalize(force_parse=True)
556
+
955
557
  def _validate_codeblock_size(self, cparams):
956
558
  """Code block dimensions must satisfy certain restrictions.
957
559
 
@@ -1091,13 +693,13 @@ class Jp2k(Jp2kBox):
1091
693
  stack.callback(opj2.destroy_codec, codec)
1092
694
 
1093
695
  if self._verbose:
1094
- info_handler = _INFO_CALLBACK
696
+ info_handler = opj2._INFO_CALLBACK
1095
697
  else:
1096
698
  info_handler = None
1097
699
 
1098
700
  opj2.set_info_handler(codec, info_handler)
1099
- opj2.set_warning_handler(codec, _WARNING_CALLBACK)
1100
- opj2.set_error_handler(codec, _ERROR_CALLBACK)
701
+ opj2.set_warning_handler(codec, opj2._WARNING_CALLBACK)
702
+ opj2.set_error_handler(codec, opj2._ERROR_CALLBACK)
1101
703
 
1102
704
  opj2.setup_encoder(codec, self._cparams, image)
1103
705
 
@@ -1175,7 +777,7 @@ class Jp2k(Jp2kBox):
1175
777
  with self.path.open('ab') as ofile:
1176
778
  box.write(ofile)
1177
779
 
1178
- self.parse()
780
+ self.parse(force=True)
1179
781
 
1180
782
  def wrap(self, filename, boxes=None):
1181
783
  """Create a new JP2/JPX file wrapped in a new set of JP2 boxes.
@@ -1375,421 +977,6 @@ class Jp2k(Jp2kBox):
1375
977
 
1376
978
  return newindex
1377
979
 
1378
- def __getitem__(self, pargs):
1379
- """Slicing protocol."""
1380
- if not self.path.exists():
1381
- msg = f"Cannot read from {self.filename}, it does not yet exist."
1382
- raise FileNotFoundError(msg)
1383
- if len(self.shape) == 2:
1384
- numrows, numcols = self.shape
1385
- numbands = 1
1386
- else:
1387
- numrows, numcols, numbands = self.shape
1388
-
1389
- if isinstance(pargs, int):
1390
- # Not a very good use of this protocol, but technically legal.
1391
- # This retrieves a single row.
1392
- row = pargs
1393
- area = (row, 0, row + 1, numcols)
1394
- return self._read(area=area).squeeze()
1395
-
1396
- if pargs is Ellipsis:
1397
- # Case of jp2[...]
1398
- return self._read()
1399
-
1400
- if isinstance(pargs, slice):
1401
- if (
1402
- pargs.start is None
1403
- and pargs.stop is None
1404
- and pargs.step is None
1405
- ):
1406
- # Case of jp2[:]
1407
- return self._read()
1408
-
1409
- # Corner case of jp2[x] where x is a slice object with non-null
1410
- # members. Just augment it with an ellipsis and let the code
1411
- # below handle it.
1412
- pargs = (pargs, Ellipsis)
1413
-
1414
- if isinstance(pargs, tuple) and any(x is Ellipsis for x in pargs):
1415
- newindex = self._remove_ellipsis(pargs, numrows, numcols, numbands)
1416
-
1417
- # Run once again because it is possible that there's another
1418
- # Ellipsis object in the 2nd or 3rd position.
1419
- return self.__getitem__(newindex)
1420
-
1421
- if isinstance(pargs, tuple) and any(isinstance(x, int) for x in pargs):
1422
- # Replace the first such integer argument, replace it with a slice.
1423
- lst = list(pargs)
1424
- g = filterfalse(lambda x: not isinstance(x[1], int),
1425
- enumerate(pargs))
1426
- idx = next(g)[0]
1427
- lst[idx] = slice(pargs[idx], pargs[idx] + 1)
1428
- newindex = tuple(lst)
1429
-
1430
- # Invoke array-based slicing again, as there may be additional
1431
- # integer argument remaining.
1432
- data = self.__getitem__(newindex)
1433
-
1434
- # Reduce dimensionality in the scalar dimension.
1435
- return np.squeeze(data, axis=idx)
1436
-
1437
- # Assuming pargs is a tuple of slices from now on.
1438
- rows = pargs[0]
1439
- cols = pargs[1]
1440
- if len(pargs) == 2:
1441
- bands = slice(None, None, None)
1442
- else:
1443
- bands = pargs[2]
1444
-
1445
- rows_step = 1 if rows.step is None else rows.step
1446
- cols_step = 1 if cols.step is None else cols.step
1447
- if rows_step != cols_step:
1448
- msg = "Row and column strides must be the same."
1449
- raise ValueError(msg)
1450
-
1451
- # Ok, reduce layer step is the same in both xy directions, so just take
1452
- # one of them.
1453
- step = rows_step
1454
- if step == -1:
1455
- # This is a shortcut for the last decomposition (or reduce layer
1456
- # step).
1457
- step = 2 ** self.codestream.segment[2].num_res
1458
-
1459
- # Check if the step size is a power of 2.
1460
- if np.abs(np.log2(step) - np.round(np.log2(step))) > 1e-6:
1461
- msg = "Row and column strides must be powers of 2."
1462
- raise ValueError(msg)
1463
- rlevel = int(np.round(np.log2(step)))
1464
-
1465
- area = (
1466
- 0 if rows.start is None else rows.start,
1467
- 0 if cols.start is None else cols.start,
1468
- numrows if rows.stop is None else rows.stop,
1469
- numcols if cols.stop is None else cols.stop
1470
- )
1471
- data = self._read(area=area, rlevel=rlevel)
1472
- if len(pargs) == 2:
1473
- return data
1474
-
1475
- # Ok, 3 arguments in pargs.
1476
- return data[:, :, bands]
1477
-
1478
- def read(self, **kwargs):
1479
- """Read a JPEG 2000 image.
1480
-
1481
- Returns
1482
- -------
1483
- img_array : ndarray
1484
- The image data.
1485
- """
1486
-
1487
- if 'ignore_pclr_cmap_cdef' in kwargs:
1488
- self.ignore_pclr_cmap_cdef = kwargs['ignore_pclr_cmap_cdef']
1489
- kwargs.pop('ignore_pclr_cmap_cdef')
1490
- warnings.warn("Use array-style slicing instead.", DeprecationWarning)
1491
- img = self._read(**kwargs)
1492
- return img
1493
-
1494
- def _subsampling_sanity_check(self):
1495
- """Check for differing subsample factors."""
1496
- if self._decoded_components is None:
1497
- dxs = np.array(self.codestream.segment[1].xrsiz)
1498
- dys = np.array(self.codestream.segment[1].yrsiz)
1499
- else:
1500
- dxs = np.array([
1501
- self.codestream.segment[1].xrsiz[i]
1502
- for i in self._decoded_components
1503
- ])
1504
- dys = np.array([
1505
- self.codestream.segment[1].yrsiz[i]
1506
- for i in self._decoded_components
1507
- ])
1508
-
1509
- if np.any(dxs - dxs[0]) or np.any(dys - dys[0]):
1510
- msg = (
1511
- f"The read_bands method should be used when the subsampling "
1512
- f"factors are different."
1513
- f"\n\n"
1514
- f"{self.codestream.segment[1]}"
1515
- )
1516
- raise RuntimeError(msg)
1517
-
1518
- def _read(self, rlevel=0, layer=None, area=None, tile=None, verbose=False):
1519
- """Read a JPEG 2000 image using libopenjp2.
1520
-
1521
- Parameters
1522
- ----------
1523
- layer : int, optional
1524
- Number of quality layer to decode.
1525
- rlevel : int, optional
1526
- Factor by which to rlevel output resolution. Use -1 to get the
1527
- lowest resolution thumbnail.
1528
- area : tuple, optional
1529
- Specifies decoding image area,
1530
- (first_row, first_col, last_row, last_col)
1531
- tile : int, optional
1532
- Number of tile to decode.
1533
- verbose : bool, optional
1534
- Print informational messages produced by the OpenJPEG library.
1535
-
1536
- Returns
1537
- -------
1538
- ndarray
1539
- The image data.
1540
-
1541
- Raises
1542
- ------
1543
- RuntimeError
1544
- If the image has differing subsample factors.
1545
- """
1546
- if re.match("0|1|2.[012]", version.openjpeg_version):
1547
- msg = (
1548
- f"You must have a version of OpenJPEG at least as high as "
1549
- f"2.3.0 before you can read JPEG2000 images with glymur. "
1550
- f"Your version is {version.openjpeg_version}"
1551
- )
1552
- raise RuntimeError(msg)
1553
-
1554
- self._subsampling_sanity_check()
1555
- self._populate_dparams(rlevel, tile=tile, area=area)
1556
- image = self._read_openjp2()
1557
- return image
1558
-
1559
- def _read_openjp2(self):
1560
- """Read a JPEG 2000 image using libopenjp2.
1561
-
1562
- Returns
1563
- -------
1564
- ndarray or lst
1565
- Either the image as an ndarray or a list of ndarrays, each item
1566
- corresponding to one band.
1567
- """
1568
- with ExitStack() as stack:
1569
- filename = self.filename
1570
- stream = opj2.stream_create_default_file_stream(filename, True)
1571
- stack.callback(opj2.stream_destroy, stream)
1572
- codec = opj2.create_decompress(self._codec_format)
1573
- stack.callback(opj2.destroy_codec, codec)
1574
-
1575
- opj2.set_error_handler(codec, _ERROR_CALLBACK)
1576
- opj2.set_warning_handler(codec, _WARNING_CALLBACK)
1577
-
1578
- if self._verbose:
1579
- opj2.set_info_handler(codec, _INFO_CALLBACK)
1580
- else:
1581
- opj2.set_info_handler(codec, None)
1582
-
1583
- opj2.setup_decoder(codec, self._dparams)
1584
- if version.openjpeg_version >= '2.2.0':
1585
- opj2.codec_set_threads(codec, get_option('lib.num_threads'))
1586
-
1587
- raw_image = opj2.read_header(stream, codec)
1588
- stack.callback(opj2.image_destroy, raw_image)
1589
-
1590
- if self._decoded_components is not None:
1591
- opj2.set_decoded_components(codec, self._decoded_components)
1592
-
1593
- if self._dparams.nb_tile_to_decode:
1594
- opj2.get_decoded_tile(codec, stream, raw_image,
1595
- self._dparams.tile_index)
1596
- else:
1597
- opj2.set_decode_area(
1598
- codec, raw_image,
1599
- self._dparams.DA_x0, self._dparams.DA_y0,
1600
- self._dparams.DA_x1, self._dparams.DA_y1
1601
- )
1602
- opj2.decode(codec, stream, raw_image)
1603
-
1604
- opj2.end_decompress(codec, stream)
1605
-
1606
- image = self._extract_image(raw_image)
1607
-
1608
- return image
1609
-
1610
- def _populate_dparams(self, rlevel, tile=None, area=None):
1611
- """Populate decompression structure with appropriate input parameters.
1612
-
1613
- Parameters
1614
- ----------
1615
- rlevel : int
1616
- Factor by which to rlevel output resolution.
1617
- area : tuple
1618
- Specifies decoding image area,
1619
- (first_row, first_col, last_row, last_col)
1620
- tile : int
1621
- Number of tile to decode.
1622
- """
1623
- dparam = opj2.set_default_decoder_parameters()
1624
-
1625
- infile = self.filename.encode()
1626
- nelts = opj2.PATH_LEN - len(infile)
1627
- infile += b'0' * nelts
1628
- dparam.infile = infile
1629
-
1630
- # Return raw codestream components instead of "interpolating" the
1631
- # colormap?
1632
- dparam.flags |= 1 if self.ignore_pclr_cmap_cdef else 0
1633
-
1634
- dparam.decod_format = self._codec_format
1635
- dparam.cp_layer = self.layer
1636
-
1637
- # Must check the specified rlevel against the maximum.
1638
- if rlevel != 0:
1639
- # Must check the specified rlevel against the maximum.
1640
- cod_seg = [
1641
- segment for segment in self.codestream.segment
1642
- if segment.marker_id == 'COD'
1643
- ][0]
1644
- max_rlevel = cod_seg.num_res
1645
- if rlevel == -1:
1646
- # -1 is shorthand for the largest rlevel
1647
- rlevel = max_rlevel
1648
- elif rlevel < -1 or rlevel > max_rlevel:
1649
- msg = (f"rlevel must be in the range [-1, {max_rlevel}] "
1650
- "for this image.")
1651
- raise ValueError(msg)
1652
-
1653
- dparam.cp_reduce = rlevel
1654
-
1655
- if area is not None:
1656
- if area[0] < 0 or area[1] < 0 or area[2] <= 0 or area[3] <= 0:
1657
- msg = (
1658
- f"The upper left corner coordinates must be nonnegative "
1659
- f"and the lower right corner coordinates must be positive."
1660
- f" The specified upper left and lower right coordinates "
1661
- f"are ({area[0]}, {area[1]}) and ({area[2]}, {area[3]})."
1662
- )
1663
- raise ValueError(msg)
1664
- dparam.DA_y0 = area[0]
1665
- dparam.DA_x0 = area[1]
1666
- dparam.DA_y1 = area[2]
1667
- dparam.DA_x1 = area[3]
1668
-
1669
- if tile is not None:
1670
- dparam.tile_index = tile
1671
- dparam.nb_tile_to_decode = 1
1672
-
1673
- self._dparams = dparam
1674
-
1675
- def read_bands(self, rlevel=0, layer=0, area=None, tile=None,
1676
- verbose=False, ignore_pclr_cmap_cdef=False):
1677
- """Read a JPEG 2000 image.
1678
-
1679
- The only time you should use this method is when the image has
1680
- different subsampling factors across components. Otherwise you should
1681
- use the read method.
1682
-
1683
- Parameters
1684
- ----------
1685
- layer : int, optional
1686
- Number of quality layer to decode.
1687
- rlevel : int, optional
1688
- Factor by which to rlevel output resolution.
1689
- area : tuple, optional
1690
- Specifies decoding image area,
1691
- (first_row, first_col, last_row, last_col)
1692
- tile : int, optional
1693
- Number of tile to decode.
1694
- ignore_pclr_cmap_cdef : bool
1695
- Whether or not to ignore the pclr, cmap, or cdef boxes during any
1696
- color transformation. Defaults to False.
1697
- verbose : bool, optional
1698
- Print informational messages produced by the OpenJPEG library.
1699
-
1700
- Returns
1701
- -------
1702
- list
1703
- List of the individual image components.
1704
-
1705
- See also
1706
- --------
1707
- read : read JPEG 2000 image
1708
-
1709
- Examples
1710
- --------
1711
- >>> import glymur
1712
- >>> jfile = glymur.data.nemo()
1713
- >>> jp = glymur.Jp2k(jfile)
1714
- >>> components_lst = jp.read_bands(rlevel=1)
1715
- """
1716
- if version.openjpeg_version < '2.3.0':
1717
- msg = (
1718
- f"You must have at least version 2.3.0 of OpenJPEG installed "
1719
- f"before using this method. Your version of OpenJPEG is "
1720
- f"{version.openjpeg_version}."
1721
- )
1722
- raise RuntimeError(msg)
1723
-
1724
- self.ignore_pclr_cmap_cdef = ignore_pclr_cmap_cdef
1725
- self.layer = layer
1726
- self._populate_dparams(rlevel, tile=tile, area=area)
1727
- lst = self._read_openjp2()
1728
- return lst
1729
-
1730
- def _extract_image(self, raw_image):
1731
- """Extract unequally-sized image bands.
1732
-
1733
- Parameters
1734
- ----------
1735
- raw_image : reference to openjpeg ImageType instance
1736
- The image structure initialized with image characteristics.
1737
-
1738
- Returns
1739
- -------
1740
- list or ndarray
1741
- If the JPEG 2000 image has unequally-sized components, they are
1742
- extracted into a list, otherwise a numpy array.
1743
-
1744
- """
1745
- ncomps = raw_image.contents.numcomps
1746
-
1747
- # Make a pass thru the image, see if any of the band datatypes or
1748
- # dimensions differ.
1749
- dtypes, nrows, ncols = [], [], []
1750
- for k in range(raw_image.contents.numcomps):
1751
- component = raw_image.contents.comps[k]
1752
- dtypes.append(self._component2dtype(component))
1753
- nrows.append(component.h)
1754
- ncols.append(component.w)
1755
- is_cube = all(
1756
- r == nrows[0] and c == ncols[0] and d == dtypes[0]
1757
- for r, c, d in zip(nrows, ncols, dtypes)
1758
- )
1759
-
1760
- if is_cube:
1761
- image = np.zeros((nrows[0], ncols[0], ncomps), dtypes[0])
1762
- else:
1763
- image = []
1764
-
1765
- for k in range(raw_image.contents.numcomps):
1766
- component = raw_image.contents.comps[k]
1767
-
1768
- self._validate_nonzero_image_size(nrows[k], ncols[k], k)
1769
-
1770
- addr = ctypes.addressof(component.data.contents)
1771
- with warnings.catch_warnings():
1772
- warnings.simplefilter("ignore")
1773
-
1774
- band_i32 = np.ctypeslib.as_array(
1775
- (ctypes.c_int32 * nrows[k] * ncols[k]).from_address(addr)
1776
- )
1777
- band = np.reshape(
1778
- band_i32.astype(dtypes[k]), (nrows[k], ncols[k])
1779
- )
1780
-
1781
- if is_cube:
1782
- image[:, :, k] = band
1783
- else:
1784
- image.append(band)
1785
-
1786
- if is_cube and image.shape[2] == 1:
1787
- # The third dimension has just a single layer. Make the image
1788
- # data 2D instead of 3D.
1789
- image.shape = image.shape[0:2]
1790
-
1791
- return image
1792
-
1793
980
  def _component2dtype(self, component):
1794
981
  """Determin the appropriate numpy datatype for an OpenJPEG component.
1795
982
 
@@ -1820,59 +1007,6 @@ class Jp2k(Jp2kBox):
1820
1007
 
1821
1008
  return dtype
1822
1009
 
1823
- def get_codestream(self, header_only=True):
1824
- """Retrieve codestream.
1825
-
1826
- Parameters
1827
- ----------
1828
- header_only : bool, optional
1829
- If True, only marker segments in the main header are parsed.
1830
- Supplying False may impose a large performance penalty.
1831
-
1832
- Returns
1833
- -------
1834
- Codestream
1835
- Object describing the codestream syntax.
1836
-
1837
- Examples
1838
- --------
1839
- >>> import glymur
1840
- >>> jfile = glymur.data.nemo()
1841
- >>> jp2 = glymur.Jp2k(jfile)
1842
- >>> codestream = jp2.get_codestream()
1843
- >>> print(codestream.segment[1])
1844
- SIZ marker segment @ (3233, 47)
1845
- Profile: no profile
1846
- Reference Grid Height, Width: (1456 x 2592)
1847
- Vertical, Horizontal Reference Grid Offset: (0 x 0)
1848
- Reference Tile Height, Width: (1456 x 2592)
1849
- Vertical, Horizontal Reference Tile Offset: (0 x 0)
1850
- Bitdepth: (8, 8, 8)
1851
- Signed: (False, False, False)
1852
- Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1))
1853
- """
1854
- with self.path.open('rb') as fptr:
1855
- if self._codec_format == opj2.CODEC_J2K:
1856
- codestream = Codestream(fptr, self.length,
1857
- header_only=header_only)
1858
- else:
1859
- box = [x for x in self.box if x.box_id == 'jp2c']
1860
- fptr.seek(box[0].offset)
1861
- read_buffer = fptr.read(8)
1862
- (box_length, _) = struct.unpack('>I4s', read_buffer)
1863
- if box_length == 0:
1864
- # The length of the box is presumed to last until the end
1865
- # of the file. Compute the effective length of the box.
1866
- box_length = self.path.stat().st_size - fptr.tell() + 8
1867
- elif box_length == 1:
1868
- # Seek past the XL field.
1869
- read_buffer = fptr.read(8)
1870
- box_length, = struct.unpack('>Q', read_buffer)
1871
- codestream = Codestream(fptr, box_length - 8,
1872
- header_only=header_only)
1873
-
1874
- return codestream
1875
-
1876
1010
  def _populate_image_struct(
1877
1011
  self, image, imgdata, tile_x_factor=1, tile_y_factor=1
1878
1012
  ):
@@ -1964,16 +1098,6 @@ class Jp2k(Jp2kBox):
1964
1098
 
1965
1099
  self._comptparms = comptparms
1966
1100
 
1967
- def _validate_nonzero_image_size(self, nrows, ncols, component_index):
1968
- """The image cannot have area of zero."""
1969
- if nrows == 0 or ncols == 0:
1970
- # Letting this situation continue would segfault openjpeg.
1971
- msg = (
1972
- f"Component {component_index} has dimensions "
1973
- f"{nrows} x {ncols}"
1974
- )
1975
- raise InvalidJp2kError(msg)
1976
-
1977
1101
  def _validate_jp2_box_sequence(self, boxes):
1978
1102
  """Run through series of tests for JP2 box legality.
1979
1103
 
@@ -2313,13 +1437,13 @@ class _TileWriter(object):
2313
1437
  self.codec = opj2.create_compress(self.jp2k._cparams.codec_fmt)
2314
1438
 
2315
1439
  if self.jp2k.verbose:
2316
- info_handler = _INFO_CALLBACK
1440
+ info_handler = opj2._INFO_CALLBACK
2317
1441
  else:
2318
1442
  info_handler = None
2319
1443
 
2320
1444
  opj2.set_info_handler(self.codec, info_handler)
2321
- opj2.set_warning_handler(self.codec, _WARNING_CALLBACK)
2322
- opj2.set_error_handler(self.codec, _ERROR_CALLBACK)
1445
+ opj2.set_warning_handler(self.codec, opj2._WARNING_CALLBACK)
1446
+ opj2.set_error_handler(self.codec, opj2._ERROR_CALLBACK)
2323
1447
 
2324
1448
  self.image = opj2.image_tile_create(
2325
1449
  self.jp2k._comptparms, self.jp2k._colorspace
@@ -2368,32 +1492,3 @@ def _set_planar_pixel_order(img):
2368
1492
  img = np.swapaxes(img, 0, 1)
2369
1493
 
2370
1494
  return img.copy()
2371
-
2372
-
2373
- # Setup the default callback handlers. See the callback functions subsection
2374
- # in the ctypes section of the Python documentation for a solid explanation of
2375
- # what's going on here.
2376
- _CMPFUNC = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_char_p, ctypes.c_void_p)
2377
-
2378
-
2379
- def _default_error_handler(msg, _):
2380
- """Default error handler callback for libopenjp2."""
2381
- msg = "OpenJPEG library error: {0}".format(msg.decode('utf-8').rstrip())
2382
- opj2.set_error_message(msg)
2383
-
2384
-
2385
- def _default_info_handler(msg, _):
2386
- """Default info handler callback."""
2387
- print("[INFO] {0}".format(msg.decode('utf-8').rstrip()))
2388
-
2389
-
2390
- def _default_warning_handler(library_msg, _):
2391
- """Default warning handler callback."""
2392
- library_msg = library_msg.decode('utf-8').rstrip()
2393
- msg = "OpenJPEG library warning: {0}".format(library_msg)
2394
- warnings.warn(msg, UserWarning)
2395
-
2396
-
2397
- _ERROR_CALLBACK = _CMPFUNC(_default_error_handler)
2398
- _INFO_CALLBACK = _CMPFUNC(_default_info_handler)
2399
- _WARNING_CALLBACK = _CMPFUNC(_default_warning_handler)