From dc2b2bda15447c89dc3a693f703bc4edaa5defc9 Mon Sep 17 00:00:00 2001 From: John Evans Date: Sun, 4 Jan 2015 21:39:14 -0500 Subject: [PATCH 1/9] the main header will be printed by default --- glymur/command_line.py | 16 +++++++++------- glymur/jp2box.py | 13 ++++++++----- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/glymur/command_line.py b/glymur/command_line.py index ff442f3..0836108 100644 --- a/glymur/command_line.py +++ b/glymur/command_line.py @@ -13,23 +13,25 @@ def main(): Entry point for console script jp2dump. """ - description = 'Print JPEG2000 metadata.' - parser = argparse.ArgumentParser(description=description) + kwargs = {'description': 'Print JPEG2000 metadata.', + 'formatter_class': argparse.ArgumentDefaultsHelpFormatter} + parser = argparse.ArgumentParser(**kwargs) parser.add_argument('-x', '--noxml', - help='Suppress XML.', + help='suppress XML', action='store_true') parser.add_argument('-s', '--short', - help='Only print box id, offset, and length.', + help='only print box id, offset, and length', action='store_true') - chelp = 'Level of codestream information. 0 suppressed all details, ' - chelp += '1 prints headers, 2 prints the full codestream' + chelp = 'Level of codestream information. 0 suppresses all details, ' + chelp += '1 prints the main header, 2 prints the full codestream.' parser.add_argument('-c', '--codestream', help=chelp, + metavar='LEVEL', nargs=1, type=int, - default=[0]) + default=[1]) parser.add_argument('filename') diff --git a/glymur/jp2box.py b/glymur/jp2box.py index e29df28..ba204a4 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1040,10 +1040,11 @@ class ContiguousCodestreamBox(Jp2kBox): msg = Jp2kBox.__str__(self) if _printoptions['short'] is True: return msg - if _printoptions['codestream'] is False: + if _printoptions['siz']: + msg += '\n' + self._indent(str(self.main_header.segment[1]), + indent_level=4) return msg - msg += '\n Main header:' for segment in self.main_header.segment: msg += '\n' + self._indent(str(segment), indent_level=8) @@ -3360,7 +3361,7 @@ def get_parseoptions(): """ return _parseoptions -_printoptions = {'short': False, 'xml': True, 'codestream': True} +_printoptions = {'short': False, 'xml': True, 'siz': True} def set_printoptions(**kwargs): @@ -3377,8 +3378,10 @@ def set_printoptions(**kwargs): xml : bool, optional When False, printing of the XML contents of any XML boxes or UUID XMP boxes is suppressed. - codestream : bool, optional - When False, printing of the codestream contents is suppressed. + siz : bool, optional + When True, only the SIZ segment is printed. When False, the entire + codestream is printed. This option has no effect when the 'short' + option is set to True. See also -------- From e93392a10e8ec0f9c849aa129d86d5a81fd3f509 Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 5 Jan 2015 08:13:31 -0500 Subject: [PATCH 2/9] print the main header by default --- glymur/jp2box.py | 10 +- glymur/test/fixtures.py | 184 +++++++++++++++++++++++++++++++++++ glymur/test/test_printing.py | 7 +- 3 files changed, 194 insertions(+), 7 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index ba204a4..4307f48 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1040,13 +1040,11 @@ class ContiguousCodestreamBox(Jp2kBox): msg = Jp2kBox.__str__(self) if _printoptions['short'] is True: return msg - if _printoptions['siz']: - msg += '\n' + self._indent(str(self.main_header.segment[1]), - indent_level=4) + if _printoptions['codestream'] is False: return msg for segment in self.main_header.segment: - msg += '\n' + self._indent(str(segment), indent_level=8) + msg += '\n' + self._indent(str(segment), indent_level=4) return msg @@ -1321,7 +1319,7 @@ class FileTypeBox(Jp2kBox): if sys.hexversion >= 0x03000000: try: entry = entry.decode('utf-8') - except UnicodeDecodeError as err: + except UnicodeDecodeError: # The entry is invalid, but we've got code to catch this # later on. pass @@ -3361,7 +3359,7 @@ def get_parseoptions(): """ return _parseoptions -_printoptions = {'short': False, 'xml': True, 'siz': True} +_printoptions = {'short': False, 'xml': True, 'codestream': True} def set_printoptions(**kwargs): diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index c7bab78..12b0c71 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -709,6 +709,190 @@ UUID Box (uuid) @ (77, 3146) UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) Contiguous Codestream Box (jp2c) @ (3223, 1132296)""" +nemo = """JPEG 2000 Signature Box (jP ) @ (0, 12) + Signature: 0d0a870a +File Type Box (ftyp) @ (12, 20) + Brand: jp2 + Compatibility: ['jp2 '] +JP2 Header Box (jp2h) @ (32, 45) + Image Header Box (ihdr) @ (40, 22) + Size: [1456 2592 3] + Bitdepth: 8 + Signed: False + Compression: wavelet + Colorspace Unknown: False + Colour Specification Box (colr) @ (62, 15) + Method: enumerated colorspace + Precedence: 0 + Colorspace: sRGB +UUID Box (uuid) @ (77, 3146) + UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) + UUID Data: + + + + + Google + 2013-02-09T14:47:53 + + + 1 + 72/1 + 72/1 + 2 + HTC + HTC Glacier + 2592 + 1456 + + + 8 + 8 + 8 + + + 2 + 3 + + + 1343036288/4294967295 + 1413044224/4294967295 + + + + + 2748779008/4294967295 + 1417339264/4294967295 + 1288490240/4294967295 + 2576980480/4294967295 + 644245120/4294967295 + 257698032/4294967295 + + + + + 1 + 2528 + 1424 + 353/100 + 0 + 0/1 + WGS-84 + 2013-02-09T14:47:53 + + + 76 + + + 0220 + 0100 + + + 1 + 2 + 3 + 0 + + + 42,20.56N + 71,5.29W + 2013-02-09T19:47:53Z + NETWORK + + + 2013-02-09T14:47:53 + + + + + Glymur + Python XMP Toolkit + + + + + + +Contiguous Codestream Box (jp2c) @ (3223, 1132296) + SOC marker segment @ (3231, 0) + SIZ marker segment @ (3233, 47) + Profile: no profile + Reference Grid Height, Width: (1456 x 2592) + Vertical, Horizontal Reference Grid Offset: (0 x 0) + Reference Tile Height, Width: (1456 x 2592) + Vertical, Horizontal Reference Tile Offset: (0 x 0) + Bitdepth: (8, 8, 8) + Signed: (False, False, False) + Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1)) + COD marker segment @ (3282, 12) + Coding style: + Entropy coder, without partitions + SOP marker segments: False + EPH marker segments: False + Coding style parameters: + Progression order: LRCP + Number of layers: 2 + Multiple component transformation usage: reversible + Number of resolutions: 2 + Code block height, width: (64 x 64) + Wavelet transform: 5-3 reversible + 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 + QCD marker segment @ (3296, 7) + Quantization style: no quantization, 2 guard bits + Step size: [(0, 8), (0, 9), (0, 9), (0, 10)] + CME marker segment @ (3305, 37) + "Created by OpenJPEG version 2.0.0" + SOT marker segment @ (3344, 10) + Tile part index: 0 + Tile part length: 1132173 + Tile part instance: 0 + Number of tile parts: 1 + COC marker segment @ (3356, 9) + Associated component: 1 + Coding style for this component: Entropy coder, PARTITION = 0 + Coding style parameters: + Number of resolutions: 2 + Code block height, width: (64 x 64) + Wavelet transform: 5-3 reversible + 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 + QCC marker segment @ (3367, 8) + Associated Component: 1 + Quantization style: no quantization, 2 guard bits + Step size: [(0, 8), (0, 9), (0, 9), (0, 10)] + COC marker segment @ (3377, 9) + Associated component: 2 + Coding style for this component: Entropy coder, PARTITION = 0 + Coding style parameters: + Number of resolutions: 2 + Code block height, width: (64 x 64) + Wavelet transform: 5-3 reversible + 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 + QCC marker segment @ (3388, 8) + Associated Component: 2 + Quantization style: no quantization, 2 guard bits + Step size: [(0, 8), (0, 9), (0, 9), (0, 10)] + SOD marker segment @ (3398, 0) + EOC marker segment @ (1135517, 0)""" + # Output of reader requirements printing for text_GBR.jp2 text_GBR_rreq = r"""Reader Requirements Box (rreq) @ (40, 109) Fully Understands Aspect Mask: 0xffff diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 49fec57..55e3018 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -1098,7 +1098,12 @@ class TestJp2dump(unittest.TestCase): """Verify dumping with -x, suppress XML.""" actual = self.run_jp2dump(['', '-x', self.jp2file]) - self.assertEqual(actual, fixtures.nemo_dump_no_codestream_no_xml) + # shave off the XML and non-main-header segments + lines = fixtures.nemo.split('\n') + expected = lines[0:18] + expected.extend(lines[104:140]) + expected = '\n'.join(expected) + self.assertEqual(actual, expected) @unittest.skipIf(sys.hexversion < 0x03000000, "assertRegex not in 2.7") def test_codestream_0_with_j2k_file(self): From 2755e8edd439765acedc7fb11cac7bbfeeae9591 Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 5 Jan 2015 20:23:52 -0500 Subject: [PATCH 3/9] Jp2dump tests passing, more need to be written --- glymur/command_line.py | 16 +++++++++---- glymur/jp2box.py | 45 ++++++++++++++++++++++++++++-------- glymur/test/test_printing.py | 40 ++++++++++++++++++++------------ 3 files changed, 72 insertions(+), 29 deletions(-) diff --git a/glymur/command_line.py b/glymur/command_line.py index 0836108..4cd080d 100644 --- a/glymur/command_line.py +++ b/glymur/command_line.py @@ -60,11 +60,19 @@ def main(): # JP2 metadata can be extensive, so don't print any warnings until we # are done with the metadata. jp2 = Jp2k(filename) - if (((jp2._codec_format == lib.openjp2.CODEC_J2K) and - (codestream_level == 0))): - print('File: {0}'.format(os.path.basename(filename))) + if jp2._codec_format == lib.openjp2.CODEC_J2K: + if codestream_level == 0: + print('File: {0}'.format(os.path.basename(filename))) + elif codestream_level == 1: + print(jp2) + elif codestream_level == 2: + print('File: {0}'.format(os.path.basename(filename))) + print(jp2.get_codestream(header_only=False)) elif print_full_codestream: - print(jp2.get_codestream(header_only=False)) + for box in jp2.box: + if box.box_id == 'jp2c': + box._get_codestream(header_only=False) + print(jp2) else: print(jp2) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 4307f48..bd60c30 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1001,18 +1001,19 @@ class ContiguousCodestreamBox(Jp2kBox): offset of the box from the start of the file. longname : str more verbose description of the box. - main_header : Codestream object - contains list of main header marker/segments + codestream : Codestream object + Contains list of codestream marker/segments. By default, only the main + header is retrieved. main_header_offset : int offset of main header from start of file """ box_id = 'jp2c' longname = 'Contiguous Codestream' - def __init__(self, main_header=None, main_header_offset=None, length=0, + def __init__(self, codestream=None, main_header_offset=None, length=0, offset=-1): Jp2kBox.__init__(self) - self._main_header = main_header + self._codestream = codestream self.length = length self.offset = offset self.main_header_offset = main_header_offset @@ -1021,16 +1022,16 @@ class ContiguousCodestreamBox(Jp2kBox): self._filename = None @property - def main_header(self): - if self._main_header is None: + def codestream(self): + if self._codestream is None: if self._filename is not None: with open(self._filename, 'rb') as fptr: fptr.seek(self.main_header_offset) - main_header = Codestream(fptr, + codestream = Codestream(fptr, self._length, header_only=True) - self._main_header = main_header - return self._main_header + self._codestream = codestream + return self._codestream def __repr__(self): msg = "glymur.jp2box.ContiguousCodeStreamBox(main_header={0})" @@ -1043,7 +1044,7 @@ class ContiguousCodestreamBox(Jp2kBox): if _printoptions['codestream'] is False: return msg - for segment in self.main_header.segment: + for segment in self.codestream.segment: msg += '\n' + self._indent(str(segment), indent_level=4) return msg @@ -1076,6 +1077,30 @@ class ContiguousCodestreamBox(Jp2kBox): box._length = length return box + def _get_codestream(self, header_only=True): + """retrieve codestream + + Parameters + ---------- + header_only : bool, optional + If True, only marker segments in the main header are parsed. + Supplying False may impose a large performance penalty. + """ + with open(self.filename, 'rb') as fptr: + fptr.seek(self.offset) + read_buffer = fptr.read(8) + (box_length, _) = struct.unpack('>I4s', read_buffer) + if box_length == 0: + # The length of the box is presumed to last until the end + # of the file. Compute the effective length of the box. + box_length = os.path.getsize(fptr.name) - fptr.tell() + 8 + elif box_length == 1: + # Seek past the XL field. + read_buffer = fptr.read(8) + box_length, = struct.unpack('>Q', read_buffer) + self._codestream = Codestream(fptr, box_length - 8, + header_only=header_only) + class DataReferenceBox(Jp2kBox): """Container for Data Reference box information. diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 55e3018..bef59ad 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -1055,25 +1055,44 @@ class TestJp2dump(unittest.TestCase): return actual def test_default_nemo(self): - """Should be able to dump a JP2 file's metadata with no codestream.""" + """by default one should get the main header""" actual = self.run_jp2dump(['', self.jp2file]) - self.assertEqual(actual, fixtures.nemo_dump_no_codestream) + # shave off the non-main-header segments + lines = fixtures.nemo.split('\n') + expected = lines[0:140] + expected = '\n'.join(expected) + self.assertEqual(actual, expected) - def test_codestream_0(self): + @unittest.skipIf(sys.hexversion < 0x03000000, "assertRegex not in 2.7") + + def test_jp2_codestream_0(self): """Verify dumping with -c 0, supressing all codestream details.""" actual = self.run_jp2dump(['', '-c', '0', self.jp2file]) expected = fixtures.nemo_dump_no_codestream self.assertEqual(actual, expected) - def test_codestream_1(self): + def test_jp2_codestream_1(self): """Verify dumping with -c 1, print just the header.""" actual = self.run_jp2dump(['', '-c', '1', self.jp2file]) - self.assertEqual(actual, fixtures.nemo_with_codestream_header) + # shave off the non-main-header segments + lines = fixtures.nemo.split('\n') + expected = lines[0:140] + expected = '\n'.join(expected) + self.assertEqual(actual, expected) - def test_codestream_2(self): + @unittest.skipIf(sys.hexversion < 0x03000000, "assertRegex not in 2.7") + def test_j2k_codestream_0(self): + """-c 0 should print just a single line when used on a codestream.""" + sys.argv = ['', '-c', '0', self.j2kfile] + with patch('sys.stdout', new=StringIO()) as fake_out: + command_line.main() + actual = fake_out.getvalue().strip() + self.assertRegex(actual, "File: .*") + + def test_j2k_codestream_2(self): """Verify dumping with -c 2, full details.""" with patch('sys.stdout', new=StringIO()) as fake_out: sys.argv = ['', '-c', '2', self.j2kfile] @@ -1104,12 +1123,3 @@ class TestJp2dump(unittest.TestCase): expected.extend(lines[104:140]) expected = '\n'.join(expected) self.assertEqual(actual, expected) - - @unittest.skipIf(sys.hexversion < 0x03000000, "assertRegex not in 2.7") - def test_codestream_0_with_j2k_file(self): - """-c 0 should print just a single line when used on a codestream.""" - sys.argv = ['', '-c', '0', self.j2kfile] - with patch('sys.stdout', new=StringIO()) as fake_out: - command_line.main() - actual = fake_out.getvalue().strip() - self.assertRegex(actual, "File: .*") From 7dfcc0fd7c77f7f90c1c44fe84ae23f10b026157 Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 5 Jan 2015 21:10:02 -0500 Subject: [PATCH 4/9] documented rename of ContiguousCodestream attribute codesream. --- docs/source/whatsnew/0.8.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/whatsnew/0.8.rst b/docs/source/whatsnew/0.8.rst index f36b593..5c87f5a 100644 --- a/docs/source/whatsnew/0.8.rst +++ b/docs/source/whatsnew/0.8.rst @@ -6,5 +6,7 @@ Changes in 0.8.0 ================= * Simplified writing images by moving data and options into the - constructor. This is backwards-incompatible with 0.7.x. + constructor. + * The main_header attribute of the ContiguousCodestream class is now called + codestream. * Deprecated :py:meth:`read` method in favor of array-style slicing. From aeec03c82986e9b0f796c8ed5978a8052fb74947 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 6 Jan 2015 09:23:23 -0500 Subject: [PATCH 5/9] blah --- glymur/test/test_printing.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 55e3018..3e5eefb 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -1055,10 +1055,15 @@ class TestJp2dump(unittest.TestCase): return actual def test_default_nemo(self): - """Should be able to dump a JP2 file's metadata with no codestream.""" + """should dump everything but non-main-header codestream segments""" actual = self.run_jp2dump(['', self.jp2file]) - self.assertEqual(actual, fixtures.nemo_dump_no_codestream) + # shave off the non-main-header segments + lines = fixtures.nemo.split('\n') + expected = lines[0:140] + expected = '\n'.join(expected) + + self.assertEqual(actual, expected) def test_codestream_0(self): """Verify dumping with -c 0, supressing all codestream details.""" @@ -1068,10 +1073,15 @@ class TestJp2dump(unittest.TestCase): self.assertEqual(actual, expected) def test_codestream_1(self): - """Verify dumping with -c 1, print just the header.""" + """Verify dumping with -c 1, same as default""" actual = self.run_jp2dump(['', '-c', '1', self.jp2file]) - self.assertEqual(actual, fixtures.nemo_with_codestream_header) + # shave off the non-main-header segments + lines = fixtures.nemo.split('\n') + expected = lines[0:140] + expected = '\n'.join(expected) + + self.assertEqual(actual, expected) def test_codestream_2(self): """Verify dumping with -c 2, full details.""" From fe5e784b585131b615c8cace4866b06e5841c8a3 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 6 Jan 2015 11:00:08 -0500 Subject: [PATCH 6/9] get all tests passing --- glymur/jp2box.py | 4 ++-- glymur/test/fixtures.py | 9 +++++++++ glymur/test/test_glymur_warnings.py | 7 +++++++ glymur/test/test_jp2box.py | 4 ++-- glymur/test/test_jp2k.py | 15 +++------------ glymur/test/test_opj_suite.py | 2 +- glymur/test/test_opj_suite_dump.py | 22 +++++++++++----------- glymur/test/test_opj_suite_write.py | 2 +- 8 files changed, 36 insertions(+), 29 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index bd60c30..c946e6b 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1034,8 +1034,8 @@ class ContiguousCodestreamBox(Jp2kBox): return self._codestream def __repr__(self): - msg = "glymur.jp2box.ContiguousCodeStreamBox(main_header={0})" - return msg.format(repr(self.main_header)) + msg = "glymur.jp2box.ContiguousCodeStreamBox(codestream={0})" + return msg.format(repr(self.codestream)) def __str__(self): msg = Jp2kBox.__str__(self) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 12b0c71..dc27b0e 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -2,6 +2,7 @@ Test fixtures common to more than one test point. """ import os +import platform import re import sys import textwrap @@ -33,6 +34,14 @@ elif re.match('1.[0-6]', six.__version__) is not None: WARNING_INFRASTRUCTURE_ISSUE = True msg = "Cannot run test with version {0} of python-six" WARNING_INFRASTRUCTURE_MSG = msg.format(six.__version__) +elif ((re.match('1.8', six.__version__) is not None) and + (sys.platform.startswith('linux')) and + (platform.linux_distribution() == ('LinuxMint', '17', 'qiana'))): + WARNING_INFRASTRUCTURE_ISSUE = True + linux_distribution = platform.linux_distribution() + msg = "Cannot run test with version {0} of python-six on {1}" + WARNING_INFRASTRUCTURE_MSG = msg.format(six.__version__, + platform.linux_distribution) # Cannot reopen a named temporary file in windows. WINDOWS_TMP_FILE_MSG = "cannot use NamedTemporaryFile like this in windows" diff --git a/glymur/test/test_glymur_warnings.py b/glymur/test/test_glymur_warnings.py index ff18c6e..1d3dc2e 100644 --- a/glymur/test/test_glymur_warnings.py +++ b/glymur/test/test_glymur_warnings.py @@ -20,6 +20,13 @@ from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG class TestWarnings(unittest.TestCase): """Test suite for warnings issued by glymur.""" + def test_invalid_compatibility_list_entry(self): + """should not error out with invalid compatibility list entry""" + filename = opj_data_file('input/nonregression/issue397.jp2') + with self.assertWarns(UserWarning): + Jp2k(filename) + self.assertTrue(True) + def test_exceeded_box_length(self): """ should warn if reading past end of a box diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 9ccd75b..e43eae6 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -1016,7 +1016,7 @@ class TestJp2Boxes(unittest.TestCase): """Raw instantiation should not produce a main_header.""" box = ContiguousCodestreamBox() self.assertEqual(box.box_id, 'jp2c') - self.assertIsNone(box.main_header) + self.assertIsNone(box.codestream) def test_codestream_main_header_offset(self): """main_header_offset is an attribute of the CCS box""" @@ -1318,7 +1318,7 @@ class TestRepr(MetadataBase): # Difficult to eval(repr()) this, so just match the general pattern. regexp = "glymur.jp2box.ContiguousCodeStreamBox" - regexp += "[(]main_header= Date: Tue, 6 Jan 2015 16:35:38 -0500 Subject: [PATCH 7/9] replaced main_header codestream attribute with codestream changed "codestream" parameter of set_parseoptions to "full_codestream" --- glymur/command_line.py | 14 ++------- glymur/jp2box.py | 61 +++++++++++++----------------------- glymur/test/test_jp2k.py | 13 ++++---- glymur/test/test_printing.py | 11 ++++++- 4 files changed, 40 insertions(+), 59 deletions(-) diff --git a/glymur/command_line.py b/glymur/command_line.py index 4cd080d..3d1d57e 100644 --- a/glymur/command_line.py +++ b/glymur/command_line.py @@ -5,7 +5,7 @@ import argparse import os import warnings -from . import Jp2k, set_printoptions, lib +from . import Jp2k, set_printoptions, set_parseoptions, lib def main(): @@ -47,11 +47,8 @@ def main(): if codestream_level == 0: set_printoptions(codestream=False) - print_full_codestream = False - elif codestream_level == 1: - print_full_codestream = False - else: - print_full_codestream = True + elif codestream_level == 2: + set_parseoptions(full_codestream=True) filename = args.filename @@ -68,11 +65,6 @@ def main(): elif codestream_level == 2: print('File: {0}'.format(os.path.basename(filename))) print(jp2.get_codestream(header_only=False)) - elif print_full_codestream: - for box in jp2.box: - if box.box_id == 'jp2c': - box._get_codestream(header_only=False) - print(jp2) else: print(jp2) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index c946e6b..1cbd53a 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1023,13 +1023,17 @@ class ContiguousCodestreamBox(Jp2kBox): @property def codestream(self): + if _parseoptions['full_codestream'] is True: + header_only = False + else: + header_only = True if self._codestream is None: if self._filename is not None: with open(self._filename, 'rb') as fptr: fptr.seek(self.main_header_offset) codestream = Codestream(fptr, self._length, - header_only=True) + header_only=header_only) self._codestream = codestream return self._codestream @@ -1067,40 +1071,16 @@ class ContiguousCodestreamBox(Jp2kBox): ContiguousCodestreamBox instance """ main_header_offset = fptr.tell() - if _parseoptions['codestream'] is True: - main_header = Codestream(fptr, length, header_only=True) + if _parseoptions['full_codestream'] is True: + codestream = Codestream(fptr, length, header_only=False) else: - main_header = None - box = cls(main_header, main_header_offset=main_header_offset, + codestream = None + box = cls(codestream, main_header_offset=main_header_offset, length=length, offset=offset) box._filename = fptr.name box._length = length return box - def _get_codestream(self, header_only=True): - """retrieve codestream - - Parameters - ---------- - header_only : bool, optional - If True, only marker segments in the main header are parsed. - Supplying False may impose a large performance penalty. - """ - with open(self.filename, 'rb') as fptr: - fptr.seek(self.offset) - read_buffer = fptr.read(8) - (box_length, _) = struct.unpack('>I4s', read_buffer) - if box_length == 0: - # The length of the box is presumed to last until the end - # of the file. Compute the effective length of the box. - box_length = os.path.getsize(fptr.name) - fptr.tell() + 8 - elif box_length == 1: - # Seek past the XL field. - read_buffer = fptr.read(8) - box_length, = struct.unpack('>Q', read_buffer) - self._codestream = Codestream(fptr, box_length - 8, - header_only=header_only) - class DataReferenceBox(Jp2kBox): """Container for Data Reference box information. @@ -3338,19 +3318,20 @@ _BOX_WITH_ID = { b'uuid': UUIDBox, b'xml ': XMLBox} -_parseoptions = {'codestream': True} +_parseoptions = {'full_codestream': False} -def set_parseoptions(codestream=True): +def set_parseoptions(full_codestream=True): """Set parsing options. These options determine the way JPEG 2000 boxes are parsed. Parameters ---------- - codestream : bool, defaults to True - When False, the codestream header is only parsed when accessed. This - can results in faster JP2/JPX parsing. + full_codestream : bool, defaults to True + When False, only the codestream header is parsed for metadata. This + can results in faster JP2/JPX parsing. When True, the entire + codestream is parsed for metadata. See also -------- @@ -3361,9 +3342,9 @@ def set_parseoptions(codestream=True): To put back the default options, you can use: >>> import glymur - >>> glymur.set_parseoptions(codestream=True) + >>> glymur.set_parseoptions(full_codestream=True) """ - _parseoptions['codestream'] = codestream + _parseoptions['full_codestream'] = full_codestream def get_parseoptions(): @@ -3401,10 +3382,10 @@ def set_printoptions(**kwargs): xml : bool, optional When False, printing of the XML contents of any XML boxes or UUID XMP boxes is suppressed. - siz : bool, optional - When True, only the SIZ segment is printed. When False, the entire - codestream is printed. This option has no effect when the 'short' - option is set to True. + codestream : bool, optional + When False, only the codestream header is printed. When True, the + entire codestream is printed. This option has no effect when the + 'short' option is set to True. See also -------- diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 53f174a..fe18022 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -1043,26 +1043,25 @@ class TestParsing(unittest.TestCase): def setUp(self): self.jp2file = glymur.data.nemo() # Reset parseoptions for every test. - glymur.set_parseoptions(codestream=True) + glymur.set_parseoptions(full_codestream=False) def tearDown(self): - glymur.set_parseoptions(codestream=True) + glymur.set_parseoptions(full_codestream=False) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_bad_rsiz(self): """Should not warn if RSIZ when parsing is turned off.""" filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') - glymur.set_parseoptions(codestream=False) - Jp2k(filename) + glymur.set_parseoptions(full_codestream=False) + jp2 = Jp2k(filename) - glymur.set_parseoptions(codestream=True) + glymur.set_parseoptions(full_codestream=True) with self.assertWarnsRegex(UserWarning, 'Invalid profile'): Jp2k(filename) def test_main_header(self): - """Verify that the main header isn't loaded when parsing turned off.""" + """verify that the main header isn't loaded during normal parsing""" # The hidden _main_header attribute should show up after accessing it. - glymur.set_parseoptions(codestream=False) jp2 = Jp2k(self.jp2file) jp2c = jp2.box[4] self.assertIsNone(jp2c._codestream) diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index bef59ad..c141d65 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -1040,9 +1040,10 @@ class TestJp2dump(unittest.TestCase): # Reset printoptions for every test. glymur.set_printoptions(short=False, xml=True, codestream=True) + glymur.set_parseoptions(full_codestream=False) def tearDown(self): - pass + glymur.set_parseoptions(full_codestream=False) def run_jp2dump(self, args): sys.argv = args @@ -1083,6 +1084,14 @@ class TestJp2dump(unittest.TestCase): expected = '\n'.join(expected) self.assertEqual(actual, expected) + def test_jp2_codestream_2(self): + """Verify dumping with -c 2, print entire jp2 jacket, codestream.""" + actual = self.run_jp2dump(['', '-c', '2', self.jp2file]) + + # shave off the non-main-header segments + expected = fixtures.nemo + self.assertEqual(actual, expected) + @unittest.skipIf(sys.hexversion < 0x03000000, "assertRegex not in 2.7") def test_j2k_codestream_0(self): """-c 0 should print just a single line when used on a codestream.""" From 4ceb4dce61b0f69719d5cdacdf4cc24e2ee3da86 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 6 Jan 2015 16:37:55 -0500 Subject: [PATCH 8/9] pep8 work --- glymur/jp2box.py | 3 +-- glymur/test/test_jp2k.py | 2 +- glymur/test/test_printing.py | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 1cbd53a..bc23ebe 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1031,8 +1031,7 @@ class ContiguousCodestreamBox(Jp2kBox): if self._filename is not None: with open(self._filename, 'rb') as fptr: fptr.seek(self.main_header_offset) - codestream = Codestream(fptr, - self._length, + codestream = Codestream(fptr, self._length, header_only=header_only) self._codestream = codestream return self._codestream diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index fe18022..8affe7d 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -1053,7 +1053,7 @@ class TestParsing(unittest.TestCase): """Should not warn if RSIZ when parsing is turned off.""" filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') glymur.set_parseoptions(full_codestream=False) - jp2 = Jp2k(filename) + Jp2k(filename) glymur.set_parseoptions(full_codestream=True) with self.assertWarnsRegex(UserWarning, 'Invalid profile'): diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index c141d65..dfdd505 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -1066,7 +1066,6 @@ class TestJp2dump(unittest.TestCase): self.assertEqual(actual, expected) @unittest.skipIf(sys.hexversion < 0x03000000, "assertRegex not in 2.7") - def test_jp2_codestream_0(self): """Verify dumping with -c 0, supressing all codestream details.""" actual = self.run_jp2dump(['', '-c', '0', self.jp2file]) From 059bee50e2bd9a3a5b69f6b87f1cb868c27af31b Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 6 Jan 2015 19:55:59 -0500 Subject: [PATCH 9/9] all tests passing on opensuse --- glymur/test/test_glymur_warnings.py | 16 ++++++++-------- glymur/test/test_jp2k.py | 9 ++++++++- glymur/test/test_opj_suite_dump.py | 27 ++++++++++++++++++--------- glymur/test/test_printing.py | 4 ++-- 4 files changed, 36 insertions(+), 20 deletions(-) diff --git a/glymur/test/test_glymur_warnings.py b/glymur/test/test_glymur_warnings.py index 1d3dc2e..8086004 100644 --- a/glymur/test/test_glymur_warnings.py +++ b/glymur/test/test_glymur_warnings.py @@ -69,7 +69,7 @@ class TestWarnings(unittest.TestCase): \(\d+\)\.""", re.VERBOSE) with self.assertWarnsRegex(UserWarning, regex): - Jp2k(jfile) + Jp2k(jfile).get_codestream() @unittest.skipIf(re.match("1.5|2.0.0", glymur.version.openjpeg_version), "Test not passing on 1.5.x, not introduced until 2.x") @@ -84,7 +84,7 @@ class TestWarnings(unittest.TestCase): \(\d+\)\.""", re.VERBOSE) with self.assertWarnsRegex(UserWarning, regex): - Jp2k(jfile) + Jp2k(jfile).get_codestream() def test_NR_gdal_fuzzer_check_comp_dx_dy_jp2_dump(self): """ @@ -97,7 +97,7 @@ class TestWarnings(unittest.TestCase): dx=\d+,\s*dy=\d+""", re.VERBOSE) with self.assertWarnsRegex(UserWarning, regex): - Jp2k(jfile) + Jp2k(jfile).get_codestream() def test_NR_gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc_patch_jp2(self): lst = ['input', 'nonregression', @@ -107,32 +107,32 @@ class TestWarnings(unittest.TestCase): number\sof\scomponents\sis\sonly\s\d+""", re.VERBOSE) with self.assertWarnsRegex(UserWarning, regex): - Jp2k(jfile) + Jp2k(jfile).get_codestream() def test_bad_rsiz(self): """Should warn if RSIZ is bad. Issue196""" filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') with self.assertWarnsRegex(UserWarning, 'Invalid profile'): - Jp2k(filename) + Jp2k(filename).get_codestream() def test_bad_wavelet_transform(self): """Should warn if wavelet transform is bad. Issue195""" filename = opj_data_file('input/nonregression/edf_c2_10025.jp2') with self.assertWarnsRegex(UserWarning, 'Invalid wavelet transform'): - Jp2k(filename) + Jp2k(filename).get_codestream() 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') with self.assertWarnsRegex(UserWarning, 'Invalid progression order'): - Jp2k(jfile) + Jp2k(jfile).get_codestream() def test_tile_height_is_zero(self): """Zero tile height should not cause an exception.""" filename = 'input/nonregression/2539.pdf.SIGFPE.706.1712.jp2' filename = opj_data_file(filename) with self.assertWarnsRegex(UserWarning, 'Invalid tile dimensions'): - Jp2k(filename) + Jp2k(filename).get_codestream() @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_unknown_marker_segment(self): diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 8affe7d..668f8d5 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -36,6 +36,9 @@ from .fixtures import OPJ_DATA_ROOT, opj_data_file from . import fixtures +def docTearDown(doctest_obj): + glymur.set_parseoptions(full_codestream=False) + # Doc tests should be run as well. def load_tests(loader, tests, ignore): """Should run doc tests as well""" @@ -43,7 +46,8 @@ def load_tests(loader, tests, ignore): # Can't do it on windows, temporary file issue. return tests if glymur.lib.openjp2.OPENJP2 is not None: - tests.addTests(doctest.DocTestSuite('glymur.jp2k')) + tests.addTests(doctest.DocTestSuite('glymur.jp2k', + tearDown=docTearDown)) return tests @@ -1075,6 +1079,9 @@ class TestParsing(unittest.TestCase): class TestJp2kOpjDataRootWarnings(unittest.TestCase): """These tests should be run by just about all configuration.""" + def tearDown(self): + glymur.set_parseoptions(full_codestream=False) + def test_undecodeable_box_id(self): """Should warn in case of undecodeable box ID but not error out.""" filename = opj_data_file('input/nonregression/edf_c2_1013627.jp2') diff --git a/glymur/test/test_opj_suite_dump.py b/glymur/test/test_opj_suite_dump.py index 8920473..7ac513f 100644 --- a/glymur/test/test_opj_suite_dump.py +++ b/glymur/test/test_opj_suite_dump.py @@ -4,6 +4,7 @@ suite. """ import re import unittest +import warnings import numpy as np @@ -2913,21 +2914,29 @@ class TestSuiteWarns(MetadataBase): def test_NR_broken4_jp2_dump(self): jfile = opj_data_file('input/nonregression/broken4.jp2') - with self.assertWarns(UserWarning): - jp2 = Jp2k(jfile) - - self.assertEqual(jp2.box[-1].codestream.segment[-1].marker_id, 'QCC') + with warnings.catch_warnings(): + # Suppress a warning, all we really care is parsing the entire + # file. + warnings.simplefilter("ignore") + with self.assertWarns(UserWarning): + jp2 = Jp2k(jfile) + self.assertEqual(jp2.box[-1].codestream.segment[-1].marker_id, + 'QCC') def test_NR_broken2_jp2_dump(self): """ Invalid marker ID in the codestream. """ jfile = opj_data_file('input/nonregression/broken2.jp2') - with self.assertWarns(UserWarning): - # Invalid marker ID on codestream. - jp2 = Jp2k(jfile) - - self.assertEqual(jp2.box[-1].codestream.segment[-1].marker_id, 'QCC') + with warnings.catch_warnings(): + # Suppress a warning, all we really care is parsing the entire + # file. + warnings.simplefilter("ignore") + with self.assertWarns(UserWarning): + # Invalid marker ID on codestream. + jp2 = Jp2k(jfile) + self.assertEqual(jp2.box[-1].codestream.segment[-1].marker_id, + 'QCC') def test_NR_file1_dump(self): jfile = opj_data_file('input/conformance/file1.jp2') diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index dfdd505..5310fa9 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -42,7 +42,7 @@ class TestPrinting(unittest.TestCase): glymur.set_printoptions(short=False, xml=True, codestream=True) def tearDown(self): - pass + glymur.set_parseoptions(full_codestream=False) def test_version_info(self): """Should be able to print(glymur.version.info)""" @@ -854,7 +854,7 @@ class TestPrintingOpjDataRootWarns(unittest.TestCase): """Should still be able to print if rsiz is bad, issue196""" filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') with self.assertWarns(UserWarning): - j = Jp2k(filename) + j = Jp2k(filename).get_codestream() with patch('sys.stdout', new=StringIO()): print(j)