diff --git a/CHANGES.txt b/CHANGES.txt index 042e351..1bdd8ed 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,5 @@ +Mar 18, 2015 - Added support for JP2 bits per component box. + Jan 10, 2015 - v0.8.0 Reduced number of steps required for writing images. Deprecated old read and write methods in favor of array-style slicing. Added ignore_pclr_cmap_cdef, verbose, diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 698711a..9bf8e01 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1830,6 +1830,79 @@ class AssociationBox(Jp2kBox): self._write_superbox(fptr, b'asoc') +class BitsPerComponentBox(Jp2kBox): + """Container for bits per component 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. + bpc : list + bits per component for each component + signed : list + True if signed, false if not, for each component + """ + box_id = 'bpcc' + longname = 'Bits Per Component' + + def __init__(self, bpc, signed, length=0, offset=-1): + Jp2kBox.__init__(self) + self.length = length + self.offset = offset + self.bpc = bpc + self.signed = signed + + def __repr__(self): + msg = "glymur.jp2box.BitsPerComponentBox(box={0})".format(self.box) + return msg + + def __str__(self): + title = Jp2kBox.__str__(self) + if _printoptions['short'] is True: + return title + + body = 'Bits per component: [' + body += ', '.join(str(x) for x in self.bpc) + body += ']' + body += '\n' + body += 'Signed: [' + ', '.join(str(x) for x in self.signed) + ']' + + body = self._indent(body) + + text = '\n'.join([title, body]) + return text + + @classmethod + def parse(cls, fptr, offset, length): + """Parse bits per component box. + + Parameters + ---------- + fptr : file + Open file object. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + AssociationBox instance + """ + nbytes = length - 8 + data = fptr.read(nbytes) + bpc = tuple(((x & 0x7f) + 1) for x in data) + signed = tuple(((x & 0x80) > 0) for x in data) + + return cls(bpc, signed, length=length, offset=offset) + + class JP2HeaderBox(Jp2kBox): """Container for JP2 header box information. @@ -3404,6 +3477,7 @@ class UUIDBox(Jp2kBox): # Map each box ID to the corresponding class. _BOX_WITH_ID = { b'asoc': AssociationBox, + b'bpcc': BitsPerComponentBox, b'cdef': ChannelDefinitionBox, b'cgrp': ColourGroupBox, b'cmap': ComponentMappingBox, diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 34327fc..11b9d16 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -1092,3 +1092,8 @@ goodstuff_with_full_header = r"""Codestream: Step size: [(0, 8), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10)] SOD marker segment @ (164, 0) EOC marker segment @ (115218, 0)""" + +bpcc = """Bits Per Component Box (bpcc) @ (62, 12) + Bits per component: [5, 5, 5, 1] + Signed: [False, False, False, False]""" + diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index ba5dd09..f17414d 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -618,6 +618,17 @@ class TestPrintingOpjDataRoot(unittest.TestCase): def tearDown(self): pass + def test_bpcc(self): + """BPCC boxes are rare :-)""" + self.maxDiff = None + filename = opj_data_file('input/nonregression/issue458.jp2') + jp2 = Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + box = jp2.box[2].box[1] + print(box) + actual = fake_out.getvalue().strip() + self.assertEqual(actual, fixtures.bpcc) + def test_cinema_profile(self): """Should print Cinema 2K when the profile is 3.""" filename = opj_data_file('input/nonregression/_00042.j2k')