From afc48d5436c8d6a949b6a14a584c95de15ee7fbe Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 28 May 2013 14:09:57 -0400 Subject: [PATCH 01/14] Test added for missing config directory (HOME). --- glymur/__init__.py | 19 +++++-------------- glymur/test/test_jp2k.py | 11 +++++++++++ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/glymur/__init__.py b/glymur/__init__.py index 29e8cc4..3525b6a 100644 --- a/glymur/__init__.py +++ b/glymur/__init__.py @@ -16,24 +16,15 @@ def _glymurrc_fname(): if os.path.exists(fname): return fname - # environ var GLYMURCONFIGDIR - if 'GLYMURCONFIGDIR' in os.environ: - path = os.environ['GLYMURCONFIGDIR'] - if os.path.exists(path): - fname = os.path.join(path, 'glymurrc') - if os.path.exists(fname): - return fname - else: - msg = "glymurrc file hinted at by GLYMURCONFIGDIR does not " - msg += "exist." - warnings.warn(msg, UserWarning) - - # HOME/.glymur/glymurrc + # Either GLYMURCONFIGDIR/glymurrc or $HOME/.glymur/glymurrc confdir = _get_configdir() if confdir is not None: - fname = os.path.join(_get_configdir(), 'glymurrc') + fname = os.path.join(confdir, 'glymurrc') if os.path.exists(fname): return fname + else: + msg = "Configuration file '{0}' does not exist.".format(confdir) + warnings.warn(msg, UserWarning) # didn't find a configuration file. return None diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 5234e0b..49abd9f 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -597,6 +597,17 @@ class TestJp2k(unittest.TestCase): with self.assertWarns(UserWarning) as cw: imp.reload(glymur) + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + def test_home_dir_missing_config_dir(self): + # Verify no exception is raised if $HOME is missing .glymur directory. + with tempfile.TemporaryDirectory() as tdir: + with patch.dict('os.environ', {'HOME': tdir}): + # Misconfigured new configuration file should + # be rejected. + with self.assertWarns(UserWarning) as cw: + imp.reload(glymur) + if __name__ == "__main__": unittest.main() From d8ad8494f83a64cffcbf71f5e3efd448259f2021 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 28 May 2013 14:19:06 -0400 Subject: [PATCH 02/14] TODO file is unneeded, use github instead. --- TODO | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 TODO diff --git a/TODO b/TODO deleted file mode 100644 index 48883ad..0000000 --- a/TODO +++ /dev/null @@ -1,4 +0,0 @@ -Test -c options with multiple values. -Test -b options with h*w>4096 -Test -b options with h or w > 1024 -Test -b options with h or w < 4 From beb39419a29a8aadb07b34ac96f7875bc5de170b Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 28 May 2013 14:25:12 -0400 Subject: [PATCH 03/14] Pep8 work. --- glymur/codestream.py | 2 -- glymur/jp2k.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/glymur/codestream.py b/glymur/codestream.py index 506450d..f433eca 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -472,8 +472,6 @@ class Codestream: SPcod = f.read(n) kwargs['SPcod'] = np.frombuffer(SPcod, dtype=np.uint8) - # 0: - # 1: layers params = struct.unpack('>BHBBBBBB', SPcod[0:9]) kwargs['_layers'] = params[1] kwargs['_numresolutions'] = params[3] diff --git a/glymur/jp2k.py b/glymur/jp2k.py index dbfc74d..6eb23fd 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -152,7 +152,7 @@ class Jp2k(Jp2kBox): data : array Image data to be written to file. callbacks : bool, optional - If true, enable default info handler such that INFO messages + If true, enable default info handler such that INFO messages produced by the OpenJPEG library are output to the console. By default, OpenJPEG warning and error messages are captured by Python's own warning and error mechanisms. From 11c46e0cf1f434598d2160562bd7d1b9a780da9a Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 28 May 2013 15:52:57 -0400 Subject: [PATCH 04/14] Refactored some tests, introduced class-level fixtures. --- glymur/test/test_jp2k.py | 52 ++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 49abd9f..ae6af19 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -50,19 +50,13 @@ def chdir(dirname=None): class TestJp2k(unittest.TestCase): - def setUp(self): - self.jp2file = pkg_resources.resource_filename(glymur.__name__, + @classmethod + def setUpClass(cls): + jp2file = pkg_resources.resource_filename(glymur.__name__, "data/nemo.jp2") - - def tearDown(self): - pass - - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") - def test_invalid_xml_box(self): - # Should be able to recover from xml box with bad xml. - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - with open(self.jp2file, 'rb') as ifile: + with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile: + cls._bad_xml_file = tfile.name + with open(jp2file, 'rb') as ifile: # Everything up until the jp2c box. buffer = ifile.read(77) tfile.write(buffer) @@ -81,13 +75,35 @@ class TestJp2k(unittest.TestCase): tfile.write(buffer) tfile.flush() - with self.assertWarns(UserWarning) as cw: - jp2k = Jp2k(tfile.name) + @classmethod + def tearDownClass(cls): + os.unlink(cls._bad_xml_file) - self.assertEqual(jp2k.box[3].id, 'xml ') - self.assertEqual(jp2k.box[3].offset, 77) - self.assertEqual(jp2k.box[3].length, 28) - self.assertIsNone(jp2k.box[3].xml) + def setUp(self): + self.jp2file = pkg_resources.resource_filename(glymur.__name__, + "data/nemo.jp2") + + def tearDown(self): + pass + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + def test_invalid_xml_box_warning(self): + # Should be able to recover from xml box with bad xml. + # Just verify that a warning is issued on 3.2+ + with self.assertWarns(UserWarning) as cw: + jp2k = Jp2k(self._bad_xml_file) + + def test_invalid_xml_box(self): + # Should be able to recover from xml box with bad xml. + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + jp2k = Jp2k(self._bad_xml_file) + + self.assertEqual(jp2k.box[3].id, 'xml ') + self.assertEqual(jp2k.box[3].offset, 77) + self.assertEqual(jp2k.box[3].length, 28) + self.assertIsNone(jp2k.box[3].xml) def test_bad_area_parameter(self): # Verify that we error out appropriately if given a bad area parameter. From 244660ec9e19429d2e8856ad497c4cee220d4bb6 Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 28 May 2013 20:04:23 -0400 Subject: [PATCH 05/14] Can brute-force write a file such that exiv2 can read it. --- glymur/test/__init__.py | 1 + glymur/test/test_xmp.py | 84 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 glymur/test/test_xmp.py diff --git a/glymur/test/__init__.py b/glymur/test/__init__.py index 9677af5..f396e49 100644 --- a/glymur/test/__init__.py +++ b/glymur/test/__init__.py @@ -5,3 +5,4 @@ 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 .test_opj_suite_neg import TestSuiteNegative as suiteneg +from .test_xmp import TestSuite as xmp diff --git a/glymur/test/test_xmp.py b/glymur/test/test_xmp.py new file mode 100644 index 0000000..14b3b6f --- /dev/null +++ b/glymur/test/test_xmp.py @@ -0,0 +1,84 @@ +import os +import struct +import sys +import tempfile +import uuid +import unittest +if sys.hexversion <= 0x03030000: + from mock import patch +else: + from unittest.mock import patch +import warnings +from xml.etree import cElementTree as ET + +import pkg_resources + +from glymur import Jp2k +from glymur.lib import openjp2 as opj2 +import glymur + +lines = [' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' Rubbertree' + + ' ' + + ' ' + + ' ' + + ' \n' + + ''] +packet = '\n'.join(lines) + +class TestSuite(unittest.TestCase): + + @classmethod + def setUpClass(cls): + jp2file = pkg_resources.resource_filename(glymur.__name__, + "data/nemo.jp2") + with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile: + cls._xmp_file = tfile.name + with open(jp2file, 'rb') as ifile: + # Everything up until the jp2c box. + buffer = ifile.read(77) + tfile.write(buffer) + + # Write the uuid box with bad xml + # Length = 4 + 4 + 16 + length of packet, id is 'xml '. + L = 4 + 4 + 16 + len(packet.encode()) + buffer = struct.pack('>I4s', int(L), b'uuid') + tfile.write(buffer) + tfile.write(b'\xbe\x7a\xcf\xcb\x97\xa9\x42\xe8') + tfile.write(b'\x9c\x71\x99\x94\x91\xe3\xaf\xac') + tfile.write(packet.encode()) + + # Get the rest of the input file. + buffer = ifile.read() + tfile.write(buffer) + tfile.flush() + + @classmethod + def tearDownClass(cls): + os.unlink(cls._xmp_file) + + def setUp(self): + self.jp2file = pkg_resources.resource_filename(glymur.__name__, + "data/nemo.jp2") + + def tearDown(self): + pass + + def test_basic_xmp(self): + b = b'\xbe\x7a\xcf\xcb\x97\xa9\x42\xe8\x9c\x71\x99\x94\x91\xe3\xaf\xac' + xmp_uuid = uuid.UUID(bytes=b) + jp2 = Jp2k(self._xmp_file) + self.assertEqual(jp2.box[3].uuid, xmp_uuid) + + +if __name__ == "__main__": + unittest.main() From bc67f1f3cd14d54398ef743e81416fcbda9dd7a1 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 30 May 2013 15:44:24 -0400 Subject: [PATCH 06/14] Pep8 work. --- glymur/test/test_jp2k.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index ae6af19..ff46d8c 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -53,7 +53,7 @@ class TestJp2k(unittest.TestCase): @classmethod def setUpClass(cls): jp2file = pkg_resources.resource_filename(glymur.__name__, - "data/nemo.jp2") + "data/nemo.jp2") with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile: cls._bad_xml_file = tfile.name with open(jp2file, 'rb') as ifile: From be0163f418470250e4977c0204181490c77ac65d Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 30 May 2013 15:44:32 -0400 Subject: [PATCH 07/14] Requiring precinct size to be a multiple of two. --- glymur/jp2k.py | 16 ++++++++++++---- glymur/test/test_opj_suite_neg.py | 13 ++++++++++++- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 6eb23fd..5c7e361 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -8,6 +8,7 @@ if sys.hexversion >= 0x03030000: else: from contextlib2 import ExitStack import ctypes +import math import os import struct import warnings @@ -181,7 +182,8 @@ class Jp2k(Jp2kBox): psnr : list, optional Different PSNR for successive layers. psizes : list, optional - List of precinct sizes. + List of precinct sizes. Each precinct size tuple is defined in + (height x width). sop : bool, optional If true, write SOP marker before each packet. subsam : tuple, optional @@ -266,9 +268,15 @@ class Jp2k(Jp2kBox): cparams.cp_fixed_quality = 1 if psizes is not None: - for j, precinct in enumerate(psizes): - cparams.prcw_init[j] = precinct[0] - cparams.prch_init[j] = precinct[1] + for j, (prch, prcw) in enumerate(psizes): + if ((math.log(prch, 2) != math.floor(math.log(prch, 2)) or + math.log(prcw, 2) != math.floor(math.log(prcw, 2)))): + msg = "Bad precinct size ({0}, {1}), " + msg += "must be multiple of 2." + raise IOError(msg.format(prch, prcw)) + + cparams.prcw_init[j] = prcw + cparams.prch_init[j] = prch cparams.csty |= 0x01 cparams.res_spec = len(psizes) diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index 8296861..f33b981 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -9,6 +9,7 @@ import unittest import warnings import numpy as np +import pkg_resources from ..lib import openjp2 as opj2 @@ -62,7 +63,8 @@ def read_image(infile): class TestSuiteNegative(unittest.TestCase): def setUp(self): - pass + self.jp2file = pkg_resources.resource_filename(glymur.__name__, + "data/nemo.jp2") def tearDown(self): pass @@ -150,5 +152,14 @@ class TestSuiteNegative(unittest.TestCase): with self.assertWarns(UserWarning) as cw: j = Jp2k(infile) + def test_precinct_size_not_multiple_of_two(self): + # Seems like precinct size should be a multiple of two. + ifile = Jp2k(self.jp2file) + data = ifile.read(reduce=3) + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + ofile = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError) as ce: + ofile.write(data, psizes=[(13, 13)]) + if __name__ == "__main__": unittest.main() From e44c817b4eb5db1413a0ac7d6af5f5f793ad4ff9 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 30 May 2013 15:45:34 -0400 Subject: [PATCH 08/14] Precinct sizes required to be multiple of two. --- CHANGES.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 8af21bd..2c87915 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ -May 28, 2013 - Added jp2 boxes to rst docs, XMLBox.indent method made private. +May 30, 2013 - Added jp2 boxes to rst docs, XMLBox.indent method made private. + Precinct sizes must be multiples of two. May 27, 2013 - v0.1.1, Changed write example to not rely on matplotlib. Fixed readthedocs.org setup to build documentation automatically. Can import From e359f3f66690bf4d58fb834748e94135e74cf02c Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 30 May 2013 20:31:58 -0400 Subject: [PATCH 09/14] Added exif and xmp uuids from original jpeg. --- glymur/data/nemo.jp2 | Bin 1133504 -> 1136554 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/glymur/data/nemo.jp2 b/glymur/data/nemo.jp2 index 1b97d0dc85cb4722b6b04f2973e03583a0d25048..2bb8638b80ce3f116bd5f2197a40198c0dfc7e5c 100644 GIT binary patch delta 3137 zcmcJR&ui0Q7{{Na?S6FA`Rn8lh*41nZPKhm8`=)->NfSAvK8hvYqKq^ZEBj?t_O=k zaCn*`h~QyYMcB!cpdP$=@Lv$Tco6&-eBLD6(z(&Nu7S7j_j%rLp3n0>(fnOAFZrtgPu@uXwu|7{lN!|a}z}+uc-!1VocLp`v6>C zbIRPI*7aVy9Z{0Rz&Z=3Fm(#;L04Zv%{+HJ4C2EtoHmoR|h*FyjySC7+|@sY&Xh%8F7nBZb@Iet|h9oaLdpud2A7BAvVZzgdK)eat@2{3>b z1_IQG)1p>7v}yarke&aHg#vDV4)Z~9@pxrq9N4FyP|K7%Ji zSYsUZIbfSMK8FE)hH=qibewcQf5f@Lgd5{9?EKdrCy${WpLBP@-ACPhZeliaEe>#f zHZwJax#?_f?(WQO&$RIV0R2V!34UsP|Mu$bjbCpbyngxo=jQw5SiPd;msOLSR~NN% zivRQRGtX&-6hAjgL{iD9OWI_^P-h#{xqM?epD6HS>2R_x*GrX>YARg4R4iBJdWu&H z`n)QmrfGraY>T;^;_s8@JuaggDi@E&qhbWV`iByq4s%JPuprNj-*jtWl;W36vmy&Z ztyYWHMxwg0D8v$pgdj?SBt_5>sjikyr5-6)FVGTBjd8V_H?)eW>1B@AROa;+GsW|G z&e7sc+F_}(UA21-Ib*1bsT(<6FQ)j#;%aHd;04;Q;P#96 z7q$mLn`rkm+Zo!Q`=IP!#5cbqif?|GD6M=i#P|JMOZbSql>P0U^8Had<#$T)$L}7+ z!`}tzbpD>oUb&0nJ!;h*@qJTTncm0;{}3o%VyoWfca^fm{1>v#-?ALS{x4y z59Q$e!=bd}cbj7K_k9t6oMfxogw#a}*1}$>LB1bJUWRYVZ$nom!za8(-@c;k- delta 81 zcmZ3r-u1wA*9m^j7Ch}1Jd8lh1jNih%mT!$K+Fcj>_E%`#GF9P1;pGy3{uYv#C$-^ N55xl7EqDZrya9uu6X5^= From b0b08d537ca79fd67c302577025a7e9f69fc7c6b Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 30 May 2013 20:32:37 -0400 Subject: [PATCH 10/14] Recognizing XMP uuid data as xml. --- glymur/jp2box.py | 94 +++++++++++++++++++++++------------ glymur/jp2k.py | 2 +- glymur/test/__init__.py | 1 - glymur/test/test_callbacks.py | 2 +- glymur/test/test_jp2k.py | 32 ++++++++---- glymur/test/test_printing.py | 55 +++++++++++++------- glymur/test/test_xmp.py | 84 ------------------------------- 7 files changed, 121 insertions(+), 149 deletions(-) delete mode 100644 glymur/test/test_xmp.py diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 47f7bda..a435f67 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1288,37 +1288,11 @@ class XMLBox(Jp2kBox): Jp2kBox.__init__(self, id='', longname='XML') self.__dict__.update(**kwargs) - def _indent(self, elem, level=0): - """recipe for pretty printing XML. Please see - - http://effbot.org/zone/element-lib.htm#prettyprint - """ - i = "\n" + level * " " - if len(elem): - if not elem.text or not elem.text.strip(): - elem.text = i + " " - if not elem.tail or not elem.tail.strip(): - elem.tail = i - for elem in elem: - self._indent(elem, level + 1) - if not elem.tail or not elem.tail.strip(): - elem.tail = i - else: - if level and (not elem.tail or not elem.tail.strip()): - elem.tail = i - def __str__(self): msg = Jp2kBox.__str__(self) xml = self.xml if self.xml is not None: - xml = copy.deepcopy(self.xml) - self._indent(xml) - xmltext = ET.tostring(xml).decode('utf-8') - - # Indent it a bit. - lst = [(' ' + x) for x in xmltext.split('\n')] - xml = '\n'.join(lst) - msg += '\n{0}'.format(xml) + msg += _pretty_print_xml(self.xml) else: msg += '\n {0}'.format(xml) return msg @@ -1581,17 +1555,30 @@ class UUIDBox(Jp2kBox): more verbose description of the box. uuid : uuid.UUID 16-byte UUID - data : bytes - Vendor-specific UUID data. + data : bytes or ElementTree.Element + Vendor-specific UUID data. XMP UUIDs are interpreted as standard XML. """ def __init__(self, **kwargs): Jp2kBox.__init__(self, id='', longname='UUID') self.__dict__.update(**kwargs) def __str__(self): - msg = Jp2kBox.__str__(self) - msg += '\n UUID: {0}'.format(self.uuid) - msg += '\n UUID Data: {0} bytes'.format(len(self.data)) + msg = '{0}\n' + msg += ' UUID: {1}{2}\n' + msg += ' UUID Data: {3}' + + if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): + uuid_type = ' (XMP)' + uuid_data = _pretty_print_xml(self.data) + else: + uuid_type = '' + uuid_data = '{0} bytes'.format(len(self.data)) + + msg = msg.format(Jp2kBox.__str__(self), + self.uuid, + uuid_type, + uuid_data) + return msg @staticmethod @@ -1622,7 +1609,17 @@ class UUIDBox(Jp2kBox): n = offset + length - f.tell() buffer = f.read(n) - kwargs['data'] = buffer + if kwargs['uuid'] == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): + # XMP data. Parse as XML. Seems to be a difference between + # ElementTree in version 2.7 and 3.3. + if sys.hexversion < 0x03000000: + parser = ET.XMLParser(encoding='utf-8') + kwargs['data'] = ET.fromstringlist(buffer, parser=parser) + else: + text = buffer.decode('utf-8') + kwargs['data'] = ET.fromstring(text) + else: + kwargs['data'] = buffer box = UUIDBox(**kwargs) return box @@ -1649,3 +1646,34 @@ _box_with_id = { 'url ': DataEntryURLBox, 'uuid': UUIDBox, 'xml ': XMLBox} + +def _indent(elem, level=0): + """Recipe for pretty printing XML. Please see + + http://effbot.org/zone/element-lib.htm#prettyprint + """ + i = "\n" + level * " " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + _indent(elem, level + 1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + +def _pretty_print_xml(xml, level=0): + """Pretty print XML data. + """ + xml = copy.deepcopy(xml) + _indent(xml, level=level) + xmltext = ET.tostring(xml).decode('utf-8') + + # Indent it a bit. + lst = [(' ' + x) for x in xmltext.split('\n')] + xml = '\n'.join(lst) + return '\n{0}'.format(xml) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 6eb23fd..a4ba10a 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -634,7 +634,7 @@ class Jp2k(Jp2kBox): >>> jp = glymur.Jp2k(jfile) >>> codestream = jp.get_codestream() >>> print(codestream.segment[1]) - SIZ marker segment @ (87, 47) + SIZ marker segment @ (3137, 47) Profile: 2 Reference Grid Height, Width: (1456 x 2592) Vertical, Horizontal Reference Grid Offset: (0 x 0) diff --git a/glymur/test/__init__.py b/glymur/test/__init__.py index f396e49..9677af5 100644 --- a/glymur/test/__init__.py +++ b/glymur/test/__init__.py @@ -5,4 +5,3 @@ 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 .test_opj_suite_neg import TestSuiteNegative as suiteneg -from .test_xmp import TestSuite as xmp diff --git a/glymur/test/test_callbacks.py b/glymur/test/test_callbacks.py index 1c70743..aae4ddc 100644 --- a/glymur/test/test_callbacks.py +++ b/glymur/test/test_callbacks.py @@ -53,7 +53,7 @@ class TestCallbacks(unittest.TestCase): d = j.read(reduce=3, verbose=True, area=(0, 0, 512, 1024)) actual = sys.stdout.getvalue().strip() - lines = ['[INFO] Start to read j2k main header (85).', + lines = ['[INFO] Start to read j2k main header (3135).', '[INFO] Main header has been correctly decoded.', '[INFO] Setting decoding area to 0,0,1024,512', '[INFO] Header of tile 0 / 17 has been read.', diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index ae6af19..4606414 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -155,7 +155,7 @@ class TestJp2k(unittest.TestCase): jp2k = Jp2k(self.jp2file) # top-level boxes - self.assertEqual(len(jp2k.box), 4) + self.assertEqual(len(jp2k.box), 6) self.assertEqual(jp2k.box[0].id, 'jP ') self.assertEqual(jp2k.box[0].offset, 0) @@ -172,9 +172,17 @@ class TestJp2k(unittest.TestCase): self.assertEqual(jp2k.box[2].length, 45) self.assertEqual(jp2k.box[2].longname, 'JP2 Header') - self.assertEqual(jp2k.box[3].id, 'jp2c') + self.assertEqual(jp2k.box[3].id, 'uuid') self.assertEqual(jp2k.box[3].offset, 77) - self.assertEqual(jp2k.box[3].length, 1133427) + self.assertEqual(jp2k.box[3].length, 638) + + self.assertEqual(jp2k.box[4].id, 'uuid') + self.assertEqual(jp2k.box[4].offset, 715) + self.assertEqual(jp2k.box[4].length, 2412) + + self.assertEqual(jp2k.box[5].id, 'jp2c') + self.assertEqual(jp2k.box[5].offset, 3127) + self.assertEqual(jp2k.box[5].length, 1133427) # jp2h super box self.assertEqual(len(jp2k.box[2].box), 2) @@ -216,7 +224,7 @@ class TestJp2k(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with open(self.jp2file, 'rb') as ifile: # Everything up until the jp2c box. - buffer = ifile.read(77) + buffer = ifile.read(3127) tfile.write(buffer) # The L field must be 1 in order to signal the presence of the @@ -237,9 +245,9 @@ class TestJp2k(unittest.TestCase): jp2k = Jp2k(tfile.name) - self.assertEqual(jp2k.box[3].id, 'jp2c') - self.assertEqual(jp2k.box[3].offset, 77) - self.assertEqual(jp2k.box[3].length, 1133427 + 8) + self.assertEqual(jp2k.box[5].id, 'jp2c') + self.assertEqual(jp2k.box[5].offset, 3127) + self.assertEqual(jp2k.box[5].length, 1133427 + 8) def test_L_is_zero(self): # Verify that boxes with the L field as zero are correctly read. @@ -544,17 +552,19 @@ class TestJp2k(unittest.TestCase): with open(self.jp2file, 'rb') as fp: data = fp.read() with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - tfile.write(data[0:129]) + # Codestream starts at byte 3127. SIZ marker at 3137. + # COD marker at 3186. Subsampling at 3180. + tfile.write(data[0:3179]) # Make the DY bytes of the SIZ segment zero. That means that # a subsampling factor is zero, which is illegal. tfile.write(b'\x00') - tfile.write(data[130:132]) + tfile.write(data[3180:3182]) tfile.write(b'\x00') - tfile.write(data[134:136]) + tfile.write(data[3184:3186]) tfile.write(b'\x00') - tfile.write(data[136:]) + tfile.write(data[3186:]) tfile.flush() with warnings.catch_warnings(): warnings.simplefilter("ignore") diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 299085d..0278d63 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -47,10 +47,26 @@ class TestPrinting(unittest.TestCase): ' Method: enumerated colorspace', ' Precedence: 0', ' Colorspace: sRGB', - 'Contiguous Codestream Box (jp2c) @ (77, 1133427)', + 'UUID Box (uuid) @ (77, 638)', + ' UUID: 4a706754-6966-6645-7869-662d3e4a5032', + ' UUID Data: 614 bytes', + 'UUID Box (uuid) @ (715, 2412)', + ' UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP)', + ' UUID Data: ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + 'Contiguous Codestream Box (jp2c) @ (3127, 1133427)', ' Main header:', - ' SOC marker segment @ (85, 0)', - ' SIZ marker segment @ (87, 47)', + ' SOC marker segment @ (3135, 0)', + ' SIZ marker segment @ (3137, 47)', ' Profile: 2', ' Reference Grid Height, Width: (1456 x 2592)', ' Vertical, Horizontal Reference Grid Offset: ' @@ -62,7 +78,7 @@ class TestPrinting(unittest.TestCase): ' Signed: (False, False, False)', ' Vertical, Horizontal Subsampling: ' + '((1, 1), (1, 1), (1, 1))', - ' COD marker segment @ (136, 12)', + ' COD marker segment @ (3186, 12)', ' Coding style:', ' Entropy coder, without partitions', ' SOP marker segments: False', @@ -86,7 +102,7 @@ class TestPrinting(unittest.TestCase): + 'False', ' Predictable termination: False', ' Segmentation symbols: False', - ' QCD marker segment @ (150, 19)', + ' QCD marker segment @ (3200, 19)', ' Quantization style: no quantization, ' + '2 guard bits', ' Step size: [(0, 8), (0, 9), (0, 9), ' @@ -98,10 +114,13 @@ class TestPrinting(unittest.TestCase): def tearDown(self): # Restore stdout. sys.stdout = self.stdout + #import pdb; pdb.set_trace() def test_jp2dump(self): glymur.jp2dump(self.jp2file) actual = sys.stdout.getvalue().strip() + self.actual = actual + self.expected = self.expectedNemo self.assertEqual(actual, self.expectedNemo) def test_COC_segment(self): @@ -110,7 +129,7 @@ class TestPrinting(unittest.TestCase): print(codestream.segment[5]) actual = sys.stdout.getvalue().strip() - lines = ['COC marker segment @ (183, 9)', + lines = ['COC marker segment @ (3233, 9)', ' Associated component: 1', ' Coding style for this component: ' + 'Entropy coder, PARTITION = 0', @@ -136,7 +155,7 @@ class TestPrinting(unittest.TestCase): print(codestream.segment[2]) actual = sys.stdout.getvalue().strip() - lines = ['COD marker segment @ (136, 12)', + lines = ['COD marker segment @ (3186, 12)', ' Coding style:', ' Entropy coder, without partitions', ' SOP marker segments: False', @@ -226,7 +245,7 @@ class TestPrinting(unittest.TestCase): print(codestream.segment[-1]) actual = sys.stdout.getvalue().strip() - lines = ['EOC marker segment @ (1133502, 0)'] + lines = ['EOC marker segment @ (1136552, 0)'] expected = '\n'.join(lines) self.assertEqual(actual, expected) @@ -313,7 +332,7 @@ class TestPrinting(unittest.TestCase): print(codestream.segment[6]) actual = sys.stdout.getvalue().strip() - lines = ['QCC marker segment @ (194, 20)', + lines = ['QCC marker segment @ (3244, 20)', ' Associated Component: 1', ' Quantization style: no quantization, 2 guard bits', ' Step size: [(0, 8), (0, 9), (0, 9), (0, 10), (0, 9), ' @@ -329,7 +348,7 @@ class TestPrinting(unittest.TestCase): print(codestream.segment[3]) actual = sys.stdout.getvalue().strip() - lines = ['QCD marker segment @ (150, 19)', + lines = ['QCD marker segment @ (3200, 19)', ' Quantization style: no quantization, 2 guard bits', ' Step size: [(0, 8), (0, 9), (0, 9), (0, 10), (0, 9), ' + '(0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), ' @@ -344,7 +363,7 @@ class TestPrinting(unittest.TestCase): print(codestream.segment[1]) actual = sys.stdout.getvalue().strip() - lines = ['SIZ marker segment @ (87, 47)', + lines = ['SIZ marker segment @ (3137, 47)', ' Profile: 2', ' Reference Grid Height, Width: (1456 x 2592)', ' Vertical, Horizontal Reference Grid Offset: (0 x 0)', @@ -364,7 +383,7 @@ class TestPrinting(unittest.TestCase): print(codestream.segment[0]) actual = sys.stdout.getvalue().strip() - lines = ['SOC marker segment @ (85, 0)'] + lines = ['SOC marker segment @ (3135, 0)'] expected = '\n'.join(lines) self.assertEqual(actual, expected) @@ -374,7 +393,7 @@ class TestPrinting(unittest.TestCase): print(codestream.segment[9]) actual = sys.stdout.getvalue().strip() - lines = ['SOD marker segment @ (249, 0)'] + lines = ['SOD marker segment @ (3299, 0)'] expected = '\n'.join(lines) self.assertEqual(actual, expected) @@ -384,7 +403,7 @@ class TestPrinting(unittest.TestCase): print(codestream.segment[4]) actual = sys.stdout.getvalue().strip() - lines = ['SOT marker segment @ (171, 10)', + lines = ['SOT marker segment @ (3221, 10)', ' Tile part index: 0', ' Tile part length: 78629', ' Tile part instance: 0', @@ -422,8 +441,8 @@ class TestPrinting(unittest.TestCase): actual = sys.stdout.getvalue().strip() lst = ['Codestream:', - ' SOC marker segment @ (85, 0)', - ' SIZ marker segment @ (87, 47)', + ' SOC marker segment @ (3135, 0)', + ' SIZ marker segment @ (3137, 47)', ' Profile: 2', ' Reference Grid Height, Width: (1456 x 2592)', ' Vertical, Horizontal Reference Grid Offset: (0 x 0)', @@ -433,7 +452,7 @@ class TestPrinting(unittest.TestCase): ' Signed: (False, False, False)', ' Vertical, Horizontal Subsampling: ' + '((1, 1), (1, 1), (1, 1))', - ' COD marker segment @ (136, 12)', + ' COD marker segment @ (3186, 12)', ' Coding style:', ' Entropy coder, without partitions', ' SOP marker segments: False', @@ -455,7 +474,7 @@ class TestPrinting(unittest.TestCase): ' Vertically stripe causal context: False', ' Predictable termination: False', ' Segmentation symbols: False', - ' QCD marker segment @ (150, 19)', + ' QCD marker segment @ (3200, 19)', ' Quantization style: no quantization, ' + '2 guard bits', ' Step size: [(0, 8), (0, 9), (0, 9), ' diff --git a/glymur/test/test_xmp.py b/glymur/test/test_xmp.py deleted file mode 100644 index 14b3b6f..0000000 --- a/glymur/test/test_xmp.py +++ /dev/null @@ -1,84 +0,0 @@ -import os -import struct -import sys -import tempfile -import uuid -import unittest -if sys.hexversion <= 0x03030000: - from mock import patch -else: - from unittest.mock import patch -import warnings -from xml.etree import cElementTree as ET - -import pkg_resources - -from glymur import Jp2k -from glymur.lib import openjp2 as opj2 -import glymur - -lines = [' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' Rubbertree' - + ' ' - + ' ' - + ' ' - + ' \n' - + ''] -packet = '\n'.join(lines) - -class TestSuite(unittest.TestCase): - - @classmethod - def setUpClass(cls): - jp2file = pkg_resources.resource_filename(glymur.__name__, - "data/nemo.jp2") - with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile: - cls._xmp_file = tfile.name - with open(jp2file, 'rb') as ifile: - # Everything up until the jp2c box. - buffer = ifile.read(77) - tfile.write(buffer) - - # Write the uuid box with bad xml - # Length = 4 + 4 + 16 + length of packet, id is 'xml '. - L = 4 + 4 + 16 + len(packet.encode()) - buffer = struct.pack('>I4s', int(L), b'uuid') - tfile.write(buffer) - tfile.write(b'\xbe\x7a\xcf\xcb\x97\xa9\x42\xe8') - tfile.write(b'\x9c\x71\x99\x94\x91\xe3\xaf\xac') - tfile.write(packet.encode()) - - # Get the rest of the input file. - buffer = ifile.read() - tfile.write(buffer) - tfile.flush() - - @classmethod - def tearDownClass(cls): - os.unlink(cls._xmp_file) - - def setUp(self): - self.jp2file = pkg_resources.resource_filename(glymur.__name__, - "data/nemo.jp2") - - def tearDown(self): - pass - - def test_basic_xmp(self): - b = b'\xbe\x7a\xcf\xcb\x97\xa9\x42\xe8\x9c\x71\x99\x94\x91\xe3\xaf\xac' - xmp_uuid = uuid.UUID(bytes=b) - jp2 = Jp2k(self._xmp_file) - self.assertEqual(jp2.box[3].uuid, xmp_uuid) - - -if __name__ == "__main__": - unittest.main() From ae12204a3871b6f5ded0e7e410fb984f0090f2f5 Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 30 May 2013 20:48:33 -0400 Subject: [PATCH 11/14] Bumping for 0.2 release. --- CHANGES.txt | 12 ++++++------ docs/source/conf.py | 4 ++-- docs/source/introduction.rst | 6 +++--- setup.py | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2c87915..081f4f2 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,8 +1,8 @@ -May 30, 2013 - Added jp2 boxes to rst docs, XMLBox.indent method made private. - Precinct sizes must be multiples of two. +May 30, 2013 - v0.2.0. Added XMP UUID read support. Added jp2 boxes to rst + docs, XMLBox.indent method made into a private module method. Precinct + sizes restricted to be multiples of two. -May 27, 2013 - v0.1.1, Changed write example to not rely on matplotlib. Fixed +May 27, 2013 - v0.1.1. Changed write example to not rely on matplotlib. Fixed readthedocs.org setup to build documentation automatically. Can import - glymur without libopenjp2 actually being present. - -May 27, 2013 - Changed write example to not rely on matplotlib. + glymur without libopenjp2 actually being present. Changed write example + to not rely on matplotlib. diff --git a/docs/source/conf.py b/docs/source/conf.py index a467ed2..a12c112 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -73,9 +73,9 @@ copyright = u'2013, John Evans' # built documents. # # The short X.Y version. -version = '0.1' +version = '0.2' # The full version, including alpha/beta/rc tags. -release = '0.1.1' +release = '0.2.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index 5f4ccb3..d07943f 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -7,9 +7,9 @@ which allows linux and mac users to read and write JPEG 2000 files. For more information about OpenJPEG, please consult http://www.openjpeg.org. glymur should be considered to be alpha-quality software. -glymur tries to support reading (including all metadata) and writing of -JP2 and J2C files. Writing J2C/JP2 files is currently limited to images that -can fit in memory, however. +glymur tries to support reading and writing of JP2 and J2C files. Writing +J2C/JP2 files is currently limited to images that can fit in memory, however. +Of particular focus is metadata retrieval, such as XMP packets. There is some very limited support for reading JPX metadata. For instance, **asoc** and **labl** boxes are recognized, so GMLJP2 diff --git a/setup.py b/setup.py index 48ef364..85ceb91 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from distutils.core import setup kwargs = {'name': 'glymur', - 'version': '0.1.1', + 'version': '0.2.0', 'description': 'Tools for manipulating JPEG2000 files', 'long_description': open('README').read(), 'author': 'John Evans', From e90df063913bd44eafcb96b14064d26a7073b150 Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 30 May 2013 21:00:59 -0400 Subject: [PATCH 12/14] Camelcased the name. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 85ceb91..3f06179 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from distutils.core import setup -kwargs = {'name': 'glymur', +kwargs = {'name': 'Glymur', 'version': '0.2.0', 'description': 'Tools for manipulating JPEG2000 files', 'long_description': open('README').read(), From 864086099822affd71c2fed2ff109058f4992974 Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 31 May 2013 08:25:04 -0400 Subject: [PATCH 13/14] Fixes issue#9. Just needed to flush the file stream. --- glymur/test/test_jp2k.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 2d372a7..64355fa 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -537,10 +537,9 @@ class TestJp2k(unittest.TestCase): # Now append the codestream. tfile2.write(codestream) + tfile2.flush() - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - jasoc = Jp2k(tfile2.name) + jasoc = Jp2k(tfile2.name) self.assertEqual(jasoc.box[3].id, 'asoc') self.assertEqual(jasoc.box[3].box[0].id, 'lbl ') self.assertEqual(jasoc.box[3].box[0].label, 'label') From 68b60aec95af1f710d62cdecbd5a0c4e7ef7ce35 Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 31 May 2013 08:30:07 -0400 Subject: [PATCH 14/14] Bumping back to 0.1.2 --- CHANGES.txt | 2 +- docs/source/conf.py | 4 ++-- setup.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 081f4f2..76abc9b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,4 @@ -May 30, 2013 - v0.2.0. Added XMP UUID read support. Added jp2 boxes to rst +May 30, 2013 - v0.1.2. Added XMP UUID read support. Added jp2 boxes to rst docs, XMLBox.indent method made into a private module method. Precinct sizes restricted to be multiples of two. diff --git a/docs/source/conf.py b/docs/source/conf.py index a12c112..8d48cf2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -73,9 +73,9 @@ copyright = u'2013, John Evans' # built documents. # # The short X.Y version. -version = '0.2' +version = '0.1' # The full version, including alpha/beta/rc tags. -release = '0.2.0' +release = '0.1.2' # 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 3f06179..97c7fb8 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,9 @@ from distutils.core import setup kwargs = {'name': 'Glymur', - 'version': '0.2.0', - 'description': 'Tools for manipulating JPEG2000 files', - 'long_description': open('README').read(), + 'version': '0.1.2', + 'description': 'Tools for accessing JPEG2000 files', + 'long_description': open('README.md').read(), 'author': 'John Evans', 'author_email': 'johnevans938 at gmail dot com', 'url': 'https://github.com/quintusdias/glymur',