From 3e490d0c28c962358f4b62a1ec43bec58ee9f9f6 Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 10 Mar 2014 20:44:24 -0400 Subject: [PATCH 01/11] Rewrote the test for a file truncated by 5000 bytes. #180 --- glymur/test/test_conformance.py | 38 +++++++++++++++++---------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/glymur/test/test_conformance.py b/glymur/test/test_conformance.py index 765f2f6..48db78a 100644 --- a/glymur/test/test_conformance.py +++ b/glymur/test/test_conformance.py @@ -62,6 +62,26 @@ class TestSuiteConformance(unittest.TestCase): with self.assertRaises(OSError): j2k.read(rlevel=-1) + def test_truncated_5000(self): + """File is missing last 5000 bytes.""" + with open(self.j2kfile, 'rb') as ifile: + data = ifile.read() + with tempfile.NamedTemporaryFile(suffix='.j2k') as ofile: + ofile.write(data[:-5000]) + ofile.flush() + + j2k = Jp2k(ofile.name) + with self.assertWarns(UserWarning): + codestream = j2k.get_codestream(header_only=False) + + # The last segment is truncated, so there should not be an EOC + # marker. + self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC') + + # The codestream is not as long as claimed. + with self.assertRaises(OSError): + j2k.read(rlevel=-1) + @unittest.skipIf(FORMAT_CORPUS_DATA_ROOT is None, "FORMAT_CORPUS_DATA_ROOT environment variable not set") @@ -70,24 +90,6 @@ class TestSuiteConformance(unittest.TestCase): class TestSuiteFormatCorpus(unittest.TestCase): """Test suite for files in format corpus repository.""" - @unittest.skipIf(re.match(r"""1\.[01234]""", - glymur.version.openjpeg_version) is not None, - "Needs 1.4+ to catch this.") - def test_balloon_trunc2(self): - """Shortened by 5000 bytes.""" - jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT, - 'jp2k-test/byteCorruption/balloon_trunc2.jp2') - j2k = Jp2k(jfile) - with self.assertWarns(UserWarning): - codestream = j2k.get_codestream(header_only=False) - - # The last segment is truncated, so there should not be an EOC marker. - self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC') - - # The codestream is not as long as claimed. - with self.assertRaises(OSError): - j2k.read(rlevel=-1) - def test_balloon_trunc3(self): """Most of last tile is missing.""" jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT, From 417503764e350ae3fe738a211653550c0215ad4f Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 11 Mar 2014 15:46:17 -0400 Subject: [PATCH 02/11] Updated through r2691. #139 --- docs/source/detailed_installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index 6bbc2f2..67c230a 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -13,7 +13,7 @@ both read and write JPEG 2000 files, but you may wish to install version 2.0 or the 2.0+ version from OpenJPEG's development trunk for better performance. If you do that, you should compile it as a shared library (named *openjp2* instead of *openjpeg*) from the developmental source that you can retrieve -via subversion. As of this time of writing, svn revision r2651 works. +via subversion. As of this time of writing, svn revision r2691 works. You should also download the test data for the purpose of configuring and running OpenJPEG's test suite, check their instructions for all this. You should set the **OPJ_DATA_ROOT** environment variable for the purpose From 1cefd80a76b3e47e0ac688814eff3dce38161bda Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 11 Mar 2014 21:02:15 -0400 Subject: [PATCH 03/11] Just remove the conformance tests, they aren't really needed. #180 --- glymur/test/test_conformance.py | 150 -------------------------------- glymur/test/test_opj_suite.py | 13 ++- 2 files changed, 9 insertions(+), 154 deletions(-) delete mode 100644 glymur/test/test_conformance.py diff --git a/glymur/test/test_conformance.py b/glymur/test/test_conformance.py deleted file mode 100644 index 48db78a..0000000 --- a/glymur/test/test_conformance.py +++ /dev/null @@ -1,150 +0,0 @@ -""" -These tests deal with JPX/JP2/J2K images in the format-corpus repository. -""" -# R0904: Not too many methods in unittest. -# pylint: disable=R0904 - -# E1101: assertWarns introduced in python 3.2 -# pylint: disable=E1101 - -import os -from os.path import join -import re -import sys -import tempfile -import unittest - -import glymur -from glymur import Jp2k - -try: - FORMAT_CORPUS_DATA_ROOT = os.environ['FORMAT_CORPUS_DATA_ROOT'] -except KeyError: - FORMAT_CORPUS_DATA_ROOT = None - -try: - OPJ_DATA_ROOT = os.environ['OPJ_DATA_ROOT'] -except KeyError: - OPJ_DATA_ROOT = None - - -@unittest.skipIf(sys.hexversion < 0x03020000, - "Requires features introduced in 3.2 (assertWarns)") -class TestSuiteConformance(unittest.TestCase): - """Test suite for conformance.""" - - def setUp(self): - self.j2kfile = glymur.data.goodstuff() - - def tearDown(self): - pass - - @unittest.skipIf(re.match(r"""1\.[0123]""", - glymur.version.openjpeg_version) is not None, - "Needs 1.3+ to catch this.") - def test_truncated_eoc(self): - """Has one byte shaved off of EOC marker.""" - with open(self.j2kfile, 'rb') as ifile: - data = ifile.read() - with tempfile.NamedTemporaryFile(suffix='.j2k') as ofile: - ofile.write(data[:-1]) - ofile.flush() - - j2k = Jp2k(ofile.name) - with self.assertWarns(UserWarning): - codestream = j2k.get_codestream(header_only=False) - - # The last segment is truncated, so there should not be an EOC - # marker. - self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC') - - # The codestream is not as long as claimed. - with self.assertRaises(OSError): - j2k.read(rlevel=-1) - - def test_truncated_5000(self): - """File is missing last 5000 bytes.""" - with open(self.j2kfile, 'rb') as ifile: - data = ifile.read() - with tempfile.NamedTemporaryFile(suffix='.j2k') as ofile: - ofile.write(data[:-5000]) - ofile.flush() - - j2k = Jp2k(ofile.name) - with self.assertWarns(UserWarning): - codestream = j2k.get_codestream(header_only=False) - - # The last segment is truncated, so there should not be an EOC - # marker. - self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC') - - # The codestream is not as long as claimed. - with self.assertRaises(OSError): - j2k.read(rlevel=-1) - - -@unittest.skipIf(FORMAT_CORPUS_DATA_ROOT is None, - "FORMAT_CORPUS_DATA_ROOT environment variable not set") -@unittest.skipIf(sys.hexversion < 0x03020000, - "Requires features introduced in 3.2 (assertWarns)") -class TestSuiteFormatCorpus(unittest.TestCase): - """Test suite for files in format corpus repository.""" - - def test_balloon_trunc3(self): - """Most of last tile is missing.""" - jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT, - 'jp2k-test/byteCorruption/balloon_trunc3.jp2') - j2k = Jp2k(jfile) - with self.assertWarns(UserWarning): - codestream = j2k.get_codestream(header_only=False) - - # The last segment is truncated, so there should not be an EOC marker. - self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC') - - # Should error out, it does not. - #with self.assertRaises(OSError): - # j2k.read(rlevel=-1) - - def test_jp2_brand_any_icc_profile(self): - """If 'jp2 ', then the method cannot be any icc profile.""" - jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT, - 'jp2k-test', 'icc', - 'balloon_eciRGBv2_ps_adobeplugin.jpf') - with self.assertWarns(UserWarning): - Jp2k(jfile) - - def test_jp2_brand_iccpr_mult_colr(self): - """Has colr box, one that conforms, one that does not.""" - - # Wrong 'brand' field; contains two versions of ICC profile: one - # embedded using "Any ICC" method; other embedded using "Restricted - # ICC" method, with description ("Modified eciRGB v2") and profileClass - # ("Input Device") changed relative to original profile. - jfile = join(FORMAT_CORPUS_DATA_ROOT, 'jp2k-test', 'icc', - 'balloon_eciRGBv2_ps_adobeplugin_jp2compatible.jpf') - with self.assertWarns(UserWarning): - Jp2k(jfile) - - -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -@unittest.skipIf(sys.hexversion < 0x03020000, - "Requires features introduced in 3.2 (assertWarns)") -class TestSuiteOpj(unittest.TestCase): - """Test suite for files in openjpeg repository.""" - - def setUp(self): - pass - - def tearDown(self): - pass - - def test_jp2_brand_any_icc_profile(self): - """If 'jp2 ', then the method cannot be any icc profile.""" - filename = os.path.join(OPJ_DATA_ROOT, - 'input/nonregression/text_GBR.jp2') - with self.assertWarns(UserWarning): - Jp2k(filename) - -if __name__ == "__main__": - unittest.main() diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index be85de9..df4cde2 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -6307,11 +6307,16 @@ class TestSuiteDump(unittest.TestCase): [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) def test_NR_text_GBR_dump(self): + # brand is 'jp2 ', but has any icc profile. + # Verify the warning on python3, but ignore it otherwise. jfile = opj_data_file('input/nonregression/text_GBR.jp2') - with warnings.catch_warnings(): - # brand is 'jp2 ', but has any icc profile. - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) + if sys.hexversion > 0x03030000: + with self.assertWarns(UserWarning): + jp2 = Jp2k(jfile) + else: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) ids = [box.box_id for box in jp2.box] lst = ['jP ', 'ftyp', 'rreq', 'jp2h', From d098dd5d5ed863e755fe9c9abc678eebb7150233 Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 11 Mar 2014 21:40:19 -0400 Subject: [PATCH 04/11] Removed consideration of openjpeg versions 1.3 and 1.4. #159 --- docs/source/changelog.rst | 1 + docs/source/introduction.rst | 12 +----------- glymur/jp2k.py | 13 ------------- glymur/test/test_jp2k.py | 8 +------- glymur/test/test_opj_suite.py | 6 +----- glymur/test/test_opj_suite_neg.py | 4 ---- glymur/test/test_opj_suite_write.py | 3 --- 7 files changed, 4 insertions(+), 43 deletions(-) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 7a96571..c3eae6c 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -9,6 +9,7 @@ ChangeLog * Added lxml requirement. * added set_printoptions, get_printoptions function * dropped support for Python 2.6, added support for Python 3.4 + * dropped support for OpenJPEG versions 1.3 and 1.4 * dropped windows support (it might work, it might not, I don't much care) * added write support for JP2 UUID, dataEntryURL, palette, and component mapping boxes * added read/write support for JPX free, number list, and data reference boxes diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index 38193eb..10df117 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -14,17 +14,7 @@ XMP UUIDs. There is some very limited support for reading JPX metadata. Glymur 0.6 works on Python versions 2.7, 3.3 and 3.4. If you have Python 2.6, you should use the 0.5 series of Glymur. -OpenJPEG Installation -===================== -Glymur will read JPEG 2000 images with versions 1.3, 1.4, 1.5, 2.0, -and the trunk/development version of OpenJPEG. Writing images is -only supported with the 1.5 or better, however, and the trunk/development -version of OpenJPEG is strongly recommended. For more information about -OpenJPEG, please consult http://www.openjpeg.org. - -If you use MacPorts or if you have a sufficiently recent version of -Linux, your package manager should already provide you with a version of -OpenJPEG 1.X which glymur can already use. +For more information about OpenJPEG, please consult http://www.openjpeg.org. Glymur Installation =================== diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 7522d35..3467426 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -772,19 +772,6 @@ class Jp2k(Jp2kBox): """ self._subsampling_sanity_check() - if rlevel != 0: - # Must check the specified rlevel against the maximum. - # OpenJPEG 1.3 will segfault if rlevel is too high. - codestream = self.get_codestream() - max_rlevel = codestream.segment[2].spcod[4] - if rlevel == -1: - # -1 is shorthand for the largest rlevel - rlevel = max_rlevel - if rlevel < -1 or rlevel > max_rlevel: - msg = "rlevel must be in the range [-1, {0}] for this image." - msg = msg.format(max_rlevel) - raise IOError(msg) - with ExitStack() as stack: try: # Set decoding parameters. diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index dd55299..433531f 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -386,15 +386,9 @@ class TestJp2k(unittest.TestCase): # The file in question has multiple codestreams. jpx = Jp2k(self.jpxfile) data = jpx.read() - if re.match(r"""1\.[0123]""", glymur.version.openjpeg_version): - # openjpeg 1.3 doesn't apply the palette, so it's a 2D image here - self.assertEqual(data.shape, (1024, 1024)) - else: - self.assertEqual(data.shape, (1024, 1024, 3)) + self.assertEqual(data.shape, (1024, 1024, 3)) -@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version), - "Requires at least version 1.5") class TestJp2k_write(unittest.TestCase): """Write tests, can be run by versions 1.5+""" diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index df4cde2..5cc9250 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -381,11 +381,7 @@ class TestSuite(unittest.TestCase): jfile = opj_data_file('input/conformance/file9.jp2') jp2k = Jp2k(jfile) jpdata = jp2k.read() - if re.match(r"""1\.3""", glymur.version.openjpeg_version): - # Version 1.3 reads the indexed image as indices, not as RGB. - self.assertEqual(jpdata.shape, (512, 768)) - else: - self.assertEqual(jpdata.shape, (512, 768, 3)) + self.assertEqual(jpdata.shape, (512, 768, 3)) def test_NR_DEC_Bretagne2_j2k_1_decode(self): jfile = opj_data_file('input/nonregression/Bretagne2.j2k') diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index d31ea48..c93d624 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -23,8 +23,6 @@ from glymur import Jp2k import glymur -@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version), - "Functionality not implemented for 1.3, 1.4") @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_OPJ_DATA_ROOT environment variable not set") class TestSuiteNegative2pointzero(unittest.TestCase): @@ -48,8 +46,6 @@ class TestSuiteNegative2pointzero(unittest.TestCase): j.write(data, psnr=[30, 35, 40], cratios=[2, 3, 4]) -@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version), - "Functionality not implemented for 1.3, 1.4") @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_OPJ_DATA_ROOT environment variable not set") class TestSuiteNegative(unittest.TestCase): diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 220115c..1db11c4 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -249,9 +249,6 @@ class TestSuiteNegative2pointzero(unittest.TestCase): @unittest.skipIf(os.name == "nt", "no write support on windows, period") -@unittest.skipIf(re.match(r"""1\.[01234]\.\d""", - glymur.version.openjpeg_version) is not None, - "Writing only supported with openjpeg version 1.5+.") @unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG) @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") From ac3aa4d6648c1955b220f158a9b81f94c9ca7b47 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 12 Mar 2014 08:57:11 -0400 Subject: [PATCH 05/11] alpha is a uint16, not a uint16 pointer. #139 --- glymur/lib/openjp2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py index 5430110..39dd5a4 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -395,7 +395,7 @@ class ImageCompType(ctypes.Structure): # alpha channel # TODO: exclude for 2.0, 1.5 - ("alpha", ctypes.POINTER(ctypes.c_uint16))] + ("alpha", ctypes.c_uint16)] class ImageType(ctypes.Structure): From 539ee438f65637c5cacab5b8f6d551b313b582ca Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 12 Mar 2014 09:16:15 -0400 Subject: [PATCH 06/11] Removed repeated/mangled testpoint. --- glymur/test/test_opj_suite_neg.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index c93d624..c9a7e8c 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -23,29 +23,6 @@ from glymur import Jp2k import glymur -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_OPJ_DATA_ROOT environment variable not set") -class TestSuiteNegative2pointzero(unittest.TestCase): - """Feature set not supported for versions less than 2.0""" - - def setUp(self): - self.jp2file = glymur.data.nemo() - self.j2kfile = glymur.data.goodstuff() - - def tearDown(self): - pass - - @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") - def test_cinema_mode(self): - """Cinema mode not supported for less than 2.0.1.""" - infile = opj_data_file('input/nonregression/Bretagne1.ppm') - data = read_image(infile) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - with self.assertRaises(IOError): - j.write(data, psnr=[30, 35, 40], cratios=[2, 3, 4]) - - @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_OPJ_DATA_ROOT environment variable not set") class TestSuiteNegative(unittest.TestCase): From b479ece7b488e236418014f9dd1ff5db1877b522 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 12 Mar 2014 10:10:38 -0400 Subject: [PATCH 07/11] Bring back check for bad rlevel. #159 OpenJPEG 1.5 needs the protection, it would seem. --- glymur/jp2k.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 3467426..7a513fe 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -772,6 +772,19 @@ class Jp2k(Jp2kBox): """ self._subsampling_sanity_check() + # Must check the specified rlevel against the maximum. + if rlevel != 0: + # Must check the specified rlevel against the maximum. + codestream = self.get_codestream() + max_rlevel = codestream.segment[2].spcod[4] + if rlevel == -1: + # -1 is shorthand for the largest rlevel + rlevel = max_rlevel + elif rlevel < -1 or rlevel > max_rlevel: + msg = "rlevel must be in the range [-1, {0}] for this image." + msg = msg.format(max_rlevel) + raise IOError(msg) + with ExitStack() as stack: try: # Set decoding parameters. From c56b919c36b0a7312dc9ea2c10617984549c0457 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 12 Mar 2014 12:27:52 -0400 Subject: [PATCH 08/11] Fixed printing of cmap boxes when not pclr. #182 --- glymur/jp2box.py | 2 +- glymur/test/fixtures.py | 5 +++++ glymur/test/test_printing.py | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 6db6c87..a106ee4 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -829,7 +829,7 @@ class ComponentMappingBox(Jp2kBox): msg = msg.format(self.component_index[k], self.palette_index[k]) else: - msg += '\n Component %d ==> %d' + msg += '\n Component {0} ==> {1}' msg = msg.format(self.component_index[k], k) return msg diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 2004d2b..63f5837 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -577,3 +577,8 @@ file1_xml = """XML Box (xml ) @ (36, 439) \t\tProfessional 120 Image \t """ + +issue_182_cmap = """Component Mapping Box (cmap) @ (130, 24) + Component 0 ==> palette column 0 + Component 1 ==> palette column 0 + Component 2 ==> 2""" diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 0deaebd..3dd58f5 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -905,6 +905,22 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) + def test_issue182(self): + """Should not show the format string in output.""" + # The cmap box is wildly broken, but printing was still wrong. + # Format strings like %d were showing up in the output. + filename = opj_data_file('input/nonregression/mem-b2ace68c-1381.jp2') + + with warnings.catch_warnings(): + # Ignore warning about bad pclr box. + warnings.simplefilter("ignore") + jp2 = Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(jp2.box[3].box[3]) + actual = fake_out.getvalue().strip() + self.assertEqual(actual, fixtures.issue_182_cmap) + + @unittest.skipIf(sys.hexversion < 0x03000000, "Ordered dicts not printing well in 2.7") def test_exif_uuid(self): From 77d2ab194a6f1d1b7d486c11b55bbff85a190f70 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 12 Mar 2014 19:21:35 -0400 Subject: [PATCH 09/11] Checking for ICC profile that is None. #183 --- glymur/jp2box.py | 15 +++++++++------ glymur/test/fixtures.py | 5 +++++ glymur/test/test_printing.py | 11 +++++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index a106ee4..c309a26 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -359,13 +359,16 @@ class ColourSpecificationBox(Jp2kBox): else: # 2.7 has trouble pretty-printing ordered dicts so we just have # to print as a regular dict in this case. - if sys.hexversion < 0x03000000: - icc_profile = dict(self.icc_profile) + if self.icc_profile is None: + msg += '\n ICC Profile: None' else: - icc_profile = self.icc_profile - dispvalue = pprint.pformat(icc_profile) - lines = [' ' * 8 + y for y in dispvalue.split('\n')] - msg += '\n ICC Profile:\n{0}'.format('\n'.join(lines)) + if sys.hexversion < 0x03000000: + icc_profile = dict(self.icc_profile) + else: + icc_profile = self.icc_profile + dispvalue = pprint.pformat(icc_profile) + lines = [' ' * 8 + y for y in dispvalue.split('\n')] + msg += '\n ICC Profile:\n{0}'.format('\n'.join(lines)) return msg diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 63f5837..b47f211 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -582,3 +582,8 @@ issue_182_cmap = """Component Mapping Box (cmap) @ (130, 24) Component 0 ==> palette column 0 Component 1 ==> palette column 0 Component 2 ==> 2""" + +issue_183_colr = """Colour Specification Box (colr) @ (62, 12) + Method: restricted ICC profile + Precedence: 0 + ICC Profile: None""" diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 3dd58f5..6a819ac 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -920,6 +920,17 @@ class TestPrinting(unittest.TestCase): actual = fake_out.getvalue().strip() self.assertEqual(actual, fixtures.issue_182_cmap) + def test_issue183(self): + filename = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2') + + with warnings.catch_warnings(): + # Ignore warning about bad pclr box. + warnings.simplefilter("ignore") + jp2 = Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(jp2.box[2].box[1]) + actual = fake_out.getvalue().strip() + self.assertEqual(actual, fixtures.issue_183_colr) @unittest.skipIf(sys.hexversion < 0x03000000, "Ordered dicts not printing well in 2.7") From 2ae848b6f0cf3d025c1877851e249cea03118738 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 12 Mar 2014 20:03:04 -0400 Subject: [PATCH 10/11] Checking for division by zero with bad tile dimensions. #181 --- glymur/codestream.py | 20 ++++++++++------- glymur/test/test_codestream.py | 39 +++++++++++++++++++++------------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/glymur/codestream.py b/glymur/codestream.py index 4259788..cf21ec5 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -673,6 +673,18 @@ class Codestream(object): msg = msg.format(j, subsampling[0], subsampling[1]) warnings.warn(msg) + try: + num_tiles_x = (xysiz[0] - xyosiz[0]) / (xytsiz[0] - xytosiz[0]) + num_tiles_y = (xysiz[1] - xyosiz[1]) / (xytsiz[1] - xytosiz[1]) + except ZeroDivisionError as err: + warnings.warn("Invalid tile dimensions.") + else: + numtiles = math.ceil(num_tiles_x) * math.ceil(num_tiles_y) + if numtiles > 65535: + msg = "Invalid number of tiles ({0}).".format(numtiles) + warnings.warn(msg) + + kwargs = {'rsiz': rsiz, 'xysiz': xysiz, 'xyosiz': xyosiz, @@ -1514,14 +1526,6 @@ class SIZsegment(Segment): lst.append(bitdepth - 1) self.ssiz = tuple(lst) - num_tiles_x = (self.xsiz - self.xosiz) / (self.xtsiz - self.xtosiz) - num_tiles_y = (self.ysiz - self.yosiz) / (self.ytsiz - self.ytosiz) - numtiles = math.ceil(num_tiles_x) * math.ceil(num_tiles_y) - if numtiles > 65535: - msg = "Invalid number of tiles ({0}).".format(numtiles) - warnings.warn(msg) - - def __repr__(self): msg = "glymur.codestream.SIZsegment(rsiz={rsiz}, xysiz={xysiz}, " msg += "xyosiz={xyosiz}, xytsiz={xytsiz}, xytosiz={xytosiz}, " diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py index 5edd144..6e5cc43 100644 --- a/glymur/test/test_codestream.py +++ b/glymur/test/test_codestream.py @@ -13,17 +13,12 @@ import struct import sys import tempfile import unittest +import warnings from glymur import Jp2k import glymur -try: - DATA_ROOT = os.environ['OPJ_DATA_ROOT'] -except KeyError: - DATA_ROOT = None -except: - raise - +from .fixtures import opj_data_file, OPJ_DATA_ROOT class TestCodestream(unittest.TestCase): """Test suite for unusual codestream cases.""" @@ -34,7 +29,21 @@ class TestCodestream(unittest.TestCase): def tearDown(self): pass - @unittest.skipIf(DATA_ROOT is None, + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_tile_height_is_zero(self): + """Zero tile height should not cause an exception.""" + filename = opj_data_file('input/nonregression/2539.pdf.SIGFPE.706.1712.jp2') + if sys.hexversion < 0x03000000: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + Jp2k(filename) + else: + with self.assertWarns(UserWarning): + Jp2k(jfile) + + + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_reserved_marker_segment(self): @@ -45,7 +54,7 @@ class TestCodestream(unittest.TestCase): # # Let's inject a reserved marker segment into a file that # we know something about to make sure we can still parse it. - filename = os.path.join(DATA_ROOT, 'input/conformance/p0_01.j2k') + filename = os.path.join(OPJ_DATA_ROOT, 'input/conformance/p0_01.j2k') with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with open(filename, 'rb') as ifile: # Everything up until the first QCD marker. @@ -67,7 +76,7 @@ class TestCodestream(unittest.TestCase): self.assertEqual(codestream.segment[2].length, 3) self.assertEqual(codestream.segment[2].data, b'\x00') - @unittest.skipIf(DATA_ROOT is None, + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(sys.hexversion < 0x03020000, "Uses features introduced in 3.2.") @@ -77,7 +86,7 @@ class TestCodestream(unittest.TestCase): # Let's inject a marker segment whose marker does not appear to # be valid. We still parse the file, but warn about the offending # marker. - filename = os.path.join(DATA_ROOT, 'input/conformance/p0_01.j2k') + filename = os.path.join(OPJ_DATA_ROOT, 'input/conformance/p0_01.j2k') with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with open(filename, 'rb') as ifile: # Everything up until the first QCD marker. @@ -100,11 +109,11 @@ class TestCodestream(unittest.TestCase): self.assertEqual(codestream.segment[2].length, 3) self.assertEqual(codestream.segment[2].data, b'\x00') - @unittest.skipIf(DATA_ROOT is None, + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") def test_psot_is_zero(self): """Psot=0 in SOT is perfectly legal. Issue #78.""" - filename = os.path.join(DATA_ROOT, + filename = os.path.join(OPJ_DATA_ROOT, 'input/nonregression/123.j2c') j = Jp2k(filename) codestream = j.get_codestream(header_only=False) @@ -125,11 +134,11 @@ class TestCodestream(unittest.TestCase): self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7)) - @unittest.skipIf(DATA_ROOT is None, + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") def test_siz_segment_ssiz_signed(self): """ssiz attribute to be removed in future release""" - filename = os.path.join(DATA_ROOT, 'input/conformance/p0_03.j2k') + filename = os.path.join(OPJ_DATA_ROOT, 'input/conformance/p0_03.j2k') j = Jp2k(filename) codestream = j.get_codestream() From 42e1b213561b3952e42321218ae87bc1108cdf4a Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 12 Mar 2014 20:50:42 -0400 Subject: [PATCH 11/11] Should not raise an exception during parsing of XML box. #184 --- glymur/jp2box.py | 15 +++++++++------ glymur/test/test_codestream.py | 2 +- glymur/test/test_jp2box_xml.py | 17 +++++++++++++++++ 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index c309a26..5dba568 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2631,19 +2631,22 @@ class XMLBox(Jp2kBox): read_buffer = fptr.read(num_bytes) try: text = read_buffer.decode('utf-8') - except UnicodeDecodeError as ude: + except UnicodeDecodeError as err: # Possibly bad string of bytes to begin with. # Try to search for -1: - text = read_buffer[decl_start:].decode('utf-8') - else: - raise + if decl_start <= -1: + msg = 'A problem was encountered while parsing an XML box:' + msg += '\n\n\t"{0}"\n\nNo XML was retrieved.' + warnings.warn(msg.format(str(err))) + return XMLBox(xml=None, length=length, offset=offset) + + text = read_buffer[decl_start:].decode('utf-8') # Let the user know that the XML box was problematic. msg = 'A UnicodeDecodeError was encountered parsing an XML box at ' msg += 'byte position {0} ({1}), but the XML was still recovered.' - msg = msg.format(offset, ude.reason) + msg = msg.format(offset, err.reason) warnings.warn(msg, UserWarning) # Strip out any trailing nulls, as they can foul up XML parsing. diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py index 6e5cc43..ce03ef7 100644 --- a/glymur/test/test_codestream.py +++ b/glymur/test/test_codestream.py @@ -40,7 +40,7 @@ class TestCodestream(unittest.TestCase): Jp2k(filename) else: with self.assertWarns(UserWarning): - Jp2k(jfile) + Jp2k(filename) @unittest.skipIf(OPJ_DATA_ROOT is None, diff --git a/glymur/test/test_jp2box_xml.py b/glymur/test/test_jp2box_xml.py index facc4df..9194a49 100644 --- a/glymur/test/test_jp2box_xml.py +++ b/glymur/test/test_jp2box_xml.py @@ -39,6 +39,7 @@ from glymur.jp2box import ColourSpecificationBox, ContiguousCodestreamBox from glymur.jp2box import FileTypeBox, ImageHeaderBox, JP2HeaderBox from glymur.jp2box import JPEG2000SignatureBox +from .fixtures import OPJ_DATA_ROOT, opj_data_file @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") class TestXML(unittest.TestCase): @@ -93,6 +94,22 @@ class TestXML(unittest.TestCase): def tearDown(self): os.unlink(self.xmlfile) + def test_invalid_utf8(self): + """Bad byte sequence that cannot be parsed.""" + filename = opj_data_file(os.path.join('input', + 'nonregression', + '26ccf3651020967f7778238ef5af08af.SIGFPE.d25.527.jp2')) + if sys.hexversion < 0x03000000: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + jp2 = Jp2k(filename) + else: + with self.assertWarns(UserWarning): + jp2 = Jp2k(filename) + + self.assertIsNone(jp2.box[3].box[1].box[1].xml) + + def test_negative_file_and_xml(self): """The XML should come from only one source.""" xml_object = ET.parse(self.xmlfile)