[Git][debian-gis-team/glymur][master] 5 commits: New upstream version 0.11.7

Antonio Valentino (@antonio.valentino) gitlab at salsa.debian.org
Sat Oct 29 08:24:59 BST 2022



Antonio Valentino pushed to branch master at Debian GIS Project / glymur


Commits:
c9ea60fa by Antonio Valentino at 2022-09-22T05:51:30+00:00
New upstream version 0.11.7
- - - - -
64066e82 by Antonio Valentino at 2022-10-29T07:09:44+00:00
New upstream version 0.12.0
- - - - -
982bc396 by Antonio Valentino at 2022-10-29T07:09:47+00:00
Update upstream source from tag 'upstream/0.12.0'

Update to upstream version '0.12.0'
with Debian dir 1fa3ca8eed3b0700ccfeab4dd7a05cfff1b62cd0
- - - - -
7997a926 by Antonio Valentino at 2022-10-29T07:10:56+00:00
New upstream release

- - - - -
9b019afd by Antonio Valentino at 2022-10-29T07:13:07+00:00
Set distribution to unstable

- - - - -


28 changed files:

- CHANGES.txt
- appveyor.yml
- + ci/travis-310-no-opj.yaml
- debian/changelog
- docs/source/conf.py
- docs/source/how_do_i.rst
- + docs/source/whatsnew/0.12.rst
- docs/source/whatsnew/index.rst
- glymur/_tiff.py
- glymur/codestream.py
- glymur/command_line.py
- glymur/jp2box.py
- glymur/jp2k.py
- glymur/lib/tiff.py
- glymur/tiff.py
- glymur/version.py
- setup.cfg
- + tests/data/basn6a08.tif
- + tests/data/issue572.tif
- tests/fixtures.py
- tests/test_cinema.py
- tests/test_codestream.py
- tests/test_jp2box.py
- tests/test_jp2box_uuid.py
- tests/test_jp2k.py
- tests/test_printing.py
- tests/test_set_decoded_components.py
- tests/test_tiff2jp2.py


Changes:

=====================================
CHANGES.txt
=====================================
@@ -1,3 +1,11 @@
+October 20, 2022 - v0.12.0
+    Add support for ICC profiles, colormaps when converting from TIFF.
+    Add shortcut for retrieving lowest resolution thumbnail.
+    Remove setuptools from runtime requirement.
+    Improve pretty-printing of Exif UUIDs, TLM segments.
+    Change default value of --create-xmp-uuid to True.
+    Minor bugfixes.
+
 September 16, 2022 - v0.11.7
     Error out early when writing 1x1 tile-by-tile
 


=====================================
appveyor.yml
=====================================
@@ -32,6 +32,13 @@ environment:
       CONDA_NPY: "21"
       USE_PATH_FOR_GDAL_PYTHON: "YES"
 
+    - CONDA_ROOT: "C:\\Miniconda3_64"
+      PYTHON_VERSION: "3.10"
+      PYTHON_ARCH: "64"
+      CONDA_PY: "310-no-opj"
+      CONDA_NPY: "21"
+      USE_PATH_FOR_GDAL_PYTHON: "YES"
+
     - CONDA_ROOT: "C:\\Miniconda3_64"
       PYTHON_VERSION: "3.10"
       PYTHON_ARCH: "64"


=====================================
ci/travis-310-no-opj.yaml
=====================================
@@ -0,0 +1,12 @@
+name: glymur
+channels:
+    - defaults
+dependencies:
+    - python=3.10.*
+    - gdal
+    - lxml
+    - numpy
+    - pip
+    - pytest-xdist
+    - scikit-image
+    - libtiff


=====================================
debian/changelog
=====================================
@@ -1,3 +1,9 @@
+glymur (0.12.0-1) unstable; urgency=medium
+
+  * New upstream release.
+
+ -- Antonio Valentino <antonio.valentino at tiscali.it>  Sat, 29 Oct 2022 07:12:53 +0000
+
 glymur (0.11.7-1) unstable; urgency=medium
 
   * New upstream release.


=====================================
docs/source/conf.py
=====================================
@@ -76,9 +76,9 @@ copyright = '2013-2022, John Evans'
 # built documents.
 #
 # The short X.Y version.
-version = '0.11'
+version = '0.12'
 # The full version, including alpha/beta/rc tags.
-release = '0.11.7'
+release = '0.12.0'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.


=====================================
docs/source/how_do_i.rst
=====================================
@@ -13,7 +13,7 @@ array-style slicing, i.e. strides.  For example, here's how
 to retrieve a full resolution and first lower-resolution image. ::
 
     >>> import glymur
-    >>> jp2file = glymur.data.nemo() # just a path to a JPEG2000 file
+    >>> jp2file = glymur.data.nemo() # just a path to a JPEG 2000 file
     >>> jp2 = glymur.Jp2k(jp2file)
     >>> fullres = jp2[:]
     >>> fullres.shape
@@ -22,6 +22,44 @@ to retrieve a full resolution and first lower-resolution image. ::
     >>> thumbnail.shape
     (728, 1296, 3)
 
+Large JPEG 2000 images may have a large number of decomposition levels.  With
+version 0.12.0 of glymur, there exists a shortcut for retrieving
+the lowest resolution thumbnail without having to inspect the JPEG 2000
+codestream in order to specify the highest decomposition level. ::
+
+    >>> import glymur
+    >>> j2kfile = glymur.data.goodstuff() # just a path to a raw JPEG 2000 codestream
+    >>> j2k = glymur.Jp2k(jp2file)
+    >>> j2k.shape
+    (800, 480, 3)
+    >>> thumbnail = j2k[::-1, ::-1]
+    >>> thumbnail.shape # this is ridiculously small
+    (25, 15, 3)
+
+****************************
+... read really large images
+****************************
+JPEG 2000 images can be much larger than what can fit into your computer's memory.  
+While you can use strides that align with the JPEG 2000 decomposition levels to
+retrieve lower resolution images, retrieving the lowest resolution image would seem
+to require that you know just how many decomposition levels are available.  While you
+can get that information from the COD segment in the codestream, glymur provides you
+with a shortcut.  Normally the stride must be a power of 2, but you can provide -1
+instead to get the smallest thumbnail.::
+
+    >>> import glymur
+    >>> j2kfile = glymur.data.goodstuff() # just a path to a JPEG 2000 file
+    >>> j2k = glymur.Jp2k(j2kfile)
+    >>> j2k.shape
+    (800, 480, 3)
+    >>> j2k.codestream.segment[2].num_res
+    5
+    >>> j2k[::32, ::32].shape
+    (25, 15, 3)
+    >>> thumbnail = j2k[::-1, ::-1]
+    >>> thumbnail.shape
+    (25, 15, 3)
+
 ************************
 ... read an image layer?
 ************************


=====================================
docs/source/whatsnew/0.12.rst
=====================================
@@ -0,0 +1,13 @@
+######################
+Changes in glymur 0.12
+######################
+
+*****************
+Changes in 0.12.0
+*****************
+    * Add support for ICC profiles, colormaps when converting from TIFF.
+    * Add shortcut for retrieving lowest resolution thumbnail.
+    * Remove setuptools from runtime requirement.
+    * Improve pretty-printing of Exif UUIDs, TLM segments.
+    * Change default value of --create-xmp-uuid to True.
+    * Minor bugfixes.


=====================================
docs/source/whatsnew/index.rst
=====================================
@@ -8,6 +8,7 @@ These document the changes between minor (or major) versions of glymur.
 
 .. toctree::
 
+    0.12
     0.11
     0.10
     0.9


=====================================
glymur/_tiff.py
=====================================
@@ -7,6 +7,9 @@ from collections import OrderedDict
 import struct
 import warnings
 
+# 3rd party library imports
+import numpy as np
+
 
 def tiff_header(read_buffer):
     """
@@ -31,8 +34,7 @@ def tiff_header(read_buffer):
     _, offset = struct.unpack(endian + 'HI', read_buffer[2:8])
 
     # This is the 'Exif Image' portion.
-    exif = Ifd(endian, read_buffer, offset)
-    return exif.processed_ifd
+    return Ifd(endian, read_buffer, offset).processed_ifd
 
 
 class BadTiffTagDatatype(RuntimeError):
@@ -76,15 +78,15 @@ class Ifd(object):
             # plus 2 bytes for the number of tags plus 12 bytes for each
             # tag entry plus 8 bytes to the offset/payload itself.
             toffp = read_buffer[offset + 10 + j * 12:offset + 10 + j * 12 + 4]
-            tag_data = self.parse_tag(
+            self.raw_ifd[tag] = self.parse_tag(
                 tag, data[j * 4 + 1], data[j * 4 + 2], toffp
             )
-            self.raw_ifd[tag] = tag_data
 
         self.post_process()
 
     def parse_tag(self, tag, dtype, count, offset_buf):
-        """Interpret an Exif image tag data payload.
+        """
+        Interpret an Exif image tag data payload.
         """
 
         try:
@@ -115,16 +117,19 @@ class Ifd(object):
                 for j in range(count):
                     value = float(payload[j * 2]) / float(payload[j * 2 + 1])
                     rational_payload.append(value)
-                payload = rational_payload
+                payload = np.array(rational_payload)
             if count == 1:
                 # If just a single value, then return a scalar instead of a
                 # tuple.
                 payload = payload[0]
+            else:
+                payload = np.array(payload, dtype=TIFFTYPE2NP[dtype])
 
         return payload
 
     def post_process(self):
-        """Map the tag name instead of tag number to the tag value.
+        """
+        Map the tag name instead of tag number to the tag value.
         """
         for tag, value in self.raw_ifd.items():
             try:
@@ -244,7 +249,7 @@ TAGNUM2NAME = {
     34264: 'ModelTransformation',
     34377: 'ImageResources',
     34665: 'ExifTag',
-    34675: 'InterColorProfile',
+    34675: 'ICCProfile',
     34735: 'GeoKeyDirectory',
     34736: 'GeoDoubleParams',
     34737: 'GeoAsciiParams',
@@ -448,3 +453,22 @@ DATATYPE2FMT = {
     17: ('q', 8),
     18: ('Q', 8)
 }
+
+TIFFTYPE2NP = {
+    1: np.ubyte,
+    2: str,
+    3: np.ushort,
+    4: np.uint32,
+    5: np.double,
+    6: np.byte,
+    7: np.ubyte,
+    8: np.short,
+    9: np.int32,
+    10: np.double,
+    11: np.double,
+    12: np.double,
+    13: np.uint32,
+    16: np.uint64,
+    17: np.int64,
+    18: np.uint64,
+}


=====================================
glymur/codestream.py
=====================================
@@ -1852,16 +1852,17 @@ class TLMsegment(Segment):
         self.length = length
         self.offset = offset
         self.ztlm = ztlm
-        self.ttlm = ttlm
-        self.ptlm = ptlm
+        self.ttlm = np.array(ttlm)
+        self.ptlm = np.array(ptlm)
 
     def __str__(self):
         msg = Segment.__str__(self)
-        msg += (
-            f'\n    Index:  {self.ztlm}'
-            f'\n    Tile number:  {self.ttlm}'
-            f'\n    Length:  {self.ptlm}'
-        )
+        with np.printoptions(threshold=4):
+            msg += (
+                f'\n    Index:  {self.ztlm}'
+                f'\n    Tile number:  {self.ttlm}'
+                f'\n    Length:  {self.ptlm}'
+            )
 
         return msg
 


=====================================
glymur/command_line.py
=====================================
@@ -9,7 +9,7 @@ import warnings
 
 # Local imports ...
 from . import Jp2k, set_option, lib
-from .tiff import Tiff2Jp2k
+from . import tiff
 
 
 def main():
@@ -166,11 +166,21 @@ def tiff2jp2():
         'Extract XMLPacket tag value from TIFF IFD and store in XMP UUID box. '
         'This will exclude the XMLPacket tag from the Exif UUID box.'
     )
-    group2.add_argument('--create-xmp-uuid', help=help, action='store_true')
+    group2.add_argument(
+        '--create-xmp-uuid', help=help, action='store_true', default=True
+    )
+
+    help = (
+        'If specified, subsume any ICC profile (tag 34675) from the '
+        'TIFF IFD into the colour specification box.'
+    )
+    group2.add_argument(
+        '--include-icc-profile', help=help, action='store_true'
+    )
 
     help = (
-        'Exclude TIFF tag(s) from EXIF UUID (if creating such a UUID).  '
-        'This option may be specified as tag numbers or names.'
+        'Exclude TIFF tag(s) from EXIF UUID.  This option may be specified as '
+        'tag numbers or names.'
     )
     group2.add_argument('--exclude-tags', help=help, nargs='*')
 
@@ -208,6 +218,7 @@ def tiff2jp2():
     kwargs = {
         'cbsize': args.codeblocksize,
         'cratios': args.cratio,
+        'include_icc_profile': args.include_icc_profile,
         'capture_resolution': args.capture_resolution,
         'create_exif_uuid': args.create_exif_uuid,
         'create_xmp_uuid': args.create_xmp_uuid,
@@ -224,5 +235,5 @@ def tiff2jp2():
         'verbosity': logging_level,
     }
 
-    with Tiff2Jp2k(tiffpath, jp2kpath, **kwargs) as j:
+    with tiff.Tiff2Jp2k(tiffpath, jp2kpath, **kwargs) as j:
         j.run()


=====================================
glymur/jp2box.py
=====================================
@@ -29,7 +29,7 @@ try:
     from osgeo import gdal
     from osgeo import osr
     _HAVE_GDAL = True
-except ModuleNotFoundError:
+except (ImportError, ModuleNotFoundError):
     _HAVE_GDAL = False
 else:
     gdal.UseExceptions()
@@ -379,17 +379,16 @@ class ColourSpecificationBox(Jp2kBox):
             warnings.warn(msg, UserWarning)
 
     def _write_validate(self):
-        """In addition to constructor validation steps, run validation steps
-        for writing."""
-        if self.colorspace is None:
-            msg = ("Writing colr boxes without enumerated "
-                   "colorspaces is not supported at this time.")
-            self._dispatch_validation_error(msg, writing=True)
-
+        """
+        In addition to constructor validation steps, run validation steps
+        for writing.
+        """
         if self.icc_profile is None:
             if self.colorspace not in [SRGB, GREYSCALE, YCC]:
-                msg = ("Colorspace should correspond to one of SRGB, "
-                       "GREYSCALE, or YCC.")
+                msg = (
+                    "Colorspace should correspond to one of SRGB, GREYSCALE, "
+                    "or YCC."
+                )
                 self._dispatch_validation_error(msg, writing=True)
 
         self._validate(writing=True)
@@ -453,20 +452,35 @@ class ColourSpecificationBox(Jp2kBox):
         return text
 
     def write(self, fptr):
-        """Write an Colour Specification box to file.
         """
+        Write an Colour Specification box to file.
+        """
+
         self._write_validate()
         length = 15 if self.icc_profile is None else 11 + len(self.icc_profile)
         fptr.write(struct.pack('>I4s', length, b'colr'))
 
-        read_buffer = struct.pack(
-            '>BBBI',
-            self.method,
-            self.precedence,
-            self.approximation,
-            self.colorspace
-        )
-        fptr.write(read_buffer)
+        if self.icc_profile is None:
+
+            buffer = struct.pack(
+                '>BBBI',
+                self.method,
+                self.precedence,
+                self.approximation,
+                self.colorspace
+            )
+            fptr.write(buffer)
+
+        else:
+
+            buffer = struct.pack(
+                '>BBB',
+                self.method,
+                self.precedence,
+                self.approximation,
+            )
+            fptr.write(buffer)
+            fptr.write(self.icc_profile)
 
     @classmethod
     def parse(cls, fptr, offset, length):
@@ -1030,7 +1044,7 @@ class ContiguousCodestreamBox(Jp2kBox):
                 with open(self._filename, 'rb') as fptr:
                     fptr.seek(self.main_header_offset)
                     codestream = Codestream(
-                        fptr, self._length, header_only=header_only
+                        fptr, self.length, header_only=header_only
                     )
                     self._codestream = codestream
         return self._codestream
@@ -1086,7 +1100,6 @@ class ContiguousCodestreamBox(Jp2kBox):
             offset=offset
         )
         box._filename = fptr.name
-        box._length = length
         return box
 
 
@@ -2126,6 +2139,11 @@ class PaletteBox(Jp2kBox):
         self._validate(writing=True)
         bytes_per_row = sum(self.bits_per_component) / 8
         bytes_per_palette = bytes_per_row * self.palette.shape[0]
+
+        # 8 bytes for the box length and identifier
+        # 3 bytes for NE and NPC
+        # n bytes for the number of columns in the palette
+        # m bytes for the palette itself
         box_length = 8 + 3 + self.palette.shape[1] + bytes_per_palette
 
         # Write the usual (L, T) header.
@@ -2133,8 +2151,9 @@ class PaletteBox(Jp2kBox):
         fptr.write(write_buffer)
 
         # NE, NPC
-        write_buffer = struct.pack('>HB', self.palette.shape[0],
-                                   self.palette.shape[1])
+        write_buffer = struct.pack(
+            '>HB', self.palette.shape[0], self.palette.shape[1]
+        )
         fptr.write(write_buffer)
 
         # Bits Per Sample.  Signed components aren't supported.
@@ -3489,20 +3508,8 @@ class UUIDBox(Jp2kBox):
                 text = 'UUID Data:  Invalid Exif UUID'
                 lst.append(text)
             else:
-                data_copy = self.data.copy()
-                for tag in [
-                    'JPEGTables', 'StripByteCounts', 'StripOffsets',
-                    'TileOffsets', 'TileByteCounts'
-                ]:
-                    if tag in self.data:
-                        data_copy[tag] = '... skipped ...'
-
-                if 'ExifTag' in data_copy:
-                    for tag in ['MakerNote']:
-                        if tag in data_copy['ExifTag']:
-                            data_copy['ExifTag'][tag] = '... skipped ...'
-
-                pprint.pprint(data_copy, stream=s, indent=4)
+                with np.printoptions(threshold=4):
+                    pprint.pprint(self.data, stream=s, indent=4)
                 text = f'UUID Data:  {s.getvalue().rstrip()}'
                 lst.append(text)
         elif self.uuid == _GEOTIFF_UUID:


=====================================
glymur/jp2k.py
=====================================
@@ -851,8 +851,17 @@ class Jp2k(Jp2kBox):
         in memory.
         """
         if version.openjpeg_version < '2.3.0':
-            msg = ("You must have at least version 2.3.0 of OpenJPEG "
-                   "in order to write images.")
+            msg = (
+                "You must have at least version 2.3.0 of OpenJPEG in order to "
+                "write images."
+            )
+            raise RuntimeError(msg)
+
+        if hasattr(self, '_cparams'):
+            msg = (
+                "You cannot write image data to a JPEG 2000 file "
+                "that already exists."
+            )
             raise RuntimeError(msg)
 
         self._determine_colorspace()
@@ -1371,6 +1380,10 @@ class Jp2k(Jp2kBox):
         # Ok, reduce layer step is the same in both xy directions, so just take
         # one of them.
         step = rows_step
+        if step == -1:
+            # This is a shortcut for the last decomposition (or reduce layer
+            # step).
+            step = 2 ** self.codestream.segment[2].num_res
 
         # Check if the step size is a power of 2.
         if np.abs(np.log2(step) - np.round(np.log2(step))) > 1e-6:
@@ -1404,7 +1417,7 @@ class Jp2k(Jp2kBox):
         RuntimeError
             if the proper version of the OpenJPEG library is not available
         """
-        if re.match("0|1.[01234]", version.openjpeg_version):
+        if re.match("0|1|2.[012]", version.openjpeg_version):
             msg = (
                 f"You must have a version of OpenJPEG at least as high as "
                 f"2.3.0 before you can read JPEG2000 images with glymur.  "
@@ -2234,7 +2247,7 @@ class _TileWriter(object):
             opj2.stream_destroy(self.stream)
             opj2.image_destroy(self.image)
             opj2.destroy_codec(self.codec)
-            raise(e)
+            raise e
 
         if self.tile_index == self.number_of_tiles - 1:
             # properly dispose of these resources


=====================================
glymur/lib/tiff.py
=====================================
@@ -596,6 +596,25 @@ def getVersion():
     return m.group('version')
 
 
+def printDirectory(tiff_fp, ofp, mode=0):
+    """
+    Corresponds to TIFFPrintDirectory
+
+    Parameters
+    ----------
+    filename : path or str
+        Path to TIFF
+    """
+    err_handler, warn_handler = _set_error_warning_handlers()
+
+    ARGTYPES = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_long]
+    _LIBTIFF.TIFFPrintDirectory.argtypes = ARGTYPES
+    _LIBTIFF.TIFFPrintDirectory.restype = ctypes.c_void_p
+    _LIBTIFF.TIFFPrintDirectory(tiff_fp, ofp, mode)
+
+    _reset_error_warning_handlers(err_handler, warn_handler)
+
+
 def open(filename, mode='r'):
     """
     Corresponds to TIFFOpen


=====================================
glymur/tiff.py
=====================================
@@ -1,6 +1,7 @@
 # standard library imports
 import io
 import logging
+import shutil
 import struct
 import warnings
 
@@ -10,6 +11,7 @@ from uuid import UUID
 
 # local imports
 from glymur import Jp2k
+from glymur.core import SRGB, RESTRICTED_ICC_PROFILE
 from .lib import tiff as libtiff
 from . import jp2box
 from ._tiff import TAGNUM2NAME
@@ -51,6 +53,8 @@ class Tiff2Jp2k(object):
         If true, then this TIFF must be a GEOTIFF
     tiff_filename : path or str
         Path to TIFF file.
+    jp2 : JP2K object
+        Write to this JPEG2000 file
     jp2_filename : path or str
         Path to JPEG 2000 file to be written.
     jp2_kwargs : dict
@@ -61,12 +65,15 @@ class Tiff2Jp2k(object):
         Create a UUIDBox for the TIFF IFD metadata.
     version : int
         Identifies the TIFF as 32-bit TIFF or 64-bit TIFF.
+    xmp_data : bytes
+        Encoded bytes from XML_PACKET tag (700), or None if not present.
     """
 
     def __init__(
         self, tiff_filename, jp2_filename,
-        create_exif_uuid=True, create_xmp_uuid=False, exclude_tags=None,
-        tilesize=None, verbosity=logging.CRITICAL,
+        create_exif_uuid=True, create_xmp_uuid=True,
+        exclude_tags=None, tilesize=None,
+        verbosity=logging.CRITICAL, include_icc_profile=False,
         **kwargs
     ):
         """
@@ -74,20 +81,23 @@ class Tiff2Jp2k(object):
         ----------
         create_exif_uuid : bool
             If true, create an EXIF UUID out of the TIFF metadata (tags)
+        create_xmp_uuid : bool
+            If true and if there is an XMLPacket (700) tag in the TIFF main
+            IFD, it will be removed from the IFD and placed in a UUID box.
+        include_icc_profile : bool
+            If true, consume any ICC profile tag (34765) into the colour
+            specification box.
         exclude_tags : list or None
             If not None and if create_exif_uuid is True, exclude any listed
             tags from the EXIF UUID.
-        tiff_filename : path or str
-            Path to TIFF file.
         jp2_filename : path or str
             Path to JPEG 2000 file to be written.
+        tiff_filename : path or str
+            Path to TIFF file.
         tilesize : tuple
             The dimensions of a tile in the JP2K file.
         verbosity : int
             Set the level of logging, i.e. WARNING, INFO, etc.
-        create_xmp_uuid : bool
-            If true and if there is an XMLPacket (700) tag in the TIFF main
-            IFD, it will be removed from the IFD and placed in a UUID box.
         """
 
         self.tiff_filename = tiff_filename
@@ -98,11 +108,24 @@ class Tiff2Jp2k(object):
         self.tilesize = tilesize
         self.create_exif_uuid = create_exif_uuid
         self.create_xmp_uuid = create_xmp_uuid
+        self.include_icc_profile = include_icc_profile
 
+        if exclude_tags is None:
+            exclude_tags = []
         self.exclude_tags = self._process_exclude_tags(exclude_tags)
 
+        self.jp2 = None
         self.jp2_kwargs = kwargs
 
+        # Assume that there is no ColorMap tag until we know otherwise.
+        self._colormap = None
+
+        # Assume that there is no ICC profile tag until we know otherwise.
+        self.icc_profile = None
+
+        # Assume no XML_PACKET tag until we know otherwise.
+        self.xmp_data = None
+
         self.setup_logging(verbosity)
 
     def _process_exclude_tags(self, exclude_tags):
@@ -158,9 +181,7 @@ class Tiff2Jp2k(object):
                 # numeric value
                 lst.append(tag_num)
 
-        exclude_tags = lst
-
-        return exclude_tags
+        return lst
 
     def setup_logging(self, verbosity):
         self.logger = logging.getLogger('tiff2jp2')
@@ -170,6 +191,9 @@ class Tiff2Jp2k(object):
         self.logger.addHandler(ch)
 
     def __enter__(self):
+        """
+        The Tiff2Jp2k must be used with a context manager.
+        """
         self.tiff_fp = libtiff.open(self.tiff_filename)
         return self
 
@@ -181,12 +205,101 @@ class Tiff2Jp2k(object):
         self.get_main_ifd()
         self.copy_image()
         self.append_extra_jp2_boxes()
+        self.rewrap_jp2()
+
+    def rewrap_jp2(self):
+        """
+        These re-wrap operations should be mutually exclusive.  An ICC profile
+        should not exist in a TIFF with a colormap.
+        """
+        self.rewrap_for_colormap()
+        self.rewrap_for_icc_profile()
+
+    def rewrap_for_colormap(self):
+        """
+        If the photometric interpretation was PALETTE, then we need to insert
+        a pclr box and a cmap (component mapping box).
+        """
+
+        photo = self.get_tag_value(262)
+        if photo != libtiff.Photometric.PALETTE:
+            return
+
+        jp2h = [box for box in self.jp2.box if box.box_id == 'jp2h'][0]
+
+        bps = (8, 8, 8)
+        pclr = jp2box.PaletteBox(
+            palette=self._colormap,
+            bits_per_component=bps,
+            signed=(False, False, False)
+        )
+        jp2h.box.append(pclr)
+
+        # append the component mapping box
+        cmap = jp2box.ComponentMappingBox(
+            component_index=(0, 0, 0),
+            mapping_type=(1, 1, 1),
+            palette_index=(0, 1, 2)
+        )
+        jp2h.box.append(cmap)
+
+        # fix the colr box.  the colorspace needs to be changed from greyscale
+        # to rgb
+        colr = [box for box in jp2h.box if box.box_id == 'colr'][0]
+        colr.colorspace = SRGB
+
+        temp_filename = str(self.jp2_filename) + '.tmp'
+        self.jp2.wrap(temp_filename, boxes=self.jp2.box)
+        shutil.move(temp_filename, self.jp2_filename)
+        self.jp2.parse()
+
+    def rewrap_for_icc_profile(self):
+        """
+        Consume a TIFF ICC profile, if one is there.
+        """
+        if self.icc_profile is None and self.include_icc_profile:
+            self.logger.warning("No ICC profile was found.")
+
+        if self.icc_profile is None or not self.include_icc_profile:
+            return
+
+        self.logger.info(
+            'Consuming an ICC profile into JP2 color specification box.'
+        )
+
+        colr = jp2box.ColourSpecificationBox(
+            method=RESTRICTED_ICC_PROFILE,
+            precedence=0,
+            icc_profile=self.icc_profile
+        )
+
+        # construct the new set of JP2 boxes, insert the color specification
+        # box with the ICC profile
+        jp2 = Jp2k(self.jp2_filename)
+        boxes = jp2.box
+        boxes[2].box = [boxes[2].box[0], colr]
+
+        # re-wrap the codestream, involves a file copy
+        tmp_filename = str(self.jp2_filename) + '.tmp'
+
+        with open(tmp_filename, mode='wb') as tfile:
+            jp2.wrap(tfile.name, boxes=boxes)
+
+        shutil.move(tmp_filename, self.jp2_filename)
 
     def append_extra_jp2_boxes(self):
         """
         Copy over the TIFF IFD.  Place it in a UUID box.  Append to the JPEG
         2000 file.
         """
+        self.append_exif_uuid_box()
+        self.append_xmp_uuid_box()
+
+    def append_exif_uuid_box(self):
+        """
+        Append an EXIF UUID box onto the end of the JPEG 2000 file.  It will
+        contain metadata from the TIFF IFD.
+        """
         if not self.create_exif_uuid:
             return
 
@@ -198,12 +311,6 @@ class Tiff2Jp2k(object):
         data = struct.pack('<BBHI', 73, 73, 42, 8)
         b.write(data)
 
-        if 700 in self.tags and self.create_xmp_uuid:
-            # remove the XMLPacket data from the IFD dictionary
-            xmp_data = self.tags.pop(700)
-        else:
-            xmp_data = None
-
         self._write_ifd(b, self.tags)
 
         # create the Exif UUID
@@ -224,12 +331,23 @@ class Tiff2Jp2k(object):
         with open(self.jp2_filename, mode='ab') as f:
             uuid_box.write(f)
 
-        if xmp_data is None:
+        self.jp2.finalize(force_parse=True)
+
+    def append_xmp_uuid_box(self):
+        """
+        Append an XMP UUID box onto the end of the JPEG 2000 file if there was
+        an XMP tag in the TIFF IFD.
+        """
+
+        if self.xmp_data is None:
+            return
+
+        if not self.create_xmp_uuid:
             return
 
         # create the XMP UUID
         the_uuid = jp2box.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac')
-        payload = bytes(xmp_data['payload'])
+        payload = bytes(self.xmp_data)
         box_length = len(payload) + 8
         uuid_box = jp2box.UUIDBox(the_uuid, payload, box_length)
         with open(self.jp2_filename, mode='ab') as f:
@@ -248,6 +366,22 @@ class Tiff2Jp2k(object):
 
             self.tags = self.read_ifd(tfp)
 
+            if 320 in self.tags:
+
+                # the TIFF must have PALETTE photometric interpretation
+                data = np.array(self.tags[320]['payload'])
+                self._colormap = data.reshape(len(data) // 3, 3)
+                self._colormap = self._colormap / 65535
+                self._colormap = (self._colormap * 255).astype(np.uint8)
+
+            if 700 in self.tags:
+
+                # XMLPacket
+                self.xmp_data = self.tags[700]['payload']
+
+            else:
+                self.xmp_data = None
+
             if 34665 in self.tags:
                 # we have an EXIF IFD
                 offset = self.tags[34665]['payload'][0]
@@ -256,6 +390,13 @@ class Tiff2Jp2k(object):
 
                 self.tags[34665]['payload'] = exif_ifd
 
+            if 34675 in self.tags:
+                # ICC profile
+                self.icc_profile = bytes(self.tags[34675]['payload'])
+
+            else:
+                self.icc_profile = None
+
     def read_ifd(self, tfp):
         """
         Process either the main IFD or an Exif IFD
@@ -361,33 +502,31 @@ class Tiff2Jp2k(object):
         return tags
 
     def _write_ifd(self, b, tags):
+        """
+        Write the IFD out to the UUIDBox.  We will always write IFDs
+        for 32-bit TIFFs, i.e. 12 byte tags, meaning just 4 bytes within
+        the tag for the tag data
+        """
 
-        # keep this for writing to the UUID, which will always be for 32-bit
-        # TIFFs
         little_tiff_tag_length = 12
+        max_tag_payload_length = 4
 
+        # exclude any unwanted tags
         if self.exclude_tags is not None:
             for tag in self.exclude_tags:
                 if tag in tags:
                     tags.pop(tag)
 
         num_tags = len(tags)
-
         write_buffer = struct.pack('<H', num_tags)
         b.write(write_buffer)
 
         # Ok, so now we have the IFD main body, but following that we have
         # the tag payloads that cannot fit into 4 bytes.
 
-        # the IFD main body in the TIFF.  As it might be big endian, we cannot
-        # just process it as one big chunk.
-
         ifd_start_loc = b.tell()
         after_ifd_position = ifd_start_loc + num_tags * little_tiff_tag_length
 
-        # We write a little-TIFF IFD
-        max_tag_payload_length = 4
-
         for idx, tag in enumerate(tags):
 
             tag_offset = ifd_start_loc + idx * little_tiff_tag_length
@@ -410,25 +549,26 @@ class Tiff2Jp2k(object):
 
                 # write the tag entry to the UUID
                 new_offset = after_ifd_position
-                outbuffer = struct.pack(
+                buffer = struct.pack(
                     '<HHII', tag, dtype, nvalues, new_offset
                 )
-                b.write(outbuffer)
+                b.write(buffer)
 
                 # now write the payload at the outlying position and then come
                 # back to the same position in the file stream
                 cpos = b.tell()
                 b.seek(new_offset)
 
-                out_format = '<' + tag_dtype[dtype]['format'] * nvalues
-                outbuffer = struct.pack(out_format, *payload)
-                b.write(outbuffer)
+                format = '<' + tag_dtype[dtype]['format'] * nvalues
+                buffer = struct.pack(format, *payload)
+                b.write(buffer)
 
                 # keep track of the next position to write out-of-IFD data
                 after_ifd_position = b.tell()
                 b.seek(cpos)
 
             else:
+
                 # the payload DOES fit into the TIFF tag entry
 
                 payload_format = (
@@ -436,11 +576,9 @@ class Tiff2Jp2k(object):
                     * int(max_tag_payload_length / tag_dtype[dtype]['nbytes'])
                 )
 
-                # the payload DOES fit into the UUID tag entry
-
-                # so write it back into the tag entry in the UUID
-                outbuffer = struct.pack('<HHI', tag, dtype, nvalues)
-                b.write(outbuffer)
+                # write the tag metadata
+                buffer = struct.pack('<HHI', tag, dtype, nvalues)
+                b.write(buffer)
 
                 payload_format = tag_dtype[dtype]['format'] * nvalues
 
@@ -451,14 +589,17 @@ class Tiff2Jp2k(object):
 
                 if tag == 34665:
                     # special case for an EXIF IFD
-                    outbuffer = struct.pack('<I', after_ifd_position)
-                    b.write(outbuffer)
+                    buffer = struct.pack('<I', after_ifd_position)
+                    b.write(buffer)
                     b.seek(after_ifd_position)
-                    self._write_ifd(b, payload)
+                    after_ifd_position = self._write_ifd(b, payload)
+
                 else:
                     # write a normal tag
-                    outbuffer = struct.pack('<' + payload_format, *payload)
-                    b.write(outbuffer)
+                    buffer = struct.pack('<' + payload_format, *payload)
+                    b.write(buffer)
+
+        return after_ifd_position
 
     def read_tiff_header(self, tfp):
         """
@@ -518,8 +659,7 @@ class Tiff2Jp2k(object):
 
     def copy_image(self):
         """
-        Transfer the image data from the TIFF to the JPEG 2000 file.  If the
-        TIFF has a stripped configuration, this may be somewhat inefficient.
+        Transfer the image data from the TIFF to the JPEG 2000 file.
         """
 
         if libtiff.isTiled(self.tiff_fp):
@@ -585,15 +725,10 @@ class Tiff2Jp2k(object):
             # Using the RGBA interface is the only reasonable way to deal with
             # this.
             use_rgba_interface = True
-        elif photo == libtiff.Photometric.PALETTE:
-            # Using the RGBA interface is the only reasonable way to deal with
-            # this.  The single plane gets turned into RGB.
-            use_rgba_interface = True
-            spp = 3
         else:
             use_rgba_interface = False
 
-        jp2 = Jp2k(
+        self.jp2 = Jp2k(
             self.jp2_filename,
             shape=(imageheight, imagewidth, spp),
             tilesize=self.tilesize,
@@ -613,9 +748,9 @@ class Tiff2Jp2k(object):
             # this handles both cases of a striped TIFF and a tiled TIFF
 
             self._write_rgba_single_tile(
-                photo, imagewidth, imageheight, spp, jp2
+                    photo, imagewidth, imageheight, spp
             )
-            jp2.finalize(force_parse=True)
+            self.jp2.finalize(force_parse=True)
 
         elif isTiled and self.tilesize is not None:
 
@@ -624,8 +759,7 @@ class Tiff2Jp2k(object):
                 jtw, jth, tw, th,
                 num_jp2k_tile_cols, num_jp2k_tile_rows,
                 dtype,
-                use_rgba_interface,
-                jp2
+                use_rgba_interface
             )
 
         elif not isTiled and self.tilesize is not None:
@@ -635,8 +769,7 @@ class Tiff2Jp2k(object):
                 jtw, jth, rps,
                 num_jp2k_tile_cols, num_jp2k_tile_rows,
                 dtype,
-                use_rgba_interface,
-                jp2
+                use_rgba_interface
             )
 
     def tagvalue2str(self, cls, tag_value):
@@ -652,7 +785,7 @@ class Tiff2Jp2k(object):
         return tag_value_string
 
     def _write_rgba_single_tile(
-        self, photo, imagewidth, imageheight, spp, jp2
+        self, photo, imagewidth, imageheight, spp
     ):
         """
         If no jp2k tiling was specified and if the image is ok to read
@@ -665,8 +798,6 @@ class Tiff2Jp2k(object):
         photo, imagewidth, imageheight, spp : int
             TIFF tag values corresponding to the photometric interpretation,
             image width and height, and samples per pixel
-        jp2 : JP2K object
-            Write to this JPEG2000 file
         """
         msg = (
             "Reading using the RGBA interface, writing as a single tile "
@@ -695,11 +826,11 @@ class Tiff2Jp2k(object):
         if spp < 4:
             image = image[:, :, :3]
 
-        jp2[:] = image
+        self.jp2[:] = image
 
     def _write_tiled_tiff_to_tiled_jp2k(
         self, imagewidth, imageheight, spp, jtw, jth, tw, th,
-        num_jp2k_tile_cols, num_jp2k_tile_rows, dtype, use_rgba_interface, jp2
+        num_jp2k_tile_cols, num_jp2k_tile_rows, dtype, use_rgba_interface
     ):
         """
         The input TIFF image is tiled and we are to create the output JPEG2000
@@ -721,8 +852,6 @@ class Tiff2Jp2k(object):
             Datatype of the image.
         use_rgba_interface : bool
             If true, use the RGBA interface to read the TIFF image data.
-        jp2 : JP2K object
-            Write to this JPEG2000 file
         """
 
         num_tiff_tile_cols = int(np.ceil(imagewidth / tw))
@@ -735,10 +864,16 @@ class Tiff2Jp2k(object):
         self.logger.debug(f'image:  {imageheight} x {imagewidth}')
         self.logger.debug(f'jptile:  {jth} x {jtw}')
         self.logger.debug(f'ttile:  {th} x {tw}')
-        for idx, tilewriter in enumerate(jp2.get_tilewriters()):
+        for idx, tilewriter in enumerate(self.jp2.get_tilewriters()):
 
             # populate the jp2k tile with tiff tiles
-            self.logger.info(f'Tile:  #{idx}')
+            msg = (
+                f'Tile:  #{idx} '
+                f'row #{idx // num_jp2k_tile_cols} '
+                f'col #{idx % num_jp2k_tile_cols} '
+            )
+            self.logger.info(msg)
+
             self.logger.debug(f'J tile row:  #{idx // num_jp2k_tile_cols}')
             self.logger.debug(f'J tile col:  #{idx % num_jp2k_tile_cols}')
 
@@ -840,7 +975,7 @@ class Tiff2Jp2k(object):
 
     def _write_striped_tiff_to_tiled_jp2k(
         self, imagewidth, imageheight, spp, jtw, jth, rps,
-        num_jp2k_tile_cols, num_jp2k_tile_rows, dtype, use_rgba_interface, jp2
+        num_jp2k_tile_cols, num_jp2k_tile_rows, dtype, use_rgba_interface
     ):
         """
         The input TIFF image is striped and we are to create the output
@@ -862,8 +997,6 @@ class Tiff2Jp2k(object):
             Datatype of the image.
         use_rgba_interface : bool
             If true, use the RGBA interface to read the TIFF image data.
-        jp2 : JP2K object
-            Write to this JPEG2000 file
         """
 
         self.logger.debug(f'image:  {imageheight} x {imagewidth}')
@@ -872,13 +1005,18 @@ class Tiff2Jp2k(object):
 
         num_jp2k_tile_cols = int(np.ceil(imagewidth / jtw))
 
-        for idx, tilewriter in enumerate(jp2.get_tilewriters()):
-
-            self.logger.info(f'Tile: #{idx}')
+        for idx, tilewriter in enumerate(self.jp2.get_tilewriters()):
 
             jp2k_tile_row = idx // num_jp2k_tile_cols
             jp2k_tile_col = idx % num_jp2k_tile_cols
 
+            msg = (
+                f'Tile:  #{idx} '
+                f'row #{jp2k_tile_row} '
+                f'col #{jp2k_tile_col}'
+            )
+            self.logger.info(msg)
+
             # the coordinates of the upper left pixel of the jp2k tile
             julr, julc = jp2k_tile_row * jth, jp2k_tile_col * jtw
 


=====================================
glymur/version.py
=====================================
@@ -21,7 +21,7 @@ from .lib import tiff
 
 # Do not change the format of this next line!  Doing so risks breaking
 # setup.py
-version = "0.11.7"
+version = "0.12.0"
 
 version_tuple = parse(version).release
 


=====================================
setup.cfg
=====================================
@@ -1,6 +1,6 @@
 [metadata]
 name = Glymur
-version = 0.11.7
+version = 0.12.0
 author = 'John Evans'
 author_email = "John Evans" <john.g.evans.ne at gmail.com>
 license = 'MIT'
@@ -28,7 +28,6 @@ install_requires =
     numpy
     lxml
     packaging
-    setuptools
 python_requires = >=3.7
 include_package_data = True
 zip_safe = False


=====================================
tests/data/basn6a08.tif
=====================================
Binary files /dev/null and b/tests/data/basn6a08.tif differ


=====================================
tests/data/issue572.tif
=====================================
Binary files /dev/null and b/tests/data/issue572.tif differ


=====================================
tests/fixtures.py
=====================================
@@ -11,7 +11,7 @@ import unittest
 try:
     from osgeo import gdal
     _HAVE_GDAL = True
-except ModuleNotFoundError:
+except (ImportError, ModuleNotFoundError):
     _HAVE_GDAL = False
 try:
     import skimage.data  # noqa : F401


=====================================
tests/test_cinema.py
=====================================
@@ -76,9 +76,8 @@ class WriteCinema(CinemaBase):
 
         EXPECTED RESULT:  ValueError
         """
-        with open(self.temp_j2k_filename, mode='wb') as tfile:
-            with self.assertRaises(ValueError):
-                Jp2k(tfile.name, data=self.jp2_data, cinema2k=36)
+        with self.assertRaises(ValueError):
+            Jp2k(self.temp_j2k_filename, data=self.jp2_data, cinema2k=36)
 
     def test_NR_ENC_X_6_2K_24_FULL_CBR_CIRCLE_000_tif_17_encode(self):
         """


=====================================
tests/test_codestream.py
=====================================
@@ -40,7 +40,10 @@ class TestSuite(unittest.TestCase):
         segment = j2k.codestream._parse_tlm_segment(b)
 
         self.assertEqual(segment.ztlm, 0)
-        self.assertIsNone(segment.ttlm)
+
+        # ttlm is an array, but None is the singleton element
+        self.assertIsNone(segment.ttlm.item())
+
         self.assertEqual(segment.ptlm, (22871,))
 
     def test_ppt_segment(self):


=====================================
tests/test_jp2box.py
=====================================
@@ -1005,6 +1005,22 @@ class TestWrap(fixtures.TestCommon):
 class TestJp2Boxes(fixtures.TestCommon):
     """Tests for canonical JP2 boxes."""
 
+    def test_issue588(self):
+        """
+        Scenario:  Construct a raw codestream box without parsing it.
+        Retrieve the codestream attribute.
+
+        Expected results:  no errors
+        """
+        j = Jp2k(self.jp2file)
+
+        box = ContiguousCodestreamBox(
+            main_header_offset=j.box[-1].main_header_offset,
+            length=j.box[-1].length
+        )
+        box._filename = str(self.jp2file)
+        box.codestream
+
     def test_no_ihdr_box(self):
         """
         SCENARIO:  The JP2/IHDR box cannot be parsed.


=====================================
tests/test_jp2box_uuid.py
=====================================
@@ -4,7 +4,6 @@
 # Standard library imports
 import importlib.resources as ir
 import io
-import platform
 import shutil
 import struct
 import unittest
@@ -13,6 +12,7 @@ import warnings
 
 # Third party library imports ...
 import lxml.etree
+import numpy as np
 
 # Local imports
 import glymur
@@ -214,7 +214,10 @@ class TestSuite(fixtures.TestCommon):
         expected = uuid.UUID(bytes=b'JpgTiffExif->JP2')
         self.assertEqual(actual, expected)
 
-        self.assertEqual(box.data['ExifTag']['ExifVersion'], (48, 50, 51, 50))
+        np.testing.assert_array_equal(
+            box.data['ExifTag']['ExifVersion'],
+            np.array([48, 50, 51, 50], dtype=np.uint8)
+        )
 
     def test__read_malformed_exif_uuid(self):
         """
@@ -233,10 +236,6 @@ class TestSuite(fixtures.TestCommon):
         expected = uuid.UUID(bytes=b'JpgTiffExif->JP2')
         self.assertEqual(actual, expected)
 
-    @unittest.skipIf(
-            platform.system().startswith('Windows'),
-            "Skipping on windows, see issue 560"
-    )
     def test__printing__geotiff_uuid__xml_sidecar(self):
         """
         SCENARIO:  Print a geotiff UUID with XML sidecar file.
@@ -361,10 +360,6 @@ class TestSuite(fixtures.TestCommon):
         expected = 'UTM Zone 16N NAD27"|Clarke, 1866 by Default| '
         self.assertEqual(box.data['GeoAsciiParams'], expected)
 
-    @unittest.skipIf(
-            platform.system().startswith('Windows'),
-            "Skipping on windows, see issue 560"
-    )
     def test_print_bad_geotiff(self):
         """
         SCENARIO:  A GeoTIFF UUID is corrupt.
@@ -419,35 +414,46 @@ class TestSuiteHiRISE(fixtures.TestCommon):
 
     def test_tags(self):
         jp2 = Jp2k(self.hirise_jp2file_name)
-        self.assertEqual(jp2.box[4].data['GeoDoubleParams'],
-                         (0.0, 180.0, 0.0, 0.0, 3396190.0, 3396190.0))
-        self.assertEqual(jp2.box[4].data['GeoAsciiParams'],
-                         'Equirectangular MARS|GCS_MARS|')
-        self.assertEqual(jp2.box[4].data['GeoKeyDirectory'], (
-            1,        1,  0,    18,  # noqa
-            1024,     0,  1,     1,  # noqa
-            1025,     0,  1,     1,  # noqa
-            1026, 34737, 21,     0,  # noqa
-            2048,     0,  1, 32767,  # noqa
-            2049, 34737,  9,    21,  # noqa
-            2050,     0,  1, 32767,  # noqa
-            2054,     0,  1,  9102,  # noqa
-            2056,     0,  1, 32767,  # noqa
-            2057, 34736,  1,     4,  # noqa
-            2058, 34736,  1,     5,  # noqa
-            3072,     0,  1, 32767,  # noqa
-            3074,     0,  1, 32767,  # noqa
-            3075,     0,  1,    17,  # noqa
-            3076,     0,  1,  9001,  # noqa
-            3082, 34736,  1,     2,  # noqa
-            3083, 34736,  1,     3,  # noqa
-            3088, 34736,  1,     1,  # noqa
-            3089, 34736,  1,     0,  # noqa
-        ))
-        self.assertEqual(jp2.box[4].data['ModelPixelScale'], (0.25, 0.25, 0.0))
-        self.assertEqual(jp2.box[4].data['ModelTiePoint'], (
-            0.0, 0.0, 0.0, -2523306.125, -268608.875, 0.0
-        ))
+        np.testing.assert_array_equal(
+            jp2.box[4].data['GeoDoubleParams'],
+            np.array([0.0, 180.0, 0.0, 0.0, 3396190.0, 3396190.0])
+        )
+        self.assertEqual(
+            jp2.box[4].data['GeoAsciiParams'],
+            'Equirectangular MARS|GCS_MARS|'
+        )
+        np.testing.assert_array_equal(
+            jp2.box[4].data['GeoKeyDirectory'],
+            np.array([
+                1,        1,  0,    18,  # noqa
+                1024,     0,  1,     1,  # noqa
+                1025,     0,  1,     1,  # noqa
+                1026, 34737, 21,     0,  # noqa
+                2048,     0,  1, 32767,  # noqa
+                2049, 34737,  9,    21,  # noqa
+                2050,     0,  1, 32767,  # noqa
+                2054,     0,  1,  9102,  # noqa
+                2056,     0,  1, 32767,  # noqa
+                2057, 34736,  1,     4,  # noqa
+                2058, 34736,  1,     5,  # noqa
+                3072,     0,  1, 32767,  # noqa
+                3074,     0,  1, 32767,  # noqa
+                3075,     0,  1,    17,  # noqa
+                3076,     0,  1,  9001,  # noqa
+                3082, 34736,  1,     2,  # noqa
+                3083, 34736,  1,     3,  # noqa
+                3088, 34736,  1,     1,  # noqa
+                3089, 34736,  1,     0,  # noqa
+            ])
+        )
+        np.testing.assert_array_equal(
+            jp2.box[4].data['ModelPixelScale'],
+            np.array([0.25, 0.25, 0.0])
+        )
+        np.testing.assert_array_equal(
+            jp2.box[4].data['ModelTiePoint'],
+            np.array([0.0, 0.0, 0.0, -2523306.125, -268608.875, 0.0])
+        )
 
     @unittest.skipIf(not fixtures._HAVE_GDAL, 'Could not load GDAL')
     def test_printing_geotiff_uuid(self):


=====================================
tests/test_jp2k.py
=====================================
@@ -60,6 +60,17 @@ class TestJp2k(fixtures.TestCommon):
         super(TestJp2k, self).setUp()
         glymur.reset_option('all')
 
+    def test_last_decomposition(self):
+        """
+        Scenario:  The last decomposition image is requested using [::-1]
+        notation.
+
+        Expected response:  the image size is verified
+        """
+        j = Jp2k(self.j2kfile)
+        d = j[::-1, ::-1]
+        self.assertEqual(d.shape, (25, 15, 3))
+
     def test_dtype_jp2(self):
         """
         Scenario:  An RGB image is read from a JP2 file.
@@ -789,30 +800,6 @@ class TestJp2k(fixtures.TestCommon):
         data = jpx[:]
         self.assertEqual(data.shape, (1024, 1024, 3))
 
-    def test_read_without_openjpeg(self):
-        """
-        Don't have openjpeg or openjp2 library?  Must error out.
-        """
-        with patch('glymur.version.openjpeg_version_tuple', new=(0, 0, 0)):
-            with patch('glymur.version.openjpeg_version', new='0.0.0'):
-                with self.assertRaises(RuntimeError):
-                    with warnings.catch_warnings():
-                        # Suppress a deprecation warning for raw read method.
-                        warnings.simplefilter("ignore")
-                        glymur.Jp2k(self.jp2file).read()
-                with self.assertRaises(RuntimeError):
-                    glymur.Jp2k(self.jp2file)[:]
-
-    def test_read_bands_without_openjp2(self):
-        """
-        Don't have openjp2 library?  Must error out.
-        """
-        exp_error = RuntimeError
-        with patch('glymur.version.openjpeg_version_tuple', new=(1, 5, 0)):
-            with patch('glymur.version.openjpeg_version', new='1.5.0'):
-                with self.assertRaises(exp_error):
-                    glymur.Jp2k(self.jp2file).read_bands()
-
     def test_zero_length_reserved_segment(self):
         """
         SCENARIO:  There is a zero-length reserved marker segment just before
@@ -1041,6 +1028,48 @@ class TestJp2k(fixtures.TestCommon):
                 glymur.set_option('lib.num_threads', 4)
 
 
+class TestVersion(fixtures.TestCommon):
+    """
+    Tests for the version of openjpeg.  These can be run regardless of the
+    version of openjpeg installed, or even if openjpeg is not installed,
+    because we fully mock the openjpeg version.
+    """
+    def test_read_minimum_version(self):
+        """
+        Scenario:  we have openjpeg, but not the minimum supported version.
+
+        Expected Result:  RuntimeError
+        """
+        with patch('glymur.version.openjpeg_version_tuple', new=(2, 2, 9)):
+            with patch('glymur.version.openjpeg_version', new='2.2.9'):
+                with self.assertRaises(RuntimeError):
+                    glymur.Jp2k(self.jp2file)[:]
+
+    def test_read_without_openjpeg(self):
+        """
+        Don't have openjpeg or openjp2 library?  Must error out.
+        """
+        with patch('glymur.version.openjpeg_version_tuple', new=(0, 0, 0)):
+            with patch('glymur.version.openjpeg_version', new='0.0.0'):
+                with self.assertRaises(RuntimeError):
+                    with warnings.catch_warnings():
+                        # Suppress a deprecation warning for raw read method.
+                        warnings.simplefilter("ignore")
+                        glymur.Jp2k(self.jp2file).read()
+                with self.assertRaises(RuntimeError):
+                    glymur.Jp2k(self.jp2file)[:]
+
+    def test_read_bands_without_openjp2(self):
+        """
+        Don't have openjp2 library?  Must error out.
+        """
+        exp_error = RuntimeError
+        with patch('glymur.version.openjpeg_version_tuple', new=(1, 5, 0)):
+            with patch('glymur.version.openjpeg_version', new='1.5.0'):
+                with self.assertRaises(exp_error):
+                    glymur.Jp2k(self.jp2file).read_bands()
+
+
 class TestComponent(unittest.TestCase):
     """
     Test how a component's precision translates into a datatype.
@@ -1119,6 +1148,16 @@ class TestJp2k_write(fixtures.MetadataBase):
         os.unlink(cls.single_channel_j2k)
         os.unlink(cls.single_channel_jp2)
 
+    def test_write_to_fully_formed_jp2k(self):
+        """
+        Scenario:  Attempt to write to a fully formed file.
+
+        Expected Result:  RuntimeError
+        """
+        j = Jp2k(self.temp_jp2_filename, data=self.jp2_data)
+        with self.assertRaises(RuntimeError):
+            j[:] = np.ones((100, 100), dtype=np.uint8)
+
     @unittest.skipIf(os.cpu_count() < 2, "makes no sense if 2 cores not there")
     def test_threads(self):
         """
@@ -1129,13 +1168,13 @@ class TestJp2k_write(fixtures.MetadataBase):
         issued.
         """
         glymur.set_option('lib.num_threads', 2)
-        with open(self.temp_jp2_filename, mode='wb') as tfile:
-            with warnings.catch_warnings(record=True) as w:
-                Jp2k(tfile.name, data=self.jp2_data)
-                if glymur.version.openjpeg_version >= '2.4.0':
-                    self.assertEqual(len(w), 0)
-                else:
-                    self.assertEqual(len(w), 1)
+
+        with warnings.catch_warnings(record=True) as w:
+            Jp2k(self.temp_jp2_filename, data=self.jp2_data)
+            if glymur.version.openjpeg_version >= '2.4.0':
+                self.assertEqual(len(w), 0)
+            else:
+                self.assertEqual(len(w), 1)
 
     def test_capture_resolution(self):
         """
@@ -1265,9 +1304,11 @@ class TestJp2k_write(fixtures.MetadataBase):
 
         EXPECTED RESULT:  RuntimeError
         """
-        with open(self.temp_jp2_filename, mode='wb') as tfile:
-            with self.assertRaises(InvalidJp2kError):
-                Jp2k(tfile.name, data=np.zeros((0, 256), dtype=np.uint8))
+        with self.assertRaises(InvalidJp2kError):
+            Jp2k(
+                self.temp_jp2_filename,
+                data=np.zeros((0, 256), dtype=np.uint8)
+            )
 
     @unittest.skipIf(
         not fixtures.HAVE_SCIKIT_IMAGE, fixtures.HAVE_SCIKIT_IMAGE_MSG
@@ -1405,13 +1446,12 @@ class TestJp2k_write(fixtures.MetadataBase):
             'data': fixtures.skimage.data.camera(),
             'psnr': [30, 35, 40, 0],
         }
-        with open(self.temp_jp2_filename, mode='wb') as tfile:
-            j = Jp2k(tfile.name, **kwargs)
+        j = Jp2k(self.temp_jp2_filename, **kwargs)
 
-            d = {}
-            for layer in range(4):
-                j.layer = layer
-                d[layer] = j[:]
+        d = {}
+        for layer in range(4):
+            j.layer = layer
+            d[layer] = j[:]
 
         with warnings.catch_warnings():
             # MSE is zero for that first image, resulting in a divide-by-zero
@@ -1445,10 +1485,8 @@ class TestJp2k_write(fixtures.MetadataBase):
             'psnr': [30, 35, 40],
             'numres': 2,
         }
-        with open(self.temp_j2k_filename, mode='wb') as tfile:
-            j = Jp2k(tfile.name, **kwargs)
-
-            codestream = j.get_codestream()
+        j = Jp2k(self.temp_j2k_filename, **kwargs)
+        codestream = j.get_codestream()
 
         # COD: Coding style default
         self.assertFalse(codestream.segment[2].scod & 2)  # no sop
@@ -1473,10 +1511,9 @@ class TestJp2k_write(fixtures.MetadataBase):
         EXPECTED RESULT:  There are three layers.
         """
         data = self.jp2_data
-        with open(self.temp_j2k_filename, mode='wb') as tfile:
-            # Should be written with 3 layers.
-            j = Jp2k(tfile.name, data=data, cratios=[200, 100, 50])
-            c = j.get_codestream()
+        # Should be written with 3 layers.
+        j = Jp2k(self.temp_j2k_filename, data=data, cratios=[200, 100, 50])
+        c = j.get_codestream()
 
         # COD: Coding style default
         self.assertFalse(c.segment[2].scod & 2)  # no sop
@@ -1501,15 +1538,14 @@ class TestJp2k_write(fixtures.MetadataBase):
         EXPECTED RESULT:  Three quality layers and the specified code block
         size are present.  The precinct sizes validate.
         """
-        with open(self.temp_j2k_filename, mode='wb') as tfile:
-            j = Jp2k(
-                tfile.name,
-                data=self.jp2_data,
-                psnr=[30, 35, 40],
-                cbsize=(16, 16), psizes=[(64, 64)]
-            )
+        j = Jp2k(
+            self.temp_j2k_filename,
+            data=self.jp2_data,
+            psnr=[30, 35, 40],
+            cbsize=(16, 16), psizes=[(64, 64)]
+        )
 
-            codestream = j.get_codestream()
+        codestream = j.get_codestream()
 
         # COD: Coding style default
         self.assertFalse(codestream.segment[2].scod & 2)  # no sop
@@ -1538,50 +1574,49 @@ class TestJp2k_write(fixtures.MetadataBase):
             input/nonregression/Bretagne2.ppm
 
         """
-        with open(self.temp_j2k_filename, mode='wb') as tfile:
-            j = Jp2k(
-                tfile.name,
-                data=self.jp2_data,
-                psizes=[(128, 128)] * 3,
-                cratios=[100, 20, 2],
-                tilesize=(480, 640),
-                cbsize=(32, 32)
-            )
+        j = Jp2k(
+            self.temp_j2k_filename,
+            data=self.jp2_data,
+            psizes=[(128, 128)] * 3,
+            cratios=[100, 20, 2],
+            tilesize=(480, 640),
+            cbsize=(32, 32)
+        )
 
-            # Should be three layers.
-            codestream = j.get_codestream()
-
-            # RSIZ
-            self.assertEqual(codestream.segment[1].xtsiz, 640)
-            self.assertEqual(codestream.segment[1].ytsiz, 480)
-
-            # COD: Coding style default
-            self.assertFalse(codestream.segment[2].scod & 2)  # no sop
-            self.assertFalse(codestream.segment[2].scod & 4)  # no eph
-            self.assertEqual(codestream.segment[2].prog_order,
-                             glymur.core.LRCP)
-            self.assertEqual(codestream.segment[2].layers, 3)  # layers = 3
-            self.assertEqual(codestream.segment[2].mct, 1)  # mct
-            self.assertEqual(codestream.segment[2].num_res, 5)  # levels
-            self.assertEqual(
-                tuple(codestream.segment[2].code_block_size),
-                (32, 32)
-            )  # cblksz
-            self.verify_codeblock_style(
-                codestream.segment[2].cstyle,
-                [False, False, False, False, False, False]
-            )
-            self.assertEqual(
-                codestream.segment[2].xform,
-                glymur.core.WAVELET_XFORM_5X3_REVERSIBLE
-            )
-            self.assertEqual(
-                codestream.segment[2].precinct_size,
-                (
-                    (16, 16), (32, 32), (64, 64), (128, 128), (128, 128),
-                    (128, 128)
-                )
+        # Should be three layers.
+        codestream = j.get_codestream()
+
+        # RSIZ
+        self.assertEqual(codestream.segment[1].xtsiz, 640)
+        self.assertEqual(codestream.segment[1].ytsiz, 480)
+
+        # COD: Coding style default
+        self.assertFalse(codestream.segment[2].scod & 2)  # no sop
+        self.assertFalse(codestream.segment[2].scod & 4)  # no eph
+        self.assertEqual(codestream.segment[2].prog_order,
+                         glymur.core.LRCP)
+        self.assertEqual(codestream.segment[2].layers, 3)  # layers = 3
+        self.assertEqual(codestream.segment[2].mct, 1)  # mct
+        self.assertEqual(codestream.segment[2].num_res, 5)  # levels
+        self.assertEqual(
+            tuple(codestream.segment[2].code_block_size),
+            (32, 32)
+        )  # cblksz
+        self.verify_codeblock_style(
+            codestream.segment[2].cstyle,
+            [False, False, False, False, False, False]
+        )
+        self.assertEqual(
+            codestream.segment[2].xform,
+            glymur.core.WAVELET_XFORM_5X3_REVERSIBLE
+        )
+        self.assertEqual(
+            codestream.segment[2].precinct_size,
+            (
+                (16, 16), (32, 32), (64, 64), (128, 128), (128, 128),
+                (128, 128)
             )
+        )
 
     def test_NR_ENC_Bretagne2_ppm_5_encode(self):
         """
@@ -1590,34 +1625,33 @@ class TestJp2k_write(fixtures.MetadataBase):
             input/nonregression/Bretagne2.ppm
 
         """
-        with open(self.temp_j2k_filename, mode='wb') as tfile:
-            j = Jp2k(tfile.name, data=self.jp2_data,
-                     tilesize=(127, 127), prog="PCRL")
-
-            codestream = j.get_codestream()
-
-            # RSIZ
-            self.assertEqual(codestream.segment[1].xtsiz, 127)
-            self.assertEqual(codestream.segment[1].ytsiz, 127)
-
-            # COD: Coding style default
-            self.assertFalse(codestream.segment[2].scod & 2)  # no sop
-            self.assertFalse(codestream.segment[2].scod & 4)  # no eph
-            self.assertEqual(codestream.segment[2].prog_order,
-                             glymur.core.PCRL)
-            self.assertEqual(codestream.segment[2].layers, 1)
-            self.assertEqual(codestream.segment[2].mct, 1)  # mct
-            self.assertEqual(codestream.segment[2].num_res, 5)  # levels
-            self.assertEqual(tuple(codestream.segment[2].code_block_size),
-                             (64, 64))  # cblksz
-            self.verify_codeblock_style(
-                codestream.segment[2].cstyle,
-                [False, False, False, False, False, False]
-            )
-            self.assertEqual(codestream.segment[2].xform,
-                             glymur.core.WAVELET_XFORM_5X3_REVERSIBLE)
-            self.assertEqual(codestream.segment[2].precinct_size,
-                             ((32768, 32768)))
+        j = Jp2k(self.temp_j2k_filename, data=self.jp2_data,
+                 tilesize=(127, 127), prog="PCRL")
+
+        codestream = j.get_codestream()
+
+        # RSIZ
+        self.assertEqual(codestream.segment[1].xtsiz, 127)
+        self.assertEqual(codestream.segment[1].ytsiz, 127)
+
+        # COD: Coding style default
+        self.assertFalse(codestream.segment[2].scod & 2)  # no sop
+        self.assertFalse(codestream.segment[2].scod & 4)  # no eph
+        self.assertEqual(codestream.segment[2].prog_order,
+                         glymur.core.PCRL)
+        self.assertEqual(codestream.segment[2].layers, 1)
+        self.assertEqual(codestream.segment[2].mct, 1)  # mct
+        self.assertEqual(codestream.segment[2].num_res, 5)  # levels
+        self.assertEqual(tuple(codestream.segment[2].code_block_size),
+                         (64, 64))  # cblksz
+        self.verify_codeblock_style(
+            codestream.segment[2].cstyle,
+            [False, False, False, False, False, False]
+        )
+        self.assertEqual(codestream.segment[2].xform,
+                         glymur.core.WAVELET_XFORM_5X3_REVERSIBLE)
+        self.assertEqual(codestream.segment[2].precinct_size,
+                         ((32768, 32768)))
 
     def test_NR_ENC_Bretagne2_ppm_6_encode(self):
         """
@@ -1625,37 +1659,39 @@ class TestJp2k_write(fixtures.MetadataBase):
 
             input/nonregression/Bretagne2.ppm
         """
-        with open(self.temp_j2k_filename, mode='wb') as tfile:
-            j = Jp2k(tfile.name, data=self.jp2_data, subsam=(2, 2), sop=True)
+        j = Jp2k(
+            self.temp_j2k_filename,
+            data=self.jp2_data, subsam=(2, 2), sop=True
+        )
 
-            codestream = j.get_codestream(header_only=False)
+        codestream = j.get_codestream(header_only=False)
+
+        # RSIZ
+        self.assertEqual(codestream.segment[1].xrsiz, (2, 2, 2))
+        self.assertEqual(codestream.segment[1].yrsiz, (2, 2, 2))
+
+        # COD: Coding style default
+        self.assertTrue(codestream.segment[2].scod & 2)  # sop
+        self.assertFalse(codestream.segment[2].scod & 4)  # no eph
+        self.assertEqual(codestream.segment[2].prog_order,
+                         glymur.core.LRCP)
+        self.assertEqual(codestream.segment[2].layers, 1)  # layers = 1
+        self.assertEqual(codestream.segment[2].mct, 1)  # mct
+        self.assertEqual(codestream.segment[2].num_res, 5)  # levels
+        self.assertEqual(tuple(codestream.segment[2].code_block_size),
+                         (64, 64))  # cblksz
+        self.verify_codeblock_style(codestream.segment[2].cstyle,
+                                    [False, False, False,
+                                     False, False, False])
+        self.assertEqual(codestream.segment[2].xform,
+                         glymur.core.WAVELET_XFORM_5X3_REVERSIBLE)
+        self.assertEqual(codestream.segment[2].precinct_size,
+                         ((32768, 32768)))
 
-            # RSIZ
-            self.assertEqual(codestream.segment[1].xrsiz, (2, 2, 2))
-            self.assertEqual(codestream.segment[1].yrsiz, (2, 2, 2))
-
-            # COD: Coding style default
-            self.assertTrue(codestream.segment[2].scod & 2)  # sop
-            self.assertFalse(codestream.segment[2].scod & 4)  # no eph
-            self.assertEqual(codestream.segment[2].prog_order,
-                             glymur.core.LRCP)
-            self.assertEqual(codestream.segment[2].layers, 1)  # layers = 1
-            self.assertEqual(codestream.segment[2].mct, 1)  # mct
-            self.assertEqual(codestream.segment[2].num_res, 5)  # levels
-            self.assertEqual(tuple(codestream.segment[2].code_block_size),
-                             (64, 64))  # cblksz
-            self.verify_codeblock_style(codestream.segment[2].cstyle,
-                                        [False, False, False,
-                                         False, False, False])
-            self.assertEqual(codestream.segment[2].xform,
-                             glymur.core.WAVELET_XFORM_5X3_REVERSIBLE)
-            self.assertEqual(codestream.segment[2].precinct_size,
-                             ((32768, 32768)))
-
-            # 18 SOP segments.
-            nsops = [x.nsop for x in codestream.segment
-                     if x.marker_id == 'SOP']
-            self.assertEqual(nsops, list(range(18)))
+        # 18 SOP segments.
+        nsops = [x.nsop for x in codestream.segment
+                 if x.marker_id == 'SOP']
+        self.assertEqual(nsops, list(range(18)))
 
     def test_NR_ENC_Bretagne2_ppm_7_encode(self):
         """
@@ -1664,32 +1700,34 @@ class TestJp2k_write(fixtures.MetadataBase):
             input/nonregression/Bretagne2.ppm
 
         """
-        with open(self.temp_j2k_filename, mode='wb') as tfile:
-            j = Jp2k(tfile.name, data=self.jp2_data, modesw=38, eph=True)
+        j = Jp2k(
+            self.temp_j2k_filename,
+            data=self.jp2_data, modesw=38, eph=True
+        )
 
-            codestream = j.get_codestream(header_only=False)
+        codestream = j.get_codestream(header_only=False)
 
-            # COD: Coding style default
-            self.assertFalse(codestream.segment[2].scod & 2)  # no sop
-            self.assertTrue(codestream.segment[2].scod & 4)  # eph
-            self.assertEqual(codestream.segment[2].prog_order,
-                             glymur.core.LRCP)
-            self.assertEqual(codestream.segment[2].layers, 1)  # layers = 1
-            self.assertEqual(codestream.segment[2].mct, 1)  # mct
-            self.assertEqual(codestream.segment[2].num_res, 5)  # levels
-            self.assertEqual(tuple(codestream.segment[2].code_block_size),
-                             (64, 64))  # cblksz
-            self.verify_codeblock_style(codestream.segment[2].cstyle,
-                                        [False, True, True,
-                                         False, False, True])
-            self.assertEqual(codestream.segment[2].xform,
-                             glymur.core.WAVELET_XFORM_5X3_REVERSIBLE)
-            self.assertEqual(codestream.segment[2].precinct_size,
-                             ((32768, 32768)))
-
-            # 18 EPH segments.
-            ephs = [x for x in codestream.segment if x.marker_id == 'EPH']
-            self.assertEqual(len(ephs), 18)
+        # COD: Coding style default
+        self.assertFalse(codestream.segment[2].scod & 2)  # no sop
+        self.assertTrue(codestream.segment[2].scod & 4)  # eph
+        self.assertEqual(codestream.segment[2].prog_order,
+                         glymur.core.LRCP)
+        self.assertEqual(codestream.segment[2].layers, 1)  # layers = 1
+        self.assertEqual(codestream.segment[2].mct, 1)  # mct
+        self.assertEqual(codestream.segment[2].num_res, 5)  # levels
+        self.assertEqual(tuple(codestream.segment[2].code_block_size),
+                         (64, 64))  # cblksz
+        self.verify_codeblock_style(codestream.segment[2].cstyle,
+                                    [False, True, True,
+                                     False, False, True])
+        self.assertEqual(codestream.segment[2].xform,
+                         glymur.core.WAVELET_XFORM_5X3_REVERSIBLE)
+        self.assertEqual(codestream.segment[2].precinct_size,
+                         ((32768, 32768)))
+
+        # 18 EPH segments.
+        ephs = [x for x in codestream.segment if x.marker_id == 'EPH']
+        self.assertEqual(len(ephs), 18)
 
     def test_NR_ENC_Bretagne2_ppm_8_encode(self):
         """
@@ -1697,33 +1735,32 @@ class TestJp2k_write(fixtures.MetadataBase):
 
             input/nonregression/Bretagne2.ppm
         """
-        with open(self.temp_j2k_filename, mode='wb') as tfile:
-            j = Jp2k(tfile.name,
-                     data=self.jp2_data, grid_offset=[300, 150], cratios=[800])
+        j = Jp2k(self.temp_j2k_filename,
+                 data=self.jp2_data, grid_offset=[300, 150], cratios=[800])
 
-            codestream = j.get_codestream(header_only=False)
+        codestream = j.get_codestream(header_only=False)
 
-            # RSIZ
-            self.assertEqual(codestream.segment[1].xosiz, 150)
-            self.assertEqual(codestream.segment[1].yosiz, 300)
-
-            # COD: Coding style default
-            self.assertFalse(codestream.segment[2].scod & 2)  # no sop
-            self.assertFalse(codestream.segment[2].scod & 4)  # no eph
-            self.assertEqual(codestream.segment[2].prog_order,
-                             glymur.core.LRCP)
-            self.assertEqual(codestream.segment[2].layers, 1)  # layers = 1
-            self.assertEqual(codestream.segment[2].mct, 1)  # mct
-            self.assertEqual(codestream.segment[2].num_res, 5)  # levels
-            self.assertEqual(tuple(codestream.segment[2].code_block_size),
-                             (64, 64))  # cblksz
-            self.verify_codeblock_style(codestream.segment[2].cstyle,
-                                        [False, False, False,
-                                         False, False, False])
-            self.assertEqual(codestream.segment[2].xform,
-                             glymur.core.WAVELET_XFORM_5X3_REVERSIBLE)
-            self.assertEqual(codestream.segment[2].precinct_size,
-                             ((32768, 32768)))
+        # RSIZ
+        self.assertEqual(codestream.segment[1].xosiz, 150)
+        self.assertEqual(codestream.segment[1].yosiz, 300)
+
+        # COD: Coding style default
+        self.assertFalse(codestream.segment[2].scod & 2)  # no sop
+        self.assertFalse(codestream.segment[2].scod & 4)  # no eph
+        self.assertEqual(codestream.segment[2].prog_order,
+                         glymur.core.LRCP)
+        self.assertEqual(codestream.segment[2].layers, 1)  # layers = 1
+        self.assertEqual(codestream.segment[2].mct, 1)  # mct
+        self.assertEqual(codestream.segment[2].num_res, 5)  # levels
+        self.assertEqual(tuple(codestream.segment[2].code_block_size),
+                         (64, 64))  # cblksz
+        self.verify_codeblock_style(codestream.segment[2].cstyle,
+                                    [False, False, False,
+                                     False, False, False])
+        self.assertEqual(codestream.segment[2].xform,
+                         glymur.core.WAVELET_XFORM_5X3_REVERSIBLE)
+        self.assertEqual(codestream.segment[2].precinct_size,
+                         ((32768, 32768)))
 
     def test_NR_ENC_Cevennes1_bmp_9_encode(self):
         """
@@ -1732,28 +1769,27 @@ class TestJp2k_write(fixtures.MetadataBase):
             input/nonregression/Cevennes1.bmp
 
         """
-        with open(self.temp_j2k_filename, mode='wb') as tfile:
-            j = Jp2k(tfile.name, data=self.jp2_data, cratios=[800])
+        j = Jp2k(self.temp_j2k_filename, data=self.jp2_data, cratios=[800])
 
-            codestream = j.get_codestream(header_only=False)
+        codestream = j.get_codestream(header_only=False)
 
-            # COD: Coding style default
-            self.assertFalse(codestream.segment[2].scod & 2)  # no sop
-            self.assertFalse(codestream.segment[2].scod & 4)  # no eph
-            self.assertEqual(codestream.segment[2].prog_order,
-                             glymur.core.LRCP)
-            self.assertEqual(codestream.segment[2].layers, 1)  # layers = 1
-            self.assertEqual(codestream.segment[2].mct, 1)  # mct
-            self.assertEqual(codestream.segment[2].num_res, 5)  # levels
-            self.assertEqual(tuple(codestream.segment[2].code_block_size),
-                             (64, 64))  # cblksz
-            self.verify_codeblock_style(codestream.segment[2].cstyle,
-                                        [False, False, False,
-                                         False, False, False])
-            self.assertEqual(codestream.segment[2].xform,
-                             glymur.core.WAVELET_XFORM_5X3_REVERSIBLE)
-            self.assertEqual(codestream.segment[2].precinct_size,
-                             ((32768, 32768)))
+        # COD: Coding style default
+        self.assertFalse(codestream.segment[2].scod & 2)  # no sop
+        self.assertFalse(codestream.segment[2].scod & 4)  # no eph
+        self.assertEqual(codestream.segment[2].prog_order,
+                         glymur.core.LRCP)
+        self.assertEqual(codestream.segment[2].layers, 1)  # layers = 1
+        self.assertEqual(codestream.segment[2].mct, 1)  # mct
+        self.assertEqual(codestream.segment[2].num_res, 5)  # levels
+        self.assertEqual(tuple(codestream.segment[2].code_block_size),
+                         (64, 64))  # cblksz
+        self.verify_codeblock_style(codestream.segment[2].cstyle,
+                                    [False, False, False,
+                                     False, False, False])
+        self.assertEqual(codestream.segment[2].xform,
+                         glymur.core.WAVELET_XFORM_5X3_REVERSIBLE)
+        self.assertEqual(codestream.segment[2].precinct_size,
+                         ((32768, 32768)))
 
     def test_NR_ENC_Cevennes2_ppm_10_encode(self):
         """
@@ -1762,29 +1798,27 @@ class TestJp2k_write(fixtures.MetadataBase):
             input/nonregression/Cevennes2.ppm
 
         """
-        with open(self.temp_j2k_filename, mode='wb') as tfile:
-
-            j = Jp2k(tfile.name, data=self.jp2_data, cratios=[50])
+        j = Jp2k(self.temp_j2k_filename, data=self.jp2_data, cratios=[50])
 
-            codestream = j.get_codestream(header_only=False)
+        codestream = j.get_codestream(header_only=False)
 
-            # COD: Coding style default
-            self.assertFalse(codestream.segment[2].scod & 2)  # no sop
-            self.assertFalse(codestream.segment[2].scod & 4)  # no eph
-            self.assertEqual(codestream.segment[2].prog_order,
-                             glymur.core.LRCP)
-            self.assertEqual(codestream.segment[2].layers, 1)  # layers = 1
-            self.assertEqual(codestream.segment[2].mct, 1)  # mct
-            self.assertEqual(codestream.segment[2].num_res, 5)  # levels
-            self.assertEqual(tuple(codestream.segment[2].code_block_size),
-                             (64, 64))  # cblksz
-            self.verify_codeblock_style(codestream.segment[2].cstyle,
-                                        [False, False, False,
-                                         False, False, False])
-            self.assertEqual(codestream.segment[2].xform,
-                             glymur.core.WAVELET_XFORM_5X3_REVERSIBLE)
-            self.assertEqual(codestream.segment[2].precinct_size,
-                             ((32768, 32768)))
+        # COD: Coding style default
+        self.assertFalse(codestream.segment[2].scod & 2)  # no sop
+        self.assertFalse(codestream.segment[2].scod & 4)  # no eph
+        self.assertEqual(codestream.segment[2].prog_order,
+                         glymur.core.LRCP)
+        self.assertEqual(codestream.segment[2].layers, 1)  # layers = 1
+        self.assertEqual(codestream.segment[2].mct, 1)  # mct
+        self.assertEqual(codestream.segment[2].num_res, 5)  # levels
+        self.assertEqual(tuple(codestream.segment[2].code_block_size),
+                         (64, 64))  # cblksz
+        self.verify_codeblock_style(codestream.segment[2].cstyle,
+                                    [False, False, False,
+                                     False, False, False])
+        self.assertEqual(codestream.segment[2].xform,
+                         glymur.core.WAVELET_XFORM_5X3_REVERSIBLE)
+        self.assertEqual(codestream.segment[2].precinct_size,
+                         ((32768, 32768)))
 
     def test_NR_ENC_Rome_bmp_11_encode(self):
         """
@@ -1793,76 +1827,76 @@ class TestJp2k_write(fixtures.MetadataBase):
             input/nonregression/Rome.bmp
 
         """
-        with open(self.temp_jp2_filename, mode='wb') as tfile:
+        jp2 = Jp2k(
+            self.temp_jp2_filename,
+            data=self.jp2_data, psnr=[30, 35, 50], prog='LRCP', numres=3
+        )
+
+        ids = [box.box_id for box in jp2.box]
+        self.assertEqual(ids, ['jP  ', 'ftyp', 'jp2h', 'jp2c'])
+
+        ids = [box.box_id for box in jp2.box[2].box]
+        self.assertEqual(ids, ['ihdr', 'colr'])
+
+        # Signature box.  Check for corruption.
+        self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10))
+
+        # File type box.
+        self.assertEqual(jp2.box[1].brand, 'jp2 ')
+        self.assertEqual(jp2.box[1].minor_version, 0)
+        self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ')
+
+        # Jp2 Header
+        # Image header
+        self.assertEqual(jp2.box[2].box[0].height, 1456)
+        self.assertEqual(jp2.box[2].box[0].width, 2592)
+        self.assertEqual(jp2.box[2].box[0].num_components, 3)
+        self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
+        self.assertEqual(jp2.box[2].box[0].signed, False)
+        self.assertEqual(jp2.box[2].box[0].compression, 7)   # wavelet
+        self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
+        self.assertEqual(jp2.box[2].box[0].ip_provided, False)
+
+        # Jp2 Header
+        # Colour specification
+        self.assertEqual(jp2.box[2].box[1].method, 1)
+        self.assertEqual(jp2.box[2].box[1].precedence, 0)
+        self.assertEqual(jp2.box[2].box[1].approximation, 0)
+        self.assertIsNone(jp2.box[2].box[1].icc_profile)
+        self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB)
+
+        codestream = jp2.box[3].codestream
 
-            jp2 = Jp2k(tfile.name, data=self.jp2_data, psnr=[30, 35, 50],
-                       prog='LRCP', numres=3)
-
-            ids = [box.box_id for box in jp2.box]
-            self.assertEqual(ids, ['jP  ', 'ftyp', 'jp2h', 'jp2c'])
-
-            ids = [box.box_id for box in jp2.box[2].box]
-            self.assertEqual(ids, ['ihdr', 'colr'])
-
-            # Signature box.  Check for corruption.
-            self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10))
-
-            # File type box.
-            self.assertEqual(jp2.box[1].brand, 'jp2 ')
-            self.assertEqual(jp2.box[1].minor_version, 0)
-            self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ')
-
-            # Jp2 Header
-            # Image header
-            self.assertEqual(jp2.box[2].box[0].height, 1456)
-            self.assertEqual(jp2.box[2].box[0].width, 2592)
-            self.assertEqual(jp2.box[2].box[0].num_components, 3)
-            self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
-            self.assertEqual(jp2.box[2].box[0].signed, False)
-            self.assertEqual(jp2.box[2].box[0].compression, 7)   # wavelet
-            self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
-            self.assertEqual(jp2.box[2].box[0].ip_provided, False)
-
-            # Jp2 Header
-            # Colour specification
-            self.assertEqual(jp2.box[2].box[1].method, 1)
-            self.assertEqual(jp2.box[2].box[1].precedence, 0)
-            self.assertEqual(jp2.box[2].box[1].approximation, 0)
-            self.assertIsNone(jp2.box[2].box[1].icc_profile)
-            self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB)
-
-            codestream = jp2.box[3].codestream
-
-            kwargs = {
-                'rsiz': 0,
-                'xysiz': (2592, 1456),
-                'xyosiz': (0, 0),
-                'xytsiz': (2592, 1456),
-                'xytosiz': (0, 0),
-                'bitdepth': (8, 8, 8),
-                'signed': (False, False, False),
-                'xyrsiz': [(1, 1, 1), (1, 1, 1)]
-            }
-            self.verifySizSegment(codestream.segment[1],
-                                  glymur.codestream.SIZsegment(**kwargs))
-
-            # COD: Coding style default
-            self.assertFalse(codestream.segment[2].scod & 2)  # no sop
-            self.assertFalse(codestream.segment[2].scod & 4)  # no eph
-            self.assertEqual(codestream.segment[2].prog_order,
-                             glymur.core.LRCP)
-            self.assertEqual(codestream.segment[2].layers, 3)  # layers = 3
-            self.assertEqual(codestream.segment[2].mct, 1)  # mct
-            self.assertEqual(codestream.segment[2].num_res, 2)  # levels
-            self.assertEqual(tuple(codestream.segment[2].code_block_size),
-                             (64, 64))  # cblksz
-            self.verify_codeblock_style(codestream.segment[2].cstyle,
-                                        [False, False, False,
-                                         False, False, False])
-            self.assertEqual(codestream.segment[2].xform,
-                             glymur.core.WAVELET_XFORM_5X3_REVERSIBLE)
-            self.assertEqual(codestream.segment[2].precinct_size,
-                             ((32768, 32768)))
+        kwargs = {
+            'rsiz': 0,
+            'xysiz': (2592, 1456),
+            'xyosiz': (0, 0),
+            'xytsiz': (2592, 1456),
+            'xytosiz': (0, 0),
+            'bitdepth': (8, 8, 8),
+            'signed': (False, False, False),
+            'xyrsiz': [(1, 1, 1), (1, 1, 1)]
+        }
+        self.verifySizSegment(codestream.segment[1],
+                              glymur.codestream.SIZsegment(**kwargs))
+
+        # COD: Coding style default
+        self.assertFalse(codestream.segment[2].scod & 2)  # no sop
+        self.assertFalse(codestream.segment[2].scod & 4)  # no eph
+        self.assertEqual(codestream.segment[2].prog_order,
+                         glymur.core.LRCP)
+        self.assertEqual(codestream.segment[2].layers, 3)  # layers = 3
+        self.assertEqual(codestream.segment[2].mct, 1)  # mct
+        self.assertEqual(codestream.segment[2].num_res, 2)  # levels
+        self.assertEqual(tuple(codestream.segment[2].code_block_size),
+                         (64, 64))  # cblksz
+        self.verify_codeblock_style(codestream.segment[2].cstyle,
+                                    [False, False, False,
+                                     False, False, False])
+        self.assertEqual(codestream.segment[2].xform,
+                         glymur.core.WAVELET_XFORM_5X3_REVERSIBLE)
+        self.assertEqual(codestream.segment[2].precinct_size,
+                         ((32768, 32768)))
 
     def test_NR_ENC_random_issue_0005_tif_12_encode(self):
         """
@@ -1871,41 +1905,40 @@ class TestJp2k_write(fixtures.MetadataBase):
             input/nonregression/random-issue-0005.tif
         """
         data = self.jp2_data[:1024, :1024, 0].astype(np.uint16)
-        with open(self.temp_j2k_filename, mode='wb') as tfile:
-            j = Jp2k(tfile.name, data=data)
+        j = Jp2k(self.temp_j2k_filename, data=data)
 
-            codestream = j.get_codestream(header_only=False)
+        codestream = j.get_codestream(header_only=False)
 
-            kwargs = {
-                'rsiz': 0,
-                'xysiz': (1024, 1024),
-                'xyosiz': (0, 0),
-                'xytsiz': (1024, 1024),
-                'xytosiz': (0, 0),
-                'bitdepth': (16,),
-                'signed': (False,),
-                'xyrsiz': [(1,), (1,)]
-            }
-            self.verifySizSegment(codestream.segment[1],
-                                  glymur.codestream.SIZsegment(**kwargs))
-
-            # COD: Coding style default
-            self.assertFalse(codestream.segment[2].scod & 2)  # no sop
-            self.assertFalse(codestream.segment[2].scod & 4)  # no eph
-            self.assertEqual(codestream.segment[2].prog_order,
-                             glymur.core.LRCP)
-            self.assertEqual(codestream.segment[2].layers, 1)  # layers = 1
-            self.assertEqual(codestream.segment[2].mct, 0)
-            self.assertEqual(codestream.segment[2].num_res, 5)  # levels
-            self.assertEqual(tuple(codestream.segment[2].code_block_size),
-                             (64, 64))  # cblksz
-            self.verify_codeblock_style(codestream.segment[2].cstyle,
-                                        [False, False, False,
-                                         False, False, False])
-            self.assertEqual(codestream.segment[2].xform,
-                             glymur.core.WAVELET_XFORM_5X3_REVERSIBLE)
-            self.assertEqual(codestream.segment[2].precinct_size,
-                             ((32768, 32768)))
+        kwargs = {
+            'rsiz': 0,
+            'xysiz': (1024, 1024),
+            'xyosiz': (0, 0),
+            'xytsiz': (1024, 1024),
+            'xytosiz': (0, 0),
+            'bitdepth': (16,),
+            'signed': (False,),
+            'xyrsiz': [(1,), (1,)]
+        }
+        self.verifySizSegment(codestream.segment[1],
+                              glymur.codestream.SIZsegment(**kwargs))
+
+        # COD: Coding style default
+        self.assertFalse(codestream.segment[2].scod & 2)  # no sop
+        self.assertFalse(codestream.segment[2].scod & 4)  # no eph
+        self.assertEqual(codestream.segment[2].prog_order,
+                         glymur.core.LRCP)
+        self.assertEqual(codestream.segment[2].layers, 1)  # layers = 1
+        self.assertEqual(codestream.segment[2].mct, 0)
+        self.assertEqual(codestream.segment[2].num_res, 5)  # levels
+        self.assertEqual(tuple(codestream.segment[2].code_block_size),
+                         (64, 64))  # cblksz
+        self.verify_codeblock_style(codestream.segment[2].cstyle,
+                                    [False, False, False,
+                                     False, False, False])
+        self.assertEqual(codestream.segment[2].xform,
+                         glymur.core.WAVELET_XFORM_5X3_REVERSIBLE)
+        self.assertEqual(codestream.segment[2].precinct_size,
+                         ((32768, 32768)))
 
     def test_NR_ENC_issue141_rawl_23_encode(self):
         """
@@ -1916,14 +1949,13 @@ class TestJp2k_write(fixtures.MetadataBase):
             input/nonregression/issue141.rawl
 
         """
-        with open(self.temp_j2k_filename, mode='wb') as tfile:
-            j = Jp2k(tfile.name, data=self.jp2_data, irreversible=True)
+        j = Jp2k(self.temp_j2k_filename, data=self.jp2_data, irreversible=True)
 
-            codestream = j.get_codestream()
-            self.assertEqual(
-                codestream.segment[2].xform,
-                glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE
-            )
+        codestream = j.get_codestream()
+        self.assertEqual(
+            codestream.segment[2].xform,
+            glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE
+        )
 
     def test_cinema2K_with_others(self):
         """
@@ -2007,15 +2039,16 @@ class TestJp2k_write(fixtures.MetadataBase):
         Verify that the Irreversible option works
         """
         expdata = self.j2k_data
-        with open(self.temp_j2k_filename, mode='wb') as tfile:
-            j = Jp2k(tfile.name, data=expdata, irreversible=True, numres=5)
+        j = Jp2k(
+            self.temp_j2k_filename, data=expdata, irreversible=True, numres=5
+        )
 
-            codestream = j.get_codestream()
-            self.assertEqual(codestream.segment[2].xform,
-                             glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE)
+        codestream = j.get_codestream()
+        self.assertEqual(codestream.segment[2].xform,
+                         glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE)
 
-            actdata = j[:]
-            self.assertTrue(fixtures.mse(actdata, expdata) < 0.28)
+        actdata = j[:]
+        self.assertTrue(fixtures.mse(actdata, expdata) < 0.28)
 
     def test_shape_greyscale_jp2(self):
         """verify shape attribute for greyscale JP2 file
@@ -2038,9 +2071,11 @@ class TestJp2k_write(fixtures.MetadataBase):
         EXPECTED RESULT:  InvalidJp2kError
         """
         data = np.zeros((640, 480), dtype=np.uint8)
-        with open(self.temp_j2k_filename, mode='wb') as tfile:
-            with self.assertRaises(InvalidJp2kError):
-                Jp2k(tfile.name, data=data, cbsize=(16, 16), psizes=[(16, 16)])
+        with self.assertRaises(InvalidJp2kError):
+            Jp2k(
+                self.temp_j2k_filename,
+                data=data, cbsize=(16, 16), psizes=[(16, 16)]
+            )
 
     def test_precinct_size_not_power_of_two(self):
         """
@@ -2049,10 +2084,11 @@ class TestJp2k_write(fixtures.MetadataBase):
         EXPECTED RESULT:  InvalidJp2kError
         """
         data = np.zeros((640, 480), dtype=np.uint8)
-        with open(self.temp_j2k_filename, mode='wb') as tfile:
-            with self.assertRaises(InvalidJp2kError):
-                Jp2k(tfile.name, data=data,
-                     cbsize=(16, 16), psizes=[(48, 48)])
+        with self.assertRaises(InvalidJp2kError):
+            Jp2k(
+                self.temp_j2k_filename, data=data, cbsize=(16, 16),
+                psizes=[(48, 48)]
+            )
 
     def test_unsupported_int32(self):
         """Should raise a runtime error if trying to write int32"""
@@ -2086,13 +2122,12 @@ class TestJp2k_write(fixtures.MetadataBase):
         width.
         """
         data = np.zeros((128, 128), dtype=np.uint8)
-        with open(self.temp_j2k_filename, mode='wb') as tfile:
-            # The code block dimensions are given as rows x columns.
-            j = Jp2k(tfile.name, data=data, cbsize=(16, 32))
-            codestream = j.get_codestream()
+        # The code block dimensions are given as rows x columns.
+        j = Jp2k(self.temp_j2k_filename, data=data, cbsize=(16, 32))
+        codestream = j.get_codestream()
 
-            # Code block size is reported as XY in the codestream.
-            self.assertEqual(codestream.segment[2].code_block_size, (16, 32))
+        # Code block size is reported as XY in the codestream.
+        self.assertEqual(codestream.segment[2].code_block_size, (16, 32))
 
     def test_too_many_dimensions(self):
         """OpenJP2 only allows 2D or 3D images."""
@@ -2103,54 +2138,45 @@ class TestJp2k_write(fixtures.MetadataBase):
 
     def test_2d_rgb(self):
         """RGB must have at least 3 components."""
-        with open(self.temp_jp2_filename, mode='wb') as tfile:
-            with self.assertRaises(RuntimeError):
-                Jp2k(tfile.name,
-                     data=np.zeros((128, 128, 2), dtype=np.uint8),
-                     colorspace='rgb')
+        with self.assertRaises(RuntimeError):
+            Jp2k(self.temp_jp2_filename,
+                 data=np.zeros((128, 128, 2), dtype=np.uint8),
+                 colorspace='rgb')
 
     def test_colorspace_with_j2k(self):
         """Specifying a colorspace with J2K does not make sense"""
-        with open(self.temp_j2k_filename, mode='wb') as tfile:
-            with self.assertRaises(RuntimeError):
-                Jp2k(tfile.name,
-                     data=np.zeros((128, 128, 3), dtype=np.uint8),
-                     colorspace='rgb')
+        with self.assertRaises(RuntimeError):
+            Jp2k(self.temp_j2k_filename,
+                 data=np.zeros((128, 128, 3), dtype=np.uint8),
+                 colorspace='rgb')
 
     def test_specify_rgb(self):
         """specify RGB explicitly"""
-        with open(self.temp_jp2_filename, mode='wb') as tfile:
-            j = Jp2k(tfile.name,
-                     data=np.zeros((128, 128, 3), dtype=np.uint8),
-                     colorspace='rgb')
-            self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB)
+        j = Jp2k(self.temp_jp2_filename,
+                 data=np.zeros((128, 128, 3), dtype=np.uint8),
+                 colorspace='rgb')
+        self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB)
 
     def test_specify_gray(self):
         """test gray explicitly specified (that's GRAY, not GREY)"""
-        with open(self.temp_jp2_filename, mode='wb') as tfile:
-            data = np.zeros((128, 128), dtype=np.uint8)
-            j = Jp2k(tfile.name, data=data, colorspace='gray')
-            self.assertEqual(j.box[2].box[1].colorspace,
-                             glymur.core.GREYSCALE)
+        data = np.zeros((128, 128), dtype=np.uint8)
+        j = Jp2k(self.temp_jp2_filename, data=data, colorspace='gray')
+        self.assertEqual(j.box[2].box[1].colorspace, glymur.core.GREYSCALE)
 
     def test_specify_grey(self):
         """test grey explicitly specified"""
-        with open(self.temp_jp2_filename, mode='wb') as tfile:
-            data = np.zeros((128, 128), dtype=np.uint8)
-            j = Jp2k(tfile.name, data=data, colorspace='grey')
-            self.assertEqual(j.box[2].box[1].colorspace,
-                             glymur.core.GREYSCALE)
+        data = np.zeros((128, 128), dtype=np.uint8)
+        j = Jp2k(self.temp_jp2_filename, data=data, colorspace='grey')
+        self.assertEqual(j.box[2].box[1].colorspace, glymur.core.GREYSCALE)
 
     def test_grey_with_two_extra_comps(self):
         """should be able to write gray + two extra components"""
-        with open(self.temp_jp2_filename, mode='wb') as tfile:
-            data = np.zeros((128, 128, 3), dtype=np.uint8)
-            j = Jp2k(tfile.name, data=data, colorspace='gray')
-            self.assertEqual(j.box[2].box[0].height, 128)
-            self.assertEqual(j.box[2].box[0].width, 128)
-            self.assertEqual(j.box[2].box[0].num_components, 3)
-            self.assertEqual(j.box[2].box[1].colorspace,
-                             glymur.core.GREYSCALE)
+        data = np.zeros((128, 128, 3), dtype=np.uint8)
+        j = Jp2k(self.temp_jp2_filename, data=data, colorspace='gray')
+        self.assertEqual(j.box[2].box[0].height, 128)
+        self.assertEqual(j.box[2].box[0].width, 128)
+        self.assertEqual(j.box[2].box[0].num_components, 3)
+        self.assertEqual(j.box[2].box[1].colorspace, glymur.core.GREYSCALE)
 
     def test_specify_ycc(self):
         """Should reject YCC"""
@@ -2166,22 +2192,20 @@ class TestJp2k_write(fixtures.MetadataBase):
 
         filename = str(self.temp_jp2_filename).replace('.jp2', '.JP2')
 
-        with open(filename, mode='wb') as tfile:
-            ofile = Jp2k(tfile.name, data=expdata)
-            actdata = ofile[:]
-            np.testing.assert_array_equal(actdata, expdata)
+        ofile = Jp2k(filename, data=expdata)
+        actdata = ofile[:]
+        np.testing.assert_array_equal(actdata, expdata)
 
     def test_write_srgb_without_mct(self):
         """should be able to write RGB without specifying mct"""
         j2k = Jp2k(self.j2kfile)
         expdata = j2k[:]
-        with open(self.temp_jp2_filename, mode='wb') as tfile:
-            ofile = Jp2k(tfile.name, data=expdata, mct=False)
-            actdata = ofile[:]
-            np.testing.assert_array_equal(actdata, expdata)
+        ofile = Jp2k(self.temp_jp2_filename, data=expdata, mct=False)
+        actdata = ofile[:]
+        np.testing.assert_array_equal(actdata, expdata)
 
-            codestream = ofile.get_codestream()
-            self.assertEqual(codestream.segment[2].mct, 0)  # no mct
+        codestream = ofile.get_codestream()
+        self.assertEqual(codestream.segment[2].mct, 0)  # no mct
 
     def test_write_grayscale_with_mct(self):
         """
@@ -2198,14 +2222,12 @@ class TestJp2k_write(fixtures.MetadataBase):
         # Issue 17
         j = Jp2k(self.jp2file)
         expdata = j[::2, ::2]
-        with open(self.temp_jp2_filename, mode='wb') as tfile:
-            ofile = Jp2k(tfile.name, data=expdata, prog='CPRL')
-            actdata = ofile[:]
-            np.testing.assert_array_equal(actdata, expdata)
+        ofile = Jp2k(self.temp_jp2_filename, data=expdata, prog='CPRL')
+        actdata = ofile[:]
+        np.testing.assert_array_equal(actdata, expdata)
 
-            codestream = ofile.get_codestream()
-            self.assertEqual(codestream.segment[2].prog_order,
-                             glymur.core.CPRL)
+        codestream = ofile.get_codestream()
+        self.assertEqual(codestream.segment[2].prog_order, glymur.core.CPRL)
 
     def test_bad_area_parameter(self):
         """Should error out appropriately if given a bad area parameter."""
@@ -2296,24 +2318,21 @@ class TestJp2k_write(fixtures.MetadataBase):
 
     def test_grey_with_extra_component(self):
         """version 2.0 cannot write gray + extra"""
-        with open(self.temp_jp2_filename, mode='wb') as tfile:
-            data = np.zeros((128, 128, 2), dtype=np.uint8)
-            j = Jp2k(tfile.name, data=data)
-            self.assertEqual(j.box[2].box[0].height, 128)
-            self.assertEqual(j.box[2].box[0].width, 128)
-            self.assertEqual(j.box[2].box[0].num_components, 2)
-            self.assertEqual(j.box[2].box[1].colorspace,
-                             glymur.core.GREYSCALE)
+        data = np.zeros((128, 128, 2), dtype=np.uint8)
+        j = Jp2k(self.temp_jp2_filename, data=data)
+        self.assertEqual(j.box[2].box[0].height, 128)
+        self.assertEqual(j.box[2].box[0].width, 128)
+        self.assertEqual(j.box[2].box[0].num_components, 2)
+        self.assertEqual(j.box[2].box[1].colorspace, glymur.core.GREYSCALE)
 
     def test_rgb_with_extra_component(self):
         """v2.0+ should be able to write extra components"""
-        with open(self.temp_jp2_filename, mode='wb') as tfile:
-            data = np.zeros((128, 128, 4), dtype=np.uint8)
-            j = Jp2k(tfile.name, data=data)
-            self.assertEqual(j.box[2].box[0].height, 128)
-            self.assertEqual(j.box[2].box[0].width, 128)
-            self.assertEqual(j.box[2].box[0].num_components, 4)
-            self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB)
+        data = np.zeros((128, 128, 4), dtype=np.uint8)
+        j = Jp2k(self.temp_jp2_filename, data=data)
+        self.assertEqual(j.box[2].box[0].height, 128)
+        self.assertEqual(j.box[2].box[0].width, 128)
+        self.assertEqual(j.box[2].box[0].num_components, 4)
+        self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB)
 
     def test_openjpeg_library_error(self):
         """


=====================================
tests/test_printing.py
=====================================
@@ -975,21 +975,53 @@ class TestPrinting(fixtures.TestCommon):
             with open(self.jp2file, 'rb') as ifptr:
                 tfile.write(ifptr.read())
 
+            b = BytesIO()
+
+            # UUID stuff at byte 0
+            # Exif leader at byte 24
+            # TIFF header at byte 30
+            # IFD start at byte 38
+            # IFD tags at byte 40
+            # tile offsets at byte 88 (40 bytes)
+            #    IFD location 58
+            # Exif IFD start at byte 128
+            #    IFD byte location 98
+            # Exif IFD tag at byte 130 (12 bytes)
+
             # Write L, T, UUID identifier.
-            tfile.write(struct.pack('>I4s', 76, b'uuid'))
-            tfile.write(b'JpgTiffExif->JP2')
+            b.write(struct.pack('>I4s', 142, b'uuid'))
+            b.write(b'JpgTiffExif->JP2')
+
+            b.write(b'Exif\x00\x00')
 
-            tfile.write(b'Exif\x00\x00')
+            # write the tiff header
             xbuffer = struct.pack('<BBHI', 73, 73, 42, 8)
-            tfile.write(xbuffer)
+            b.write(xbuffer)
 
-            # We will write just three tags.
-            tfile.write(struct.pack('<H', 3))
+            # We will write just four tags.
+            b.write(struct.pack('<H', 4))
 
             # The "Make" tag is tag no. 271.
-            tfile.write(struct.pack('<HHII', 256, 4, 1, 256))
-            tfile.write(struct.pack('<HHII', 257, 4, 1, 512))
-            tfile.write(struct.pack('<HHI4s', 271, 2, 3, b'HTC\x00'))
+            b.write(struct.pack('<HHII', 256, 4, 1, 256))
+            b.write(struct.pack('<HHII', 257, 4, 1, 512))
+
+            b.write(struct.pack('<HHII', 324, 4, 10, 58))
+
+            b.write(struct.pack('<HHII', 34665, 4, 1, 98))
+
+            # write the tile offsets (fake)
+            tile_offsets = list(range(0, 100, 10))
+            b.write(struct.pack('<' + 'I' * 10, *tile_offsets))
+
+            # start writing the Exif IFD.
+
+            # We will write just one tag.
+            b.write(struct.pack('<H', 1))
+
+            b.write(struct.pack('<HHI4s', 271, 2, 3, b'HTC\x00'))
+            b.flush()
+
+            tfile.write(b.getvalue())
             tfile.flush()
 
             j = glymur.Jp2k(tfile.name)
@@ -997,10 +1029,14 @@ class TestPrinting(fixtures.TestCommon):
             actual = str(j.box[5])
 
         expected = (
-            "UUID Box (uuid) @ (1135519, 76)\n"
+            "UUID Box (uuid) @ (1135519, 142)\n"
             "    UUID:  4a706754-6966-6645-7869-662d3e4a5032 (EXIF)\n"
-            "    UUID Data:  OrderedDict([('ImageWidth', 256), "
-            "('ImageLength', 512), ('Make', 'HTC')])"
+            "    UUID Data:  OrderedDict([   ('ImageWidth', 256),\n"
+            "                    ('ImageLength', 512),\n"
+            "                    (   'TileOffsets',\n"
+            "                        "
+            "array([ 0, 10, 20, ..., 70, 80, 90], dtype=uint32)),\n"
+            "                    ('ExifTag', OrderedDict([('Make', 'HTC')]))])"
         )
         self.assertEqual(actual, expected)
 
@@ -1162,16 +1198,20 @@ class TestPrinting(fixtures.TestCommon):
 
         Original file tested was input/conformance/p0_15.j2k
         """
-        segment = glymur.codestream.TLMsegment(0,
-                                               (0, 1, 2, 3),
-                                               (4267, 2117, 4080, 2081),
-                                               28, 268)
+        segment = glymur.codestream.TLMsegment(
+            0,
+            (0, 1, 2, 3, 4, 5, 6, 7),
+            (4267, 2117, 4080, 2081, 1000, 100, 150, 400),
+            28, 268
+        )
         actual = str(segment)
 
-        expected = ('TLM marker segment @ (268, 28)\n'
-                    '    Index:  0\n'
-                    '    Tile number:  (0, 1, 2, 3)\n'
-                    '    Length:  (4267, 2117, 4080, 2081)')
+        expected = (
+            'TLM marker segment @ (268, 28)\n'
+            '    Index:  0\n'
+            '    Tile number:  [0 1 2 ... 5 6 7]\n'
+            '    Length:  [4267 2117 4080 ...  100  150  400]'
+        )
 
         self.assertEqual(actual, expected)
 


=====================================
tests/test_set_decoded_components.py
=====================================
@@ -30,7 +30,7 @@ class TestSuite(unittest.TestCase):
         self.j2kfile = pathlib.Path(self.testdir) / 'tmp.j2k'
 
         data = Jp2k(glymur.data.goodstuff())[:]
-        Jp2k(self.j2kfile.name, data=data, mct=False, cratios=[200, 100, 50])
+        Jp2k(self.j2kfile, data=data, mct=False, cratios=[200, 100, 50])
 
     @classmethod
     def tearDownClass(self):
@@ -43,7 +43,7 @@ class TestSuite(unittest.TestCase):
 
         EXPECTED RESULT:  the data matches what we get from the regular way.
         """
-        j2k = Jp2k(self.j2kfile.name)
+        j2k = Jp2k(self.j2kfile)
         expected = j2k[:, :, 0]
 
         j2k.decoded_components = 0
@@ -62,7 +62,7 @@ class TestSuite(unittest.TestCase):
 
         EXPECTED RESULT:  Match the 2nd component read in the regular way.
         """
-        j2k = Jp2k(self.j2kfile.name)
+        j2k = Jp2k(self.j2kfile)
         expected = j2k[:, :, 1]
 
         j2k.decoded_components = 1
@@ -77,7 +77,7 @@ class TestSuite(unittest.TestCase):
         EXPECTED RESULT:  Match the results the regular way.
         """
 
-        j2k = Jp2k(self.j2kfile.name)
+        j2k = Jp2k(self.j2kfile)
 
         expected = j2k[:]
 
@@ -92,7 +92,7 @@ class TestSuite(unittest.TestCase):
 
         EXPECTED RESULT:  Match the results the regular way.
         """
-        j2k = Jp2k(self.j2kfile.name)
+        j2k = Jp2k(self.j2kfile)
 
         expected = j2k[20:40, 10:30, 0]
 
@@ -108,7 +108,7 @@ class TestSuite(unittest.TestCase):
         EXPECTED RESULT:  Match the results the regular way.
         """
 
-        j2k = Jp2k(self.j2kfile.name)
+        j2k = Jp2k(self.j2kfile)
         j2k.layer = 1
 
         expected = j2k[:, :, 0]
@@ -125,7 +125,7 @@ class TestSuite(unittest.TestCase):
         EXPECTED RESULT:  Match the results the regular way.
         """
 
-        j2k = Jp2k(self.j2kfile.name)
+        j2k = Jp2k(self.j2kfile)
 
         expected = j2k[::2, ::2, 0]
 
@@ -141,7 +141,7 @@ class TestSuite(unittest.TestCase):
         EXPECTED RESULT:  exception
         """
 
-        j2k = Jp2k(self.j2kfile.name)
+        j2k = Jp2k(self.j2kfile)
 
         with self.assertRaises(glymur.lib.openjp2.OpenJPEGLibraryError):
             j2k.decoded_components = -1
@@ -154,7 +154,7 @@ class TestSuite(unittest.TestCase):
         EXPECTED RESULT:  exception
         """
 
-        j2k = Jp2k(self.j2kfile.name)
+        j2k = Jp2k(self.j2kfile)
 
         with self.assertRaises(glymur.lib.openjp2.OpenJPEGLibraryError):
             j2k.decoded_components = [0, 0]
@@ -166,7 +166,7 @@ class TestSuite(unittest.TestCase):
 
         EXPECTED RESULT:  exception
         """
-        j2k = Jp2k(self.j2kfile.name)
+        j2k = Jp2k(self.j2kfile)
 
         with self.assertRaises(ValueError):
             j2k.decoded_components = 10


=====================================
tests/test_tiff2jp2.py
=====================================
@@ -2,6 +2,7 @@
 import importlib.resources as ir
 import logging
 import pathlib
+import platform
 import shutil
 import struct
 import sys
@@ -16,6 +17,7 @@ import numpy as np
 # Local imports
 import glymur
 from glymur import Jp2k, Tiff2Jp2k, command_line
+from glymur.core import SRGB
 from . import fixtures
 from .fixtures import OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG
 from glymur.lib import tiff as libtiff
@@ -25,126 +27,7 @@ from glymur.lib import tiff as libtiff
     not fixtures.HAVE_SCIKIT_IMAGE, fixtures.HAVE_SCIKIT_IMAGE_MSG
 )
 @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG)
-class TestSuite(fixtures.TestCommon):
-
-    @classmethod
-    def setup_exif(cls, path):
-        """
-        Create a simple TIFF file that is constructed to contain an EXIF IFD.
-        """
-
-        with path.open(mode='wb') as f:
-
-            w = 256
-            h = 256
-            rps = 64
-            header_length = 8
-
-            # write the header (8 bytes).  The IFD will follow the image data
-            # (256x256 bytes), so the offset to the IFD will be 8 + h * w.
-            main_ifd_offset = header_length + h * w
-            buffer = struct.pack('<BBHI', 73, 73, 42, main_ifd_offset)
-            f.write(buffer)
-
-            # write the image data, 4 64x256 strips of all zeros
-            strip = bytes([0] * rps * w)
-            f.write(strip)
-            f.write(strip)
-            f.write(strip)
-            f.write(strip)
-
-            # write an IFD with 11 tags
-            main_ifd_data_offset = main_ifd_offset + 2 + 11 * 12 + 4
-
-            buffer = struct.pack('<H', 11)
-            f.write(buffer)
-
-            # width and length and bitspersample
-            buffer = struct.pack('<HHII', 256, 4, 1, w)
-            f.write(buffer)
-            buffer = struct.pack('<HHII', 257, 4, 1, h)
-            f.write(buffer)
-            buffer = struct.pack('<HHII', 258, 4, 1, 8)
-            f.write(buffer)
-
-            # photometric
-            buffer = struct.pack('<HHII', 262, 4, 1, 1)
-            f.write(buffer)
-
-            # strip offsets
-            buffer = struct.pack('<HHII', 273, 4, 4, main_ifd_data_offset)
-            f.write(buffer)
-
-            # spp
-            buffer = struct.pack('<HHII', 277, 4, 1, 1)
-            f.write(buffer)
-
-            # rps
-            buffer = struct.pack('<HHII', 278, 4, 1, 64)
-            f.write(buffer)
-
-            # strip byte counts
-            buffer = struct.pack('<HHII', 279, 4, 4, main_ifd_data_offset + 16)
-            f.write(buffer)
-
-            # pagenumber
-            buffer = struct.pack('<HHIHH', 297, 3, 2, 1, 0)
-            f.write(buffer)
-
-            # XMP
-            with ir.path('tests.data', 'issue555.xmp') as xmp_path:
-                with xmp_path.open() as f2:
-                    xmp = f2.read()
-                    xmp = xmp + '\0'
-            buffer = struct.pack(
-                '<HHII', 700, 1, len(xmp), main_ifd_data_offset + 32
-            )
-            f.write(buffer)
-
-            # exif tag
-            exif_ifd_offset = main_ifd_data_offset + 32 + len(xmp)
-            buffer = struct.pack('<HHII', 34665, 4, 1, exif_ifd_offset)
-            f.write(buffer)
-
-            # terminate the IFD
-            buffer = struct.pack('<I', 0)
-            f.write(buffer)
-
-            # write the strip offsets here
-            buffer = struct.pack(
-                '<IIII', 8, 8 + rps*w, 8 + 2*rps*w, 8 + 3*rps*w
-            )
-            f.write(buffer)
-
-            # write the strip byte counts
-            buffer = struct.pack('<IIII', rps*w, rps*w, rps*w, rps*w)
-            f.write(buffer)
-
-            # write the XMP data
-            f.write(xmp.encode('utf-8'))
-
-            # write a minimal Exif IFD
-            buffer = struct.pack('<H', 2)
-            f.write(buffer)
-
-            # exposure program
-            buffer = struct.pack('<HHIHH', 34850, 3, 1, 2, 0)
-            f.write(buffer)
-
-            # lens model
-            data_location = exif_ifd_offset + 2 + 2*12 + 4
-            buffer = struct.pack('<HHII', 42036, 2, 6, data_location)
-            f.write(buffer)
-
-            # terminate the IFD
-            buffer = struct.pack('<I', 0)
-            f.write(buffer)
-
-            data = 'Canon\0'.encode('utf-8')
-            buffer = struct.pack('<BBBBBB', *data)
-            f.write(buffer)
-
-        cls.exif_tiff = path
+class TestSuiteScikitImage(fixtures.TestCommon):
 
     @classmethod
     def setup_minisblack_spp1(cls, path):
@@ -590,8 +473,6 @@ class TestSuite(fixtures.TestCommon):
         cls.test_tiff_dir = tempfile.mkdtemp()
         cls.test_tiff_path = pathlib.Path(cls.test_tiff_dir)
 
-        cls.setup_exif(cls.test_tiff_path / 'exif.tif')
-
         cls.setup_minisblack_spp1(cls.test_tiff_path / 'moon.tif')
 
         cls.setup_minisblack_3x3(cls.test_tiff_path / 'minisblack_3x3.tif')
@@ -621,43 +502,6 @@ class TestSuite(fixtures.TestCommon):
     def tearDownClass(cls):
         shutil.rmtree(cls.test_tiff_dir)
 
-    def test_exclude_tags_camelcase(self):
-        """
-        Scenario:  Convert TIFF to JP2, but exclude the StripByteCounts and
-        StripOffsets tags.  Supply the argments as camel-case.
-
-        Expected Result:  No warnings, no errors.  The Exif LensModel tag is
-        recoverable from the UUIDbox.
-        """
-        with Tiff2Jp2k(
-            self.exif_tiff, self.temp_jp2_filename,
-            exclude_tags=['StripOffsets', 'StripByteCounts']
-        ) as p:
-            p.run()
-
-        j = Jp2k(self.temp_jp2_filename)
-
-        tags = j.box[-1].data
-        self.assertNotIn('StripByteCounts', tags)
-        self.assertNotIn('StripOffsets', tags)
-
-    def test_exif(self):
-        """
-        Scenario:  Convert TIFF with Exif IFD to JP2
-
-        Expected Result:  No warnings, no errors.  The Exif LensModel tag is
-        recoverable from the UUIDbox.
-        """
-        with Tiff2Jp2k(self.exif_tiff, self.temp_jp2_filename) as p:
-            with warnings.catch_warnings(record=True) as w:
-                p.run()
-                self.assertEqual(len(w), 0)
-
-        j = Jp2k(self.temp_jp2_filename)
-
-        tags = j.box[-1].data
-        self.assertEqual(tags['ExifTag']['LensModel'], 'Canon')
-
     def test_smoke(self):
         """
         SCENARIO:  Convert TIFF file to JP2
@@ -779,27 +623,6 @@ class TestSuite(fixtures.TestCommon):
         self.assertEqual(j.box[-1].data['ImageWidth'], 512)
         self.assertEqual(j.box[-1].data['ImageLength'], 512)
 
-    def test_geotiff(self):
-        """
-        SCENARIO:  Convert a one-component GEOTIFF file to JP2
-
-        EXPECTED RESULT:  there is a geotiff UUID.  The JP2 file has only one
-        component.
-        """
-        with warnings.catch_warnings():
-            warnings.simplefilter('ignore')
-            with ir.path('tests.data', 'albers27.tif') as path:
-                with Tiff2Jp2k(path, self.temp_jp2_filename) as j:
-                    j.run()
-
-        j = Jp2k(self.temp_jp2_filename)
-
-        self.assertEqual(j.box[-1].box_id, 'uuid')
-        self.assertEqual(
-            j.box[-1].uuid, UUID('b14bf8bd-083d-4b43-a5ae-8cd7d5a6ce03')
-        )
-        self.assertEqual(j.box[2].box[0].num_components, 1)
-
     def test_no_uuid(self):
         """
         SCENARIO:  Convert TIFF file to JP2, but do not include the UUID box
@@ -823,6 +646,9 @@ class TestSuite(fixtures.TestCommon):
         )
         self.assertFalse(at_least_one_uuid)
 
+    @unittest.skipIf(
+        platform.machine() == 's390x', 'See issue #546'
+    )
     def test_psnr(self):
         """
         SCENARIO:  Convert TIFF file to JP2 with the irreversible transform.
@@ -1270,47 +1096,6 @@ class TestSuite(fixtures.TestCommon):
         self.assertEqual(c.segment[1].xtsiz, 240)
         self.assertEqual(c.segment[1].ytsiz, 240)
 
-    def test_separated_configuration(self):
-        """
-        SCENARIO:  The TIFF has a planar configuration of SEPARATE which is
-        not supported if a tilesize is specified.
-
-        EXPECTED RESULT:  RuntimeError
-        """
-        with self.assertRaises(RuntimeError):
-            with ir.path(
-                'tests.data', 'flower-separated-planar-08.tif'
-            ) as path:
-                with Tiff2Jp2k(
-                    path, self.temp_jp2_filename, tilesize=(64, 64)
-                ) as j:
-                    j.run()
-
-    def test_bad_tile_size(self):
-        """
-        SCENARIO:  Specify a tilesize that exceeds the image size.  This will
-        cause a segfault unless caught.
-
-        EXPECTED RESULT:  RuntimeError
-        """
-        with self.assertRaises(RuntimeError):
-            with ir.path('tests.data', 'albers27-8.tif') as path:
-                with Tiff2Jp2k(
-                    path, self.temp_jp2_filename, tilesize=(256, 256),
-                ) as j:
-                    j.run()
-
-    def test_minisblack_spp1_bigtiff(self):
-        """
-        SCENARIO:  Convert minisblack BigTIFF file to JP2.  The TIFF has tag
-        XResolution.
-
-        EXPECTED RESULT:  no errors.
-        """
-        with ir.path('tests.data', 'albers27-8.tif') as path:
-            with Tiff2Jp2k(path, self.temp_jp2_filename) as j:
-                j.run()
-
     def test_rgb_tiled_bigtiff(self):
         """
         SCENARIO:  Convert RGB BigTIFF file to JP2.  The TIFF is evenly
@@ -1428,18 +1213,6 @@ class TestSuite(fixtures.TestCommon):
         self.assertEqual(c.segment[1].xtsiz, 512)
         self.assertEqual(c.segment[1].ytsiz, 512)
 
-    def test_tiff_file_not_there(self):
-        """
-        Scenario:  The input TIFF file is not present.
-
-        Expected Result:  FileNotFoundError
-        """
-
-        with self.assertRaises(FileNotFoundError):
-            Tiff2Jp2k(
-                self.test_dir_path / 'not_there.tif', self.temp_jp2_filename
-            )
-
     def test_rgb_uint16(self):
         """
         SCENARIO:  Convert RGB TIFF file to JP2.  The TIFF is evenly
@@ -1570,22 +1343,24 @@ class TestSuite(fixtures.TestCommon):
 
 class TestSuiteNoScikitImage(fixtures.TestCommon):
 
-    @classmethod
-    def setUpClass(cls):
-
-        cls.test_tiff_dir = tempfile.mkdtemp()
-        cls.test_tiff_path = pathlib.Path(cls.test_tiff_dir)
-
-        cls.setup_rgb_evenly_stripped(cls.test_tiff_path / 'goodstuff.tif')
-
-        cls.setup_exif(cls.test_tiff_path / 'exif.tif')
-
     @classmethod
     def setup_exif(cls, path):
         """
         Create a simple TIFF file that is constructed to contain an EXIF IFD.
         """
 
+        # main TIFF header @ 0
+        # image data @ 8
+        # main IFD @ 65544 = 256*256 + 8 (2 + 12*12 = 146 bytes)
+        # main IDF data @ 65690  = main_ifd + 2 + 12 * 12 + 4
+        #
+        # strip offsets @ 65694 (16 bytes)
+        # strip byte counts @ 65710 (16 bytes)
+        # xmp data @ 65726 (12532 bytes)
+        # camera ID data @ 78258 (8 bytes)
+        #
+        # exif IFD @ 78266 (2 + 2*12 + 4 = 30 bytes)
+        # exif IFD data @ 78296 (6 bytes)
         with path.open(mode='wb') as f:
 
             w = 256
@@ -1606,10 +1381,10 @@ class TestSuiteNoScikitImage(fixtures.TestCommon):
             f.write(strip)
             f.write(strip)
 
-            # write an IFD with 11 tags
-            main_ifd_data_offset = main_ifd_offset + 2 + 11 * 12 + 4
+            # write an IFD with 12 tags
+            main_ifd_data_offset = main_ifd_offset + 2 + 12 * 12 + 4
 
-            buffer = struct.pack('<H', 11)
+            buffer = struct.pack('<H', 12)
             f.write(buffer)
 
             # width and length and bitspersample
@@ -1655,10 +1430,16 @@ class TestSuiteNoScikitImage(fixtures.TestCommon):
             f.write(buffer)
 
             # exif tag
-            exif_ifd_offset = main_ifd_data_offset + 32 + len(xmp)
+            # write it AFTER lensinfo, which is 8 chars
+            exif_ifd_offset = main_ifd_data_offset + 32 + len(xmp) + 8
             buffer = struct.pack('<HHII', 34665, 4, 1, exif_ifd_offset)
             f.write(buffer)
 
+            # lensmodel
+            offset = main_ifd_data_offset + 32 + len(xmp)
+            buffer = struct.pack('<HHII', 50708, 2, 8, offset)
+            f.write(buffer)
+
             # terminate the IFD
             buffer = struct.pack('<I', 0)
             f.write(buffer)
@@ -1676,6 +1457,9 @@ class TestSuiteNoScikitImage(fixtures.TestCommon):
             # write the XMP data
             f.write(xmp.encode('utf-8'))
 
+            # write the camera ID
+            f.write("abcdefg\x00".encode('utf-8'))
+
             # write a minimal Exif IFD
             buffer = struct.pack('<H', 2)
             f.write(buffer)
@@ -1699,6 +1483,16 @@ class TestSuiteNoScikitImage(fixtures.TestCommon):
 
         cls.exif_tiff = path
 
+    @classmethod
+    def setUpClass(cls):
+
+        cls.test_tiff_dir = tempfile.mkdtemp()
+        cls.test_tiff_path = pathlib.Path(cls.test_tiff_dir)
+
+        cls.setup_rgb_evenly_stripped(cls.test_tiff_path / 'goodstuff.tif')
+
+        cls.setup_exif(cls.test_tiff_path / 'exif.tif')
+
     @classmethod
     def setup_rgb_evenly_stripped(cls, path):
         """
@@ -1730,6 +1524,60 @@ class TestSuiteNoScikitImage(fixtures.TestCommon):
         cls.goodstuff_data = data
         cls.goodstuff_path = path
 
+    def test_numeric_exclude_keyword_argument(self):
+        """
+        Scenario:  specify exclude_tags keyword argument as list of integer
+        keyword argument is set to True.
+
+        Expected result:  The tags are not included in the exif IFD.
+        """
+        with Tiff2Jp2k(
+            self.goodstuff_path, self.temp_jp2_filename,
+            exclude_tags=[273, 279]
+        ) as p:
+            p.run()
+
+        j = Jp2k(self.temp_jp2_filename)
+
+        self.assertNotIn('StripOffsets', j.box[-1].data)
+        self.assertNotIn('StripByteCounts', j.box[-1].data)
+
+    def test_string_exclude_keyword_argument(self):
+        """
+        Scenario:  specify exclude_tags keyword argument as list of integer
+        keyword argument is set to True.
+
+        Expected result:  The tags are not included in the exif IFD.
+        """
+        with Tiff2Jp2k(
+            self.goodstuff_path, self.temp_jp2_filename,
+            exclude_tags=['StripOffsets', 'StripByteCounts']
+        ) as p:
+            p.run()
+
+        j = Jp2k(self.temp_jp2_filename)
+
+        self.assertNotIn('StripOffsets', j.box[-1].data)
+        self.assertNotIn('StripByteCounts', j.box[-1].data)
+
+    def test_tiff_has_no_icc_profile(self):
+        """
+        Scenario:  input TIFF has no ICC profile, yet the include_icc_profile
+        keyword argument is set to True.
+
+        Expected result:  a warning is issued
+        """
+        with Tiff2Jp2k(
+            self.goodstuff_path, self.temp_jp2_filename, tilesize=(64, 64),
+            include_icc_profile=True, verbosity=logging.INFO
+        ) as j:
+            with self.assertLogs(
+                logger='tiff2jp2', level=logging.WARNING
+            ) as cm:
+                j.run()
+
+                self.assertEqual(len(cm.output), 1)
+
     def test_stripped_logging(self):
         """
         Scenario:  input TIFF is organized by strips and logging is turned on.
@@ -1798,8 +1646,8 @@ class TestSuiteNoScikitImage(fixtures.TestCommon):
         Scenario:  Convert TIFF to JP2, but exclude the StripByteCounts and
         StripOffsets tags.
 
-        Expected Result:  No warnings, no errors.  The Exif LensModel tag is
-        recoverable from the UUIDbox.
+        Expected Result:  The Exif UUID box prints without error.
+        The StripByteCounts and StripOffsets tags are not present.
         """
         with Tiff2Jp2k(
             self.exif_tiff, self.temp_jp2_filename,
@@ -1809,7 +1657,7 @@ class TestSuiteNoScikitImage(fixtures.TestCommon):
 
         j = Jp2k(self.temp_jp2_filename)
 
-        tags = j.box[-1].data
+        tags = j.box[-2].data
         self.assertNotIn('StripByteCounts', tags)
         self.assertNotIn('StripOffsets', tags)
 
@@ -1832,7 +1680,7 @@ class TestSuiteNoScikitImage(fixtures.TestCommon):
 
         j = Jp2k(self.temp_jp2_filename)
 
-        tags = j.box[-1].data
+        tags = j.box[-2].data
         self.assertNotIn('StripByteCounts', tags)
         self.assertNotIn('StripOffsets', tags)
 
@@ -1852,7 +1700,7 @@ class TestSuiteNoScikitImage(fixtures.TestCommon):
 
         j = Jp2k(self.temp_jp2_filename)
 
-        tags = j.box[-1].data
+        tags = j.box[-2].data
         self.assertNotIn('StripByteCounts', tags)
         self.assertNotIn('StripOffsets', tags)
 
@@ -1870,7 +1718,9 @@ class TestSuiteNoScikitImage(fixtures.TestCommon):
 
         j = Jp2k(self.temp_jp2_filename)
 
-        tags = j.box[-1].data
+        tags = j.box[-2].data
+
+        self.assertEqual(tags['UniqueCameraModel'], 'abcdefg')
         self.assertEqual(tags['ExifTag']['LensModel'], 'Canon')
 
         str(j.box[-1])
@@ -1878,11 +1728,11 @@ class TestSuiteNoScikitImage(fixtures.TestCommon):
     def test_xmp(self):
         """
         Scenario:  Convert TIFF with Exif IFD to JP2.  The main IFD has an
-        XML Packet tag (700).  Supply the 'xmp_uuid' keyword.
+        XML Packet tag (700).  Supply the 'xmp_uuid' keyword as True.
 
-        Expected Result:  The XMLPacket tag is removed from the main IFD.
-        An Exif UUID is appended to the end of the JP2 file, and then an XMP
-        UUID is appended.
+        Expected Result:  An Exif UUID is appended to the end of the
+        JP2 file, and then an XMP UUID is appended.  The XMLPacket tag is still
+        present in the UUID IFD.
         """
         with Tiff2Jp2k(
             self.exif_tiff, self.temp_jp2_filename, create_xmp_uuid=True
@@ -1891,12 +1741,69 @@ class TestSuiteNoScikitImage(fixtures.TestCommon):
 
         j = Jp2k(self.temp_jp2_filename)
 
+        # first we find the Exif UUID, then maybe the XMP UUID.  The Exif UUID
+        # data should still have have the XMLPacket tag as only the exclude
+        # tags keyword argument can do that.
+        box = j.box[-2]
+        actual = box.uuid
+        expected = UUID(bytes=b'JpgTiffExif->JP2')
+        self.assertEqual(actual, expected)
+        self.assertIn('XMLPacket', box.data)
+
+        # ok so the xmp UUID is the last box
+        xmp_box = j.box[-1]
+        actual = xmp_box.uuid
+        expected = UUID('be7acfcb-97a9-42e8-9c71-999491e3afac')
+        self.assertEqual(actual, expected)
+        self.assertEqual(
+            xmp_box.data.getroot().values(), ['Public XMP Toolkit Core 3.5']
+        )
+
+    def test_xmp_false(self):
+        """
+        Scenario:  Convert TIFF with Exif IFD to JP2.  The main IFD has an
+        XML Packet tag (700).  Supply the 'xmp_uuid' keyword as False.
+
+        Expected Result:  An Exif UUID is appended to the end of the
+        JP2 file, but no XMP UUID is appended.  The XMLPacket tag is still
+        present in the UUID data.
+        """
+        with Tiff2Jp2k(
+            self.exif_tiff, self.temp_jp2_filename, create_xmp_uuid=False
+        ) as p:
+            p.run()
+
+        j = Jp2k(self.temp_jp2_filename)
+
+        # we find the Exif UUID at the end.
+        box = j.box[-1]
+        actual = box.uuid
+        expected = UUID(bytes=b'JpgTiffExif->JP2')
+        self.assertEqual(actual, expected)
+        self.assertIn('XMLPacket', box.data)
+
+    def test_xmp__exclude_XMLPacket(self):
+        """
+        Scenario:  Convert TIFF with Exif IFD to JP2.  The main IFD has an
+        XML Packet tag (700).  Supply the 'create_xmp_uuid' keyword.  Supply
+        the exclude_tags keyword, but don't supply XMLPacket.
+
+        Expected Result:  The XMLPacket tag is not removed from the main IFD.
+        An Exif UUID is appended to the end of the JP2 file, and then an XMP
+        UUID is appended.
+        """
+        kwargs = {'create_xmp_uuid': True, 'exclude_tags': ['StripOffsets']}
+        with Tiff2Jp2k(self.exif_tiff, self.temp_jp2_filename, **kwargs) as p:
+            p.run()
+
+        j = Jp2k(self.temp_jp2_filename)
+
         # first we find the Exif UUID, then the XMP UUID.  The Exif UUID
         # data should not have the XMLPacket tag.
         actual = j.box[-2].uuid
         expected = UUID(bytes=b'JpgTiffExif->JP2')
         self.assertEqual(actual, expected)
-        self.assertNotIn('XMLPacket', j.box[-2].data)
+        self.assertIn('XMLPacket', j.box[-2].data)
 
         actual = j.box[-1].uuid
         expected = UUID('be7acfcb-97a9-42e8-9c71-999491e3afac')
@@ -1905,7 +1812,7 @@ class TestSuiteNoScikitImage(fixtures.TestCommon):
             j.box[-1].data.getroot().values(), ['Public XMP Toolkit Core 3.5']
         )
 
-    def test_commandline__capture_display_resolution__no_tilesize(self):
+    def test_commandline_capture_display_resolution(self):
         """
         Scenario:  patch sys such that we can run the command
         line tiff2jp2 script.  Supply the --capture-resolution and
@@ -1979,9 +1886,8 @@ class TestSuiteNoScikitImage(fixtures.TestCommon):
         Scenario:  patch sys such that we can run the command line tiff2jp2
         script.  Use the --create-xmp-uuid option.
 
-        Expected Result:  The XMLPacket tag is removed from the main IFD.
-        An Exif UUID is appended to the end of the JP2 file, and then an XMP
-        UUID is appended.
+        Expected Result:  An Exif UUID is appended to the end of the
+        JP2 file, and then an XMP UUID is appended.
         """
         sys.argv = [
             '', str(self.exif_tiff), str(self.temp_jp2_filename),
@@ -1992,12 +1898,10 @@ class TestSuiteNoScikitImage(fixtures.TestCommon):
 
         j = Jp2k(self.temp_jp2_filename)
 
-        # first we find the Exif UUID, then the XMP UUID.  The Exif UUID
-        # data should not have the XMLPacket tag.
+        # first we find the Exif UUID, then the XMP UUID.
         actual = j.box[-2].uuid
         expected = UUID(bytes=b'JpgTiffExif->JP2')
         self.assertEqual(actual, expected)
-        self.assertNotIn('XMLPacket', j.box[-2].data)
 
         actual = j.box[-1].uuid
         expected = UUID('be7acfcb-97a9-42e8-9c71-999491e3afac')
@@ -2035,6 +1939,123 @@ class TestSuiteNoScikitImage(fixtures.TestCommon):
 
         Jp2k(self.temp_jp2_filename)
 
+    def test_icc_profile(self):
+        """
+        Scenario:  The input TIFF has the ICC profile tag.  Provide the
+        include_icc_profile keyword as True.
+
+        Expected Result.  The ICC profile is verified in the
+        ColourSpecificationBox.  There is a logging message at the info
+        level stating that a color profile was consumed.
+        """
+        with ir.path('tests.data', 'basn6a08.tif') as path:
+
+            with path.open(mode='rb') as f:
+                buffer = f.read()
+                ifd = glymur._tiff.tiff_header(buffer)
+            icc_profile = bytes(ifd['ICCProfile'])
+
+            with Tiff2Jp2k(
+                path, self.temp_jp2_filename, include_icc_profile=True
+            ) as p:
+
+                with self.assertLogs(
+                    logger='tiff2jp2', level=logging.INFO
+                ) as cm:
+                    p.run()
+
+                self.assertEqual(
+                    sum('ICC profile' in msg for msg in cm.output), 1
+                )
+
+        j = Jp2k(self.temp_jp2_filename)
+
+        # The colour specification box has the profile
+        self.assertEqual(j.box[2].box[1].icc_profile, bytes(icc_profile))
+
+    def test_icc_profile_commandline(self):
+        """
+        Scenario:  The input TIFF has the ICC profile tag.  Provide the
+        --include-icc-profile argument.
+
+        Expected Result.  The ICC profile is verified in the
+        ColourSpecificationBox.
+        """
+        with ir.path('tests.data', 'basn6a08.tif') as path:
+
+            with path.open(mode='rb') as f:
+                buffer = f.read()
+                ifd = glymur._tiff.tiff_header(buffer)
+            icc_profile = bytes(ifd['ICCProfile'])
+
+            sys.argv = [
+                '', str(path), str(self.temp_jp2_filename),
+                '--include-icc-profile'
+            ]
+            command_line.tiff2jp2()
+
+        j = Jp2k(self.temp_jp2_filename)
+
+        # The colour specification box has the profile
+        self.assertEqual(j.box[2].box[1].icc_profile, bytes(icc_profile))
+
+    def test_exclude_icc_profile_commandline(self):
+        """
+        Scenario:  The input TIFF has the ICC profile tag.  Do not provide the
+        --include-icc-profile flag.
+
+        Expected Result.  The ColourSpecificationBox is normal (no ICC
+        profile).  The ICC profile tag will be present in the
+        JpgTiffExif->JP2 UUID box.
+        """
+        with ir.path('tests.data', 'basn6a08.tif') as path:
+
+            sys.argv = [
+                '', str(path), str(self.temp_jp2_filename),
+            ]
+            command_line.tiff2jp2()
+
+        j = Jp2k(self.temp_jp2_filename)
+
+        # The colour specification box does not have the profile
+        colr = j.box[2].box[1]
+        self.assertEqual(colr.method, glymur.core.ENUMERATED_COLORSPACE)
+        self.assertEqual(colr.precedence, 0)
+        self.assertEqual(colr.approximation, 0)
+        self.assertEqual(colr.colorspace, SRGB)
+        self.assertIsNone(colr.icc_profile)
+
+    def test_exclude_icc_profile_commandline__exclude_from_uuid(self):
+        """
+        Scenario:  The input TIFF has the ICC profile tag.  Do not specify
+        the --include-icc-profile flag.  Specify the 34675 (ICCProfile) tag
+        in the --exclude-tags flag.
+
+        Expected Result.  The ICC profile is verified to not be present in the
+        ColourSpecificationBox.  The ICC profile tag will be not present in the
+        JpgTiffExif->JP2 UUID box.
+        """
+        with ir.path('tests.data', 'basn6a08.tif') as path:
+
+            sys.argv = [
+                '', str(path), str(self.temp_jp2_filename),
+                '--exclude-tags', 'ICCProfile',
+            ]
+            command_line.tiff2jp2()
+
+        j = Jp2k(self.temp_jp2_filename)
+
+        # The colour specification box does not have the profile
+        colr = j.box[2].box[1]
+        self.assertEqual(colr.method, glymur.core.ENUMERATED_COLORSPACE)
+        self.assertEqual(colr.precedence, 0)
+        self.assertEqual(colr.approximation, 0)
+        self.assertEqual(colr.colorspace, SRGB)
+        self.assertIsNone(colr.icc_profile)
+
+        # the exif UUID box does not have the profile
+        self.assertNotIn('ICCProfile', j.box[-1].data)
+
     def test_not_a_tiff(self):
         """
         Scenario:  The input "TIFF" is not actually a TIFF.  This used to
@@ -2046,3 +2067,150 @@ class TestSuiteNoScikitImage(fixtures.TestCommon):
             with ir.path('tests.data', 'simple_rdf.txt') as path:
                 with Tiff2Jp2k(path, self.temp_jp2_filename):
                     pass
+
+    def test_colormap(self):
+        """
+        Scenario:  The input "TIFF" has a colormap tag.
+
+        Expected Result:  The output JP2 has a single layer and the jp2h box
+        has a pclr box.
+        """
+        for tag in ['ColorMap', 'StripOffsets']:
+            with self.subTest(tag=tag):
+                self._test_colormap(tag=tag)
+
+    def _test_colormap(self, tag):
+
+        kwargs = {'tilesize': (32, 32), 'exclude_tags': [tag]}
+        with ir.path('tests.data', 'issue572.tif') as path:
+            with Tiff2Jp2k(path, self.temp_jp2_filename, **kwargs) as p:
+                p.run()
+
+        j = Jp2k(self.temp_jp2_filename)
+
+        # the image header box shows just a single layer
+        shape = (
+            j.box[2].box[0].height,
+            j.box[2].box[0].width,
+            j.box[2].box[0].num_components,
+        )
+        self.assertEqual(shape, (64, 64, 1))
+
+        # the colr box says sRGB, not greyscale
+        self.assertEqual(j.box[2].box[1].colorspace, SRGB)
+
+        # a pclr box exists
+        self.assertEqual(j.box[2].box[2].box_id, 'pclr')
+
+        # a component mapping box exists
+        self.assertEqual(j.box[2].box[3].box_id, 'cmap')
+        self.assertEqual(j.box[2].box[3].component_index, (0, 0, 0))
+        self.assertEqual(j.box[2].box[3].mapping_type, (1, 1, 1))
+        self.assertEqual(j.box[2].box[3].palette_index, (0, 1, 2))
+
+        # The last box should be the exif uuid.  It may or may not have the
+        # colormap tag depending on what was specified.
+        exif_box = j.box[-1]
+        actual = exif_box.uuid
+        expected = UUID(bytes=b'JpgTiffExif->JP2')
+        self.assertEqual(actual, expected)
+        if tag == 'ColorMap':
+            self.assertNotIn('ColorMap', exif_box.data)
+        else:
+            self.assertIn('ColorMap', exif_box.data)
+
+    def test_excluded_tags_is_none(self):
+        """
+        Scenario:  Convert TIFF to JP2, but provide None for the exclude_tags
+        argument.
+
+        Expected Result:  The UUIDbox has StripOffsets, StripByteCounts, and
+        ICCProfile.
+        """
+        with ir.path('tests.data', 'basn6a08.tif') as path:
+            with Tiff2Jp2k(
+                path, self.temp_jp2_filename, exclude_tags=None
+            ) as p:
+                p.run()
+
+        j = Jp2k(self.temp_jp2_filename)
+
+        # last box is exif
+        tags = j.box[-1].data
+        self.assertIn('StripByteCounts', tags)
+        self.assertIn('StripOffsets', tags)
+        self.assertIn('ICCProfile', tags)
+
+    def test_geotiff(self):
+        """
+        SCENARIO:  Convert a one-component GEOTIFF file to JP2
+
+        EXPECTED RESULT:  there is a geotiff UUID.  The JP2 file has only one
+        component.
+        """
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore')
+            with ir.path('tests.data', 'albers27.tif') as path:
+                with Tiff2Jp2k(path, self.temp_jp2_filename) as j:
+                    j.run()
+
+        j = Jp2k(self.temp_jp2_filename)
+
+        self.assertEqual(j.box[-1].box_id, 'uuid')
+        self.assertEqual(
+            j.box[-1].uuid, UUID('b14bf8bd-083d-4b43-a5ae-8cd7d5a6ce03')
+        )
+        self.assertEqual(j.box[2].box[0].num_components, 1)
+
+    def test_separated_configuration(self):
+        """
+        SCENARIO:  The TIFF has a planar configuration of SEPARATE which is
+        not supported if a tilesize is specified.
+
+        EXPECTED RESULT:  RuntimeError
+        """
+        with self.assertRaises(RuntimeError):
+            with ir.path(
+                'tests.data', 'flower-separated-planar-08.tif'
+            ) as path:
+                with Tiff2Jp2k(
+                    path, self.temp_jp2_filename, tilesize=(64, 64)
+                ) as j:
+                    j.run()
+
+    def test_bad_tile_size(self):
+        """
+        SCENARIO:  Specify a tilesize that exceeds the image size.  This will
+        cause a segfault unless caught.
+
+        EXPECTED RESULT:  RuntimeError
+        """
+        with self.assertRaises(RuntimeError):
+            with ir.path('tests.data', 'albers27-8.tif') as path:
+                with Tiff2Jp2k(
+                    path, self.temp_jp2_filename, tilesize=(256, 256),
+                ) as j:
+                    j.run()
+
+    def test_minisblack_spp1_bigtiff(self):
+        """
+        SCENARIO:  Convert minisblack BigTIFF file to JP2.  The TIFF has tag
+        XResolution.
+
+        EXPECTED RESULT:  no errors.
+        """
+        with ir.path('tests.data', 'albers27-8.tif') as path:
+            with Tiff2Jp2k(path, self.temp_jp2_filename) as j:
+                j.run()
+
+    def test_tiff_file_not_there(self):
+        """
+        Scenario:  The input TIFF file is not present.
+
+        Expected Result:  FileNotFoundError
+        """
+
+        with self.assertRaises(FileNotFoundError):
+            Tiff2Jp2k(
+                self.test_dir_path / 'not_there.tif', self.temp_jp2_filename
+            )



View it on GitLab: https://salsa.debian.org/debian-gis-team/glymur/-/compare/40f92d009c82688b4e705778592cc4c1d8896242...9b019afd6b95d891757fc43422a7bb5af6ba5c29

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/glymur/-/compare/40f92d009c82688b4e705778592cc4c1d8896242...9b019afd6b95d891757fc43422a7bb5af6ba5c29
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20221029/2c11ef8d/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list