psot can be zero, meaning tile part extends to EOC marker.

Must pass in the codestream length to help figure this out.  #78
This commit is contained in:
jevans 2013-07-20 21:34:45 -04:00
commit b082e5e9d8
5 changed files with 45 additions and 32 deletions

View file

@ -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}. "

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -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):