From adddb134f8f07916e5cee424a4bb84fbc9e4b6a2 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 8 Jun 2013 22:21:08 -0400 Subject: [PATCH 1/8] Handling first 36 bytes of header. --- glymur/jp2box.py | 71 ++++++++++++++++++++++++++++++++++++++++- glymur/test/__init__.py | 1 + 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 789c803..1854707 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -12,6 +12,7 @@ References """ import copy +import datetime import math import os import pprint @@ -221,12 +222,80 @@ class ColourSpecificationBox(Jp2kBox): # ICC profile kwargs['colorspace'] = None n = offset + length - f.tell() - kwargs['icc_profile'] = f.read(n) + icc_profile = ICCProfile(f.read(n)) + kwargs['icc_profile'] = icc_profile box = ColourSpecificationBox(**kwargs) return box +class ICCProfile: + """ + """ + profile_class = {b'scnr': 'input device profile', + b'mntr': 'display device profile', + b'prtr': 'output device profile', + b'link': 'devicelink profile', + b'spac': 'colorspace conversion profile', + b'abst': 'abstract profile', + b'nmcl': 'name colour profile'} + + colour_space_dict = {b'XYZ ': 'XYZ', + b'Lab ': 'Lab', + b'Luv ': 'Luv', + b'YCbr': 'YCbCr', + b'Yxy ': 'Yxy', + b'RGB ': 'RGB', + b'GRAY': 'gray', + b'HSV ': 'hsv', + b'HLS ': 'hls', + b'CMYK': 'CMYK', + b'CMY ': 'cmy', + b'2CLR': '2colour', + b'3CLR': '3colour', + b'4CLR': '4colour', + b'5CLR': '5colour', + b'6CLR': '6colour', + b'7CLR': '7colour', + b'8CLR': '8colour', + b'9CLR': '9colour', + b'ACLR': '10colour', + b'BCLR': '11colour', + b'CCLR': '12colour', + b'DCLR': '13colour', + b'ECLR': '14colour', + b'FCLR': '15colour'} + + def __init__(self, buffer): + self._raw_buffer = buffer + self.parse_header() + + def parse_header(self): + """See section 7.2""" + self.size, = struct.unpack('>I', self._raw_buffer[0:4]) + self.preferred_cmm_type, = struct.unpack('>I', self._raw_buffer[4:8]) + + data = struct.unpack('>BB', self._raw_buffer[8:10]) + major = data[0] + minor = (data[1] & 0xf0) >> 4 + bugfix = (data[1] & 0x0f) + self.version = '{0}.{1}.{2}'.format(major, minor, bugfix) + + self.device_class = self.profile_class[self._raw_buffer[12:16]] + self.colour_space = self.colour_space_dict[self._raw_buffer[16:20]] + self.connection_space = self.colour_space_dict[self._raw_buffer[20:24]] + + data = struct.unpack('>HHHHHH', self._raw_buffer[24:36]) + self.datetime = datetime.datetime(*data) + + print(self._raw_buffer[24:36]) + + def __str__(self): + msg = "Profile size: {0}" + msg = "Preferred CMM type: {1}" + msg = msg.format(self.size, + self.preferred_cmm_type) + class ComponentDefinitionBox(Jp2kBox): """Container for component definition box information. diff --git a/glymur/test/__init__.py b/glymur/test/__init__.py index 9677af5..1e19d88 100644 --- a/glymur/test/__init__.py +++ b/glymur/test/__init__.py @@ -1,6 +1,7 @@ from .test_callbacks import TestCallbacks as callbacks from .test_codestream import TestCodestream as codestream from .test_jp2k import TestJp2k as jp2k +from .test_icc import TestICC as icc from .test_printing import TestPrinting as printing from .test_opj_suite import TestSuite as suite from .test_opj_suite_write import TestSuiteWrite as suitew From 362a3b62e479b11f7399feb316bf01585e9efad2 Mon Sep 17 00:00:00 2001 From: jevans Date: Sun, 9 Jun 2013 12:02:34 -0400 Subject: [PATCH 2/8] Header is done, now working on tag table. --- glymur/jp2box.py | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 1854707..850c300 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -268,9 +268,25 @@ class ICCProfile: def __init__(self, buffer): self._raw_buffer = buffer - self.parse_header() + self.parse_header(buffer) + self.parse_tag_table(buffer) - def parse_header(self): + def parse_tag_table(self, buffer): + """ + See section 7.3 of ICC1V4.2. + """ + num_tags, = struct.unpack('>I', buffer[128:132]) + tag_table = buffer[132:132 + num_tags * 12] + data = struct.unpack('>' + 'III' * num_tags, tag_table) + signature = data[0::3] + offset = data[1::3] + size = data[2::3] + for j in range(num_tags): + print("{0} at {1} of length {2}".format(signature[j], + offset[j], + size[j])) + + def parse_header(self, buffer): """See section 7.2""" self.size, = struct.unpack('>I', self._raw_buffer[0:4]) self.preferred_cmm_type, = struct.unpack('>I', self._raw_buffer[4:8]) @@ -287,8 +303,29 @@ class ICCProfile: data = struct.unpack('>HHHHHH', self._raw_buffer[24:36]) self.datetime = datetime.datetime(*data) + self.file_signature = buffer[36:40].decode('utf-8') + if buffer[40:44] == b'\x00\x00\x00\x00': + self.platform = 'unrecognized' + else: + self.platform = buffer[40:44].decode('utf-8') + + self.flags, = struct.unpack('>I', buffer[44:48]) - print(self._raw_buffer[24:36]) + self.device_manufacturer = buffer[48:52].decode('utf-8') + self.device_model = buffer[52:56].decode('utf-8') + self.device_attributes, = struct.unpack('>Q', buffer[56:64]) + self.rendering_intent, = struct.unpack('>I', buffer[64:68]) + + data = struct.unpack('>iii', buffer[68:80]) + self.illuminant = np.array(data, dtype=np.float64) / 65536 + + if buffer[80:84] == b'\x00\x00\x00\x00': + self.creator = 'unrecognized' + else: + self.creator = buffer[80:84].decode('utf-8') + + self.profile_id = buffer[84:100] + self.reserved = buffer[100:127] def __str__(self): msg = "Profile size: {0}" From 148729c4b42e66767076549952b3810822abb1cb Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 10 Jun 2013 19:46:07 -0400 Subject: [PATCH 3/8] Have figured out that we need a LOT of ICC standard documents. --- glymur/jp2box.py | 22 +++++++++++-- glymur/test/test_icc.py | 68 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 glymur/test/test_icc.py diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 850c300..6086038 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -282,9 +282,12 @@ class ICCProfile: offset = data[1::3] size = data[2::3] for j in range(num_tags): - print("{0} at {1} of length {2}".format(signature[j], - offset[j], - size[j])) + sig = buffer[132 + j * 4:132 + (j + 1) * 4] + print(sig) + import pdb; pdb.set_trace() + tag_buffer = buffer[offset[j]:offset[j] + size[j]] + if sig == b'desc': + table['desc'] = _MultiLocalizedUnicodeType(tag_buffer) def parse_header(self, buffer): """See section 7.2""" @@ -333,6 +336,19 @@ class ICCProfile: msg = msg.format(self.size, self.preferred_cmm_type) +class _MultiLocalizedUnicodeType: + def __init__(self, buffer): + import pdb; pdb.set_trace() + self.id = buffer[0:4].decode('utf-8') + data = struct.unpack('>II', buffer[8:16]) + num_names = data[0] + self.record_size = data[1] + + data = struct.unpack('>' + 'HHII' * num_names, + buffer[16:16 + 12 * num_names]) + for j in range(num_names): + print(j) + class ComponentDefinitionBox(Jp2kBox): """Container for component definition box information. diff --git a/glymur/test/test_icc.py b/glymur/test/test_icc.py new file mode 100644 index 0000000..30d41fc --- /dev/null +++ b/glymur/test/test_icc.py @@ -0,0 +1,68 @@ +import datetime +import os +import struct +import sys +import tempfile +import unittest +import warnings +from xml.etree import cElementTree as ET + +import numpy as np +import pkg_resources + +from glymur import Jp2k +import glymur + +try: + data_root = os.environ['OPJ_DATA_ROOT'] +except KeyError: + data_root = None +except: + raise + + +class TestICC(unittest.TestCase): + + def setUp(self): + self.jp2file = pkg_resources.resource_filename(glymur.__name__, + "data/nemo.jp2") + + def tearDown(self): + pass + + @unittest.skipIf(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_file5(self): + filename = os.path.join(data_root, 'input/conformance/file5.jp2') + j = Jp2k(filename) + profile = j.box[3].box[1].icc_profile + self.assertEqual(profile.size, 546) + self.assertEqual(profile.preferred_cmm_type, 0) + self.assertEqual(profile.version, '2.2.0') + self.assertEqual(profile.device_class, 'input device profile') + self.assertEqual(profile.colour_space, 'RGB') + self.assertEqual(profile.datetime, + datetime.datetime(2001,8,30,13,32,37)) + self.assertEqual(profile.file_signature, 'acsp') + self.assertEqual(profile.platform, 'unrecognized') + self.assertTrue(profile.flags & 0x01) # embedded + self.assertFalse(profile.flags & 0x02) # use anywhere + + self.assertEqual(profile.device_manufacturer, 'KODA') + self.assertEqual(profile.device_model, 'ROMM') + + self.assertFalse(profile.device_attributes & 0x01) # reflective + self.assertFalse(profile.device_attributes & 0x02) # glossy + self.assertFalse(profile.device_attributes & 0x04) # positive + self.assertFalse(profile.device_attributes & 0x08) # colour + self.assertEqual(profile.rendering_intent & 0x00ff, 0) # perceptual + + np.testing.assert_almost_equal(profile.illuminant, + (0.964203, 1.000000, 0.824905), + decimal=6) + + self.assertEqual(profile.creator, 'JPEG') + +if __name__ == "__main__": + unittest.main() + From 93132bf86d653fa99aaaeae8760be43cb411aaed Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 14 Jun 2013 14:12:48 -0400 Subject: [PATCH 4/8] Minor doc fixes. --- docs/source/how_do_i.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index 5206f79..d4a91bf 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -7,7 +7,7 @@ Read the lowest resolution thumbnail? ===================================== Printing the Jp2k object should reveal the number of resolutions (look in the COD segment section), but you can take a shortcut by supplying -1 as the reduce -level. +level. :: >>> import pkg_resources >>> import glymur @@ -38,7 +38,7 @@ codestream box, only the main header is printed. It is possible to print Work with XMP UUIDs? ==================== -The example JP2 file shipped with glymur has an XMP UUID:: +The example JP2 file shipped with glymur has an XMP UUID. :: >>> import pkg_resources >>> from glymur import Jp2k From 00db032bca35c4db6c0dfa1dc14377f05c779655 Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 14 Jun 2013 14:13:02 -0400 Subject: [PATCH 5/8] Bumping for 1.1.8 release. --- CHANGES.txt | 3 ++- docs/source/conf.py | 2 +- setup.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index f85afa4..3e761d3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ -Jun 11, 2013 - Added reduce=-1 option to get lowest resolution thumbnail +Jun 11, 2013 - v0.1.8 Added reduce=-1 option to get lowest resolution + thumbnail. Jun 07, 2013 - v0.1.7 Changed Exif dictionary names from ['Exif', 'Photo', 'Iop', 'GPSInfo'] to ['Image', 'Photo', 'Iop', 'GPSInfo']. diff --git a/docs/source/conf.py b/docs/source/conf.py index 262e5d8..10d03c2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -78,7 +78,7 @@ copyright = u'2013, John Evans' # The short X.Y version. version = '0.1' # The full version, including alpha/beta/rc tags. -release = '0.1.7' +release = '0.1.8' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 6e968ae..4f32597 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from distutils.core import setup kwargs = {'name': 'Glymur', - 'version': '0.1.7', + 'version': '0.1.8', 'description': 'Tools for accessing JPEG2000 files', 'long_description': open('README.md').read(), 'author': 'John Evans', From eb9b0e6f37c6093ac932a4713d0d6daeffdd7af6 Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 14 Jun 2013 15:36:54 -0400 Subject: [PATCH 6/8] recursive-include line is not needed (and was wrong) Were it kept, it should have had a directory before the globbing. --- MANIFEST.in | 1 - 1 file changed, 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 160d333..86279d7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,3 @@ include *.txt *.md -recursive-include *.txt *.py prune build exclude readthedocs-pip-requirements.txt From 95b21a1596b3ec0875843ad81031145c68294597 Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 14 Jun 2013 16:31:23 -0400 Subject: [PATCH 7/8] Image data docstring parameter descriptions jive with numpydoc. Closes #44 --- glymur/jp2k.py | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index adba2cc..2845cf3 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -138,7 +138,7 @@ class Jp2k(Jp2kBox): f.seek(0) self.box = self._parse_superbox(f) - def write(self, data, cratios=None, eph=False, psnr=None, numres=None, + def write(self, img_array, cratios=None, eph=False, psnr=None, numres=None, cbsize=None, psizes=None, grid_offset=None, sop=False, subsam=None, tilesize=None, prog=None, modesw=None, colorspace=None, verbose=False): @@ -150,7 +150,7 @@ class Jp2k(Jp2kBox): Parameters ---------- - data : array + img_array : ndarray Image data to be written to file. callbacks : bool, optional If true, enable default info handler such that INFO messages @@ -307,19 +307,19 @@ class Jp2k(Jp2kBox): msg = "Cannot specify cratios and psnr together." raise RuntimeError(msg) - if data.ndim == 2: - numrows, numcols = data.shape - data = data.reshape(numrows, numcols, 1) - elif data.ndim == 3: + if img_array.ndim == 2: + numrows, numcols = img_array.shape + img_array = img_array.reshape(numrows, numcols, 1) + elif img_array.ndim == 3: pass else: - msg = "{0}D imagery is not allowed.".format(data.ndim) + msg = "{0}D imagery is not allowed.".format(img_array.ndim) raise IOError(msg) - numrows, numcols, num_comps = data.shape + numrows, numcols, num_comps = img_array.shape if colorspace is None: - if data.shape[2] == 1 or data.shape[2] == 2: + if img_array.shape[2] == 1 or img_array.shape[2] == 2: colorspace = opj2._CLRSPC_GRAY else: # No YCC unless specifically told to do so. @@ -331,15 +331,15 @@ class Jp2k(Jp2kBox): if colorspace not in ('rgb', 'grey', 'gray'): msg = 'Invalid colorspace "{0}"'.format(colorspace) raise IOError(msg) - elif colorspace == 'rgb' and data.shape[2] < 3: + elif colorspace == 'rgb' and img_array.shape[2] < 3: msg = 'RGB colorspace requires at least 3 components.' raise IOError(msg) else: colorspace = _cspace_map[colorspace] - if data.dtype == np.uint8: + if img_array.dtype == np.uint8: comp_prec = 8 - elif data.dtype == np.uint16: + elif img_array.dtype == np.uint16: comp_prec = 16 else: raise RuntimeError("unhandled datatype") @@ -368,7 +368,7 @@ class Jp2k(Jp2kBox): # Stage the image data to the openjpeg data structure. for k in range(0, num_comps): - layer = np.ascontiguousarray(data[:, :, k], dtype=np.int32) + layer = np.ascontiguousarray(img_array[:, :, k], dtype=np.int32) dest = image.contents.comps[k].data src = layer.ctypes.data ctypes.memmove(dest, src, layer.nbytes) @@ -419,7 +419,7 @@ class Jp2k(Jp2kBox): Returns ------- - result : array + img_array : ndarray The image data. Raises @@ -451,18 +451,18 @@ class Jp2k(Jp2kBox): msg = "Components must all have the same subsampling factors." raise IOError(msg) - data = self._read_common(reduce=reduce, - layer=layer, - area=area, - tile=tile, - verbose=verbose, - as_bands=False) + img_array = self._read_common(reduce=reduce, + layer=layer, + area=area, + tile=tile, + verbose=verbose, + as_bands=False) - if data.shape[2] == 1: - data = data.view() - data.shape = data.shape[0:2] + if img_array.shape[2] == 1: + img_array = img_array.view() + img_array.shape = img_array.shape[0:2] - return data + return img_array def _read_common(self, reduce=0, layer=0, area=None, tile=None, verbose=False, as_bands=False): @@ -486,7 +486,7 @@ class Jp2k(Jp2kBox): Returns ------- - data : list or array + img_array : ndarray The individual image components or a single array. """ dparam = opj2._set_default_decoder_parameters() From 331ff48aa96e845f69e919e7bf7bb9000ced6f1e Mon Sep 17 00:00:00 2001 From: jevans Date: Fri, 14 Jun 2013 23:17:11 -0400 Subject: [PATCH 8/8] Added ICC header support. Closes #22 --- glymur/jp2box.py | 133 +++++++++++++++++++++------------- glymur/test/test_icc.py | 18 +++-- glymur/test/test_opj_suite.py | 27 ++++--- glymur/test/test_printing.py | 19 ++++- 4 files changed, 132 insertions(+), 65 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 6086038..9b3f4c6 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -154,9 +154,9 @@ class ColourSpecificationBox(Jp2kBox): colorspace : int or None Enumerated colorspace, corresponds to one of 'sRGB', 'greyscale', or 'YCC'. If not None, then icc_profile must be None. - icc_profile : byte array or None - ICC profile according to ICC profile specification. If not None, then - color_space must be None. + icc_profile : _ICCProfile or None + ICC profile header according to ICC profile specification. If not + None, then color_space must be None. """ def __init__(self, **kwargs): Jp2kBox.__init__(self, id='', longname='Colour Specification') @@ -175,8 +175,7 @@ class ColourSpecificationBox(Jp2kBox): x = _colorspace_map_display[self.colorspace] msg += '\n Colorspace: {0}'.format(x) else: - x = len(self.icc_profile) - msg += '\n ICC Profile: {0} bytes'.format(x) + msg += '\n ICC Profile: {0}'.format(self.icc_profile.__str__()) return msg @@ -222,14 +221,20 @@ class ColourSpecificationBox(Jp2kBox): # ICC profile kwargs['colorspace'] = None n = offset + length - f.tell() - icc_profile = ICCProfile(f.read(n)) - kwargs['icc_profile'] = icc_profile + if n < 128: + msg = "ICC profile header is corrupt, length is " + msg += "only {0} instead of 128." + warnings.warn(msg.format(n), UserWarning) + kwargs['icc_profile'] = None + else: + icc_profile = _ICCProfile(f.read(n)) + kwargs['icc_profile'] = icc_profile box = ColourSpecificationBox(**kwargs) return box -class ICCProfile: +class _ICCProfile: """ """ profile_class = {b'scnr': 'input device profile', @@ -266,31 +271,14 @@ class ICCProfile: b'ECLR': '14colour', b'FCLR': '15colour'} + rendering_intent_dict = {0: 'perceptual', + 1: 'media-relative colorimetric', + 2: 'saturation', + 3: 'ICC-absolute colorimetric'} + def __init__(self, buffer): self._raw_buffer = buffer - self.parse_header(buffer) - self.parse_tag_table(buffer) - def parse_tag_table(self, buffer): - """ - See section 7.3 of ICC1V4.2. - """ - num_tags, = struct.unpack('>I', buffer[128:132]) - tag_table = buffer[132:132 + num_tags * 12] - data = struct.unpack('>' + 'III' * num_tags, tag_table) - signature = data[0::3] - offset = data[1::3] - size = data[2::3] - for j in range(num_tags): - sig = buffer[132 + j * 4:132 + (j + 1) * 4] - print(sig) - import pdb; pdb.set_trace() - tag_buffer = buffer[offset[j]:offset[j] + size[j]] - if sig == b'desc': - table['desc'] = _MultiLocalizedUnicodeType(tag_buffer) - - def parse_header(self, buffer): - """See section 7.2""" self.size, = struct.unpack('>I', self._raw_buffer[0:4]) self.preferred_cmm_type, = struct.unpack('>I', self._raw_buffer[4:8]) @@ -311,11 +299,14 @@ class ICCProfile: self.platform = 'unrecognized' else: self.platform = buffer[40:44].decode('utf-8') - + self.flags, = struct.unpack('>I', buffer[44:48]) - + self.device_manufacturer = buffer[48:52].decode('utf-8') - self.device_model = buffer[52:56].decode('utf-8') + if buffer[52:56] == b'\x00\x00\x00\x00': + self.device_model = '' + else: + self.device_model = buffer[52:56].decode('utf-8') self.device_attributes, = struct.unpack('>Q', buffer[56:64]) self.rendering_intent, = struct.unpack('>I', buffer[64:68]) @@ -326,28 +317,72 @@ class ICCProfile: self.creator = 'unrecognized' else: self.creator = buffer[80:84].decode('utf-8') - + self.profile_id = buffer[84:100] self.reserved = buffer[100:127] def __str__(self): - msg = "Profile size: {0}" - msg = "Preferred CMM type: {1}" + msg = "\n Size: {0}" + msg += "\n Preferred CMM type: {1:x}" + msg += "\n Version: {2}" + msg += "\n Device class signature: {3}" + msg += "\n Color space: {4}" + msg += "\n Connection space: {5}" + msg += "\n Creation time: {6}" + msg += "\n File signature: {7}" + msg += "\n Platform: {8}" + msg += "\n Flags: {9}" + msg += "\n Device manufacturer: {10}" + msg += "\n Device model: {11}" + msg += "\n Device attributes: {12}" + msg += "\n Rendering intent: {13}" + msg += "\n Illuminant: {14}" + msg += "\n Creator signature: {15}" + + if self.flags & 0x01: + flag_string = 'embedded, ' + else: + flag_string = 'not embedded, ' + if self.flags & 0x02: + flag_string += 'cannot be used independently' + else: + flag_string += 'can be used independently' + + if self.device_attributes & 0x01: + attr_string = 'transparency, ' + else: + attr_string = 'reflective, ' + if self.device_attributes & 0x02: + attr_string += 'matte, ' + else: + attr_string += 'glossy, ' + if self.device_attributes & 0x04: + attr_string += 'negative media polarity, ' + else: + attr_string += 'positive media polarity, ' + if self.device_attributes & 0x08: + attr_string += 'black and white media' + else: + attr_string += 'color media' + msg = msg.format(self.size, - self.preferred_cmm_type) + self.preferred_cmm_type, + self.version, + self.device_class, + self.colour_space, + self.connection_space, + self.datetime, + self.file_signature, + self.platform, + flag_string, + self.device_manufacturer, + self.device_model, + attr_string, + self.rendering_intent_dict[self.rendering_intent], + self.illuminant, + self.creator) + return(msg) -class _MultiLocalizedUnicodeType: - def __init__(self, buffer): - import pdb; pdb.set_trace() - self.id = buffer[0:4].decode('utf-8') - data = struct.unpack('>II', buffer[8:16]) - num_names = data[0] - self.record_size = data[1] - - data = struct.unpack('>' + 'HHII' * num_names, - buffer[16:16 + 12 * num_names]) - for j in range(num_names): - print(j) class ComponentDefinitionBox(Jp2kBox): """Container for component definition box information. diff --git a/glymur/test/test_icc.py b/glymur/test/test_icc.py index 30d41fc..c6081ff 100644 --- a/glymur/test/test_icc.py +++ b/glymur/test/test_icc.py @@ -21,17 +21,16 @@ except: raise +@unittest.skipIf(data_root is None, + "OPJ_DATA_ROOT environment variable not set") class TestICC(unittest.TestCase): def setUp(self): - self.jp2file = pkg_resources.resource_filename(glymur.__name__, - "data/nemo.jp2") + pass def tearDown(self): pass - @unittest.skipIf(data_root is None, - "OPJ_DATA_ROOT environment variable not set") def test_file5(self): filename = os.path.join(data_root, 'input/conformance/file5.jp2') j = Jp2k(filename) @@ -42,7 +41,7 @@ class TestICC(unittest.TestCase): self.assertEqual(profile.device_class, 'input device profile') self.assertEqual(profile.colour_space, 'RGB') self.assertEqual(profile.datetime, - datetime.datetime(2001,8,30,13,32,37)) + datetime.datetime(2001, 8, 30, 13, 32, 37)) self.assertEqual(profile.file_signature, 'acsp') self.assertEqual(profile.platform, 'unrecognized') self.assertTrue(profile.flags & 0x01) # embedded @@ -63,6 +62,13 @@ class TestICC(unittest.TestCase): self.assertEqual(profile.creator, 'JPEG') + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + def test_invalid_profile_header(self): + jfile = os.path.join(data_root, + 'input/nonregression/orb-blue10-lin-jp2.jp2') + with self.assertWarns(UserWarning) as cw: + data = Jp2k(jfile).read() + if __name__ == "__main__": unittest.main() - diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 15913e2..80192f8 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -3579,7 +3579,7 @@ class TestSuite(unittest.TestCase): self.assertEqual(jp2.box[3].box[1].method, 2) # enumerated self.assertEqual(jp2.box[3].box[1].precedence, 0) self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(len(jp2.box[3].box[1].icc_profile), 546) + self.assertEqual(jp2.box[3].box[1].icc_profile.size, 546) self.assertIsNone(jp2.box[3].box[1].colorspace) # Jp2 Header @@ -3674,7 +3674,7 @@ class TestSuite(unittest.TestCase): self.assertEqual(jp2.box[3].box[1].method, 2) self.assertEqual(jp2.box[3].box[1].precedence, 0) self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(len(jp2.box[3].box[1].icc_profile), 13332) + self.assertEqual(jp2.box[3].box[1].icc_profile.size, 13332) self.assertIsNone(jp2.box[3].box[1].colorspace) # Jp2 Header @@ -3723,7 +3723,7 @@ class TestSuite(unittest.TestCase): self.assertEqual(jp2.box[2].box[1].method, 2) # enumerated self.assertEqual(jp2.box[2].box[1].precedence, 0) self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact - self.assertEqual(len(jp2.box[2].box[1].icc_profile), 414) + self.assertEqual(jp2.box[2].box[1].icc_profile.size, 414) self.assertIsNone(jp2.box[2].box[1].colorspace) # XML box @@ -6394,7 +6394,10 @@ class TestSuite(unittest.TestCase): def test_NR_orb_blue10_lin_jp2_dump(self): jfile = os.path.join(data_root, 'input/nonregression/orb-blue10-lin-jp2.jp2') - jp2 = Jp2k(jfile) + with warnings.catch_warnings(): + # This file has an invalid ICC profile + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) ids = [box.id for box in jp2.box] self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) @@ -6426,7 +6429,7 @@ class TestSuite(unittest.TestCase): self.assertEqual(jp2.box[2].box[1].method, 2) # res icc self.assertEqual(jp2.box[2].box[1].precedence, 0) self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertEqual(len(jp2.box[2].box[1].icc_profile), 1) + self.assertIsNone(jp2.box[2].box[1].icc_profile) self.assertIsNone(jp2.box[2].box[1].colorspace) c = jp2.box[3].main_header @@ -6490,7 +6493,10 @@ class TestSuite(unittest.TestCase): def test_NR_orb_blue10_win_jp2_dump(self): jfile = os.path.join(data_root, 'input/nonregression/orb-blue10-win-jp2.jp2') - jp2 = Jp2k(jfile) + with warnings.catch_warnings(): + # This file has an invalid ICC profile + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) ids = [box.id for box in jp2.box] self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) @@ -6522,7 +6528,7 @@ class TestSuite(unittest.TestCase): self.assertEqual(jp2.box[2].box[1].method, 2) # restricted icc self.assertEqual(jp2.box[2].box[1].precedence, 0) self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertEqual(len(jp2.box[2].box[1].icc_profile), 1) + self.assertIsNone(jp2.box[2].box[1].icc_profile) self.assertIsNone(jp2.box[2].box[1].colorspace) c = jp2.box[3].main_header @@ -6624,7 +6630,7 @@ class TestSuite(unittest.TestCase): self.assertEqual(jp2.box[3].box[1].method, 3) # any icc self.assertEqual(jp2.box[3].box[1].precedence, 2) self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(len(jp2.box[3].box[1].icc_profile), 1328) + self.assertEqual(jp2.box[3].box[1].icc_profile.size, 1328) self.assertIsNone(jp2.box[3].box[1].colorspace) # UUID boxes. All mentioned in the RREQ box. @@ -6859,7 +6865,10 @@ class TestSuite(unittest.TestCase): def test_NR_DEC_orb_blue_lin_jp2_25_decode(self): jfile = os.path.join(data_root, 'input/nonregression/orb-blue10-lin-jp2.jp2') - data = Jp2k(jfile).read() + with warnings.catch_warnings(): + # This file has an invalid ICC profile + warnings.simplefilter("ignore") + data = Jp2k(jfile).read() self.assertTrue(True) def test_NR_DEC_orb_blue_win_jp2_26_decode(self): diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 7680377..a0180c2 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -790,7 +790,24 @@ class TestPrinting(unittest.TestCase): ' Precedence: 2', ' Approximation: accurately represents ' + 'correct colorspace definition', - ' ICC Profile: 1328 bytes'] + ' ICC Profile: ', + ' Size: 1328', + ' Preferred CMM type: 6170706c', + ' Version: 2.2.0', + ' Device class signature: display device profile', + ' Color space: RGB', + ' Connection space: XYZ', + ' Creation time: 2009-02-25 11:26:11', + ' File signature: acsp', + ' Platform: APPL', + ' Flags: not embedded, can be used independently', + ' Device manufacturer: appl', + ' Device model: ', + ' Device attributes: ' + + 'reflective, glossy, positive media polarity, color media', + ' Rendering intent: perceptual', + ' Illuminant: [ 0.96420288 1. 0.8249054 ]', + ' Creator signature: appl'] expected = '\n'.join(lines) self.assertEqual(actual, expected)