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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
glymur/tiff.py CHANGED
@@ -1,13 +1,10 @@
1
1
  # standard library imports
2
2
  from __future__ import annotations
3
- import io
4
3
  import logging
5
4
  import pathlib
6
5
  import shutil
7
- import struct
8
6
  import sys
9
7
  from typing import List, Tuple
10
- from uuid import UUID
11
8
  import warnings
12
9
 
13
10
  # 3rd party library imports
@@ -15,21 +12,16 @@ import numpy as np
15
12
 
16
13
  # local imports
17
14
  from glymur import Jp2k, set_option
18
- from glymur.core import SRGB, RESTRICTED_ICC_PROFILE
15
+ from glymur.core import SRGB
16
+ from ._core_converter import _2JP2Converter
19
17
  from .lib import tiff as libtiff
20
- from .lib.tiff import DATATYPE2FMT
21
18
  from . import jp2box
22
19
 
23
20
  # we need a lower case mapping from the tag name to the tag number
24
- TAGNAME2NUM = {k.lower(): v['number'] for k, v in libtiff.TAGS.items()}
21
+ TAGNAME2NUM = {k.lower(): v["number"] for k, v in libtiff.TAGS.items()}
25
22
 
26
23
 
27
- # Mnemonics for the two TIFF format version numbers.
28
- _TIFF = 42
29
- _BIGTIFF = 43
30
-
31
-
32
- class Tiff2Jp2k(object):
24
+ class Tiff2Jp2k(_2JP2Converter):
33
25
  """
34
26
  Transform a TIFF image into a JP2 image.
35
27
 
@@ -45,7 +37,7 @@ class Tiff2Jp2k(object):
45
37
  Dimensions of the image.
46
38
  jp2 : JP2K object
47
39
  Write to this JPEG2000 file
48
- jp2_filename : path
40
+ jp2_path : path
49
41
  Path to JPEG 2000 file to be written.
50
42
  jp2_kwargs : dict
51
43
  Keyword arguments to pass along to the Jp2k constructor.
@@ -55,7 +47,7 @@ class Tiff2Jp2k(object):
55
47
  The number of rows per strip in the TIFF.
56
48
  spp : int
57
49
  Samples Per Pixel TIFF tag value
58
- tiff_filename : path
50
+ tiff_path : path
59
51
  Path to TIFF file.
60
52
  tilesize : tuple
61
53
  The dimensions of a tile in the JP2K file.
@@ -69,8 +61,8 @@ class Tiff2Jp2k(object):
69
61
 
70
62
  def __init__(
71
63
  self,
72
- tiff_filename: pathlib.Path,
73
- jp2_filename: pathlib.Path,
64
+ tiff_path: pathlib.Path,
65
+ jp2_path: pathlib.Path,
74
66
  create_exif_uuid: bool = True,
75
67
  create_xmp_uuid: bool = True,
76
68
  exclude_tags: List[int | str] | None = None,
@@ -78,7 +70,7 @@ class Tiff2Jp2k(object):
78
70
  tilesize: Tuple[int, int] | None = None,
79
71
  include_icc_profile: bool = False,
80
72
  verbosity: int = logging.CRITICAL,
81
- **kwargs
73
+ **kwargs,
82
74
  ):
83
75
  """
84
76
  Construct the object.
@@ -105,16 +97,25 @@ class Tiff2Jp2k(object):
105
97
  verbosity : int
106
98
  Set the level of logging, i.e. WARNING, INFO, etc.
107
99
  """
100
+ super().__init__(
101
+ create_exif_uuid, create_xmp_uuid, include_icc_profile, tilesize,
102
+ verbosity
103
+ )
108
104
 
109
- self.tiff_filename = tiff_filename
110
- if not self.tiff_filename.exists():
111
- raise FileNotFoundError(f'{tiff_filename} does not exist')
105
+ self.tiff_path = pathlib.Path(tiff_path)
106
+ if not self.tiff_path.exists():
107
+ raise FileNotFoundError(f"{tiff_path} does not exist")
108
+
109
+ self.jp2_path = pathlib.Path(jp2_path)
110
+ if self.jp2_path.exists():
111
+ msg = (
112
+ f'{str(self.jp2_path)} already exists, ',
113
+ 'please delete if you wish to overwrite.'
114
+ )
115
+ raise FileExistsError(msg)
112
116
 
113
- self.jp2_filename = jp2_filename
114
- self.tilesize = tilesize
115
117
  self.create_exif_uuid = create_exif_uuid
116
118
  self.create_xmp_uuid = create_xmp_uuid
117
- self.include_icc_profile = include_icc_profile
118
119
 
119
120
  if exclude_tags is None:
120
121
  exclude_tags = []
@@ -126,16 +127,13 @@ class Tiff2Jp2k(object):
126
127
  # Assume that there is no ColorMap tag until we know otherwise.
127
128
  self._colormap = None
128
129
 
129
- # Assume that there is no ICC profile tag until we know otherwise.
130
- self.icc_profile = None
131
-
132
130
  # Assume no XML_PACKET tag until we know otherwise.
133
131
  self.xmp_data = None
134
132
 
135
133
  self.setup_logging(verbosity)
136
134
 
137
135
  if num_threads > 1:
138
- set_option('lib.num_threads', num_threads)
136
+ set_option("lib.num_threads", num_threads)
139
137
 
140
138
  def _process_exclude_tags(self, exclude_tags):
141
139
  """The list of tags to exclude may be mixed type (str or integer).
@@ -188,16 +186,9 @@ class Tiff2Jp2k(object):
188
186
 
189
187
  return lst
190
188
 
191
- def setup_logging(self, verbosity):
192
- self.logger = logging.getLogger('tiff2jp2')
193
- self.logger.setLevel(verbosity)
194
- ch = logging.StreamHandler()
195
- ch.setLevel(verbosity)
196
- self.logger.addHandler(ch)
197
-
198
189
  def __enter__(self):
199
190
  """The Tiff2Jp2k must be used with a context manager."""
200
- self.tiff_fp = libtiff.open(self.tiff_filename)
191
+ self.tiff_fp = libtiff.open(str(self.tiff_path))
201
192
  return self
202
193
 
203
194
  def __exit__(self, exc_type, exc_value, exc_traceback):
@@ -226,7 +217,7 @@ class Tiff2Jp2k(object):
226
217
  if photo != libtiff.Photometric.PALETTE:
227
218
  return
228
219
 
229
- jp2h = [box for box in self.jp2.box if box.box_id == 'jp2h'][0]
220
+ jp2h = [box for box in self.jp2.box if box.box_id == "jp2h"][0]
230
221
 
231
222
  bps = (8, 8, 8)
232
223
  pclr = jp2box.PaletteBox(
@@ -246,46 +237,14 @@ class Tiff2Jp2k(object):
246
237
 
247
238
  # fix the colr box. the colorspace needs to be changed from greyscale
248
239
  # to rgb
249
- colr = [box for box in jp2h.box if box.box_id == 'colr'][0]
240
+ colr = [box for box in jp2h.box if box.box_id == "colr"][0]
250
241
  colr.colorspace = SRGB
251
242
 
252
- temp_filename = str(self.jp2_filename) + '.tmp'
243
+ temp_filename = str(self.jp2_path) + ".tmp"
253
244
  self.jp2.wrap(temp_filename, boxes=self.jp2.box)
254
- shutil.move(temp_filename, self.jp2_filename)
245
+ shutil.move(temp_filename, self.jp2_path)
255
246
  self.jp2.parse()
256
247
 
257
- def rewrap_for_icc_profile(self):
258
- """Consume a TIFF ICC profile, if one is there."""
259
- if self.icc_profile is None and self.include_icc_profile:
260
- self.logger.warning("No ICC profile was found.")
261
-
262
- if self.icc_profile is None or not self.include_icc_profile:
263
- return
264
-
265
- self.logger.info(
266
- 'Consuming an ICC profile into JP2 color specification box.'
267
- )
268
-
269
- colr = jp2box.ColourSpecificationBox(
270
- method=RESTRICTED_ICC_PROFILE,
271
- precedence=0,
272
- icc_profile=self.icc_profile
273
- )
274
-
275
- # construct the new set of JP2 boxes, insert the color specification
276
- # box with the ICC profile
277
- jp2 = Jp2k(self.jp2_filename)
278
- boxes = jp2.box
279
- boxes[2].box = [boxes[2].box[0], colr]
280
-
281
- # re-wrap the codestream, involves a file copy
282
- tmp_filename = str(self.jp2_filename) + '.tmp'
283
-
284
- with open(tmp_filename, mode='wb') as tfile:
285
- jp2.wrap(tfile.name, boxes=boxes)
286
-
287
- shutil.move(tmp_filename, self.jp2_filename)
288
-
289
248
  def append_extra_jp2_boxes(self):
290
249
  """Copy over the TIFF IFD. Place it in a UUID box. Append to the JPEG
291
250
  2000 file.
@@ -293,69 +252,13 @@ class Tiff2Jp2k(object):
293
252
  self.append_exif_uuid_box()
294
253
  self.append_xmp_uuid_box()
295
254
 
296
- def append_exif_uuid_box(self):
297
- """Append an EXIF UUID box onto the end of the JPEG 2000 file. It will
298
- contain metadata from the TIFF IFD.
299
- """
300
- if not self.create_exif_uuid:
301
- return
302
-
303
- # create a bytesio object for the IFD
304
- b = io.BytesIO()
305
-
306
- # write this 32-bit header into the UUID, no matter if we had bigtiff
307
- # or regular tiff or big endian
308
- data = struct.pack('<BBHI', 73, 73, 42, 8)
309
- b.write(data)
310
-
311
- self._write_ifd(b, self.tags)
312
-
313
- # create the Exif UUID
314
- if self.found_geotiff_tags:
315
- # geotiff UUID
316
- the_uuid = UUID('b14bf8bd-083d-4b43-a5ae-8cd7d5a6ce03')
317
- payload = b.getvalue()
318
- else:
319
- # Make it an exif UUID.
320
- the_uuid = UUID(bytes=b'JpgTiffExif->JP2')
321
- payload = b'EXIF\0\0' + b.getvalue()
322
-
323
- # the length of the box is the length of the payload plus 8 bytes
324
- # to store the length of the box and the box ID
325
- box_length = len(payload) + 8
326
-
327
- uuid_box = jp2box.UUIDBox(the_uuid, payload, box_length)
328
- with open(self.jp2_filename, mode='ab') as f:
329
- uuid_box.write(f)
330
-
331
- self.jp2.finalize(force_parse=True)
332
-
333
- def append_xmp_uuid_box(self):
334
- """Append an XMP UUID box onto the end of the JPEG 2000 file if there
335
- was an XMP tag in the TIFF IFD.
336
- """
337
-
338
- if self.xmp_data is None:
339
- return
340
-
341
- if not self.create_xmp_uuid:
342
- return
343
-
344
- # create the XMP UUID
345
- the_uuid = jp2box.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac')
346
- payload = bytes(self.xmp_data)
347
- box_length = len(payload) + 8
348
- uuid_box = jp2box.UUIDBox(the_uuid, payload, box_length)
349
- with open(self.jp2_filename, mode='ab') as f:
350
- uuid_box.write(f)
351
-
352
255
  def get_main_ifd(self):
353
256
  """Read all the tags in the main IFD. We do it this way because of the
354
257
  difficulty in using TIFFGetFieldDefaulted when the datatype of a tag
355
258
  can differ.
356
259
  """
357
260
 
358
- with open(self.tiff_filename, 'rb') as tfp:
261
+ with self.tiff_path.open(mode="rb") as tfp:
359
262
 
360
263
  self.read_tiff_header(tfp)
361
264
 
@@ -364,7 +267,7 @@ class Tiff2Jp2k(object):
364
267
  if 320 in self.tags:
365
268
 
366
269
  # the TIFF must have PALETTE photometric interpretation
367
- data = np.array(self.tags[320]['payload'])
270
+ data = np.array(self.tags[320]["payload"])
368
271
  self._colormap = data.reshape(len(data) // 3, 3)
369
272
  self._colormap = self._colormap / 65535
370
273
  self._colormap = (self._colormap * 255).astype(np.uint8)
@@ -372,256 +275,18 @@ class Tiff2Jp2k(object):
372
275
  if 700 in self.tags:
373
276
 
374
277
  # XMLPacket
375
- self.xmp_data = self.tags[700]['payload']
278
+ self.xmp_data = bytes(self.tags[700]["payload"])
376
279
 
377
280
  else:
378
281
  self.xmp_data = None
379
282
 
380
- if 34665 in self.tags:
381
- # we have an EXIF IFD
382
- offset = self.tags[34665]['payload'][0]
383
- tfp.seek(offset)
384
- exif_ifd = self.read_ifd(tfp)
385
-
386
- self.tags[34665]['payload'] = exif_ifd
387
-
388
283
  if 34675 in self.tags:
389
284
  # ICC profile
390
- self.icc_profile = bytes(self.tags[34675]['payload'])
285
+ self.icc_profile = bytes(self.tags[34675]["payload"])
391
286
 
392
287
  else:
393
288
  self.icc_profile = None
394
289
 
395
- def read_ifd(self, tfp):
396
- """Process either the main IFD or an Exif IFD
397
-
398
- Parameters
399
- ----------
400
- tfp : file-like
401
- FILE pointer for TIFF
402
-
403
- Returns
404
- -------
405
- dictionary of the TIFF IFD
406
- """
407
-
408
- self.found_geotiff_tags = False
409
-
410
- tag_length = 20 if self.version == _BIGTIFF else 12
411
-
412
- # how many tags?
413
- if self.version == _BIGTIFF:
414
- buffer = tfp.read(8)
415
- num_tags, = struct.unpack(self.endian + 'Q', buffer)
416
- else:
417
- buffer = tfp.read(2)
418
- num_tags, = struct.unpack(self.endian + 'H', buffer)
419
-
420
- # Ok, so now we have the IFD main body, but following that we have
421
- # the tag payloads that cannot fit into 4 bytes.
422
-
423
- # the IFD main body in the TIFF. As it might be big endian, we
424
- # cannot just process it as one big chunk.
425
- buffer = tfp.read(num_tags * tag_length)
426
-
427
- if self.version == _BIGTIFF:
428
- tag_format_str = self.endian + 'HHQQ'
429
- tag_payload_offset = 12
430
- max_tag_payload_length = 8
431
- else:
432
- tag_format_str = self.endian + 'HHII'
433
- tag_payload_offset = 8
434
- max_tag_payload_length = 4
435
-
436
- tags = {}
437
-
438
- for idx in range(num_tags):
439
-
440
- self.logger.debug(f'tag #: {idx}')
441
-
442
- tag_data = buffer[idx * tag_length:(idx + 1) * tag_length]
443
-
444
- tag, dtype, nvalues, offset = struct.unpack(tag_format_str, tag_data) # noqa : E501
445
-
446
- if tag == 34735:
447
- self.found_geotiff_tags = True
448
-
449
- payload_length = DATATYPE2FMT[dtype]['nbytes'] * nvalues
450
-
451
- if payload_length > max_tag_payload_length:
452
- # the payload does not fit into the tag entry, so use the
453
- # offset to seek to that position
454
- current_position = tfp.tell()
455
- tfp.seek(offset)
456
- payload_buffer = tfp.read(payload_length)
457
- tfp.seek(current_position)
458
-
459
- # read the payload from the TIFF
460
- payload_format = DATATYPE2FMT[dtype]['format'] * nvalues
461
- payload = struct.unpack(
462
- self.endian + payload_format, payload_buffer
463
- )
464
-
465
- else:
466
- # the payload DOES fit into the TIFF tag entry
467
- payload_buffer = tag_data[tag_payload_offset:]
468
-
469
- # read ALL of the payload buffer
470
- fmt = DATATYPE2FMT[dtype]['format']
471
- num_items = (
472
- int(max_tag_payload_length / DATATYPE2FMT[dtype]['nbytes'])
473
- )
474
- payload_format = self.endian + fmt * num_items
475
- payload = struct.unpack(payload_format, payload_buffer)
476
-
477
- # Extract the actual payload. Two things going
478
- # on here. First of all, not all of the items may
479
- # be used. For example, if the payload length is
480
- # 4 bytes but the format string was HHH, the that
481
- # last 16 bit value is not wanted, so we should
482
- # discard it. Second thing is that the signed and
483
- # unsigned rational datatypes effectively have twice
484
- # the number of values so we need to account for that.
485
- if dtype in [5, 10]:
486
- payload = payload[:2 * nvalues]
487
- else:
488
- payload = payload[:nvalues]
489
-
490
- tags[tag] = {
491
- 'dtype': dtype,
492
- 'nvalues': nvalues,
493
- 'payload': payload
494
- }
495
-
496
- return tags
497
-
498
- def _write_ifd(self, b, tags):
499
- """Write the IFD out to the UUIDBox. We will always write IFDs
500
- for 32-bit TIFFs, i.e. 12 byte tags, meaning just 4 bytes within
501
- the tag for the tag data
502
- """
503
-
504
- little_tiff_tag_length = 12
505
- max_tag_payload_length = 4
506
-
507
- # exclude any unwanted tags
508
- if self.exclude_tags is not None:
509
- for tag in self.exclude_tags:
510
- if tag in tags:
511
- tags.pop(tag)
512
-
513
- num_tags = len(tags)
514
- write_buffer = struct.pack('<H', num_tags)
515
- b.write(write_buffer)
516
-
517
- # Ok, so now we have the IFD main body, but following that we have
518
- # the tag payloads that cannot fit into 4 bytes.
519
-
520
- ifd_start_loc = b.tell()
521
- after_ifd_position = ifd_start_loc + num_tags * little_tiff_tag_length
522
-
523
- for idx, tag in enumerate(tags):
524
-
525
- tag_offset = ifd_start_loc + idx * little_tiff_tag_length
526
- self.logger.debug(f'tag #: {tag}, writing to {tag_offset}')
527
- self.logger.debug(f'tag #: {tag}, after IFD {after_ifd_position}')
528
-
529
- b.seek(tag_offset)
530
-
531
- dtype = tags[tag]['dtype']
532
- nvalues = tags[tag]['nvalues']
533
- payload = tags[tag]['payload']
534
-
535
- payload_length = DATATYPE2FMT[dtype]['nbytes'] * nvalues
536
-
537
- if payload_length > max_tag_payload_length:
538
- # the payload does not fit into the tag entry
539
-
540
- # read the payload from the TIFF
541
- payload_format = DATATYPE2FMT[dtype]['format'] * nvalues
542
-
543
- # write the tag entry to the UUID
544
- new_offset = after_ifd_position
545
- buffer = struct.pack(
546
- '<HHII', tag, dtype, nvalues, new_offset
547
- )
548
- b.write(buffer)
549
-
550
- # now write the payload at the outlying position and then come
551
- # back to the same position in the file stream
552
- cpos = b.tell()
553
- b.seek(new_offset)
554
-
555
- format = '<' + DATATYPE2FMT[dtype]['format'] * nvalues
556
- buffer = struct.pack(format, *payload)
557
- b.write(buffer)
558
-
559
- # keep track of the next position to write out-of-IFD data
560
- after_ifd_position = b.tell()
561
- b.seek(cpos)
562
-
563
- else:
564
-
565
- # the payload DOES fit into the TIFF tag entry
566
- # write the tag metadata
567
- buffer = struct.pack('<HHI', tag, dtype, nvalues)
568
- b.write(buffer)
569
-
570
- payload_format = DATATYPE2FMT[dtype]['format'] * nvalues
571
-
572
- # we may need to alter the output format
573
- if payload_format in ['H', 'B', 'I']:
574
- # just write it as an integer
575
- payload_format = 'I'
576
-
577
- if tag == 34665:
578
- # special case for an EXIF IFD
579
- buffer = struct.pack('<I', after_ifd_position)
580
- b.write(buffer)
581
- b.seek(after_ifd_position)
582
- after_ifd_position = self._write_ifd(b, payload)
583
-
584
- else:
585
- # write a normal tag
586
- buffer = struct.pack('<' + payload_format, *payload)
587
- b.write(buffer)
588
-
589
- return after_ifd_position
590
-
591
- def read_tiff_header(self, tfp):
592
- """Get the endian-ness of the TIFF, seek to the main IFD"""
593
-
594
- buffer = tfp.read(4)
595
- data = struct.unpack('BB', buffer[:2])
596
-
597
- # big endian or little endian?
598
- if data[0] == 73 and data[1] == 73:
599
- # little endian
600
- self.endian = '<'
601
- elif data[0] == 77 and data[1] == 77:
602
- # big endian
603
- self.endian = '>'
604
- # no other option is possible, libtiff.open would have errored out
605
- # else:
606
- # msg = (
607
- # f"The byte order indication in the TIFF header "
608
- # f"({data}) is invalid. It should be either "
609
- # f"{bytes([73, 73])} or {bytes([77, 77])}."
610
- # )
611
- # raise RuntimeError(msg)
612
-
613
- # version number and offset to the first IFD
614
- version, = struct.unpack(self.endian + 'H', buffer[2:4])
615
- self.version = _TIFF if version == 42 else _BIGTIFF
616
-
617
- if self.version == _BIGTIFF:
618
- buffer = tfp.read(12)
619
- _, _, offset = struct.unpack(self.endian + 'HHQ', buffer)
620
- else:
621
- buffer = tfp.read(4)
622
- offset, = struct.unpack(self.endian + 'I', buffer)
623
- tfp.seek(offset)
624
-
625
290
  def get_tag_value(self, tagnum):
626
291
  """Return the value associated with the tag. Some tags are not
627
292
  actually written into the IFD, but are instead "defaulted".
@@ -640,11 +305,10 @@ class Tiff2Jp2k(object):
640
305
  return 1
641
306
 
642
307
  # The tag value is always stored as a tuple with at least one member.
643
- return self.tags[tagnum]['payload'][0]
308
+ return self.tags[tagnum]["payload"][0]
644
309
 
645
310
  def copy_image(self):
646
- """Transfer the image data from the TIFF to the JPEG 2000 file.
647
- """
311
+ """Transfer the image data from the TIFF to the JPEG 2000 file."""
648
312
 
649
313
  if libtiff.isTiled(self.tiff_fp):
650
314
  isTiled = True
@@ -695,7 +359,12 @@ class Tiff2Jp2k(object):
695
359
  self.th = self.get_tag_value(323)
696
360
  else:
697
361
  self.tw = self.imagewidth
698
- self.rps = self.get_tag_value(278)
362
+ try:
363
+ self.rps = self.get_tag_value(278)
364
+ except KeyError:
365
+ # stripped but no RowsPerStrip tag? default to the image
366
+ # height
367
+ self.rps = self.imageheight
699
368
 
700
369
  if self.spp == 1:
701
370
  shape = (self.imageheight, self.imagewidth)
@@ -703,7 +372,7 @@ class Tiff2Jp2k(object):
703
372
  shape = (self.imageheight, self.imagewidth, self.spp)
704
373
 
705
374
  self.jp2 = Jp2k(
706
- self.jp2_filename,
375
+ self.jp2_path,
707
376
  shape=shape,
708
377
  tilesize=self.tilesize,
709
378
  **self.jp2_kwargs
@@ -759,7 +428,7 @@ class Tiff2Jp2k(object):
759
428
  )
760
429
 
761
430
  # must reorder image planes on big-endian
762
- if sys.byteorder == 'big':
431
+ if sys.byteorder == "big":
763
432
  image = np.flip(image, axis=2)
764
433
 
765
434
  # potentially get rid of the alpha plane
@@ -777,7 +446,9 @@ class Tiff2Jp2k(object):
777
446
  # This might be a bit bigger than the actual image because of a
778
447
  # possibly partial last strip.
779
448
  stripped_shape = (
780
- num_tiff_strip_rows * self.rps, self.imagewidth, self.spp
449
+ num_tiff_strip_rows * self.rps,
450
+ self.imagewidth,
451
+ self.spp
781
452
  )
782
453
  image = np.zeros(stripped_shape, dtype=self.dtype)
783
454
 
@@ -793,7 +464,7 @@ class Tiff2Jp2k(object):
793
464
 
794
465
  if self.imageheight != stripped_shape[0]:
795
466
  # cut the image down due to a partial last strip
796
- image = image[:self.imageheight, :, :]
467
+ image = image[: self.imageheight, :, :]
797
468
 
798
469
  self.jp2[:] = image
799
470
 
@@ -810,7 +481,7 @@ class Tiff2Jp2k(object):
810
481
  tiled_shape = (
811
482
  num_tiff_tile_rows * self.th,
812
483
  num_tiff_tile_cols * self.tw,
813
- self.spp
484
+ self.spp,
814
485
  )
815
486
 
816
487
  image = np.zeros(tiled_shape, dtype=self.dtype)
@@ -830,7 +501,7 @@ class Tiff2Jp2k(object):
830
501
  image[rows, cols, :] = tiff_tile
831
502
 
832
503
  if final_shape != tiled_shape:
833
- image = image[:final_shape[0], :final_shape[1], :]
504
+ image = image[: final_shape[0], : final_shape[1], :]
834
505
 
835
506
  self.jp2[:] = image
836
507
 
@@ -841,7 +512,7 @@ class Tiff2Jp2k(object):
841
512
  for jp2k_tilenum, tilewriter in enumerate(self.jp2.get_tilewriters()):
842
513
  tiff_tiles = self._get_covering_tiles(jp2k_tilenum)
843
514
  jp2k_tile = self._cover_tile(jp2k_tilenum, tiff_tiles)
844
- self.logger.info(f'Writing tile {jp2k_tilenum}')
515
+ self.logger.info(f"Writing tile {jp2k_tilenum}")
845
516
  tilewriter[:] = jp2k_tile
846
517
 
847
518
  def _cover_tile(self, jp2k_tile_num, tiff_tile_nums):
@@ -859,8 +530,12 @@ class Tiff2Jp2k(object):
859
530
 
860
531
  # Does the JP2K have partial tiles on the far right and bottom of the
861
532
  # image.
862
- partial_jp2_tile_rows = (self.imageheight / jth) != (self.imageheight // jth) # noqa : E501
863
- partial_jp2_tile_cols = (self.imagewidth / jtw) != (self.imagewidth // jtw) # noqa : E501
533
+ partial_jp2_tile_rows = (self.imageheight / jth) != (
534
+ self.imageheight // jth
535
+ ) # noqa : E501
536
+ partial_jp2_tile_cols = (self.imagewidth / jtw) != (
537
+ self.imagewidth // jtw
538
+ ) # noqa : E501
864
539
 
865
540
  num_jp2k_tile_rows = int(np.ceil(self.imageheight / jth))
866
541
  num_jp2k_tile_cols = int(np.ceil(self.imagewidth / jtw))
@@ -896,7 +571,7 @@ class Tiff2Jp2k(object):
896
571
  libtiff.readRGBATile(self.tiff_fp, x, y, rgba_tile)
897
572
 
898
573
  # The RGBA interface requires some reordering.
899
- if sys.byteorder == 'little':
574
+ if sys.byteorder == "little":
900
575
  # image is upside down
901
576
  dims = [0]
902
577
  else:
@@ -911,7 +586,8 @@ class Tiff2Jp2k(object):
911
586
  else:
912
587
 
913
588
  tiff_tile = np.zeros(
914
- (self.th, self.tw, self.spp), dtype=self.dtype
589
+ (self.th, self.tw, self.spp),
590
+ dtype=self.dtype
915
591
  )
916
592
  libtiff.readEncodedTile(self.tiff_fp, ttile_num, tiff_tile)
917
593
 
@@ -936,17 +612,11 @@ class Tiff2Jp2k(object):
936
612
  jp2k_tile[jrows, jcols, :] = tiff_tile[trows, tcols, :]
937
613
 
938
614
  # last tile column? last tile row? If so, we may have a partial tile.
939
- if (
940
- partial_jp2_tile_cols
941
- and jp2k_tile_col == num_jp2k_tile_cols - 1
942
- ):
615
+ if partial_jp2_tile_cols and jp2k_tile_col == num_jp2k_tile_cols - 1:
943
616
  last_j2k_cols = slice(0, self.imagewidth - jp2k_ulx)
944
617
  jp2k_tile = jp2k_tile[:, last_j2k_cols, :].copy()
945
618
 
946
- if (
947
- partial_jp2_tile_rows
948
- and jp2k_tile_row == num_jp2k_tile_rows - 1
949
- ):
619
+ if partial_jp2_tile_rows and jp2k_tile_row == num_jp2k_tile_rows - 1:
950
620
  last_j2k_rows = slice(0, self.imageheight - jp2k_uly)
951
621
  jp2k_tile = jp2k_tile[last_j2k_rows, :, :].copy()
952
622
 
@@ -979,8 +649,6 @@ class Tiff2Jp2k(object):
979
649
  # lower left corner
980
650
  llx = ulx
981
651
  lly = min(uly + jth - 1, self.imageheight - 1)
982
- ll_tiff_tilenum = libtiff.computeTile(self.tiff_fp, llx, lly, 0, 0)
983
- lower_tiff_tile_row = int(np.ceil(ll_tiff_tilenum // num_tiff_tile_cols)) # noqa : E501
984
652
 
985
653
  # lower right corner
986
654
  lrx = min(llx + jtw - 1, self.imagewidth - 1)
@@ -1014,8 +682,8 @@ class Tiff2Jp2k(object):
1014
682
 
1015
683
  jth, jtw = self.tilesize
1016
684
 
1017
- self.logger.debug(f'image: {self.imageheight} x {self.imagewidth}')
1018
- self.logger.debug(f'jptile: {jth} x {jtw}')
685
+ self.logger.debug(f"image: {self.imageheight} x {self.imagewidth}")
686
+ self.logger.debug(f"jptile: {jth} x {jtw}")
1019
687
  num_strips = libtiff.numberOfStrips(self.tiff_fp)
1020
688
 
1021
689
  num_jp2k_tile_cols = int(np.ceil(self.imagewidth / jtw))
@@ -1025,7 +693,7 @@ class Tiff2Jp2k(object):
1025
693
  jp2k_tile_row = idx // num_jp2k_tile_cols
1026
694
  jp2k_tile_col = idx % num_jp2k_tile_cols
1027
695
 
1028
- msg = f'Tile: #{idx} row #{jp2k_tile_row} col #{jp2k_tile_col}'
696
+ msg = f"Tile: #{idx} row #{jp2k_tile_row} col #{jp2k_tile_col}"
1029
697
  self.logger.info(msg)
1030
698
 
1031
699
  # the coordinates of the upper left pixel of the jp2k tile
@@ -1037,7 +705,9 @@ class Tiff2Jp2k(object):
1037
705
  # jp2k tiles from this same TIFF multi-strip.
1038
706
  if jp2k_tile_col == 0:
1039
707
  tiff_multi_strip = self._construct_multi_strip(
1040
- july, num_strips, jth,
708
+ july,
709
+ num_strips,
710
+ jth,
1041
711
  )
1042
712
 
1043
713
  # construct the TIFF row and column slices from the multi-strip,
@@ -1102,7 +772,8 @@ class Tiff2Jp2k(object):
1102
772
  # This may result in a multi-strip that has more rows than the jp2k
1103
773
  # tile
1104
774
  tiff_multi_strip = np.zeros(
1105
- (num_rows, self.imagewidth, spp), dtype=dtype
775
+ (num_rows, self.imagewidth, spp),
776
+ dtype=dtype
1106
777
  )
1107
778
 
1108
779
  # Fill the multi-strip
@@ -1111,7 +782,8 @@ class Tiff2Jp2k(object):
1111
782
  if self.photo == libtiff.Photometric.YCBCR:
1112
783
 
1113
784
  tiff_rgba_strip = np.zeros(
1114
- (self.rps, self.imagewidth, 4), dtype=dtype
785
+ (self.rps, self.imagewidth, 4),
786
+ dtype=dtype
1115
787
  )
1116
788
 
1117
789
  libtiff.readRGBAStrip(
@@ -1138,24 +810,23 @@ class Tiff2Jp2k(object):
1138
810
 
1139
811
  # The rgba interface requires at least flipping the image
1140
812
  # upside down, and also reordering the planes on big endian
1141
- if sys.byteorder == 'little':
813
+ if sys.byteorder == "little":
1142
814
  dims = [0]
1143
815
  else:
1144
816
  dims = [0, 2]
1145
817
  tiff_rgba_strip = np.flip(tiff_rgba_strip, axis=dims)
1146
818
 
1147
819
  # potentially get rid of alpha plane
1148
- tiff_strip = tiff_rgba_strip[:, :, :self.spp]
820
+ tiff_strip = tiff_rgba_strip[:, :, : self.spp]
1149
821
 
1150
822
  else:
1151
823
 
1152
824
  tiff_strip = np.zeros(
1153
- (self.rps, self.imagewidth, spp), dtype=dtype
825
+ (self.rps, self.imagewidth, spp),
826
+ dtype=dtype
1154
827
  )
1155
828
 
1156
- libtiff.readEncodedStrip(
1157
- self.tiff_fp, stripnum, tiff_strip
1158
- )
829
+ libtiff.readEncodedStrip(self.tiff_fp, stripnum, tiff_strip)
1159
830
 
1160
831
  # push the strip into the multi-strip
1161
832
  top_row = (stripnum - top_strip_num) * self.rps