From ef629e041cd944603c7a58915f838b1c32f99518 Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 30 Jan 2014 20:00:18 -0500 Subject: [PATCH] Added support for NumberListBox. closes #143 --- glymur/jp2box.py | 67 ++++++++++++++++++++++++++++++++ glymur/test/test_jp2box_jpx.py | 70 +++++++++++++++------------------- 2 files changed, 97 insertions(+), 40 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 6e62f88..51f8567 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1903,6 +1903,72 @@ class LabelBox(Jp2kBox): return box +class NumberListBox(Jp2kBox): + """Container for Number List box information. + + Attributes + ---------- + box_id : str + 4-character identifier for the box. + length : int + length of the box in bytes. + offset : int + offset of the box from the start of the file. + longname : str + more verbose description of the box. + AN : list + Descriptors of an entity with which the data contained within the same + Association box is associated. + """ + def __init__(self, associations, length=0, offset=-1): + Jp2kBox.__init__(self, box_id='nlst', longname='Number List') + self.associations = associations + self.length = length + self.offset = offset + + def __str__(self): + msg = Jp2kBox.__str__(self) + for j, association in enumerate(self.associations): + if association == 0: + msg += '\n Association[{0}]: the rendered result'.format(j) + elif (association >> 24) == 1: + idx = association & 0x00FFFFFF + msg += '\n Association[{0}]: Codestream {0} '.format(idx) + elif (association >> 24) == 2: + idx = association & 0x00FFFFFF + msg += '\n Association[{0}]: Compositing Layer {0}' + msg = msg.format(idx) + return msg + + def __repr__(self): + msg = 'glymur.jp2box.NumberListBox()' + return msg + + @staticmethod + def parse(fptr, offset, length): + """Parse Label box. + + Parameters + ---------- + fptr : file + Open file object. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + LabelBox instance + """ + num_bytes = offset + length - fptr.tell() + raw_data = fptr.read(num_bytes) + num_associations = int(len(raw_data) / 4) + lst = struct.unpack('>' + 'I' * num_associations, raw_data) + box = NumberListBox(lst, length=length, offset=offset) + return box + + class XMLBox(Jp2kBox): """Container for XML box information. @@ -2865,6 +2931,7 @@ _BOX_WITH_ID = { 'free': FreeBox, 'jp2h': JP2HeaderBox, 'lbl ': LabelBox, + 'nlst': NumberListBox, 'pclr': PaletteBox, 'res ': ResolutionBox, 'resc': CaptureResolutionBox, diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 57f5e6f..4a95528 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -20,46 +20,6 @@ from glymur import Jp2k from glymur.jp2box import ReaderRequirementsBox -@unittest.skipIf(sys.hexversion < 0x03000000, "Warning assert on 2.x.") -@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") -class TestReaderRequirements(unittest.TestCase): - """Test suite for XML boxes.""" - - def setUp(self): - self.jp2file = glymur.data.nemo() - pass - - def tearDown(self): - pass - - def test_mask_length_is_3(self): - """The standard says that the mask length should be 1, 2, 4, or 8.""" - # Rewrite nemo to include this kind of rreq box. - with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: - with open(self.jp2file, 'rb') as nemof: - # Read the jP and ftyp boxes as-is. - write_buffer = nemof.read(32) - tfile.write(write_buffer) - - # Fake a rreq box with ML = 3. - write_buffer = struct.pack('>I4sB', 74, b'rreq', 3) - tfile.write(write_buffer) - - # pad the rest with zeros - write_buffer = struct.pack('>65s', b'\x00' * 65) - tfile.write(write_buffer) - - # Write the rest of nemo. - tfile.write(nemof.read()) - tfile.flush() - - with self.assertWarns(UserWarning): - j = Jp2k(tfile.name) - self.assertEqual(j.box[2].box_id, 'rreq') - self.assertEqual(type(j.box[2]), - glymur.jp2box.ReaderRequirementsBox) - - @unittest.skipIf(sys.hexversion < 0x03000000, "Warning assert on 2.x.") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") class TestJPXOther(unittest.TestCase): @@ -72,6 +32,16 @@ class TestJPXOther(unittest.TestCase): def tearDown(self): pass + def test_rreq_box_strange_mask_length(self): + """The standard says that the mask length should be 1, 2, 4, or 8.""" + with warnings.catch_warnings(): + # This file has a rreq mask length that we do not recognize. + warnings.simplefilter("ignore") + j = Jp2k(self.jpxfile) + self.assertEqual(j.box[2].box_id, 'rreq') + self.assertEqual(type(j.box[2]), + glymur.jp2box.ReaderRequirementsBox) + def test_free_box(self): """Verify that we can handle a free box.""" with warnings.catch_warnings(): @@ -81,3 +51,23 @@ class TestJPXOther(unittest.TestCase): self.assertEqual(j.box[16].box[0].box_id, 'free') self.assertEqual(type(j.box[16].box[0]), glymur.jp2box.FreeBox) + def test_nlst(self): + """Verify that we can handle a free box.""" + with warnings.catch_warnings(): + # This file has a rreq mask length that we do not recognize. + warnings.simplefilter("ignore") + j = Jp2k(self.jpxfile) + self.assertEqual(j.box[16].box[1].box[0].box_id, 'nlst') + self.assertEqual(type(j.box[16].box[1].box[0]), + glymur.jp2box.NumberListBox) + + # Two associations. + self.assertEqual(len(j.box[16].box[1].box[0].associations), 2) + + # Codestream 0 + self.assertEqual(j.box[16].box[1].box[0].associations[0], 1 << 24) + + # Compositing Layer 0 + self.assertEqual(j.box[16].box[1].box[0].associations[1], 2 << 24) + +