diff --git a/glymur/codestream.py b/glymur/codestream.py index ce11e9d..49d7acb 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -57,7 +57,12 @@ class Codestream(object): Attributes ---------- - segment : list of marker segments + segment : iterable + list of marker segments + offset : int + Offset of the codestream from start of the file in bytes. + length : int + Length of the codestream in bytes. Raises ------ @@ -70,17 +75,22 @@ class Codestream(object): 15444-1:2004 - Information technology -- JPEG 2000 image coding system: Core coding system """ - def __init__(self, fptr, header_only=True): + def __init__(self, fptr, length, header_only=True): """ Parameters ---------- fptr : file Open file object. + length : int + Length of the codestream in bytes. header_only : bool, optional If True, only marker segments in the main header are parsed. Supplying False may impose a large performance penalty. """ + self.offset = fptr.tell() + self.length = length + # Number of components. Must be kept track of for the processing of # many segments. self._csiz = -1 @@ -111,9 +121,9 @@ class Codestream(object): try: segment = self._process_marker_segment(fptr, marker_id) - except InconsistentStartOfTileError as isote: + except Exception as error: # Treat this as a warning. - msg = str(isote) + msg = str(error) warnings.warn(msg) break @@ -199,7 +209,11 @@ class Codestream(object): segment = _parse_sot_segment(fptr) if segment.offset not in self._tile_offset: self._tile_offset.append(segment.offset) - self._tile_length.append(segment.psot) + if segment.psot == 0: + tile_part_length = self.offset + self.length - segment.offset - 2 + else: + tile_part_length = segment.psot + self._tile_length.append(tile_part_length) else: msg = "Inconsistent start-of-tile (SOT) marker segment " msg += "encountered in tile with index {0}. " diff --git a/glymur/jp2box.py b/glymur/jp2box.py index b6f92f7..d205e1b 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -644,7 +644,7 @@ class ContiguousCodestreamBox(Jp2kBox): ------- ContiguousCodestreamBox instance """ - main_header = Codestream(fptr, header_only=True) + main_header = Codestream(fptr, length, header_only=True) box = ContiguousCodestreamBox(main_header, length=length, offset=offset) return box diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 23aed62..5baebac 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -1033,7 +1033,8 @@ class Jp2k(Jp2kBox): """ with open(self.filename, 'rb') as fptr: if self._codec_format == _opj2.CODEC_J2K: - codestream = Codestream(fptr, header_only=header_only) + codestream = Codestream(fptr, self.length, + header_only=header_only) else: box = [x for x in self.box if x.box_id == 'jp2c'] if len(box) != 1: @@ -1042,9 +1043,15 @@ class Jp2k(Jp2kBox): fptr.seek(box[0].offset) read_buffer = fptr.read(8) (box_length, _) = struct.unpack('>I4s', read_buffer) - if box_length == 1: + 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) - codestream = Codestream(fptr, header_only=header_only) + box_length, = struct.unpack('>Q', read_buffer) + codestream = Codestream(fptr, box_length - 8, + header_only=header_only) return codestream diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py index 87d7df1..31c674c 100644 --- a/glymur/test/test_codestream.py +++ b/glymur/test/test_codestream.py @@ -28,8 +28,7 @@ except: class TestCodestream(unittest.TestCase): def setUp(self): - self.jp2file = pkg_resources.resource_filename(glymur.__name__, - "data/nemo.jp2") + self.jp2file = glymur.data.nemo() def tearDown(self): pass @@ -95,5 +94,16 @@ class TestCodestream(unittest.TestCase): self.assertEqual(c.segment[2].length, 3) self.assertEqual(c.segment[2]._data, b'\x00') + def test_psot_is_zero(self): + # Psot=0 in SOT is perfectly legal. Issue #78. + filename = os.path.join(data_root, + 'input/nonregression/123.j2c') + j = Jp2k(filename) + c = j.get_codestream(header_only=False) + + # The codestream is valid, so we should be able to get the entire + # codestream, so the last one is EOC. + self.assertEqual(c.segment[-1].marker_id, 'EOC') + if __name__ == "__main__": unittest.main() diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index 17962f7..48b8117 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -73,25 +73,16 @@ class TestSuiteNegative(unittest.TestCase): with self.assertRaises(RuntimeError): j.write(data, psnr=[30, 35, 40], cratios=[2, 3, 4]) - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") def test_NR_MarkerIsNotCompliant_j2k_dump(self): - # SOT marker gives bad offset. relpath = 'input/nonregression/MarkerIsNotCompliant.j2k' jfile = os.path.join(data_root, relpath) jp2k = Jp2k(jfile) - with self.assertWarns(UserWarning) as cw: - c = jp2k.get_codestream(header_only=False) - - # Verify that the last segment returned in the codestream is SOD, - # not EOC. Codestream parsing should stop when we try to jump to - # the end of SOT. - self.assertEqual(c.segment[-1].marker_id, 'SOD') + c = jp2k.get_codestream(header_only=False) @unittest.skipIf(sys.hexversion < 0x03020000, "Uses features introduced in 3.2.") def test_NR_illegalcolortransform_dump(self): - # SOT marker gives bad offset. + # EOC marker is bad relpath = 'input/nonregression/illegalcolortransform.j2k' jfile = os.path.join(data_root, relpath) jp2k = Jp2k(jfile) @@ -103,20 +94,11 @@ class TestSuiteNegative(unittest.TestCase): # the end of SOT. self.assertEqual(c.segment[-1].marker_id, 'SOD') - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") def test_NR_Cannotreaddatawithnosizeknown_j2k(self): - # SOT marker gives bad offset. relpath = 'input/nonregression/Cannotreaddatawithnosizeknown.j2k' jfile = os.path.join(data_root, relpath) jp2k = Jp2k(jfile) - with self.assertWarns(UserWarning) as cw: - c = jp2k.get_codestream(header_only=False) - - # Verify that the last segment returned in the codestream is SOD, - # not EOC. Codestream parsing should stop when we try to jump to - # the end of SOT. - self.assertEqual(c.segment[-1].marker_id, 'SOD') + c = jp2k.get_codestream(header_only=False) @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_code_block_dimensions(self):