From 6a59e38aede9a3246d00c206d6d8d2ad6fafba93 Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 21 Nov 2014 17:56:52 -0500 Subject: [PATCH 1/2] reading metadata still works if library not installed, closes #304 --- glymur/jp2k.py | 2 +- glymur/lib/config.py | 2 +- glymur/lib/test/test_printing.py | 2 +- glymur/test/fixtures.py | 8 ++++++++ glymur/test/test_callbacks.py | 2 ++ glymur/test/test_jp2box.py | 2 +- glymur/test/test_jp2k.py | 15 ++++++++++++++- glymur/test/test_opj_suite.py | 12 +++++++----- 8 files changed, 35 insertions(+), 10 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 74834a9..097bb07 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -484,7 +484,7 @@ class Jp2k(Jp2kBox): This method can only be used to create JPEG 2000 images that can fit in memory. """ - if re.match("1.[0-4]", version.openjpeg_version) is not None: + if re.match("0|1.[0-4]", version.openjpeg_version) is not None: raise RuntimeError("You must have at least version 1.5 of OpenJPEG " "in order to write images.") diff --git a/glymur/lib/config.py b/glymur/lib/config.py index 4b92b9d..3aca5aa 100644 --- a/glymur/lib/config.py +++ b/glymur/lib/config.py @@ -144,7 +144,7 @@ def glymur_config(): lst.append(load_openjpeg_library(libname)) if all(handle is None for handle in lst): msg = "Neither the openjp2 nor the openjpeg library could be loaded. " - raise IOError(msg) + warnings.warn(msg) return tuple(lst) def get_configdir(): diff --git a/glymur/lib/test/test_printing.py b/glymur/lib/test/test_printing.py index 3d0e2b6..fb78676 100644 --- a/glymur/lib/test/test_printing.py +++ b/glymur/lib/test/test_printing.py @@ -17,7 +17,7 @@ import glymur from . import fixtures @unittest.skipIf(sys.hexversion < 0x03000000, "do not care about 2.7 here") -@unittest.skipIf(re.match('1|2.0', glymur.version.openjpeg_version), +@unittest.skipIf(re.match('0|1|2.0', glymur.version.openjpeg_version), "Requires openjpeg 2.1.0 or higher") class TestPrintingOpenjp2(unittest.TestCase): """Tests for verifying how printing works on openjp2 library structures.""" diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 481da2d..113a47b 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -13,6 +13,14 @@ import six import glymur +# If openjpeg is not installed, many tests cannot be run. +if glymur.version.openjpeg_version == '0.0.0': + OPENJPEG_NOT_AVAILABLE = True + OPENJPEG_NOT_AVAILABLE_MSG = 'OpenJPEG library not installed' +else: + OPENJPEG_NOT_AVAILABLE = False + OPENJPEG_NOT_AVAILABLE_MSG = None + # Some versions of "six" on python3 cause problems when verifying warnings. # Only use when the version is 1.7 or higher. # And moreover, we only test using the 3.x infrastructure, never on 2.x. diff --git a/glymur/test/test_callbacks.py b/glymur/test/test_callbacks.py index c29f816..849d987 100644 --- a/glymur/test/test_callbacks.py +++ b/glymur/test/test_callbacks.py @@ -65,6 +65,8 @@ class TestCallbacks(unittest.TestCase): expected = '[INFO] tile number 1 / 1' self.assertEqual(actual, expected) + @unittest.skipIf(glymur.version.openjpeg_version[0] == '0', + "Missing openjpeg/openjp2 library.") def test_info_callbacks_on_read(self): """stdio output when info callback handler is enabled""" diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 9d1f9cf..28ae4fe 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -114,7 +114,7 @@ class TestDataEntryURL(unittest.TestCase): self.assertEqual(url + chr(0), read_url) -@unittest.skipIf(re.match(r'''(1|2.0.0)''', +@unittest.skipIf(re.match(r'''0|1|2.0.0''', glymur.version.openjpeg_version) is not None, "Not supported until 2.1") @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index b903dda..edb19a1 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -36,6 +36,7 @@ from glymur.version import openjpeg_version from .fixtures import HAS_PYTHON_XMP_TOOLKIT from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG +from .fixtures import OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG if HAS_PYTHON_XMP_TOOLKIT: import libxmp @@ -76,6 +77,7 @@ class SliceProtocolBase(unittest.TestCase): self.j2k_data_r1 = self.j2k[::2, ::2] self.j2k_data_r5 = self.j2k[::32, ::32] +@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(re.match("1.5|2", glymur.version.openjpeg_version) is None, "Must have openjpeg 1.5 or higher to run") @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) @@ -143,6 +145,7 @@ class TestSliceProtocolBaseWrite(SliceProtocolBase): j[:25, :45, :] = self.j2k_data[:25, :25, :] +@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) class TestSliceProtocolRead(SliceProtocolBase): def test_resolution_strides_cannot_differ(self): @@ -251,6 +254,7 @@ class TestJp2k(unittest.TestCase): def tearDown(self): pass + @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_warn_if_using_read_method(self): """Should warn if deprecated read method is called""" @@ -313,6 +317,7 @@ class TestJp2k(unittest.TestCase): actdata = j2[:] self.assertTrue(fixtures.mse(actdata[0], expdata[0]) < 0.38) + @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(re.match('1.[0-4]', openjpeg_version) is not None, "Not supported with OpenJPEG {0}".format(openjpeg_version)) @unittest.skipIf(re.match('1.5.(1|2)', openjpeg_version) is not None, @@ -345,6 +350,7 @@ class TestJp2k(unittest.TestCase): self.assertEqual(newjp2.filename, self.j2kfile) self.assertEqual(len(newjp2.box), 0) + @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_rlevel_max_backwards_compatibility(self): """ @@ -367,6 +373,7 @@ class TestJp2k(unittest.TestCase): np.testing.assert_array_equal(thumbnail1, thumbnail2) self.assertEqual(thumbnail1.shape, (25, 15, 3)) + @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) def test_rlevel_too_high(self): """Should error out appropriately if reduce level too high""" j = Jp2k(self.jp2file) @@ -518,12 +525,14 @@ class TestJp2k(unittest.TestCase): self.assertEqual(new_jp2.box[j].length, baseline_jp2.box[j].length) + @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) def test_basic_jp2(self): """Just a very basic test that reading a JP2 file does not error out. """ j2k = Jp2k(self.jp2file) j2k[::2, ::2] + @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) def test_basic_j2k(self): """This test is only useful when openjp2 is not available and OPJ_DATA_ROOT is not set. We need at least one @@ -648,6 +657,7 @@ class TestJp2k(unittest.TestCase): creator_tool = xmp.get_property(libxmp.consts.XMP_NS_XMP, 'CreatorTool') self.assertEqual(creator_tool, 'Google') + @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(re.match(r'''(1|2.0.0)''', glymur.version.openjpeg_version) is not None, "Not supported until 2.0.1") @@ -664,6 +674,7 @@ class TestJp2k(unittest.TestCase): with self.assertRaises(RuntimeError): glymur.Jp2k(self.jp2file).read_bands() +@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(re.match('1.[0-4]', openjpeg_version) is not None, "Not supported with OpenJPEG {0}".format(openjpeg_version)) @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) @@ -869,6 +880,7 @@ class TestJp2k_1_x(unittest.TestCase): @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) +@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) class Test_2p0_official(unittest.TestCase): """Tests specific to v2.0.0""" @@ -962,6 +974,7 @@ class TestJp2k_2_0(unittest.TestCase): self.assertEqual(jasoc.box[3].box[1].box_id, 'xml ') +@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(re.match(r'''(1|2.0.0)''', glymur.version.openjpeg_version) is not None, "Not to be run until unless 2.0.1 or higher is present") @@ -1135,7 +1148,7 @@ class TestJp2kOpjDataRoot(unittest.TestCase): actdata = j[:] self.assertTrue(fixtures.mse(actdata, expdata) < 250) - + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_no_cxform_pclr_jp2(self): """Indices for pclr jpxfile if no color transform""" diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index cf44eaa..028734d 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -41,10 +41,12 @@ from glymur.jp2box import FileTypeBox, ImageHeaderBox, ColourSpecificationBox from .fixtures import ( OPJ_DATA_ROOT, MetadataBase, WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG, - mse, peak_tolerance, read_pgx, opj_data_file + mse, peak_tolerance, read_pgx, opj_data_file, + OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG ) +@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") class TestSuite(unittest.TestCase): @@ -482,7 +484,7 @@ class TestSuiteWarns(MetadataBase): @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") -@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1, +@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] != 2, "Feature not supported in glymur until openjpeg 2.0") class TestSuiteBands(unittest.TestCase): """ @@ -577,7 +579,7 @@ class TestSuiteBands(unittest.TestCase): @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") -@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1, +@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2, "Tests not passing until 2.0") class TestSuite2point0(unittest.TestCase): """Runs tests introduced in version 2.0 or that pass only in 2.0""" @@ -641,7 +643,7 @@ class TestSuite2point0(unittest.TestCase): @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") -@unittest.skipIf(re.match(r'''(1|2.0.0)''', +@unittest.skipIf(re.match(r'''0|1|2.0.0''', glymur.version.openjpeg_version) is not None, "Only supported in 2.0.1 or higher") class TestSuite2point1(unittest.TestCase): @@ -798,7 +800,7 @@ class TestSuite2point1(unittest.TestCase): @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") -@unittest.skipIf(re.match(r'''(1|2.0.0)''', +@unittest.skipIf(re.match(r'''0|1|2.0.0''', glymur.version.openjpeg_version) is not None, "Only supported in 2.0.1 or higher") class TestReadArea(unittest.TestCase): From c39661b586626d9a819fbd7e25f6d89bd53eb48a Mon Sep 17 00:00:00 2001 From: jevans Date: Fri, 21 Nov 2014 21:53:39 -0500 Subject: [PATCH 2/2] pep8 cleanup --- glymur/_uuid_io.py | 9 +-- glymur/codestream.py | 13 ++- glymur/command_line.py | 28 ++++--- glymur/core.py | 24 +++--- glymur/jp2box.py | 119 ++++++++++++++-------------- glymur/jp2k.py | 152 +++++++++++++++++++---------------- glymur/lib/openjp2.py | 14 +--- glymur/lib/openjpeg.py | 176 +++++++++++++++++++---------------------- 8 files changed, 267 insertions(+), 268 deletions(-) diff --git a/glymur/_uuid_io.py b/glymur/_uuid_io.py index bf9960a..7bcf2cb 100644 --- a/glymur/_uuid_io.py +++ b/glymur/_uuid_io.py @@ -11,6 +11,7 @@ import warnings import lxml.etree as ET + def xml(raw_data): """ XMP data to be parsed as XML. @@ -23,6 +24,7 @@ def xml(raw_data): return ET.ElementTree(elt) + def tiff_header(read_buffer): """ Interpret the uuid raw data as a tiff header. @@ -37,8 +39,8 @@ def tiff_header(read_buffer): # big endian endian = '>' else: - msg = "The byte order indication in the TIFF header ({0}) is invalid. " - msg += "It should be either {1} or {2}." + msg = "The byte order indication in the TIFF header ({0}) is " + msg += "invalid. It should be either {1} or {2}." msg = msg.format(read_buffer[6:8], bytes([73, 73]), bytes([77, 77])) raise IOError(msg) @@ -503,6 +505,3 @@ class _ExifInteroperabilityIfd(_Ifd): def __init__(self, endian, read_buffer, offset): _Ifd.__init__(self, endian, read_buffer, offset) self.post_process(self.tagnum2name) - - - diff --git a/glymur/codestream.py b/glymur/codestream.py index c8ab00f..aa12bd7 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -24,11 +24,10 @@ import warnings import numpy as np -from .core import ( - LRCP, RLCP, RPCL, PCRL, CPRL, - WAVELET_XFORM_9X7_IRREVERSIBLE, WAVELET_XFORM_5X3_REVERSIBLE, - _Keydefaultdict -) +from .core import (LRCP, RLCP, RPCL, PCRL, CPRL, + WAVELET_XFORM_9X7_IRREVERSIBLE, + WAVELET_XFORM_5X3_REVERSIBLE, + _Keydefaultdict) from .lib import openjp2 as opj2 _factory = lambda x: '{0} (invalid)'.format(x) @@ -57,7 +56,7 @@ _CAPABILITIES_DISPLAY = _Keydefaultdict(_factory, _PROFILE_0: '0', _PROFILE_1: '1', _PROFILE_3: 'Cinema 2K', - _PROFILE_4: 'Cinema 4K'} ) + _PROFILE_4: 'Cinema 4K'}) # Need a catch-all list of valid markers. # See table A-1 in ISO/IEC FCD15444-1. @@ -703,7 +702,6 @@ class Codestream(object): msg = "Invalid number of tiles ({0}).".format(numtiles) warnings.warn(msg) - kwargs = {'rsiz': rsiz, 'xysiz': xysiz, 'xyosiz': xyosiz, @@ -1614,6 +1612,7 @@ class SOCsegment(Segment): msg = "glymur.codestream.SOCsegment()" return msg + class SODsegment(Segment): """Container for Start of Data (SOD) segment information. diff --git a/glymur/command_line.py b/glymur/command_line.py index bf96293..5f0d357 100644 --- a/glymur/command_line.py +++ b/glymur/command_line.py @@ -8,28 +8,29 @@ import warnings from . import Jp2k, set_printoptions, lib + def main(): """ Entry point for console script jp2dump. """ - description='Print JPEG2000 metadata.' + description = 'Print JPEG2000 metadata.' parser = argparse.ArgumentParser(description=description) parser.add_argument('-x', '--noxml', - help='Suppress XML.', - action='store_true') + help='Suppress XML.', + action='store_true') parser.add_argument('-s', '--short', - help='Only print box id, offset, and length.', - action='store_true') + help='Only print box id, offset, and length.', + action='store_true') chelp = 'Level of codestream information. 0 suppressed all details, ' chelp += '1 prints headers, 2 prints the full codestream' parser.add_argument('-c', '--codestream', - help=chelp, - nargs=1, - type=int, - default=[0]) + help=chelp, + nargs=1, + type=int, + default=[0]) parser.add_argument('filename') @@ -38,7 +39,7 @@ def main(): set_printoptions(xml=False) if args.short: set_printoptions(short=True) - + codestream_level = args.codestream[0] if codestream_level not in [0, 1, 2]: raise ValueError("Invalid level of codestream information specified.") @@ -50,15 +51,16 @@ def main(): print_full_codestream = False else: print_full_codestream = True - + filename = args.filename - + with warnings.catch_warnings(record=True) as wctx: # JP2 metadata can be extensive, so don't print any warnings until we # are done with the metadata. jp2 = Jp2k(filename) - if jp2._codec_format == lib.openjp2.CODEC_J2K and codestream_level == 0: + if (((jp2._codec_format == lib.openjp2.CODEC_J2K) and + (codestream_level == 0))): print('File: {0}'.format(os.path.basename(filename))) elif print_full_codestream: print(jp2.get_codestream(header_only=False)) diff --git a/glymur/core.py b/glymur/core.py index 4d9a3af..3327253 100644 --- a/glymur/core.py +++ b/glymur/core.py @@ -4,6 +4,7 @@ import collections import copy import lxml.etree as ET + class _Keydefaultdict(collections.defaultdict): """Unlisted keys help form their own error message. @@ -121,12 +122,12 @@ ROMM_RGB = 21 _factory = lambda x: '{0} (unrecognized)'.format(x) _COLORSPACE_MAP_DISPLAY = _Keydefaultdict(_factory, - { CMYK: 'CMYK', - SRGB: 'sRGB', - GREYSCALE: 'greyscale', - YCC: 'YCC', - E_SRGB: 'e-sRGB', - ROMM_RGB: 'ROMM-RGB'} ) + {CMYK: 'CMYK', + SRGB: 'sRGB', + GREYSCALE: 'greyscale', + YCC: 'YCC', + E_SRGB: 'e-sRGB', + ROMM_RGB: 'ROMM-RGB'}) # enumerated color channel types COLOR = 0 @@ -134,11 +135,11 @@ OPACITY = 1 PRE_MULTIPLIED_OPACITY = 2 _UNSPECIFIED = 65535 _factory = lambda x: '{0} (invalid)'.format(x) -_COLOR_TYPE_MAP_DISPLAY = _Keydefaultdict(_factory, - { COLOR: 'color', - OPACITY: 'opacity', - PRE_MULTIPLIED_OPACITY: 'pre-multiplied opacity', - _UNSPECIFIED: 'unspecified'}) +_dict = {COLOR: 'color', + OPACITY: 'opacity', + PRE_MULTIPLIED_OPACITY: 'pre-multiplied opacity', + _UNSPECIFIED: 'unspecified'} +_COLOR_TYPE_MAP_DISPLAY = _Keydefaultdict(_factory, _dict) # color channel definitions. RED = 1 @@ -153,4 +154,3 @@ _COLORSPACE = {SRGB: {"R": 1, "G": 2, "B": 3}, YCC: {"Y": 1, "Cb": 2, "Cr": 3}, E_SRGB: {"R": 1, "G": 2, "B": 3}, ROMM_RGB: {"R": 1, "G": 2, "B": 3}} - diff --git a/glymur/jp2box.py b/glymur/jp2box.py index a2148ae..39e1f6a 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -29,13 +29,11 @@ import lxml.etree as ET import numpy as np from .codestream import Codestream -from .core import ( - _COLORSPACE_MAP_DISPLAY, _COLOR_TYPE_MAP_DISPLAY, - SRGB, GREYSCALE, YCC, - ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE, - ANY_ICC_PROFILE, VENDOR_COLOR_METHOD, - _Keydefaultdict -) +from .core import (_COLORSPACE_MAP_DISPLAY, _COLOR_TYPE_MAP_DISPLAY, + SRGB, GREYSCALE, YCC, + ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE, + ANY_ICC_PROFILE, VENDOR_COLOR_METHOD, + _Keydefaultdict) from . import _uuid_io @@ -52,6 +50,7 @@ _APPROX_DISPLAY = _Keydefaultdict(_factory, 3: 'approximates correct colorspace definition, reasonable quality', 4: 'approximates correct colorspace definition, poor quality'}) + class Jp2kBox(object): """Superclass for JPEG 2000 boxes. @@ -109,7 +108,6 @@ class Jp2kBox(object): msg += '\n' + self._indent(boxstr) return msg - def _indent(self, textstr, indent_level=4): """ Indent a string. @@ -135,7 +133,6 @@ class Jp2kBox(object): lst = [(' ' * indent_level + x) for x in textstr.split('\n')] return '\n'.join(lst) - def _write_superbox(self, fptr, box_id): """Write a superbox. @@ -191,13 +188,14 @@ class Jp2kBox(object): try: box = parser(fptr, start, num_bytes) except ValueError as err: - msg = "Encountered an unrecoverable ValueError while parsing a {0} " - msg += "box at byte offset {1}. The original error message was " - msg += "\"{2}\"" + msg = "Encountered an unrecoverable ValueError while parsing a " + msg += "{0} box at byte offset {1}. The original error message " + msg += "was \"{2}\"" msg = msg.format(_BOX_WITH_ID[box_id].longname, start, str(err)) warnings.warn(msg, UserWarning) box = UnknownBox(box_id.decode('utf-8'), - length=num_bytes, offset=start, longname='Unknown') + length=num_bytes, + offset=start, longname='Unknown') return box @@ -299,6 +297,7 @@ class ColourSpecificationBox(Jp2kBox): """ longname = 'Colour Specification' box_id = 'colr' + def __init__(self, method=ENUMERATED_COLORSPACE, precedence=0, approximation=0, colorspace=None, icc_profile=None, length=0, offset=-1): @@ -337,16 +336,16 @@ class ColourSpecificationBox(Jp2kBox): if self.icc_profile is None: if self.colorspace not in [SRGB, GREYSCALE, YCC]: - msg = "Colorspace should correspond to one of SRGB, GREYSCALE, " - msg += "or YCC." + msg = "Colorspace should correspond to one of SRGB, " + msg += "GREYSCALE, or YCC." self._dispatch_validation_error(msg, writing=True) self._validate(writing=True) - def __repr__(self): msg = "glymur.jp2box.ColourSpecificationBox(" - msg += "method={0}, precedence={1}, approximation={2}, colorspace={3}, " + msg += "method={0}, precedence={1}, approximation={2}, " + msg += "colorspace={3}, " msg += "icc_profile={4})" msg = msg.format(self.method, self.precedence, @@ -357,7 +356,7 @@ class ColourSpecificationBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg msg += '\n Method: {0}'.format(_METHOD_DISPLAY[self.method]) @@ -619,10 +618,9 @@ class ChannelDefinitionBox(Jp2kBox): msg += " 65535 - unspecified" self._dispatch_validation_error(msg, writing=writing) - def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg for j in range(len(self.association)): @@ -842,7 +840,7 @@ class CompositingLayerHeaderBox(Jp2kBox): List of boxes contained in this superbox. """ box_id = 'jplh' - longname='Compositing Layer Header' + longname = 'Compositing Layer Header' def __init__(self, box=None, length=0, offset=-1): Jp2kBox.__init__(self) @@ -931,7 +929,7 @@ class ComponentMappingBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg for k in range(len(self.component_index)): @@ -1027,7 +1025,9 @@ class ContiguousCodestreamBox(Jp2kBox): if self._filename is not None: with open(self._filename, 'rb') as fptr: fptr.seek(self.main_header_offset) - main_header = Codestream(fptr, self._length, header_only=True) + main_header = Codestream(fptr, + self._length, + header_only=True) self._main_header = main_header return self._main_header @@ -1037,9 +1037,9 @@ class ContiguousCodestreamBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg - if _printoptions['codestream'] == False: + if _printoptions['codestream'] is False: return msg msg += '\n Main header:' @@ -1118,7 +1118,8 @@ class DataReferenceBox(Jp2kBox): """Verify that the box obeys the specifications for writing. """ if len(self.DR) == 0: - msg = "A data reference box cannot be empty when written to a file." + msg = "A data reference box cannot be empty when written to a " + msg += "file." self._dispatch_validation_error(msg, writing=True) self._validate(writing=True) @@ -1145,7 +1146,7 @@ class DataReferenceBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg for box in self.DR: @@ -1248,7 +1249,7 @@ class FileTypeBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg lst = [msg, @@ -1311,7 +1312,7 @@ class FileTypeBox(Jp2kBox): brand = brand.decode('utf-8') # Extract the compatibility list. Each entry has 4 bytes. - num_entries = int((length - 16)/ 4) + num_entries = int((length - 16) / 4) compatibility_list = [] for j in range(int(num_entries)): entry, = struct.unpack_from('>4s', read_buffer, 8 + j * 4) @@ -1374,7 +1375,7 @@ class FragmentListBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg for j in range(len(self.fragment_offset)): @@ -1458,7 +1459,10 @@ class FragmentTableBox(Jp2kBox): def __repr__(self): msg = "glymur.jp2box.FragmentTableBox(box={0})" - msg = msg.format(None) if (len(self.box) == 0) else msg.format(self.box) + if len(self.box) == 0: + msg = msg.format(None) + else: + msg = msg.format(self.box) return msg def __str__(self): @@ -1505,7 +1509,6 @@ class FragmentTableBox(Jp2kBox): self._write_superbox(fptr, b'ftbl') - class FreeBox(Jp2kBox): """Container for JPX free box information. @@ -1534,7 +1537,7 @@ class FreeBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg return msg @@ -1630,7 +1633,7 @@ class ImageHeaderBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg msg = "{0}" @@ -1861,7 +1864,7 @@ class JPEG2000SignatureBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg msg += '\n Signature: {0:02x}{1:02x}{2:02x}{3:02x}' @@ -1950,7 +1953,7 @@ class PaletteBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg msg += '\n Size: ({0} x {1})'.format(*self.palette.shape) @@ -2203,7 +2206,7 @@ class ReaderRequirementsBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg msg += '\n Fully Understands Aspect Mask: 0x{0:x}'.format(self.fuam) @@ -2262,7 +2265,8 @@ class ReaderRequirementsBox(Jp2kBox): standard_flag, standard_mask = data nflags = len(standard_flag) - vendor_offset = 1 + 2 * mask_length + 2 + (2 + mask_length) * nflags + vendor_offset = 1 + 2 * mask_length + 2 \ + + (2 + mask_length) * nflags data = _parse_vendor_features(read_buffer[vendor_offset:], mask_length) vendor_feature, vendor_mask = data @@ -2348,14 +2352,11 @@ def _parse_standard_flag(read_buffer, mask_length): # from the buffer read from file. mask_format = {1: 'B', 2: 'H', 4: 'I'}[mask_length] - #read_buffer = fptr.read(2) num_standard_flags, = struct.unpack_from('>H', read_buffer, offset=0) # Read in standard flags and standard masks. Each standard flag should # be two bytes, but the standard mask flag is as long as specified by # the mask length. - #read_buffer = fptr.read(num_standard_flags * (2 + mask_length)) - fmt = '>' + ('H' + mask_format) * num_standard_flags data = struct.unpack_from(fmt, read_buffer, offset=2) @@ -2386,7 +2387,6 @@ def _parse_vendor_features(read_buffer, mask_length): # Each vendor feature consists of a 16-byte UUID plus a mask whose # length is specified by, you guessed it, "mask_length". entry_length = 16 + mask_length - #read_buffer = fptr.read(num_vendor_features * entry_length) vendor_feature = [] vendor_mask = [] for j in range(num_vendor_features): @@ -2494,7 +2494,7 @@ class CaptureResolutionBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg msg += '\n VCR: {0}'.format(self.vertical_resolution) @@ -2560,7 +2560,7 @@ class DisplayResolutionBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg msg += '\n VDR: {0}'.format(self.vertical_resolution) @@ -2620,7 +2620,7 @@ class LabelBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg msg += '\n Label: {0}'.format(self.label) @@ -2688,7 +2688,7 @@ class NumberListBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg for j, association in enumerate(self.associations): @@ -2738,7 +2738,8 @@ class NumberListBox(Jp2kBox): def write(self, fptr): """Write a NumberList box to file. """ - fptr.write(struct.pack('>I4s', len(self.associations) * 4 + 8, b'nlst')) + fptr.write(struct.pack('>I4s', + len(self.associations) * 4 + 8, b'nlst')) fmt = '>' + 'I' * len(self.associations) write_buffer = struct.pack(fmt, *self.associations) @@ -2790,9 +2791,9 @@ class XMLBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg - if _printoptions['xml'] == False: + if _printoptions['xml'] is False: return msg msg += '\n' @@ -2911,7 +2912,7 @@ class UUIDListBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg for j, uuid_item in enumerate(self.ulst): @@ -2942,7 +2943,7 @@ class UUIDListBox(Jp2kBox): ulst = [] for j in range(num_uuids): - uuid_buffer = read_buffer[2 + j * 16 : 2 + (j + 1) * 16] + uuid_buffer = read_buffer[2 + j * 16:2 + (j + 1) * 16] ulst.append(uuid.UUID(bytes=uuid_buffer)) return cls(ulst, length=length, offset=offset) @@ -3056,7 +3057,6 @@ class DataEntryURLBox(Jp2kBox): fptr.write(write_buffer) fptr.write(url) - def __repr__(self): msg = "glymur.jp2box.DataEntryURLBox({0}, {1}, '{2}')" msg = msg.format(self.version, self.flag, self.url) @@ -3064,7 +3064,7 @@ class DataEntryURLBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg msg += '\n ' @@ -3216,7 +3216,7 @@ class UUIDBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg msg = '{0}\n UUID: {1}'.format(msg, self.uuid) @@ -3227,7 +3227,7 @@ class UUIDBox(Jp2kBox): else: msg += ' (unknown)' - if (((_printoptions['xml'] == False) and + if (((_printoptions['xml'] is False) and (self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac')))): # If it's an XMP UUID, don't print the XML contents. return msg @@ -3312,6 +3312,7 @@ _BOX_WITH_ID = { _parseoptions = {'codestream': True} + def set_parseoptions(codestream=True): """Set parsing options. @@ -3336,6 +3337,7 @@ def set_parseoptions(codestream=True): """ _parseoptions['codestream'] = codestream + def get_parseoptions(): """Return the current parsing options. @@ -3356,6 +3358,7 @@ def get_parseoptions(): _printoptions = {'short': False, 'xml': True, 'codestream': True} + def set_printoptions(**kwargs): """Set printing options. @@ -3365,7 +3368,8 @@ def set_printoptions(**kwargs): ---------- short : bool, optional When True, only the box ID, offset, and length are displayed. Useful - for displaying only the basic structure or skeleton of a JPEG 2000 file. + for displaying only the basic structure or skeleton of a JPEG 2000 + file. xml : bool, optional When False, printing of the XML contents of any XML boxes or UUID XMP boxes is suppressed. @@ -3388,6 +3392,7 @@ def set_printoptions(**kwargs): raise TypeError('"{0}" not a valid keyword parameter.'.format(key)) _printoptions[key] = value + def get_printoptions(): """Return the current print options. @@ -3407,5 +3412,3 @@ def get_printoptions(): set_printoptions """ return _printoptions - - diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 097bb07..b1e5c60 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -31,12 +31,12 @@ import numpy as np from .codestream import Codestream from . import core, version -from .jp2box import ( - Jp2kBox, JPEG2000SignatureBox, FileTypeBox, JP2HeaderBox, - ColourSpecificationBox, ContiguousCodestreamBox, ImageHeaderBox -) +from .jp2box import (Jp2kBox, JPEG2000SignatureBox, FileTypeBox, + JP2HeaderBox, ColourSpecificationBox, + ContiguousCodestreamBox, ImageHeaderBox) from .lib import openjpeg as opj, openjp2 as opj2, c as libc + class Jp2k(Jp2kBox): """JPEG 2000 file. @@ -138,7 +138,6 @@ class Jp2k(Jp2kBox): not (X, Y) verbose : bool, optional print informational messages produced by the OpenJPEG library - """ Jp2kBox.__init__(self) self.filename = filename @@ -176,8 +175,8 @@ class Jp2k(Jp2kBox): @layer.setter def layer(self, layer): if version.openjpeg_version_tuple[0] < 2: - msg = "Layer property not supported unless the version of OpenJPEG " - msg += "is 2.0 or higher." + msg = "Layer property not supported unless the version of " + msg += "OpenJPEG is 2.0 or higher." raise RuntimeError(msg) self._layer = layer @@ -361,8 +360,8 @@ class Jp2k(Jp2kBox): kwargs : dictionary non-image keyword inputs provided to write method """ - if (('cinema2k' in kwargs or 'cinema4k' in kwargs) and - (len(set(kwargs)) > 1)): + if ((('cinema2k' in kwargs or 'cinema4k' in kwargs) and + (len(set(kwargs)) > 1))): msg = "Cannot specify cinema2k/cinema4k along with other options." raise IOError(msg) @@ -485,8 +484,9 @@ class Jp2k(Jp2kBox): in memory. """ if re.match("0|1.[0-4]", version.openjpeg_version) is not None: - raise RuntimeError("You must have at least version 1.5 of OpenJPEG " - "in order to write images.") + msg = "You must have at least version 1.5 of OpenJPEG " + msg += "in order to write images." + raise RuntimeError(msg) self._determine_colorspace(**kwargs) self._populate_cparams(img_array, **kwargs) @@ -518,9 +518,11 @@ class Jp2k(Jp2kBox): image.contents.x0 = self._cparams.image_offset_x0 image.contents.y0 = self._cparams.image_offset_y0 image.contents.x1 = image.contents.x0 \ - + (numcols - 1) * self._cparams.subsampling_dx + 1 + + (numcols - 1) * self._cparams.subsampling_dx \ + + 1 image.contents.y1 = image.contents.y0 \ - + (numrows - 1) * self._cparams.subsampling_dy + 1 + + (numrows - 1) * self._cparams.subsampling_dy \ + + 1 # Stage the image data to the openjpeg data structure. for k in range(0, numlayers): @@ -633,7 +635,7 @@ class Jp2k(Jp2kBox): def _determine_colorspace(self, colorspace=None, **kwargs): """Determine the colorspace from the supplied inputs. - + Parameters ---------- colorspace : str, optional @@ -658,7 +660,7 @@ class Jp2k(Jp2kBox): elif colorspace.lower() == 'rgb' and self.shape[2] < 3: msg = 'RGB colorspace requires at least 3 components.' raise IOError(msg) - + # Turn the colorspace from a string to the enumerated value that # the library expects. COLORSPACE_MAP = {'rgb': opj2.CLRSPC_SRGB, @@ -667,8 +669,7 @@ class Jp2k(Jp2kBox): 'ycc': opj2.CLRSPC_YCC} self._colorspace = COLORSPACE_MAP[colorspace.lower()] - - + def _write_openjp2(self, img_array, verbose=False): """ Write JPEG 2000 file using OpenJPEG 2.x interface. @@ -734,7 +735,8 @@ class Jp2k(Jp2kBox): if not ((box.box_id == 'xml ') or (box.box_id == 'uuid' and box.uuid == UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'))): - msg = "Only XML boxes and XMP UUID boxes can currently be appended." + msg = "Only XML boxes and XMP UUID boxes can currently be " + msg += "appended." raise IOError(msg) # Check the last box. If the length field is zero, then rewrite @@ -891,9 +893,9 @@ class Jp2k(Jp2kBox): Slicing protocol. """ if ((isinstance(index, slice) and - (index.start == None and - index.stop == None and - index.step == None)) or (index is Ellipsis)): + (index.start is None and + index.stop is None and + index.step is None)) or (index is Ellipsis)): # Case of jp2[:] = data, i.e. write the entire image. # # Should have a slice object where start = stop = step = None @@ -923,12 +925,14 @@ class Jp2k(Jp2kBox): return self._read() if isinstance(pargs, slice): - if pargs.start is None and pargs.stop is None and pargs.step is None: + if (((pargs.start is None) and + (pargs.stop is None) and + (pargs.step is None))): # Case of jp2[:] return self._read() # Corner case of jp2[x] where x is a slice object with non-null - # members. Just augment it with an ellipsis and let the code + # members. Just augment it with an ellipsis and let the code # below handle it. pargs = (pargs, Ellipsis) @@ -971,8 +975,7 @@ class Jp2k(Jp2kBox): # Reduce dimensionality in the scalar dimension. return np.squeeze(data, axis=idx) - - # Assuming pargs is a tuple of slices from now on. + # Assuming pargs is a tuple of slices from now on. rows = pargs[0] cols = pargs[1] if len(pargs) == 2: @@ -989,15 +992,14 @@ class Jp2k(Jp2kBox): # Ok, reduce layer step is the same in both xy directions, so just take # one of them. step = rows_step - + # Check if the step size is a power of 2. if np.abs(np.log2(step) - np.round(np.log2(step))) > 1e-6: msg = "Row and column strides must be powers of 2." raise IndexError(msg) rlevel = np.int(np.round(np.log2(step))) - area = ( - 0 if rows.start is None else rows.start, + area = (0 if rows.start is None else rows.start, 0 if cols.start is None else cols.start, numrows if rows.stop is None else rows.stop, numcols if cols.stop is None else cols.stop @@ -1009,7 +1011,6 @@ class Jp2k(Jp2kBox): # Ok, 3 arguments in pargs. return data[:, :, bands] - def _read(self, **kwargs): """Read a JPEG 2000 image. @@ -1032,34 +1033,34 @@ class Jp2k(Jp2kBox): def read(self, **kwargs): """ """ - #Read a JPEG 2000 image. + # Read a JPEG 2000 image. # - #Parameters - #---------- - #rlevel : int, optional - # Factor by which to rlevel output resolution. Use -1 to get the - # lowest resolution thumbnail. This is the only keyword option - # available to use when the OpenJPEG version is 1.5 or earlier. - #layer : int, optional - # Number of quality layer to decode. - #area : tuple, optional - # Specifies decoding image area, - # (first_row, first_col, last_row, last_col) - #tile : int, optional - # Number of tile to decode. - #verbose : bool, optional - # Print informational messages produced by the OpenJPEG library. + # Parameters + # ---------- + # rlevel : int, optional + # Factor by which to rlevel output resolution. Use -1 to get the + # lowest resolution thumbnail. This is the only keyword option + # available to use when the OpenJPEG version is 1.5 or earlier. + # layer : int, optional + # Number of quality layer to decode. + # area : tuple, optional + # Specifies decoding image area, + # (first_row, first_col, last_row, last_col) + # tile : int, optional + # Number of tile to decode. + # verbose : bool, optional + # Print informational messages produced by the OpenJPEG library. # - #Returns - #------- - #img_array : ndarray - # The image data. + # Returns + # ------- + # img_array : ndarray + # The image data. # - #Raises - #------ - #IOError - # If the image has differing subsample factors. - + # Raises + # ------ + # IOError + # If the image has differing subsample factors. + if 'ignore_pclr_cmap_cdef' in kwargs: self.ignore_pclr_cmap_cdef = kwargs['ignore_pclr_cmap_cdef'] warnings.warn("Use array-style slicing instead.", DeprecationWarning) @@ -1163,7 +1164,8 @@ class Jp2k(Jp2kBox): return data - def _read_openjp2(self, rlevel=0, layer=None, area=None, tile=None, verbose=False): + def _read_openjp2(self, rlevel=0, layer=None, area=None, tile=None, + verbose=False): """Read a JPEG 2000 image using libopenjp2. Parameters @@ -1453,7 +1455,7 @@ class Jp2k(Jp2kBox): def _populate_image_struct(self, image, imgdata): """Populates image struct needed for compression. - + Parameters ---------- image : ImageType(ctypes.Structure) @@ -1461,9 +1463,9 @@ class Jp2k(Jp2kBox): img_array : ndarray Image data to be written to file. """ - + numrows, numcols, num_comps = imgdata.shape - + # set image offset and reference grid image.contents.x0 = self._cparams.image_offset_x0 image.contents.y0 = self._cparams.image_offset_y0 @@ -1471,7 +1473,7 @@ class Jp2k(Jp2kBox): (numcols - 1) * self._cparams.subsampling_dx + 1) image.contents.y1 = (image.contents.y0 + (numrows - 1) * self._cparams.subsampling_dy + 1) - + # Stage the image data to the openjpeg data structure. for k in range(0, num_comps): if re.match("2.0", version.openjpeg_version) is not None: @@ -1485,19 +1487,19 @@ class Jp2k(Jp2kBox): core.OPJ_PROFILE_CINEMA_4K): image.contents.comps[k].prec = 12 image.contents.comps[k].bpp = 12 - + layer = np.ascontiguousarray(imgdata[:, :, k], dtype=np.int32) dest = image.contents.comps[k].data src = layer.ctypes.data ctypes.memmove(dest, src, layer.nbytes) - + return image - + def _populate_comptparms(self, img_array): """Instantiate and populate comptparms structure. - + This structure defines the image components. - + Parameters ---------- img_array : ndarray @@ -1526,8 +1528,8 @@ class Jp2k(Jp2kBox): comptparms[j].sgnd = 0 self._comptparms = comptparms - - + + def _component2dtype(component): """Take an OpenJPEG component structure and determine the numpy datatype. @@ -1573,6 +1575,7 @@ JP2_IDS = ['colr', 'cdef', 'cmap', 'jp2c', 'ftyp', 'ihdr', 'jp2h', 'jP ', 'pclr', 'res ', 'resc', 'resd', 'xml ', 'ulst', 'uinf', 'url ', 'uuid'] + def _validate_jp2_box_sequence(boxes): """Run through series of tests for JP2 box legality. @@ -1588,12 +1591,13 @@ def _validate_jp2_box_sequence(boxes): count = _collect_box_count(boxes) for box_id in count.keys(): if box_id not in JP2_IDS: - msg = "The presence of a '{0}' box requires that the file type " - msg += "brand be set to 'jpx '." + msg = "The presence of a '{0}' box requires that the file " + msg += "type brand be set to 'jpx '." raise IOError(msg.format(box_id)) _validate_jp2_colr(boxes) + def _validate_jp2_colr(boxes): """ Validate JP2 requirements on colour specification boxes. @@ -1605,6 +1609,7 @@ def _validate_jp2_colr(boxes): msg = "A JP2 colr box cannot have a non-zero approximation field." raise IOError(msg) + def _validate_jpx_box_sequence(boxes): """Run through series of tests for JPX box legality.""" _validate_label(boxes) @@ -1613,6 +1618,7 @@ def _validate_jpx_box_sequence(boxes): _validate_singletons(boxes) _validate_top_level(boxes) + def _validate_signature_compatibility(boxes): """Validate the file signature and compatibility status.""" # Check for a bad sequence of boxes. @@ -1700,6 +1706,8 @@ def _validate_channel_definition(jp2h, colr): JP2H_CHILDREN = set(['bpcc', 'cdef', 'cmap', 'ihdr', 'pclr']) + + def _check_jp2h_child_boxes(boxes, parent_box_name): """Certain boxes can only reside in the JP2 header.""" box_ids = set([box.box_id for box in boxes]) @@ -1727,6 +1735,7 @@ def _collect_box_count(boxes): TOP_LEVEL_ONLY_BOXES = set(['dtbl']) + def _check_superbox_for_top_levels(boxes): """Several boxes can only occur at the top level.""" # We are only looking at the boxes contained in a superbox, so if any of @@ -1742,6 +1751,7 @@ def _check_superbox_for_top_levels(boxes): if hasattr(box, 'box'): _check_superbox_for_top_levels(box.box) + def _validate_top_level(boxes): """Several boxes can only occur at the top level.""" # Add the counts in the superboxes. @@ -1761,6 +1771,7 @@ def _validate_top_level(boxes): msg += 'a fragment table box as well.' raise IOError(msg) + def _validate_singletons(boxes): """Several boxes can only occur once.""" count = _collect_box_count(boxes) @@ -1771,6 +1782,7 @@ def _validate_singletons(boxes): JPX_IDS = ['asoc', 'nlst'] + def _validate_jpx_brand(boxes, brand): """ If there is a JPX box then the brand must be 'jpx '. @@ -1785,6 +1797,7 @@ def _validate_jpx_brand(boxes, brand): # Same set of checks on any child boxes. _validate_jpx_brand(box.box, brand) + def _validate_jpx_compatibility(boxes, compatibility_list): """ If there is a JPX box then the compatibility list must also contain 'jpx '. @@ -1800,6 +1813,7 @@ def _validate_jpx_compatibility(boxes, compatibility_list): # Same set of checks on any child boxes. _validate_jpx_compatibility(box.box, compatibility_list) + def _validate_label(boxes): """ Label boxes can only be inside association, codestream headers, or @@ -1816,6 +1830,7 @@ def _validate_label(boxes): # Same set of checks on any child boxes. _validate_label(box.box) + def extract_image_cube(image): """Extract 3D image from openjpeg data structure. """ @@ -1871,7 +1886,6 @@ def extract_image_bands(image): return data - # Setup the default callback handlers. See the callback functions subsection # in the ctypes section of the Python documentation for a solid explanation of # what's going on here. diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py index 5e58cf4..24122b9 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -13,6 +13,7 @@ from .config import glymur_config OPENJP2, OPENJPEG = glymur_config() + def version(): """Wrapper for opj_version library routine.""" try: @@ -50,13 +51,6 @@ JPWL_MAX_NO_TILESPECS = 16 TRUE = 1 FALSE = 0 -#PROFILE = {'none': 0, # No profile -# 0: 1, # Profile 0 -# 1: 2, # Profile 1 -# 'part2': 0x8000, # At least one extension -# 'Cinema2K': 0x0003, # 2K cinema profile -# 'Cinema4K': 0x0004, # 4K cinema profile - # supported color spaces CLRSPC_UNKNOWN = -1 CLRSPC_UNSPECIFIED = 0 @@ -548,7 +542,6 @@ class ImageType(ctypes.Structure): return msg - class ImageComptParmType(ctypes.Structure): """Component parameters structure used by image_create function. @@ -958,7 +951,7 @@ def read_header(stream, codec): ARGTYPES = [STREAM_TYPE_P, CODEC_TYPE, ctypes.POINTER(ctypes.POINTER(ImageType))] OPENJP2.opj_read_header.argtypes = ARGTYPES - OPENJP2.opj_read_header.restype = check_error + OPENJP2.opj_read_header.restype = check_error imagep = ctypes.POINTER(ImageType)() OPENJP2.opj_read_header(stream, codec, ctypes.byref(imagep)) @@ -1317,6 +1310,7 @@ def _stream_create_default_file_stream_2p0(fptr, isa_read_stream): stream = OPENJP2.opj_stream_create_default_file_stream(fptr, read_stream) return stream + def _stream_create_default_file_stream_2p1(fname, isa_read_stream): """Wraps openjp2 library function opj_stream_create_default_vile_stream. @@ -1343,7 +1337,7 @@ def _stream_create_default_file_stream_2p1(fname, isa_read_stream): stream = OPENJP2.opj_stream_create_default_file_stream(file_argument, read_stream) return stream - + if re.match(r'''2.0''', version()): stream_create_default_file_stream = _stream_create_default_file_stream_2p0 else: diff --git a/glymur/lib/openjpeg.py b/glymur/lib/openjpeg.py index d1918ad..924cac5 100644 --- a/glymur/lib/openjpeg.py +++ b/glymur/lib/openjpeg.py @@ -59,8 +59,10 @@ class CommonStructType(ctypes.Structure): ("mj2_handle", ctypes.c_void_p)] -STREAM_READ = 0x0001 # The stream was opened for reading. -STREAM_WRITE = 0x0002 # The stream was opened for writing. +STREAM_READ = 0x0001 # The stream was opened for reading. +STREAM_WRITE = 0x0002 # The stream was opened for writing. + + class CioType(ctypes.Structure): """Byte input-output stream (CIO) @@ -91,70 +93,57 @@ class CompressionInfoType(CommonStructType): class PocType(ctypes.Structure): """Progression order changes.""" _fields_ = [("resno", ctypes.c_int), - # Resolution num start, Component num start, given by POC - ("compno0", ctypes.c_int), + # Resolution num start, Component num start, given by POC + ("compno0", ctypes.c_int), - # Layer num end,Resolution num end, Component num end, given by POC - ("layno1", ctypes.c_int), - ("resno1", ctypes.c_int), - ("compno1", ctypes.c_int), + # Layer num end,Resolution num end, Component num end, given + # by POC + ("layno1", ctypes.c_int), + ("resno1", ctypes.c_int), + ("compno1", ctypes.c_int), - # Layer num start,Precinct num start, Precinct num end - ("layno0", ctypes.c_int), - ("precno0", ctypes.c_int), - ("precno1", ctypes.c_int), + # Layer num start,Precinct num start, Precinct num end + ("layno0", ctypes.c_int), + ("precno0", ctypes.c_int), + ("precno1", ctypes.c_int), - # Progression order enum - # OPJ_PROG_ORDER prg1,prg; - ("prg1", ctypes.c_int), - ("prg", ctypes.c_int), + # Progression order enum + # OPJ_PROG_ORDER prg1,prg; + ("prg1", ctypes.c_int), + ("prg", ctypes.c_int), - # Progression order string - # char progorder[5]; - ("progorder", ctypes.c_char * 5), + # Progression order string + # char progorder[5]; + ("progorder", ctypes.c_char * 5), - # Tile number - # int tile; - ("tile", ctypes.c_int), + # Tile number + # int tile; + ("tile", ctypes.c_int), - # /** Start and end values for Tile width and height*/ - # int tx0,tx1,ty0,ty1; - ("tx0", ctypes.c_int), - ("tx1", ctypes.c_int), - ("ty0", ctypes.c_int), - ("ty1", ctypes.c_int), - - # /** Start value, initialised in pi_initialise_encode*/ - # int layS, resS, compS, prcS; - ("layS", ctypes.c_int), - ("resS", ctypes.c_int), - ("compS", ctypes.c_int), - ("prcS", ctypes.c_int), - - # /** End value, initialised in pi_initialise_encode */ - # int layE, resE, compE, prcE; - ("layE", ctypes.c_int), - ("resE", ctypes.c_int), - ("compE", ctypes.c_int), - ("prcE", ctypes.c_int), - - # Start and end values of Tile width and height, initialised in - # pi_initialise_encode int txS,txE,tyS,tyE,dx,dy; - ("txS", ctypes.c_int), - ("txE", ctypes.c_int), - ("tyS", ctypes.c_int), - ("tyE", ctypes.c_int), - ("dx", ctypes.c_int), - ("dy", ctypes.c_int), - - # Temporary values for Tile parts, initialised in pi_create_encode - # int lay_t, res_t, comp_t, prc_t,tx0_t,ty0_t; - ("lay_t", ctypes.c_int), - ("res_t", ctypes.c_int), - ("comp_t", ctypes.c_int), - ("prc_t", ctypes.c_int), - ("tx0_t", ctypes.c_int), - ("ty0_t", ctypes.c_int)] + ("tx0", ctypes.c_int), + ("tx1", ctypes.c_int), + ("ty0", ctypes.c_int), + ("ty1", ctypes.c_int), + ("layS", ctypes.c_int), + ("resS", ctypes.c_int), + ("compS", ctypes.c_int), + ("prcS", ctypes.c_int), + ("layE", ctypes.c_int), + ("resE", ctypes.c_int), + ("compE", ctypes.c_int), + ("prcE", ctypes.c_int), + ("txS", ctypes.c_int), + ("txE", ctypes.c_int), + ("tyS", ctypes.c_int), + ("tyE", ctypes.c_int), + ("dx", ctypes.c_int), + ("dy", ctypes.c_int), + ("lay_t", ctypes.c_int), + ("res_t", ctypes.c_int), + ("comp_t", ctypes.c_int), + ("prc_t", ctypes.c_int), + ("tx0_t", ctypes.c_int), + ("ty0_t", ctypes.c_int)] class CompressionParametersType(ctypes.Structure): @@ -375,48 +364,47 @@ class DecompressionParametersType(ctypes.Structure): class ImageComptParmType(ctypes.Structure): """Component parameters structure used by the opj_image_create function. """ - _fields_ = [ - # XRsiz: horizontal separation of a sample of ith component with - # respect to the reference grid - ("dx", ctypes.c_int), + _fields_ = [# XRsiz: horizontal separation of a sample of ith component + # with respect to the reference grid + ("dx", ctypes.c_int), - # YRsiz: vertical separation of a sample of ith component with - # respect to the reference grid */ - ("dy", ctypes.c_int), + # YRsiz: vertical separation of a sample of ith component with + # respect to the reference grid */ + ("dy", ctypes.c_int), - # data width, height - ("w", ctypes.c_int), - ("h", ctypes.c_int), + # data width, height + ("w", ctypes.c_int), + ("h", ctypes.c_int), - # x component offset compared to the whole image - # y component offset compared to the whole image - ("x0", ctypes.c_int), - ("y0", ctypes.c_int), + # x component offset compared to the whole image + # y component offset compared to the whole image + ("x0", ctypes.c_int), + ("y0", ctypes.c_int), - # precision - ('prec', ctypes.c_int), + # precision + ('prec', ctypes.c_int), - # image depth in bits - ('bpp', ctypes.c_int), + # image depth in bits + ('bpp', ctypes.c_int), - # signed (1) / unsigned (0) - ('sgnd', ctypes.c_int)] + # signed (1) / unsigned (0) + ('sgnd', ctypes.c_int)] class ImageCompType(ctypes.Structure): """Defines a single image component. """ _fields_ = [("dx", ctypes.c_int), - ("dy", ctypes.c_int), - ("w", ctypes.c_int), - ("h", ctypes.c_int), - ("x0", ctypes.c_int), - ("y0", ctypes.c_int), - ("prec", ctypes.c_int), - ("bpp", ctypes.c_int), - ("sgnd", ctypes.c_int), - ("resno_decoded", ctypes.c_int), - ("factor", ctypes.c_int), - ("data", ctypes.POINTER(ctypes.c_int))] + ("dy", ctypes.c_int), + ("w", ctypes.c_int), + ("h", ctypes.c_int), + ("x0", ctypes.c_int), + ("y0", ctypes.c_int), + ("prec", ctypes.c_int), + ("bpp", ctypes.c_int), + ("sgnd", ctypes.c_int), + ("resno_decoded", ctypes.c_int), + ("factor", ctypes.c_int), + ("data", ctypes.POINTER(ctypes.c_int))] class ImageType(ctypes.Structure): @@ -468,6 +456,7 @@ def cio_tell(cio): pos = OPENJPEG.cio_tell(cio) return pos + def create_compress(fmt): """Wrapper for openjpeg library function opj_create_compress. @@ -585,9 +574,8 @@ def image_cmptparm_t_from_np(np_image): def image_create(cmptparms, cspace): """Wrapper for openjpeg library function opj_image_create. """ - OPENJPEG.opj_image_create.argtypes = [ctypes.c_int, - ctypes.POINTER(ImageComptParmType), - ctypes.c_int] + lst = [ctypes.c_int, ctypes.POINTER(ImageComptParmType), ctypes.c_int] + OPENJPEG.opj_image_create.argtypes = lst OPENJPEG.opj_image_create.restype = ctypes.POINTER(ImageType) image = OPENJPEG.opj_image_create(len(cmptparms), cmptparms, cspace)