Merge branch 'devel' into issue104

This commit is contained in:
John Evans 2013-10-27 14:07:03 -04:00
commit 6948f40f27
8 changed files with 568 additions and 146 deletions

View file

@ -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 Oct 13, 2013 - v0.5.6 Fixed handling of non-ascii chars in XML boxes. Fixed
some docstring errors in jp2box module. some docstring errors in jp2box module.

View file

@ -646,16 +646,50 @@ class Codestream(object):
length, = struct.unpack('>H', read_buffer) length, = struct.unpack('>H', read_buffer)
xy_buffer = fptr.read(36) 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 # Need to keep track of the number of components from SIZ for
# other markers # other markers.
self._csiz = len(segment.ssiz) self._csiz = Csiz
return segment return segment
@ -1441,8 +1475,8 @@ class SIZsegment(Segment):
Width and height of reference tile with respect to the reference grid. Width and height of reference tile with respect to the reference grid.
xtosiz, ytosiz : int xtosiz, ytosiz : int
Horizontal and vertical offsets of tile from origin of reference grid. Horizontal and vertical offsets of tile from origin of reference grid.
ssiz : iterable bytes Csiz : int
Encoded precision (depth) in bits and sign of each component. Number of components in image.
bitdepth : iterable bytes bitdepth : iterable bytes
Precision (depth) in bits of each component. Precision (depth) in bits of each component.
signed : iterable bool signed : iterable bool
@ -1457,21 +1491,20 @@ class SIZsegment(Segment):
15444-1:2004 - Information technology -- JPEG 2000 image coding system: 15444-1:2004 - Information technology -- JPEG 2000 image coding system:
Core coding system Core coding system
""" """
def __init__(self, xy_buffer, component_buffer, length, offset): def __init__(self, rsiz=-1, xysiz=None, xyosiz=-1, xytsiz=-1, xytosiz=-1,
Segment.__init__(self, marker_id='SIZ') 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 = rsiz
self.xsiz, self.ysiz = xysiz
self.rsiz = data[0] self.xosiz, self.yosiz = xyosiz
self.xsiz = data[1] self.xtsiz, self.ytsiz = xytsiz
self.ysiz = data[2] self.xtosiz, self.ytosiz = xytosiz
self.xosiz = data[3] self.Csiz = Csiz
self.yosiz = data[4] self.bitdepth = bitdepth
self.xtsiz = data[5] self.signed = signed
self.ytsiz = data[6] self.xrsiz, self.yrsiz = xyrsiz
self.xtosiz = data[7]
self.ytosiz = data[8]
# disregarding the last element in data
num_tiles_x = (self.xsiz - self.xosiz) / (self.xtsiz - self.xtosiz) num_tiles_x = (self.xsiz - self.xosiz) / (self.xtsiz - self.xtosiz)
num_tiles_y = (self.ysiz - self.yosiz) / (self.ytsiz - self.ytosiz) 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) msg = "Invalid number of tiles ({0}).".format(numtiles)
warnings.warn(msg) 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): def __str__(self):
msg = Segment.__str__(self) msg = Segment.__str__(self)
msg += '\n ' msg += '\n '
@ -1549,6 +1579,9 @@ class SOCsegment(Segment):
Segment.__init__(self, marker_id='SOC') Segment.__init__(self, marker_id='SOC')
self.__dict__.update(**kwargs) self.__dict__.update(**kwargs)
def __repr__(self):
msg = "glymur.codestream.SOCsegment()"
return msg
class SODsegment(Segment): class SODsegment(Segment):
"""Container for Start of Data (SOD) segment information. """Container for Start of Data (SOD) segment information.

View file

@ -75,6 +75,12 @@ class Jp2kBox(object):
self.offset = offset self.offset = offset
self.longname = longname 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): def __str__(self):
msg = "{0} Box ({1})".format(self.longname, self.box_id) msg = "{0} Box ({1})".format(self.longname, self.box_id)
msg += " @ ({0}, {1})".format(self.offset, self.length) msg += " @ ({0}, {1})".format(self.offset, self.length)
@ -206,6 +212,17 @@ class ColourSpecificationBox(Jp2kBox):
self.length = length self.length = length
self.offset = offset 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): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
@ -426,12 +443,12 @@ class ChannelDefinitionBox(Jp2kBox):
offset of the box from the start of the file. offset of the box from the start of the file.
longname : str longname : str
more verbose description of the box. more verbose description of the box.
index : int index : list
number of the channel. Defaults to monotonically increasing sequence, number of the channel. Defaults to monotonically increasing sequence,
i.e. [0, 1, 2, ...] i.e. [0, 1, 2, ...]
channel_type : int channel_type : list
type of the channel type of the channel
association : int association : list
index of the associated color index of the associated color
""" """
def __init__(self, index=None, channel_type=None, association=None, def __init__(self, index=None, channel_type=None, association=None,
@ -458,9 +475,9 @@ class ChannelDefinitionBox(Jp2kBox):
msg += " 65535 - unspecified" msg += " 65535 - unspecified"
raise IOError(msg) raise IOError(msg)
self.index = index self.index = tuple(index)
self.channel_type = channel_type self.channel_type = tuple(channel_type)
self.association = association self.association = tuple(association)
self.__dict__.update(**kwargs) self.__dict__.update(**kwargs)
def __str__(self): def __str__(self):
@ -475,6 +492,12 @@ class ChannelDefinitionBox(Jp2kBox):
msg = msg.format(self.index[j], color_type_string, assn) msg = msg.format(self.index[j], color_type_string, assn)
return msg 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): def write(self, fptr):
"""Write a channel definition box to file. """Write a channel definition box to file.
""" """
@ -537,11 +560,15 @@ class CodestreamHeaderBox(Jp2kBox):
box : list box : list
List of boxes contained in this superbox. 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') Jp2kBox.__init__(self, box_id='jpch', longname='Codestream Header')
self.length = length self.length = length
self.offset = offset 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): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
@ -595,13 +622,18 @@ class CompositingLayerHeaderBox(Jp2kBox):
box : list box : list
List of boxes contained in this superbox. 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', Jp2kBox.__init__(self, box_id='jplh',
longname='Compositing Layer Header') longname='Compositing Layer Header')
self.length = length self.length = length
self.offset = offset self.offset = offset
self.box = [] self.box = []
def __repr__(self):
msg = "glymur.jp2box.CompositingLayerHeaderBox(box={0})"
msg = msg.format(self.box)
return msg
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
for box in self.box: for box in self.box:
@ -650,11 +682,11 @@ class ComponentMappingBox(Jp2kBox):
Offset of the box from the start of the file. Offset of the box from the start of the file.
longname : str longname : str
Verbose description of the box. Verbose description of the box.
component_index : int component_index : tuple
Index of component in codestream that is mapped to this channel. 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) mapping type, either direct use (0) or palette (1)
palette_index : int palette_index : tuple
Index component from palette Index component from palette
""" """
def __init__(self, component_index, mapping_type, palette_index, def __init__(self, component_index, mapping_type, palette_index,
@ -666,6 +698,14 @@ class ComponentMappingBox(Jp2kBox):
self.length = length self.length = length
self.offset = offset 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): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
@ -733,6 +773,10 @@ class ContiguousCodestreamBox(Jp2kBox):
self.length = length self.length = length
self.offset = offset self.offset = offset
def __repr__(self):
msg = "glymur.jp2box.ContiguousCodeStreamBox(main_header={0})"
return msg.format(repr(self.main_header))
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
msg += '\n Main header:' msg += '\n Main header:'
@ -801,6 +845,13 @@ class FileTypeBox(Jp2kBox):
self.length = length self.length = length
self.offset = offset 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): def __str__(self):
lst = [Jp2kBox.__str__(self), lst = [Jp2kBox.__str__(self),
' Brand: {0}', ' Brand: {0}',
@ -914,8 +965,23 @@ class ImageHeaderBox(Jp2kBox):
self.length = length self.length = length
self.offset = offset 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): def __str__(self):
msg = Jp2kBox.__str__(self)
msg = "{0}" msg = "{0}"
msg += '\n Size: [{1} {2} {3}]' msg += '\n Size: [{1} {2} {3}]'
msg += '\n Bitdepth: {4}' msg += '\n Bitdepth: {4}'
@ -1004,11 +1070,15 @@ class AssociationBox(Jp2kBox):
box : list box : list
List of boxes contained in this superbox. 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') Jp2kBox.__init__(self, box_id='asoc', longname='Association')
self.length = length self.length = length
self.offset = offset 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): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
@ -1062,11 +1132,15 @@ class JP2HeaderBox(Jp2kBox):
box : list box : list
List of boxes contained in this superbox. 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') Jp2kBox.__init__(self, box_id='jp2h', longname='JP2 Header')
self.length = length self.length = length
self.offset = offset 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): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
@ -1141,6 +1215,9 @@ class JPEG2000SignatureBox(Jp2kBox):
self.length = length self.length = length
self.offset = offset self.offset = offset
def __repr__(self):
return 'glymur.jp2box.JPEG2000SignatureBox()'
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
msg += '\n Signature: {0:02x}{1:02x}{2:02x}{3:02x}' 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. offset of the box from the start of the file.
longname : str longname : str
more verbose description of the box. more verbose description of the box.
palette : list palette : ndarray
Colormap represented as list of 1D arrays, one per color component. Colormap array.
""" """
def __init__(self, palette, bits_per_component, signed, length=0, def __init__(self, palette, bits_per_component, signed, length=0,
offset=-1): offset=-1):
@ -1205,10 +1282,15 @@ class PaletteBox(Jp2kBox):
self.length = length self.length = length
self.offset = offset 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): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
msg += '\n Size: ({0} x {1})'.format(len(self.palette[0]), msg += '\n Size: ({0} x {1})'.format(*self.palette.shape)
len(self.palette))
return msg return msg
@staticmethod @staticmethod
@ -1238,68 +1320,29 @@ class PaletteBox(Jp2kBox):
bps = [((x & 0x07f) + 1) for x in data] bps = [((x & 0x07f) + 1) for x in data]
signed = [((x & 0x80) > 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. # Each palette component is padded out to the next largest byte.
# That means a list comprehension does this in one shot. # That means a list comprehension does this in one shot.
row_nbytes = sum([int(math.ceil(x/8.0)) for x in bps]) 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) 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) box = PaletteBox(palette, bps, signed, length=length, offset=offset)
return box 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. # Map rreq codes to display text.
_READER_REQUIREMENTS_DISPLAY = { _READER_REQUIREMENTS_DISPLAY = {
0: 'File not completely understood', 0: 'File not completely understood',
@ -1426,6 +1469,18 @@ class ReaderRequirementsBox(Jp2kBox):
self.length = length self.length = length
self.offset = offset 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): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
@ -1563,11 +1618,16 @@ class ResolutionBox(Jp2kBox):
box : list box : list
List of boxes contained in this superbox. 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') Jp2kBox.__init__(self, box_id='res ', longname='Resolution')
self.length = length self.length = length
self.offset = offset 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): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
@ -1629,6 +1689,11 @@ class CaptureResolutionBox(Jp2kBox):
self.length = length self.length = length
self.offset = offset 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): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
msg += '\n VCR: {0}'.format(self.vertical_resolution) msg += '\n VCR: {0}'.format(self.vertical_resolution)
@ -1686,6 +1751,11 @@ class DisplayResolutionBox(Jp2kBox):
self.length = length self.length = length
self.offset = offset 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): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
msg += '\n VDR: {0}'.format(self.vertical_resolution) msg += '\n VDR: {0}'.format(self.vertical_resolution)
@ -1747,6 +1817,10 @@ class LabelBox(Jp2kBox):
msg += '\n Label: {0}'.format(self.label) msg += '\n Label: {0}'.format(self.label)
return msg return msg
def __repr__(self):
msg = 'glymur.jp2box.LabelBox("{0}")'.format(self.label)
return msg
@staticmethod @staticmethod
def parse(fptr, offset, length): def parse(fptr, offset, length):
"""Parse Label box. """Parse Label box.
@ -1808,6 +1882,9 @@ class XMLBox(Jp2kBox):
self.length = length self.length = length
self.offset = offset self.offset = offset
def __repr__(self):
return "glymur.jp2box.XMLBox(xml={0})".format(self.xml)
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
xml = self.xml xml = self.xml
@ -1866,7 +1943,6 @@ class XMLBox(Jp2kBox):
msg = msg.format(offset, ude.reason) msg = msg.format(offset, ude.reason)
warnings.warn(msg, UserWarning) warnings.warn(msg, UserWarning)
# Strip out any trailing nulls, as they can foul up XML parsing. # Strip out any trailing nulls, as they can foul up XML parsing.
text = text.rstrip(chr(0)) text = text.rstrip(chr(0))
@ -1906,6 +1982,10 @@ class UUIDListBox(Jp2kBox):
self.length = length self.length = length
self.offset = offset self.offset = offset
def __repr__(self):
msg = "glymur.jp2box.UUIDListBox({0})".format(self.ulst)
return msg
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
for j, uuid_item in enumerate(self.ulst): for j, uuid_item in enumerate(self.ulst):
@ -1957,11 +2037,15 @@ class UUIDInfoBox(Jp2kBox):
box : list box : list
List of boxes contained in this superbox. 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') Jp2kBox.__init__(self, box_id='uinf', longname='UUIDInfo')
self.length = length self.length = length
self.offset = offset 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): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
@ -2030,6 +2114,11 @@ class DataEntryURLBox(Jp2kBox):
self.length = length self.length = length
self.offset = offset 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): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
msg += '\n ' msg += '\n '
@ -2112,6 +2201,7 @@ class UUIDBox(Jp2kBox):
""" """
Jp2kBox.__init__(self, box_id='uuid', longname='UUID') Jp2kBox.__init__(self, box_id='uuid', longname='UUID')
self.uuid = the_uuid self.uuid = the_uuid
self.raw_data = raw_data
if the_uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): if the_uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'):
# XMP data. Parse as XML. Seems to be a difference between # XMP data. Parse as XML. Seems to be a difference between
@ -2145,6 +2235,12 @@ class UUIDBox(Jp2kBox):
self.length = length self.length = length
self.offset = offset self.offset = offset
def __repr__(self):
msg = "glymur.jp2box.UUIDBox(the_uuid={0}, "
msg += "raw_data=<byte array {1} elements>)"
return msg.format(repr(self.uuid), len(self.raw_data))
def __str__(self): def __str__(self):
msg = '{0}\n' msg = '{0}\n'
msg += ' UUID: {1}{2}\n' msg += ' UUID: {1}{2}\n'

View file

@ -72,6 +72,10 @@ class Jp2k(Jp2kBox):
if mode == 'rb': if mode == 'rb':
self.parse() self.parse()
def __repr__(self):
msg = "glymur.Jp2k('{0}')".format(self.filename)
return msg
def __str__(self): def __str__(self):
metadata = ['File: ' + os.path.basename(self.filename)] metadata = ['File: ' + os.path.basename(self.filename)]
if len(self.box) > 0: if len(self.box) > 0:

View file

@ -116,5 +116,49 @@ class TestCodestream(unittest.TestCase):
# codestream, so the last one is EOC. # codestream, so the last one is EOC.
self.assertEqual(codestream.segment[-1].marker_id, '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__": if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -18,11 +18,13 @@ Test suite specifically targeting JP2 box layout.
import doctest import doctest
import os import os
import re
import shutil import shutil
import struct import struct
import sys import sys
import tempfile import tempfile
import uuid import uuid
from uuid import UUID
import xml.etree.cElementTree as ET import xml.etree.cElementTree as ET
if sys.hexversion < 0x02070000: if sys.hexversion < 0x02070000:
@ -40,7 +42,7 @@ from glymur.jp2box import JPEG2000SignatureBox
from glymur.core import COLOR, OPACITY from glymur.core import COLOR, OPACITY
from glymur.core import RED, GREEN, BLUE, GREY, WHOLE_IMAGE 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: try:
FORMAT_CORPUS_DATA_ROOT = os.environ['FORMAT_CORPUS_DATA_ROOT'] FORMAT_CORPUS_DATA_ROOT = os.environ['FORMAT_CORPUS_DATA_ROOT']
@ -413,7 +415,7 @@ class TestAppend(unittest.TestCase):
jp2 = Jp2k(tfile.name) jp2 = Jp2k(tfile.name)
the_xml = ET.fromstring('<?xml version="1.0"?><data>0</data>') the_xml = ET.fromstring('<?xml version="1.0"?><data>0</data>')
xmlbox = glymur.jp2box.XMLBox(xml=the_xml) xmlbox = glymur.jp2box.XMLBox(xml=ET.ElementTree(the_xml))
jp2.append(xmlbox) jp2.append(xmlbox)
# The sequence of box IDs should be the same as before, but with an # 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) 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('<?xml version="1.0"?><data>0</data>')
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=<byte\sarray\s10\selements>\)"
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=<glymur.codestream.Codestream\sobject\s"
regexp += "at\s0x([a-f0-9]*)>\)"
if sys.hexversion < 0x03000000:
self.assertRegexpMatches(repr(box), regexp)
else:
self.assertRegex(repr(box), regexp)
class TestJpxBoxes(unittest.TestCase): class TestJpxBoxes(unittest.TestCase):
"""Tests for JPX boxes.""" """Tests for JPX boxes."""

View file

@ -67,6 +67,15 @@ class TestJp2k(unittest.TestCase):
def tearDown(self): def tearDown(self):
pass pass
def test_repr(self):
"""Verify that results of __repr__ are eval-able."""
j = Jp2k(self.j2kfile)
newjp2 = eval(repr(j))
self.assertEqual(newjp2.filename, self.j2kfile)
self.assertEqual(newjp2.mode, 'rb')
self.assertEqual(len(newjp2.box), 0)
def test_rlevel_max(self): def test_rlevel_max(self):
"""Verify that rlevel=-1 gets us the lowest resolution image""" """Verify that rlevel=-1 gets us the lowest resolution image"""
j = Jp2k(self.j2kfile) j = Jp2k(self.j2kfile)

View file

@ -3797,19 +3797,16 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[2].box[0].ip_provided, False) self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Palette box. # Palette box.
self.assertEqual(len(jp2.box[2].box[1].palette), 3) self.assertEqual(jp2.box[2].box[1].palette.shape, (256, 3))
self.assertEqual(len(jp2.box[2].box[1].palette[0]), 256) np.testing.assert_array_equal(jp2.box[2].box[1].palette[0, 0], 0)
self.assertEqual(len(jp2.box[2].box[1].palette[1]), 256) np.testing.assert_array_equal(jp2.box[2].box[1].palette[0, 1], 0)
self.assertEqual(len(jp2.box[2].box[1].palette[2]), 256) np.testing.assert_array_equal(jp2.box[2].box[1].palette[0, 2], 0)
np.testing.assert_array_equal(jp2.box[2].box[1].palette[0][0], 0) np.testing.assert_array_equal(jp2.box[2].box[1].palette[128, 0], 73)
np.testing.assert_array_equal(jp2.box[2].box[1].palette[1][0], 0) np.testing.assert_array_equal(jp2.box[2].box[1].palette[128, 1], 92)
np.testing.assert_array_equal(jp2.box[2].box[1].palette[2][0], 0) np.testing.assert_array_equal(jp2.box[2].box[1].palette[128, 2], 53)
np.testing.assert_array_equal(jp2.box[2].box[1].palette[0][128], 73) np.testing.assert_array_equal(jp2.box[2].box[1].palette[255, 0], 245)
np.testing.assert_array_equal(jp2.box[2].box[1].palette[1][128], 92) np.testing.assert_array_equal(jp2.box[2].box[1].palette[255, 1], 245)
np.testing.assert_array_equal(jp2.box[2].box[1].palette[2][128], 53) np.testing.assert_array_equal(jp2.box[2].box[1].palette[255, 2], 245)
np.testing.assert_array_equal(jp2.box[2].box[1].palette[0][-1], 245)
np.testing.assert_array_equal(jp2.box[2].box[1].palette[1][-1], 245)
np.testing.assert_array_equal(jp2.box[2].box[1].palette[2][-1], 245)
# Component mapping box # Component mapping box
self.assertEqual(jp2.box[2].box[2].component_index, (0, 0, 0)) self.assertEqual(jp2.box[2].box[2].component_index, (0, 0, 0))
@ -5699,10 +5696,7 @@ class TestSuiteDump(unittest.TestCase):
# Jp2 Header # Jp2 Header
# Palette box. # Palette box.
self.assertEqual(len(jp2.box[3].box[2].palette), 3) self.assertEqual(jp2.box[3].box[2].palette.shape, (256, 3))
self.assertEqual(len(jp2.box[3].box[2].palette[0]), 256)
self.assertEqual(len(jp2.box[3].box[2].palette[1]), 256)
self.assertEqual(len(jp2.box[3].box[2].palette[2]), 256)
# Jp2 Header # Jp2 Header
# Component mapping box # Component mapping box
@ -6127,11 +6121,7 @@ class TestSuiteDump(unittest.TestCase):
# Jp2 Header # Jp2 Header
# Palette box. # Palette box.
self.assertEqual(len(jp2.box[3].box[2].palette), 4) self.assertEqual(jp2.box[3].box[2].palette.shape, (1, 4))
self.assertEqual(len(jp2.box[3].box[2].palette[0]), 1)
self.assertEqual(len(jp2.box[3].box[2].palette[1]), 1)
self.assertEqual(len(jp2.box[3].box[2].palette[2]), 1)
self.assertEqual(len(jp2.box[3].box[2].palette[3]), 1)
# Jp2 Header # Jp2 Header
# Component mapping box # Component mapping box
@ -6242,10 +6232,7 @@ class TestSuiteDump(unittest.TestCase):
# Jp2 Header # Jp2 Header
# Palette box. # Palette box.
# 3 columns with 16 entries. # 3 columns with 16 entries.
self.assertEqual(len(jp2.box[3].box[2].palette), 3) self.assertEqual(jp2.box[3].box[2].palette.shape, (16, 3))
self.assertEqual(len(jp2.box[3].box[2].palette[0]), 16)
self.assertEqual(len(jp2.box[3].box[2].palette[1]), 16)
self.assertEqual(len(jp2.box[3].box[2].palette[2]), 16)
# Jp2 Header # Jp2 Header
# Component mapping box # Component mapping box