diff --git a/glymur/codestream.py b/glymur/codestream.py index cf21ec5..35e3fee 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -17,6 +17,7 @@ codestreams. # the base Segment class. # pylint: disable=R0903 +import collections import math import struct import sys @@ -30,12 +31,26 @@ from .core import WAVELET_XFORM_5X3_REVERSIBLE from .core import _CAPABILITIES_DISPLAY from .lib import openjp2 as opj2 -_PROGRESSION_ORDER_DISPLAY = { - LRCP: 'LRCP', - RLCP: 'RLCP', - RPCL: 'RPCL', - PCRL: 'PCRL', - CPRL: 'CPRL'} +class _keydefaultdict(collections.defaultdict): + """Unlisted keys help form their own error message. + + Normally defaultdict uses a factory function with no input arguments, but + that's not quite the behavior we want. + """ + def __missing__(self, key): + if self.default_factory is None: + raise KeyError(key) + else: + ret = self[key] = self.default_factory(key) + return ret + +_factory = lambda x: '{0} (invalid)'.format(x) +_PROGRESSION_ORDER_DISPLAY = _keydefaultdict(_factory, + { LRCP: 'LRCP', + RLCP: 'RLCP', + RPCL: 'RPCL', + PCRL: 'PCRL', + CPRL: 'CPRL'}) _WAVELET_TRANSFORM_DISPLAY = { WAVELET_XFORM_9X7_IRREVERSIBLE: '9-7 irreversible', @@ -371,6 +386,9 @@ class Codestream(object): numbytes = offset + 2 + length - fptr.tell() spcod = fptr.read(numbytes) spcod = np.frombuffer(spcod, dtype=np.uint8) + if spcod[0] not in [LRCP, RLCP, RPCL, PCRL, CPRL]: + msg = "Invalid progression order in COD segment: {0}." + warnings.warn(msg.format(spcod[0])) sop = (scod & 2) > 0 eph = (scod & 4) > 0 diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index b47f211..8e8deb8 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -587,3 +587,26 @@ issue_183_colr = """Colour Specification Box (colr) @ (62, 12) Method: restricted ICC profile Precedence: 0 ICC Profile: None""" + + +# Progression order is invalid. +issue_186_progression_order = """COD marker segment @ (174, 12) + Coding style: + Entropy coder, without partitions + SOP marker segments: False + EPH marker segments: False + Coding style parameters: + Progression order: 33 (invalid) + Number of layers: 1 + Multiple component transformation usage: reversible + Number of resolutions: 6 + Code block height, width: (32 x 32) + Wavelet transform: 9-7 irreversible + Precinct size: default, 2^15 x 2^15 + Code block context: + Selective arithmetic coding bypass: False + Reset context probabilities on coding pass boundaries: False + Termination on each coding pass: False + Vertically stripe causal context: False + Predictable termination: False + Segmentation symbols: False""" diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py index ce03ef7..28ea0f8 100644 --- a/glymur/test/test_codestream.py +++ b/glymur/test/test_codestream.py @@ -29,8 +29,39 @@ class TestCodestream(unittest.TestCase): def tearDown(self): pass - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") + def test_siz_segment_ssiz_unsigned(self): + """ssiz attribute to be removed in future release""" + j = Jp2k(self.jp2file) + codestream = j.get_codestream() + + # The ssiz attribute was simply a tuple of raw bytes. + # The first 7 bits are interpreted as the bitdepth, the MSB determines + # whether or not it is signed. + self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7)) + + +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class TestCodestreamOpjData(unittest.TestCase): + """Test suite for unusual codestream cases. Uses OPJ_DATA_ROOT""" + + def setUp(self): + self.jp2file = glymur.data.nemo() + + def tearDown(self): + pass + + def test_invalid_progression_order(self): + """Should still be able to parse even if prog order is invalid.""" + jfile = opj_data_file('input/nonregression/2977.pdf.asan.67.2198.jp2') + if sys.hexversion < 0x03000000: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + Jp2k(jfile) + else: + with self.assertWarns(UserWarning): + Jp2k(jfile) + 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') @@ -43,8 +74,6 @@ class TestCodestream(unittest.TestCase): Jp2k(filename) - @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): """Reserved marker segments are ok.""" @@ -76,8 +105,6 @@ class TestCodestream(unittest.TestCase): self.assertEqual(codestream.segment[2].length, 3) self.assertEqual(codestream.segment[2].data, b'\x00') - @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.") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @@ -109,8 +136,6 @@ class TestCodestream(unittest.TestCase): self.assertEqual(codestream.segment[2].length, 3) self.assertEqual(codestream.segment[2].data, b'\x00') - @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(OPJ_DATA_ROOT, @@ -123,19 +148,6 @@ class TestCodestream(unittest.TestCase): self.assertEqual(codestream.segment[-1].marker_id, 'EOC') - def test_siz_segment_ssiz_unsigned(self): - """ssiz attribute to be removed in future release""" - j = Jp2k(self.jp2file) - codestream = j.get_codestream() - - # The ssiz attribute was simply a tuple of raw bytes. - # The first 7 bits are interpreted as the bitdepth, the MSB determines - # whether or not it is signed. - self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7)) - - - @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(OPJ_DATA_ROOT, 'input/conformance/p0_03.j2k') diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 2b8b26c..554e6de 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -645,6 +645,18 @@ class TestPrintingOpjDataRoot(unittest.TestCase): def tearDown(self): pass + def test_invalid_progression_order(self): + """Should still be able to print even if prog order is invalid.""" + jfile = opj_data_file('input/nonregression/2977.pdf.asan.67.2198.jp2') + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) + codestream = jp2.get_codestream() + with patch('sys.stdout', new=StringIO()) as fake_out: + print(codestream.segment[2]) + actual = fake_out.getvalue().strip() + self.assertEqual(actual, fixtures.issue_186_progression_order) + def test_crg(self): """verify printing of CRG segment""" filename = opj_data_file('input/conformance/p0_03.j2k')