diff --git a/CHANGES.txt b/CHANGES.txt index 40846e4..0d23501 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +Oct 22, 2013 - v0.5.7 Super box constructors now take optional box list + argument. Removed ssiz attribute from SIZsegment class. + Oct 13, 2013 - v0.5.6 Fixed handling of non-ascii chars in XML boxes. Fixed some docstring errors in jp2box module. diff --git a/glymur/codestream.py b/glymur/codestream.py index e83c5cc..d01795f 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -646,16 +646,50 @@ class Codestream(object): length, = struct.unpack('>H', read_buffer) xy_buffer = fptr.read(36) + data = struct.unpack('>HIIIIIIIIH', xy_buffer) - num_components, = struct.unpack('>H', xy_buffer[-2:]) + rsiz = data[0] + xysiz = (data[1], data[2]) + xyosiz = (data[3], data[4]) + xytsiz = (data[5], data[6]) + xytosiz = (data[7], data[8]) - component_buffer = fptr.read(num_components * 3) + # Csiz is the number of components + Csiz = data[9] - segment = SIZsegment(xy_buffer, component_buffer, length, offset) + component_buffer = fptr.read(Csiz * 3) + data = struct.unpack('>' + 'B' * len(component_buffer), + component_buffer) + + bitdepth = tuple(((x & 0x7f) + 1) for x in data[0::3]) + signed = tuple(((x & 0xb0) > 0) for x in data[0::3]) + + xrsiz = data[1::3] + yrsiz = data[2::3] + + for j, subsampling in enumerate(zip(xrsiz, yrsiz)): + if 0 in subsampling: + msg = "Invalid subsampling value for component {0}: " + msg += "dx={1}, dy={2}." + msg = msg.format(j, subsampling[0], subsampling[1]) + warnings.warn(msg) + + kwargs = {'rsiz': rsiz, + 'xysiz': xysiz, + 'xyosiz': xyosiz, + 'xytsiz': xytsiz, + 'xytosiz': xytosiz, + 'Csiz': Csiz, + 'bitdepth': bitdepth, + 'signed': signed, + 'xyrsiz': (xrsiz, yrsiz), + 'length': length, + 'offset': offset} + segment = SIZsegment(**kwargs) # Need to keep track of the number of components from SIZ for - # other markers - self._csiz = len(segment.ssiz) + # other markers. + self._csiz = Csiz return segment @@ -1441,8 +1475,8 @@ class SIZsegment(Segment): Width and height of reference tile with respect to the reference grid. xtosiz, ytosiz : int Horizontal and vertical offsets of tile from origin of reference grid. - ssiz : iterable bytes - Encoded precision (depth) in bits and sign of each component. + Csiz : int + Number of components in image. bitdepth : iterable bytes Precision (depth) in bits of each component. signed : iterable bool @@ -1457,21 +1491,20 @@ class SIZsegment(Segment): 15444-1:2004 - Information technology -- JPEG 2000 image coding system: Core coding system """ - def __init__(self, xy_buffer, component_buffer, length, offset): - Segment.__init__(self, marker_id='SIZ') + def __init__(self, rsiz=-1, xysiz=None, xyosiz=-1, xytsiz=-1, xytosiz=-1, + Csiz=-1, bitdepth=None, signed=None, xyrsiz=-1, length=-1, + offset=-1): + Segment.__init__(self, marker_id='SIZ', length=length, offset=offset) - data = struct.unpack('>HIIIIIIIIH', xy_buffer) - - self.rsiz = data[0] - self.xsiz = data[1] - self.ysiz = data[2] - self.xosiz = data[3] - self.yosiz = data[4] - self.xtsiz = data[5] - self.ytsiz = data[6] - self.xtosiz = data[7] - self.ytosiz = data[8] - # disregarding the last element in data + self.rsiz = rsiz + self.xsiz, self.ysiz = xysiz + self.xosiz, self.yosiz = xyosiz + self.xtsiz, self.ytsiz = xytsiz + self.xtosiz, self.ytosiz = xytosiz + self.Csiz = Csiz + self.bitdepth = bitdepth + self.signed = signed + self.xrsiz, self.yrsiz = xyrsiz num_tiles_x = (self.xsiz - self.xosiz) / (self.xtsiz - self.xtosiz) num_tiles_y = (self.ysiz - self.yosiz) / (self.ytsiz - self.ytosiz) @@ -1480,26 +1513,23 @@ class SIZsegment(Segment): msg = "Invalid number of tiles ({0}).".format(numtiles) warnings.warn(msg) - data = struct.unpack('>' + 'B' * len(component_buffer), - component_buffer) - - self.ssiz = data[0::3] - - for j, subsampling in enumerate(list(zip(data[1::3], data[2::3]))): - if 0 in subsampling: - msg = "Invalid subsampling value for component {0}: " - msg += "dx={1}, dy={2}." - msg = msg.format(j, subsampling[0], subsampling[1]) - warnings.warn(msg) - self.xrsiz = data[1::3] - self.yrsiz = data[2::3] - - self.bitdepth = tuple(((x & 0x7f) + 1) for x in self.ssiz) - self.signed = tuple(((x & 0xb0) > 0) for x in self.ssiz) - - self.length = length - self.offset = offset + def __repr__(self): + msg = "glymur.codestream.SIZsegment(rsiz={rsiz}, xysiz={xysiz}, " + msg += "xyosiz={xyosiz}, xytsiz={xytsiz}, xytosiz={xytosiz}, " + msg += "Csiz={Csiz}, bitdepth={bitdepth}, signed={signed}, " + msg += "xyrsiz={xyrsiz})" + msg = msg.format(rsiz=self.rsiz, + xysiz=(self.xsiz, self.ysiz), + xyosiz=(self.xosiz, self.yosiz), + xytsiz=(self.xtsiz, self.ytsiz), + xytosiz=(self.xtosiz, self.ytosiz), + Csiz=self.Csiz, + bitdepth=self.bitdepth, + signed=self.signed, + xyrsiz=(self.xrsiz, self.yrsiz)) + return msg + def __str__(self): msg = Segment.__str__(self) msg += '\n ' @@ -1549,6 +1579,9 @@ class SOCsegment(Segment): Segment.__init__(self, marker_id='SOC') self.__dict__.update(**kwargs) + def __repr__(self): + msg = "glymur.codestream.SOCsegment()" + return msg class SODsegment(Segment): """Container for Start of Data (SOD) segment information. diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 6f628df..627e164 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -75,6 +75,12 @@ class Jp2kBox(object): self.offset = offset self.longname = longname + def __repr__(self): + msg = "glymur.jp2box.Jp2kBox(box_id='{0}', offset={1}, length={2}, " + msg += "longname='{3}')" + msg = msg.format(self.box_id, self.offset, self.length, self.longname) + return msg + def __str__(self): msg = "{0} Box ({1})".format(self.longname, self.box_id) msg += " @ ({0}, {1})".format(self.offset, self.length) @@ -206,6 +212,17 @@ class ColourSpecificationBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.ColourSpecificationBox(" + msg += "method={0}, precedence={1}, approximation={2}, colorspace={3}, " + msg += "icc_profile={4})" + msg = msg.format(self.method, + self.precedence, + self.approximation, + self.colorspace, + self.icc_profile) + return msg + def __str__(self): msg = Jp2kBox.__str__(self) @@ -426,12 +443,12 @@ class ChannelDefinitionBox(Jp2kBox): offset of the box from the start of the file. longname : str more verbose description of the box. - index : int + index : list number of the channel. Defaults to monotonically increasing sequence, i.e. [0, 1, 2, ...] - channel_type : int + channel_type : list type of the channel - association : int + association : list index of the associated color """ def __init__(self, index=None, channel_type=None, association=None, @@ -458,9 +475,9 @@ class ChannelDefinitionBox(Jp2kBox): msg += " 65535 - unspecified" raise IOError(msg) - self.index = index - self.channel_type = channel_type - self.association = association + self.index = tuple(index) + self.channel_type = tuple(channel_type) + self.association = tuple(association) self.__dict__.update(**kwargs) def __str__(self): @@ -475,6 +492,12 @@ class ChannelDefinitionBox(Jp2kBox): msg = msg.format(self.index[j], color_type_string, assn) return msg + def __repr__(self): + msg = "glymur.jp2box.ChannelDefinitionBox(" + msg += "index={0}, channel_type={1}, association={2})" + msg = msg.format(self.index, self.channel_type, self.association) + return msg + def write(self, fptr): """Write a channel definition box to file. """ @@ -537,11 +560,15 @@ class CodestreamHeaderBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - def __init__(self, length=0, offset=-1): + def __init__(self, box=[], length=0, offset=-1): Jp2kBox.__init__(self, box_id='jpch', longname='Codestream Header') self.length = length self.offset = offset - self.box = [] + self.box = box + + def __repr__(self): + msg = "glymur.jp2box.CodestreamHeaderBox(box={0})".format(self.box) + return msg def __str__(self): msg = Jp2kBox.__str__(self) @@ -595,13 +622,18 @@ class CompositingLayerHeaderBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - def __init__(self, length=0, offset=-1): + def __init__(self, box=[], length=0, offset=-1): Jp2kBox.__init__(self, box_id='jplh', longname='Compositing Layer Header') self.length = length self.offset = offset self.box = [] + def __repr__(self): + msg = "glymur.jp2box.CompositingLayerHeaderBox(box={0})" + msg = msg.format(self.box) + return msg + def __str__(self): msg = Jp2kBox.__str__(self) for box in self.box: @@ -650,11 +682,11 @@ class ComponentMappingBox(Jp2kBox): Offset of the box from the start of the file. longname : str Verbose description of the box. - component_index : int + component_index : tuple Index of component in codestream that is mapped to this channel. - mapping_type : int + mapping_type : tuple mapping type, either direct use (0) or palette (1) - palette_index : int + palette_index : tuple Index component from palette """ def __init__(self, component_index, mapping_type, palette_index, @@ -666,6 +698,14 @@ class ComponentMappingBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.ComponentMappingBox(" + msg += "component_index={0}, mapping_type={1}, palette_index={2})" + msg = msg.format(self.component_index, + self.mapping_type, + self.palette_index) + return msg + def __str__(self): msg = Jp2kBox.__str__(self) @@ -733,6 +773,10 @@ class ContiguousCodestreamBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.ContiguousCodeStreamBox(main_header={0})" + return msg.format(repr(self.main_header)) + def __str__(self): msg = Jp2kBox.__str__(self) msg += '\n Main header:' @@ -801,6 +845,13 @@ class FileTypeBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.FileTypeBox(brand='{0}', minor_version={1}, " + msg += "compatibility_list={2})" + msg = msg.format(self.brand, self.minor_version, + self.compatibility_list) + return msg + def __str__(self): lst = [Jp2kBox.__str__(self), ' Brand: {0}', @@ -914,8 +965,23 @@ class ImageHeaderBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.ImageHeaderBox(" + msg += "{height}, {width}, num_components={num_components}, " + msg += "signed={signed}, bits_per_component={bits_per_component}, " + msg += "compression={compression}, " + msg += "colorspace_unknown={colorspace_unknown}, " + msg += "ip_provided={ip_provided})" + msg = msg.format(height=self.height, width=self.width, + num_components=self.num_components, + signed=self.signed, + bits_per_component=self.bits_per_component, + compression=self.compression, + colorspace_unknown=self.colorspace_unknown, + ip_provided=self.ip_provided) + return msg + def __str__(self): - msg = Jp2kBox.__str__(self) msg = "{0}" msg += '\n Size: [{1} {2} {3}]' msg += '\n Bitdepth: {4}' @@ -1004,11 +1070,15 @@ class AssociationBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - def __init__(self, length=0, offset=-1): + def __init__(self, box=[], length=0, offset=-1): Jp2kBox.__init__(self, box_id='asoc', longname='Association') self.length = length self.offset = offset - self.box = [] + self.box = box + + def __repr__(self): + msg = "glymur.jp2box.AssociationBox(box={0})".format(self.box) + return msg def __str__(self): msg = Jp2kBox.__str__(self) @@ -1062,11 +1132,15 @@ class JP2HeaderBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - def __init__(self, length=0, offset=-1): + def __init__(self, box=[], length=0, offset=-1): Jp2kBox.__init__(self, box_id='jp2h', longname='JP2 Header') self.length = length self.offset = offset - self.box = [] + self.box = box + + def __repr__(self): + msg = "glymur.jp2box.JP2HeaderBox(box={0})".format(self.box) + return msg def __str__(self): msg = Jp2kBox.__str__(self) @@ -1141,6 +1215,9 @@ class JPEG2000SignatureBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + return 'glymur.jp2box.JPEG2000SignatureBox()' + def __str__(self): msg = Jp2kBox.__str__(self) msg += '\n Signature: {0:02x}{1:02x}{2:02x}{3:02x}' @@ -1193,8 +1270,8 @@ class PaletteBox(Jp2kBox): offset of the box from the start of the file. longname : str more verbose description of the box. - palette : list - Colormap represented as list of 1D arrays, one per color component. + palette : ndarray + Colormap array. """ def __init__(self, palette, bits_per_component, signed, length=0, offset=-1): @@ -1205,10 +1282,15 @@ class PaletteBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.PaletteBox({0}, bits_per_component={1}, " + msg += "signed={2})" + msg = msg.format(self.palette, self.bits_per_component, self.signed) + return msg + def __str__(self): msg = Jp2kBox.__str__(self) - msg += '\n Size: ({0} x {1})'.format(len(self.palette[0]), - len(self.palette)) + msg += '\n Size: ({0} x {1})'.format(*self.palette.shape) return msg @staticmethod @@ -1238,68 +1320,29 @@ class PaletteBox(Jp2kBox): bps = [((x & 0x07f) + 1) for x in data] signed = [((x & 0x80) > 1) for x in data] + fmt = '>' + for bits in bps: + if bits <= 8: + fmt += 'B' + elif bits <= 16: + fmt += 'H' + elif bits <= 32: + fmt += 'I' + # Each palette component is padded out to the next largest byte. # That means a list comprehension does this in one shot. row_nbytes = sum([int(math.ceil(x/8.0)) for x in bps]) - # Form the format string so that we can intelligently unpack the - # colormap. We have to do this because it is possible that the - # colormap columns could have different datatypes. - # - # This means that we store the palette as a list of 1D arrays, - # which reverses the usual indexing scheme. read_buffer = fptr.read(num_entries * row_nbytes) - palette = _buffer2palette(read_buffer, num_entries, num_columns, bps) + palette = np.zeros((num_entries, num_columns), dtype=np.int32) + for j in range(num_entries): + palette[j] = struct.unpack_from(fmt, read_buffer, + offset=j * row_nbytes) box = PaletteBox(palette, bps, signed, length=length, offset=offset) return box -def _buffer2palette(read_buffer, num_rows, num_cols, bps): - """Construct the palette from the buffer read from file. - - Parameters - ---------- - read_buffer : iterable - Byte array of palette information read from file. - num_rows, num_cols : int - Size of palette. - bps : iterable - Bits per sample for each channel. - - Returns - ------- - palette : list of 1D arrays - Each 1D array corresponds to a channel. - """ - row_nbytes = 0 - palette = [] - fmt = '>' - for j in range(num_cols): - if bps[j] <= 8: - row_nbytes += 1 - fmt += 'B' - palette.append(np.zeros(num_rows, dtype=np.uint8)) - elif bps[j] <= 16: - row_nbytes += 2 - fmt += 'H' - palette.append(np.zeros(num_rows, dtype=np.uint16)) - elif bps[j] <= 32: - row_nbytes += 4 - fmt += 'I' - palette.append(np.zeros(num_rows, dtype=np.uint32)) - else: - msg = 'Unsupported palette bitdepth (%d).'.format(bps[j]) - raise IOError(msg) - - for j in range(num_rows): - row_buffer = read_buffer[(row_nbytes * j):(row_nbytes * (j + 1))] - row = struct.unpack(fmt, row_buffer) - for k in range(num_cols): - palette[k][j] = row[k] - - return palette - # Map rreq codes to display text. _READER_REQUIREMENTS_DISPLAY = { 0: 'File not completely understood', @@ -1426,6 +1469,18 @@ class ReaderRequirementsBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.ReaderRequirementsBox(fuam={fuam}, dcm={dcm}, " + msg += "standard_flag={standard_flag}, standard_mask={standard_mask}, " + msg += "vendor_feature={vendor_feature}, vendor_mask={vendor_mask})" + msg = msg.format(fuam=self.fuam, + dcm=self.dcm, + standard_flag=self.standard_flag, + standard_mask=self.standard_mask, + vendor_feature=self.vendor_feature, + vendor_mask=self.vendor_mask) + return msg + def __str__(self): msg = Jp2kBox.__str__(self) @@ -1563,11 +1618,16 @@ class ResolutionBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - def __init__(self, length=0, offset=-1): + def __init__(self, box=[], length=0, offset=-1): Jp2kBox.__init__(self, box_id='res ', longname='Resolution') self.length = length self.offset = offset - self.box = [] + self.box = box + + def __repr__(self): + msg = "glymur.jp2box.ResolutionBox(box={0})" + msg = msg.format(self.box) + return msg def __str__(self): msg = Jp2kBox.__str__(self) @@ -1629,6 +1689,11 @@ class CaptureResolutionBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.CaptureResolutionBox({0}, {1})" + msg = msg.format(self.vertical_resolution, self.horizontal_resolution) + return msg + def __str__(self): msg = Jp2kBox.__str__(self) msg += '\n VCR: {0}'.format(self.vertical_resolution) @@ -1686,6 +1751,11 @@ class DisplayResolutionBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.DisplayResolutionBox({0}, {1})" + msg = msg.format(self.vertical_resolution, self.horizontal_resolution) + return msg + def __str__(self): msg = Jp2kBox.__str__(self) msg += '\n VDR: {0}'.format(self.vertical_resolution) @@ -1747,6 +1817,10 @@ class LabelBox(Jp2kBox): msg += '\n Label: {0}'.format(self.label) return msg + def __repr__(self): + msg = 'glymur.jp2box.LabelBox("{0}")'.format(self.label) + return msg + @staticmethod def parse(fptr, offset, length): """Parse Label box. @@ -1808,6 +1882,9 @@ class XMLBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + return "glymur.jp2box.XMLBox(xml={0})".format(self.xml) + def __str__(self): msg = Jp2kBox.__str__(self) xml = self.xml @@ -1866,7 +1943,6 @@ class XMLBox(Jp2kBox): msg = msg.format(offset, ude.reason) warnings.warn(msg, UserWarning) - # Strip out any trailing nulls, as they can foul up XML parsing. text = text.rstrip(chr(0)) @@ -1906,6 +1982,10 @@ class UUIDListBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.UUIDListBox({0})".format(self.ulst) + return msg + def __str__(self): msg = Jp2kBox.__str__(self) for j, uuid_item in enumerate(self.ulst): @@ -1957,11 +2037,15 @@ class UUIDInfoBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - def __init__(self, length=0, offset=-1): + def __init__(self, box=[], length=0, offset=-1): Jp2kBox.__init__(self, box_id='uinf', longname='UUIDInfo') self.length = length self.offset = offset - self.box = [] + self.box = box + + def __repr__(self): + msg = "glymur.jp2box.UUIDInfoBox(box={0})".format(self.box) + return msg def __str__(self): msg = Jp2kBox.__str__(self) @@ -2030,6 +2114,11 @@ class DataEntryURLBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.DataEntryURLBox({0}, {1}, '{2}')" + msg = msg.format(self.version, self.flag, self.url) + return msg + def __str__(self): msg = Jp2kBox.__str__(self) msg += '\n ' @@ -2112,6 +2201,7 @@ class UUIDBox(Jp2kBox): """ Jp2kBox.__init__(self, box_id='uuid', longname='UUID') self.uuid = the_uuid + self.raw_data = raw_data if the_uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): # XMP data. Parse as XML. Seems to be a difference between @@ -2145,6 +2235,12 @@ class UUIDBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.UUIDBox(the_uuid={0}, " + msg += "raw_data=)" + return msg.format(repr(self.uuid), len(self.raw_data)) + + def __str__(self): msg = '{0}\n' msg += ' UUID: {1}{2}\n' diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 45eaf39..6684e3e 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -72,6 +72,10 @@ class Jp2k(Jp2kBox): if mode == 'rb': self.parse() + def __repr__(self): + msg = "glymur.Jp2k('{0}')".format(self.filename) + return msg + def __str__(self): metadata = ['File: ' + os.path.basename(self.filename)] if len(self.box) > 0: diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py index 8db5039..76042b9 100644 --- a/glymur/test/test_codestream.py +++ b/glymur/test/test_codestream.py @@ -116,5 +116,49 @@ class TestCodestream(unittest.TestCase): # codestream, so the last one is EOC. self.assertEqual(codestream.segment[-1].marker_id, 'EOC') + +class TestCodestreamRepr(unittest.TestCase): + + def setUp(self): + self.jp2file = glymur.data.nemo() + + def tearDown(self): + pass + + def test_soc(self): + """Test SOC segment repr""" + segment = glymur.codestream.SOCsegment() + newseg = eval(repr(segment)) + self.assertEqual(newseg.marker_id, 'SOC') + + def test_siz(self): + """Test SIZ segment repr""" + kwargs = {'rsiz': 0, + 'xysiz': (2592, 1456), + 'xyosiz': (0, 0), + 'xytsiz': (2592, 1456), + 'xytosiz': (0, 0), + 'Csiz': 3, + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': ((1, 1, 1), (1, 1, 1))} + segment = glymur.codestream.SIZsegment(**kwargs) + newseg = eval(repr(segment)) + self.assertEqual(newseg.marker_id, 'SIZ') + self.assertEqual(newseg.xsiz, 2592) + self.assertEqual(newseg.ysiz, 1456) + self.assertEqual(newseg.xosiz, 0) + self.assertEqual(newseg.yosiz, 0) + self.assertEqual(newseg.xtsiz, 2592) + self.assertEqual(newseg.ytsiz, 1456) + self.assertEqual(newseg.xtosiz, 0) + self.assertEqual(newseg.ytosiz, 0) + + self.assertEqual(newseg.xrsiz, (1, 1, 1)) + self.assertEqual(newseg.yrsiz, (1, 1, 1)) + self.assertEqual(newseg.bitdepth, (8, 8, 8)) + self.assertEqual(newseg.signed, (False, False, False)) + + if __name__ == "__main__": unittest.main() diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index ff61bc5..844c56c 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -18,11 +18,13 @@ Test suite specifically targeting JP2 box layout. import doctest import os +import re import shutil import struct import sys import tempfile import uuid +from uuid import UUID import xml.etree.cElementTree as ET if sys.hexversion < 0x02070000: @@ -40,7 +42,7 @@ from glymur.jp2box import JPEG2000SignatureBox from glymur.core import COLOR, OPACITY from glymur.core import RED, GREEN, BLUE, GREY, WHOLE_IMAGE -from .fixtures import OPENJP2_IS_V2_OFFICIAL +from .fixtures import OPENJP2_IS_V2_OFFICIAL, opj_data_file try: FORMAT_CORPUS_DATA_ROOT = os.environ['FORMAT_CORPUS_DATA_ROOT'] @@ -413,7 +415,7 @@ class TestAppend(unittest.TestCase): jp2 = Jp2k(tfile.name) the_xml = ET.fromstring('0') - xmlbox = glymur.jp2box.XMLBox(xml=the_xml) + xmlbox = glymur.jp2box.XMLBox(xml=ET.ElementTree(the_xml)) jp2.append(xmlbox) # The sequence of box IDs should be the same as before, but with an @@ -709,6 +711,250 @@ class TestJp2Boxes(unittest.TestCase): self.assertIsNone(box.main_header) +class TestRepr(unittest.TestCase): + """Tests for __repr__ methods.""" + def test_default_jp2k(self): + """Should be able to eval a JPEG2000SignatureBox""" + jp2k = glymur.jp2box.JPEG2000SignatureBox() + + # Test the representation instantiation. + newbox = eval(repr(jp2k)) + self.assertTrue(isinstance(newbox, glymur.jp2box.JPEG2000SignatureBox)) + self.assertEqual(newbox.signature, (13, 10, 135, 10)) + + def test_default_ftyp(self): + """Should be able to instantiate a FileTypeBox""" + ftyp = glymur.jp2box.FileTypeBox() + + # Test the representation instantiation. + newbox = eval(repr(ftyp)) + self.assertTrue(isinstance(newbox, glymur.jp2box.FileTypeBox)) + self.assertEqual(newbox.brand, 'jp2 ') + self.assertEqual(newbox.minor_version, 0) + self.assertEqual(newbox.compatibility_list, ['jp2 ']) + + def test_colourspecification_box(self): + """Verify __repr__ method on colr box.""" + # TODO: add icc_profile + box = ColourSpecificationBox(colorspace=glymur.core.SRGB) + + newbox = eval(repr(box)) + self.assertEqual(newbox.method, glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(newbox.precedence, 0) + self.assertEqual(newbox.approximation, 0) + self.assertEqual(newbox.colorspace, glymur.core.SRGB) + self.assertIsNone(newbox.icc_profile) + + def test_channeldefinition_box(self): + """Verify __repr__ method on cdef box.""" + channel_type = [COLOR, COLOR, COLOR] + association = [RED, GREEN, BLUE] + cdef = glymur.jp2box.ChannelDefinitionBox(index=[0, 1, 2], + channel_type=channel_type, + association=association) + newbox = eval(repr(cdef)) + self.assertEqual(newbox.index, (0, 1, 2)) + self.assertEqual(newbox.channel_type, (COLOR, COLOR, COLOR)) + self.assertEqual(newbox.association, (RED, GREEN, BLUE)) + + def test_jp2header_box(self): + """Verify __repr__ method on ihdr box.""" + ihdr = ImageHeaderBox(100, 200, num_components=3) + colr = ColourSpecificationBox(colorspace=glymur.core.SRGB) + jp2h = JP2HeaderBox(box=[ihdr, colr]) + newbox = eval(repr(jp2h)) + self.assertEqual(newbox.box_id, 'jp2h') + self.assertEqual(newbox.box[0].box_id, 'ihdr') + self.assertEqual(newbox.box[1].box_id, 'colr') + + def test_imageheader_box(self): + """Verify __repr__ method on jhdr box.""" + ihdr = ImageHeaderBox(100, 200, num_components=3) + + newbox = eval(repr(ihdr)) + self.assertEqual(newbox.height, 100) + self.assertEqual(newbox.width, 200) + self.assertEqual(newbox.num_components, 3) + self.assertFalse(newbox.signed) + self.assertEqual(newbox.bits_per_component, 8) + self.assertEqual(newbox.compression, 7) + self.assertFalse(newbox.colorspace_unknown) + self.assertFalse(newbox.ip_provided) + + def test_association_box(self): + """Verify __repr__ method on asoc box.""" + asoc = glymur.jp2box.AssociationBox() + newbox = eval(repr(asoc)) + self.assertEqual(newbox.box_id, 'asoc') + self.assertEqual(len(newbox.box), 0) + + def test_codestreamheader_box(self): + """Verify __repr__ method on jpch box.""" + jpch = glymur.jp2box.CodestreamHeaderBox() + newbox = eval(repr(jpch)) + self.assertEqual(newbox.box_id, 'jpch') + self.assertEqual(len(newbox.box), 0) + + def test_compositinglayerheader_box(self): + """Verify __repr__ method on jplh box.""" + jplh = glymur.jp2box.CompositingLayerHeaderBox() + newbox = eval(repr(jplh)) + self.assertEqual(newbox.box_id, 'jplh') + self.assertEqual(len(newbox.box), 0) + + def test_componentmapping_box(self): + """Verify __repr__ method on cmap box.""" + cmap = glymur.jp2box.ComponentMappingBox(component_index=(0, 0, 0), + mapping_type=(1, 1, 1), + palette_index=(0, 1, 2)) + newbox = eval(repr(cmap)) + self.assertEqual(newbox.box_id, 'cmap') + self.assertEqual(newbox.component_index, (0, 0, 0)) + self.assertEqual(newbox.mapping_type, (1, 1, 1)) + self.assertEqual(newbox.palette_index, (0, 1, 2)) + + def test_resolution_boxes(self): + """Verify __repr__ method on resolution boxes.""" + resc = glymur.jp2box.CaptureResolutionBox(0.5, 2.5) + resd = glymur.jp2box.DisplayResolutionBox(2.5, 0.5) + res_super_box = glymur.jp2box.ResolutionBox(box=[resc, resd]) + + newbox = eval(repr(res_super_box)) + + self.assertEqual(newbox.box_id, 'res ') + self.assertEqual(newbox.box[0].box_id, 'resc') + self.assertEqual(newbox.box[0].vertical_resolution, 0.5) + self.assertEqual(newbox.box[0].horizontal_resolution, 2.5) + self.assertEqual(newbox.box[1].box_id, 'resd') + self.assertEqual(newbox.box[1].vertical_resolution, 2.5) + self.assertEqual(newbox.box[1].horizontal_resolution, 0.5) + + def test_label_box(self): + """Verify __repr__ method on label box.""" + lbl = glymur.jp2box.LabelBox("this is a test") + newbox = eval(repr(lbl)) + self.assertEqual(newbox.box_id, 'lbl ') + self.assertEqual(newbox.label, "this is a test") + + def test_data_entry_url_box(self): + """Verify __repr__ method on data entry url box.""" + version = 0 + flag = (0, 0, 0) + url = "http://readthedocs.glymur.org" + box = glymur.jp2box.DataEntryURLBox(version, flag, url) + newbox = eval(repr(box)) + self.assertEqual(newbox.box_id, 'url ') + self.assertEqual(newbox.version, version) + self.assertEqual(newbox.flag, flag) + self.assertEqual(newbox.url, url) + + def test_uuidinfo_box(self): + """Verify __repr__ method on uinf box.""" + uinf = glymur.jp2box.UUIDInfoBox() + newbox = eval(repr(uinf)) + self.assertEqual(newbox.box_id, 'uinf') + self.assertEqual(len(newbox.box), 0) + + def test_uuidlist_box(self): + """Verify __repr__ method on ulst box.""" + uuid1 = uuid.UUID('00000000-0000-0000-0000-000000000001') + uuid2 = uuid.UUID('00000000-0000-0000-0000-000000000002') + uuids = [uuid1, uuid2] + ulst = glymur.jp2box.UUIDListBox(ulst=uuids) + newbox = eval(repr(ulst)) + self.assertEqual(newbox.box_id, 'ulst') + self.assertEqual(newbox.ulst[0], uuid1) + self.assertEqual(newbox.ulst[1], uuid2) + + def test_jp2k_box(self): + """Verify Superclass repr.""" + box = glymur.jp2box.Jp2kBox(box_id='one', offset=2, length=3, + longname='four') + newbox = eval(repr(box)) + self.assertEqual(newbox.box_id, 'one') + self.assertEqual(newbox.offset, 2) + self.assertEqual(newbox.length, 3) + self.assertEqual(newbox.longname, 'four') + + def test_palette_box(self): + """Verify Palette box repr.""" + palette = np.array([[255, 0, 1000], [0, 255, 0]], dtype=np.int32) + bps = (8, 8, 16) + signed = (True, False, True) + box = glymur.jp2box.PaletteBox(palette=palette, bits_per_component=bps, + signed=(True, False, True)) + # The palette can't be reinstantiated thru eval/repr. + s = repr(box) + self.assertTrue(True) + + @unittest.skipIf(sys.hexversion < 0x02070000, "Requires 2.7+") + def test_xml_box(self): + """Verify xml box repr.""" + elt = ET.fromstring('0') + tree = ET.ElementTree(elt) + box = glymur.jp2box.XMLBox(xml=tree) + + regexp = "glymur.jp2box.XMLBox" + regexp += "\(xml=<(xml.etree.ElementTree.){0,1}ElementTree object " + regexp += "at 0x([a-f0-9]*)>\)" + + if sys.hexversion < 0x03000000: + self.assertRegexpMatches(repr(box), regexp) + else: + self.assertRegex(repr(box), regexp) + + def test_readerrequirements_box(self): + """Verify rreq repr method.""" + box = glymur.jp2box.ReaderRequirementsBox(fuam=160, dcm=192, + standard_flag=(5, 61, 43), + standard_mask=(128, 96, 64), + vendor_feature=[], + vendor_mask=[]) + newbox = eval(repr(box)) + self.assertEqual(box.fuam, newbox.fuam) + self.assertEqual(box.dcm, newbox.dcm) + self.assertEqual(box.standard_flag, newbox.standard_flag) + self.assertEqual(box.standard_mask, newbox.standard_mask) + self.assertEqual(box.vendor_feature, newbox.vendor_feature) + self.assertEqual(box.vendor_mask, newbox.vendor_mask) + + @unittest.skipIf(sys.hexversion < 0x02070000, "Requires 2.7+") + def test_uuid_box(self): + """Verify uuid repr method.""" + uuid_instance = uuid.UUID('00000000-0000-0000-0000-000000000000') + data = b'0123456789' + box = glymur.jp2box.UUIDBox(the_uuid=uuid_instance, raw_data=data) + + # Since the raw_data parameter is a sequence of bytes which could be + # quite long, don't bother trying to make it conform to eval(repr()). + regexp = "glymur.jp2box.UUIDBox\(" + regexp += "the_uuid=UUID\('00000000-0000-0000-0000-000000000000'\),\s" + regexp += "raw_data=\)" + + if sys.hexversion < 0x03000000: + self.assertRegexpMatches(repr(box), regexp) + else: + self.assertRegex(repr(box), regexp) + + @unittest.skipIf(sys.hexversion < 0x02070000, "Requires 2.7+") + def test_contiguous_codestream_box(self): + """Verify contiguous codestream box repr method.""" + jp2file = glymur.data.nemo() + jp2 = Jp2k(jp2file) + box = jp2.box[-1] + + # Difficult to eval(repr()) this, so just match the general pattern. + regexp = "glymur.jp2box.ContiguousCodeStreamBox" + regexp += "\(main_header=