From 6740be931b07e75157cb5f43204677d6ee1d6068 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 1 Feb 2014 17:08:17 -0500 Subject: [PATCH 1/2] Added fragment list and fragment table boxes. #145 --- CHANGES.txt | 7 +-- glymur/jp2box.py | 132 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 135 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index e126c70..a65c20a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,7 @@ -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. +Feb 01, 2014 - Added read support for JPX free, number list, data reference, + fragment table, and fragment list boxes. 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/jp2box.py b/glymur/jp2box.py index 2d7fed7..1635aa0 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -835,7 +835,7 @@ class DataReferenceBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - for box in enumerate(self.DR): + for box in self.DR: msg += '\n ' + str(box) return msg @@ -984,6 +984,134 @@ class FileTypeBox(Jp2kBox): return box +class FragmentListBox(Jp2kBox): + """Container for JPX fragment 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. + """ + def __init__(self, fragment_offset, fragment_length, data_reference, + length=0, offset=-1): + Jp2kBox.__init__(self, box_id='flst', longname='Fragment List') + self.fragment_offset = fragment_offset + self.fragment_length = fragment_length + self.data_reference = data_reference + self.length = length + self.offset = offset + + def __repr__(self): + msg = "glymur.jp2box.FragmentListBox()" + return msg + + def __str__(self): + msg = Jp2kBox.__str__(self) + for j in range(len(self.fragment_offset)): + msg += "\n Offset {0}: {1}" + msg += "\n Fragment Length {2}: {3}" + msg += "\n Data Reference {4}: {5}" + msg = msg.format(j, self.fragment_offset[j], + j, self.fragment_length[j], + j, self.data_reference[j]) + + return msg + + @staticmethod + def parse(fptr, offset, length): + """Parse JPX free box. + + Parameters + ---------- + f : file + Open file object. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + FreeBox instance + """ + read_buffer = fptr.read(2) + nf, = struct.unpack('>H', read_buffer) + + read_buffer = fptr.read(nf * 14) + lst = struct.unpack('>' + 'QIH' * nf, read_buffer) + frag_offset = lst[0::3] + frag_len = lst[1::3] + data_reference = lst[2::3] + return FragmentListBox(frag_offset, frag_len, data_reference, + length=length, offset=offset) + + +class FragmentTableBox(Jp2kBox): + """Container for JPX fragment table 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. + """ + def __init__(self, length=0, offset=-1): + Jp2kBox.__init__(self, box_id='ftbl', longname='Fragment Table') + self.length = length + self.offset = offset + + def __repr__(self): + msg = "glymur.jp2box.FragmentTableBox()" + return msg + + def __str__(self): + msg = Jp2kBox.__str__(self) + for box in self.box: + boxstr = str(box) + + # Add indentation. + strs = [('\n ' + x) for x in boxstr.split('\n')] + msg += ''.join(strs) + return msg + + @staticmethod + def parse(fptr, offset, length): + """Parse JPX fragment table superbox box. + + Parameters + ---------- + f : file + Open file object. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + FreeBox instance + """ + box = FragmentTableBox(length=length, offset=offset) + + # The FragmentTable box is a superbox, so go ahead and parse its child + # boxes. + box.box = box.parse_superbox(fptr) + + return box + + + class FreeBox(Jp2kBox): """Container for JPX free box information. @@ -2999,6 +3127,8 @@ _BOX_WITH_ID = { 'jplh': CompositingLayerHeaderBox, 'jp2c': ContiguousCodestreamBox, 'free': FreeBox, + 'flst': FragmentListBox, + 'ftbl': FragmentTableBox, 'jp2h': JP2HeaderBox, 'lbl ': LabelBox, 'nlst': NumberListBox, From 11fa080910281e5c18588e8d6e7deb485e8e103f Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 1 Feb 2014 17:58:11 -0500 Subject: [PATCH 2/2] Added test for flst, ftbl boxes. Closes #145 --- glymur/test/test_jp2box_jpx.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 01f6f1a..ae2c2b7 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -82,6 +82,35 @@ class TestJPXOther(unittest.TestCase): self.assertEqual(len(jpx.box[-1].DR), 2) + def test_ftbl(self): + """Verify that we can interpret Fragment Table boxes.""" + # Copy the existing JPX file, add a fragment table 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', 32, b'ftbl') + tfile.write(write_buffer) + + # Just one fragment list box + write_buffer = struct.pack('>I4s', 24, b'flst') + tfile.write(write_buffer) + + # Simple offset, length, reference + write_buffer = struct.pack('>HQIH', 1, 4237, 170246, 3) + tfile.write(write_buffer) + + tfile.flush() + + with self.assertWarns(UserWarning): + jpx = Jp2k(tfile.name) + + self.assertEqual(jpx.box[-1].box_id, 'ftbl') + self.assertEqual(jpx.box[-1].box[0].box_id, 'flst') + self.assertEqual(jpx.box[-1].box[0].fragment_offset, (4237,)) + self.assertEqual(jpx.box[-1].box[0].fragment_length, (170246,)) + self.assertEqual(jpx.box[-1].box[0].data_reference, (3,)) + + def test_nlst(self): """Verify that we can handle a free box.""" with warnings.catch_warnings():