Glymur 0.12.9.post2__py3-none-any.whl → 0.13.0__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-0.12.9.post2.dist-info → Glymur-0.13.0.dist-info}/METADATA +2 -2
- Glymur-0.13.0.dist-info/RECORD +25 -0
- glymur/__init__.py +2 -2
- glymur/data/nemo.jp2 +0 -0
- glymur/jp2box.py +3 -0
- glymur/jp2k.py +74 -979
- glymur/jp2kr.py +982 -0
- glymur/lib/openjp2.py +30 -0
- glymur/tiff.py +6 -1
- glymur/version.py +1 -1
- Glymur-0.12.9.post2.dist-info/RECORD +0 -24
- {Glymur-0.12.9.post2.dist-info → Glymur-0.13.0.dist-info}/LICENSE.txt +0 -0
- {Glymur-0.12.9.post2.dist-info → Glymur-0.13.0.dist-info}/WHEEL +0 -0
- {Glymur-0.12.9.post2.dist-info → Glymur-0.13.0.dist-info}/entry_points.txt +0 -0
- {Glymur-0.12.9.post2.dist-info → Glymur-0.13.0.dist-info}/top_level.txt +0 -0
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
|
-
|
|
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(
|
|
39
|
-
"""
|
|
36
|
+
class Jp2k(Jp2kr):
|
|
37
|
+
"""Write JPEG 2000 files.
|
|
40
38
|
|
|
41
39
|
Parameters
|
|
42
40
|
----------
|
|
43
|
-
filename : str or
|
|
44
|
-
If you are reading JPEG 2000 files
|
|
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
|
|
48
|
-
tile.
|
|
45
|
+
Image data to be written to file.
|
|
49
46
|
shape : Tuple[int, int, ...], optional
|
|
50
|
-
Size of
|
|
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).
|
|
54
|
-
|
|
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)
|
|
57
|
-
cases.
|
|
52
|
+
Code block size (NROWS, NCOLS)
|
|
58
53
|
cinema2k : int, optional
|
|
59
|
-
Frames per second, either 24 or 48.
|
|
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.
|
|
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
|
|
68
|
-
other parameters.
|
|
58
|
+
The image color space. If not supplied, it will be inferred.
|
|
69
59
|
cratios : Tuple[int, ...], optional
|
|
70
|
-
|
|
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).
|
|
75
|
-
|
|
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
|
|
65
|
+
If true, write EPH marker after each header packet.
|
|
78
66
|
grid_offset : Tuple[int, int], optional
|
|
79
|
-
Offset (
|
|
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
|
|
82
|
-
reversible 5-3 transform.
|
|
69
|
+
If true, use the irreversible DWT 9-7 transform.
|
|
83
70
|
mct : bool, optional
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
+
Generate PLT markers.
|
|
102
87
|
prog : {'LRCP', 'RLCP', 'RPCL', 'PCRL', 'CPRL'}, optional
|
|
103
|
-
Progression order.
|
|
104
|
-
|
|
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.
|
|
112
|
-
cratios.
|
|
96
|
+
to be lossless, specify 0 for the last value.
|
|
113
97
|
sop : bool, optional
|
|
114
|
-
If true, write
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
260
|
-
self.
|
|
261
|
-
|
|
262
|
-
self.
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
|
|
275
|
-
|
|
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
|
|
283
|
-
self.
|
|
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.
|
|
217
|
+
self._codec_format = opj2.CODEC_J2K
|
|
287
218
|
|
|
288
219
|
self._validate_kwargs()
|
|
289
220
|
|
|
290
|
-
if
|
|
291
|
-
#
|
|
292
|
-
#
|
|
293
|
-
|
|
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
|
-
|
|
227
|
+
else:
|
|
298
228
|
# We are writing a JP2/J2K/JPX file where the image is
|
|
299
229
|
# contained in memory.
|
|
300
|
-
self
|
|
301
|
-
|
|
302
|
-
self.finalize()
|
|
230
|
+
self[:] = data
|
|
303
231
|
|
|
304
232
|
def finalize(self, force_parse=False):
|
|
305
|
-
"""For now, the only
|
|
306
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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)
|