[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