diff --git a/CHANGES.txt b/CHANGES.txt index bbf96bb..e126c70 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,6 @@ -Jan 29, 2014 - Added read support for JPX FreeBox, NumberListBox. Palette box - now a 2D numpy array instead of a list of 1D arrays. JP2 super box - constructors now take optional box list argument. +Feb 01, 2014 - Added read support for JPX FreeBox, NumberListBox, Data Reference + Box. Palette box now a 2D numpy array instead of a list of 1D arrays. + JP2 super box constructors now take optional box list argument. Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask length is unsupported. diff --git a/glymur/data/12-v6.4.jpx b/glymur/data/12-v6.4.jpx index 63f5f13..a3e0c60 100644 Binary files a/glymur/data/12-v6.4.jpx and b/glymur/data/12-v6.4.jpx differ diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 51f8567..2d7fed7 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -811,6 +811,75 @@ class ContiguousCodestreamBox(Jp2kBox): return box +class DataReferenceBox(Jp2kBox): + """Container for Data Reference 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. + DR : list + Data Entry URL boxes. + """ + def __init__(self, data_entry_url_boxes, length=0, offset=-1): + Jp2kBox.__init__(self, box_id='dtbl', longname='Data Reference') + self.DR = data_entry_url_boxes + self.length = length + self.offset = offset + + def __str__(self): + msg = Jp2kBox.__str__(self) + for box in enumerate(self.DR): + msg += '\n ' + str(box) + return msg + + def __repr__(self): + msg = 'glymur.jp2box.DataReferenceBox()' + 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 + ------- + DataReferenceBox instance + """ + # Read the number of data references + read_buffer = fptr.read(2) + ndr, = struct.unpack('>H', read_buffer) + + # Read each data entry url box. + data_entry_url_box_list = [] + for _ in range(ndr): + start = fptr.tell() + read_buffer = fptr.read(8) + (box_length, box_id) = struct.unpack('>I4s', read_buffer) + if sys.hexversion >= 0x03000000: + box_id = box_id.decode('utf-8') + + box = DataEntryURLBox.parse(fptr, start, box_length) + data_entry_url_box_list.append(box) + + return DataReferenceBox(data_entry_url_box_list, + length=length, offset=offset) + + class FileTypeBox(Jp2kBox): """Container for JPEG 2000 file type box information. @@ -2922,6 +2991,7 @@ _BOX_WITH_ID = { 'cdef': ChannelDefinitionBox, 'cmap': ComponentMappingBox, 'colr': ColourSpecificationBox, + 'dtbl': DataReferenceBox, 'ftyp': FileTypeBox, 'ihdr': ImageHeaderBox, 'jP ': JPEG2000SignatureBox, diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 4a95528..01f6f1a 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -8,7 +8,6 @@ import struct import sys import tempfile import warnings -import xml.etree.cElementTree as ET if sys.hexversion < 0x02070000: import unittest2 as unittest @@ -17,7 +16,6 @@ else: import glymur from glymur import Jp2k -from glymur.jp2box import ReaderRequirementsBox @unittest.skipIf(sys.hexversion < 0x03000000, "Warning assert on 2.x.") @@ -27,7 +25,6 @@ class TestJPXOther(unittest.TestCase): def setUp(self): self.jpxfile = glymur.data.jpxfile() - pass def tearDown(self): pass @@ -51,6 +48,40 @@ 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_dtbl(self): + """Verify that we can interpret Data Reference boxes.""" + # Copy the existing JPX file, add a data reference box onto the end. + with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile: + with open(self.jpxfile, 'rb') as ifile: + tfile.write(ifile.read()) + write_buffer = struct.pack('>I4s', 50, b'dtbl') + tfile.write(write_buffer) + + # Just two boxes. + write_buffer = struct.pack('>H', 2) + tfile.write(write_buffer) + + # First data entry url box. + write_buffer = struct.pack('>I4s', 20, b'url ') + tfile.write(write_buffer) + write_buffer = struct.pack('>BBBB8s', 0, 0, 0, 0, b'file:///') + tfile.write(write_buffer) + + # Second data entry url box. + write_buffer = struct.pack('>I4s', 20, b'url ') + tfile.write(write_buffer) + write_buffer = struct.pack('>BBBB8s', 0, 0, 0, 0, b'file:///') + tfile.write(write_buffer) + + tfile.flush() + + with self.assertWarns(UserWarning): + jpx = Jp2k(tfile.name) + + self.assertEqual(jpx.box[-1].box_id, 'dtbl') + self.assertEqual(len(jpx.box[-1].DR), 2) + + def test_nlst(self): """Verify that we can handle a free box.""" with warnings.catch_warnings(): @@ -60,7 +91,7 @@ class TestJPXOther(unittest.TestCase): 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)