Compare commits

..

No commits in common. "devel" and "master" have entirely different histories.

5 changed files with 602 additions and 845 deletions

View file

@ -1,5 +1,3 @@
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,

View file

@ -356,45 +356,35 @@ class ColourSpecificationBox(Jp2kBox):
return msg
def __str__(self):
title = Jp2kBox.__str__(self)
msg = Jp2kBox.__str__(self)
if _printoptions['short'] is True:
return title
return msg
lst = []
text = 'Method: {0}'.format(_METHOD_DISPLAY[self.method])
lst.append(text)
text = 'Precedence: {0}'.format(self.precedence)
lst.append(text)
msg += '\n Method: {0}'.format(_METHOD_DISPLAY[self.method])
msg += '\n Precedence: {0}'.format(self.precedence)
if self.approximation is not 0:
dispvalue = _APPROX_DISPLAY[self.approximation]
text = 'Approximation: {0}'.format(dispvalue)
lst.append(text)
msg += '\n Approximation: {0}'.format(dispvalue)
if self.colorspace is not None:
dispvalue = _COLORSPACE_MAP_DISPLAY[self.colorspace]
text = 'Colorspace: {0}'.format(dispvalue)
msg += '\n Colorspace: {0}'.format(dispvalue)
else:
# 2.7 has trouble pretty-printing ordered dicts so we just have
# to print as a regular dict in this case.
if self.icc_profile is None:
text = 'ICC Profile: None'
msg += '\n ICC Profile: None'
else:
if sys.hexversion < 0x03000000:
icc_profile = dict(self.icc_profile)
else:
icc_profile = self.icc_profile
text = pprint.pformat(icc_profile)
text = self._indent(text)
text = '\n'.join(['ICC Profile:', text])
dispvalue = pprint.pformat(icc_profile)
lines = [' ' * 8 + y for y in dispvalue.split('\n')]
msg += '\n ICC Profile:\n{0}'.format('\n'.join(lines))
lst.append(text)
text = '\n'.join(lst)
text = '\n'.join([title, self._indent(text)])
return text
return msg
def write(self, fptr):
"""Write an Colour Specification box to file.
@ -630,26 +620,19 @@ class ChannelDefinitionBox(Jp2kBox):
self._dispatch_validation_error(msg, writing=writing)
def __str__(self):
title = Jp2kBox.__str__(self)
msg = Jp2kBox.__str__(self)
if _printoptions['short'] is True:
return title
return msg
lst = []
for j in range(len(self.association)):
color_type_string = _COLOR_TYPE_MAP_DISPLAY[self.channel_type[j]]
if self.association[j] == 0:
assn = 'whole image'
else:
assn = str(self.association[j])
text = 'Channel {0} ({1}) ==> ({2})'.format(self.index[j],
color_type_string,
assn)
lst.append(text)
text = '\n'.join(lst)
text = self._indent(text)
text = '\n'.join([title, text])
return text
msg += '\n Channel {0} ({1}) ==> ({2})'
msg = msg.format(self.index[j], color_type_string, assn)
return msg
def __repr__(self):
msg = "glymur.jp2box.ChannelDefinitionBox("
@ -946,26 +929,19 @@ class ComponentMappingBox(Jp2kBox):
return msg
def __str__(self):
title = Jp2kBox.__str__(self)
msg = Jp2kBox.__str__(self)
if _printoptions['short'] is True:
return title
return msg
lst = []
for k in range(len(self.component_index)):
if self.mapping_type[k] == 1:
text = 'Component {0} ==> palette column {1}'
text = text.format(self.component_index[k],
self.palette_index[k])
msg += '\n Component {0} ==> palette column {1}'
msg = msg.format(self.component_index[k],
self.palette_index[k])
else:
text = 'Component {0} ==> {1}'
text = text.format(self.component_index[k], k)
lst.append(text)
text = '\n'.join(lst)
text = self._indent(text)
text = '\n'.join([title, text])
return text
msg += '\n Component {0} ==> {1}'
msg = msg.format(self.component_index[k], k)
return msg
def write(self, fptr):
"""Write a Component Mapping box to file.
@ -1065,20 +1041,16 @@ class ContiguousCodestreamBox(Jp2kBox):
return msg.format(repr(self.codestream))
def __str__(self):
title = Jp2kBox.__str__(self)
msg = Jp2kBox.__str__(self)
if _printoptions['short'] is True:
return title
return msg
if _printoptions['codestream'] is False:
return title
return msg
lst = []
for segment in self.codestream.segment:
lst.append(str(segment))
msg += '\n' + self._indent(str(segment), indent_level=4)
text = '\n'.join(lst)
text = self._indent(text)
text = '\n'.join([title, text])
return text
return msg
@classmethod
def parse(cls, fptr, offset=0, length=0):
@ -1177,18 +1149,13 @@ class DataReferenceBox(Jp2kBox):
fptr.seek(end_pos)
def __str__(self):
title = Jp2kBox.__str__(self)
msg = Jp2kBox.__str__(self)
if _printoptions['short'] is True:
return title
return msg
lst = []
for box in self.DR:
lst.append(str(box))
text = '\n'.join(lst)
text = self._indent(text)
text = '\n'.join([title, text])
return text
msg += '\n ' + str(box)
return msg
def __repr__(self):
msg = 'glymur.jp2box.DataReferenceBox()'
@ -1285,22 +1252,17 @@ class FileTypeBox(Jp2kBox):
return msg
def __str__(self):
title = Jp2kBox.__str__(self)
msg = Jp2kBox.__str__(self)
if _printoptions['short'] is True:
return title
return msg
lst = []
text = 'Brand: {0}'.format(self.brand)
lst.append(text)
text = 'Compatibility: {0}'.format(self.compatibility_list)
lst.append(text)
lst = [msg,
' Brand: {0}',
' Compatibility: {1}']
msg = '\n'.join(lst)
msg = msg.format(self.brand, self.compatibility_list)
text = '\n'.join(lst)
text = self._indent(text)
text = '\n'.join([title, text])
return text
return msg
def _validate(self, writing=False):
"""Validate the box before writing to file."""
@ -1422,24 +1384,19 @@ class FragmentListBox(Jp2kBox):
return msg
def __str__(self):
title = Jp2kBox.__str__(self)
msg = Jp2kBox.__str__(self)
if _printoptions['short'] is True:
return title
return msg
lst = []
for j in range(len(self.fragment_offset)):
text = "Offset {0}: {1}".format(j, self.fragment_offset[j])
lst.append(text)
text = "Fragment Length {0}: {1}".format(j,
self.fragment_length[j])
lst.append(text)
text = "Data Reference {0}: {1}".format(j, self.data_reference[j])
lst.append(text)
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])
text = '\n'.join(lst)
text = self._indent(text)
text = '\n'.join([title, text])
return text
return msg
def write(self, fptr):
"""Write a fragment list box to file.
@ -1589,7 +1546,11 @@ class FreeBox(Jp2kBox):
return msg
def __str__(self):
return Jp2kBox.__str__(self)
msg = Jp2kBox.__str__(self)
if _printoptions['short'] is True:
return msg
return msg
@classmethod
def parse(cls, fptr, offset, length):
@ -1681,34 +1642,23 @@ class ImageHeaderBox(Jp2kBox):
return msg
def __str__(self):
title = Jp2kBox.__str__(self)
msg = Jp2kBox.__str__(self)
if _printoptions['short'] is True:
return title
return msg
lst = []
text = 'Size: [{0} {1} {2}]'
text = text.format(self.height, self.width, self.num_components)
lst.append(text)
text = 'Bitdepth: {0}'.format(self.bits_per_component)
lst.append(text)
text = 'Signed: {0}'.format(self.signed)
lst.append(text)
text = 'Compression: {0}'
text = text.format('wavelet' if self.compression == 7 else 'unknown')
lst.append(text)
text = 'Colorspace Unknown: {0}'.format(self.colorspace_unknown)
lst.append(text)
text = '\n'.join(lst)
text = self._indent(text)
text = '\n'.join([title, text])
return text
msg = "{0}"
msg += '\n Size: [{1} {2} {3}]'
msg += '\n Bitdepth: {4}'
msg += '\n Signed: {5}'
msg += '\n Compression: {6}'
msg += '\n Colorspace Unknown: {7}'
msg = msg.format(Jp2kBox.__str__(self),
self.height, self.width, self.num_components,
self.bits_per_component,
self.signed,
'wavelet' if self.compression == 7 else 'unknown',
self.colorspace_unknown)
return msg
def write(self, fptr):
"""Write an Image Header box to file.
@ -1830,79 +1780,6 @@ 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.
@ -1996,16 +1873,14 @@ class JPEG2000SignatureBox(Jp2kBox):
return 'glymur.jp2box.JPEG2000SignatureBox()'
def __str__(self):
title = Jp2kBox.__str__(self)
msg = Jp2kBox.__str__(self)
if _printoptions['short'] is True:
return title
return msg
body = 'Signature: {0:02x}{1:02x}{2:02x}{3:02x}'
body = body.format(self.signature[0], self.signature[1],
self.signature[2], self.signature[3])
body = self._indent(body)
text = '\n'.join([title, body])
return text
msg += '\n Signature: {0:02x}{1:02x}{2:02x}{3:02x}'
msg = msg.format(self.signature[0], self.signature[1],
self.signature[2], self.signature[3])
return msg
def write(self, fptr):
"""Write a JPEG 2000 Signature box to file.
@ -2087,15 +1962,12 @@ class PaletteBox(Jp2kBox):
return msg
def __str__(self):
title = Jp2kBox.__str__(self)
msg = Jp2kBox.__str__(self)
if _printoptions['short'] is True:
return title
return msg
body = 'Size: ({0} x {1})'.format(*self.palette.shape)
body = self._indent(body)
text = '\n'.join([title, body])
return text
msg += '\n Size: ({0} x {1})'.format(*self.palette.shape)
return msg
def write(self, fptr):
"""Write a Palette box to file.
@ -2339,47 +2211,25 @@ class ReaderRequirementsBox(Jp2kBox):
return msg
def __str__(self):
title = Jp2kBox.__str__(self)
msg = Jp2kBox.__str__(self)
if _printoptions['short'] is True:
return title
return msg
lst = []
msg += '\n Fully Understands Aspect Mask: 0x{0:x}'
msg = msg.format(self.fuam)
msg += '\n Display Completely Mask: 0x{0:x}'.format(self.dcm)
text = 'Fully Understands Aspect Mask: 0x{0:x}'.format(self.fuam)
lst.append(text)
text = 'Display Completely Mask: 0x{0:x}'.format(self.dcm)
lst.append(text)
text = 'Standard Features and Masks:'
lst.append(text)
lst2 = []
msg += '\n Standard Features and Masks:'
for j in range(len(self.standard_flag)):
args = (self.standard_flag[j], self.standard_mask[j],
_READER_REQUIREMENTS_DISPLAY[self.standard_flag[j]])
text = 'Feature {0:03d}: 0x{1:x} {2}'.format(*args)
lst2.append(text)
text = '\n'.join(lst2)
text = self._indent(text)
lst.append(text)
msg += '\n Feature {0:03d}: 0x{1:x} {2}'.format(*args)
text = 'Vendor Features:'
lst.append(text)
lst2 = []
msg += '\n Vendor Features:'
for j in range(len(self.vendor_feature)):
text = 'UUID {0}'.format(self.vendor_feature[j])
lst2.append(text)
text = '\n'.join(lst2)
text = self._indent(text)
lst.append(text)
msg += '\n UUID {0}'.format(self.vendor_feature[j])
text = '\n'.join(lst)
text = self._indent(text)
text = '\n'.join([title, text])
return text
return msg
@classmethod
def parse(cls, fptr, offset, length):
@ -2650,21 +2500,13 @@ class CaptureResolutionBox(Jp2kBox):
return msg
def __str__(self):
title = Jp2kBox.__str__(self)
msg = Jp2kBox.__str__(self)
if _printoptions['short'] is True:
return title
return msg
lst = []
text = 'VCR: {0}'.format(self.vertical_resolution)
lst.append(text)
text = 'HCR: {0}'.format(self.horizontal_resolution)
lst.append(text)
text = '\n'.join(lst)
body = self._indent(text)
text = '\n'.join([title, body])
return text
msg += '\n VCR: {0}'.format(self.vertical_resolution)
msg += '\n HCR: {0}'.format(self.horizontal_resolution)
return msg
@classmethod
def parse(cls, fptr, offset, length):
@ -2724,21 +2566,13 @@ class DisplayResolutionBox(Jp2kBox):
return msg
def __str__(self):
title = Jp2kBox.__str__(self)
msg = Jp2kBox.__str__(self)
if _printoptions['short'] is True:
return title
return msg
lst = []
text = 'VDR: {0}'.format(self.vertical_resolution)
lst.append(text)
text = 'HDR: {0}'.format(self.horizontal_resolution)
lst.append(text)
text = '\n'.join(lst)
body = self._indent(text)
text = '\n'.join([title, body])
return text
msg += '\n VDR: {0}'.format(self.vertical_resolution)
msg += '\n HDR: {0}'.format(self.horizontal_resolution)
return msg
@classmethod
def parse(cls, fptr, offset, length):
@ -2792,15 +2626,12 @@ class LabelBox(Jp2kBox):
self.offset = offset
def __str__(self):
title = Jp2kBox.__str__(self)
msg = Jp2kBox.__str__(self)
if _printoptions['short'] is True:
return title
return msg
text = 'Label: {0}'.format(self.label)
body = self._indent(text)
text = '\n'.join([title, body])
return text
msg += '\n Label: {0}'.format(self.label)
return msg
def __repr__(self):
msg = 'glymur.jp2box.LabelBox("{0}")'.format(self.label)
@ -2863,30 +2694,25 @@ class NumberListBox(Jp2kBox):
self.offset = offset
def __str__(self):
title = Jp2kBox.__str__(self)
msg = Jp2kBox.__str__(self)
if _printoptions['short'] is True:
return title
return msg
lst = []
for j, association in enumerate(self.associations):
text = 'Association[{0}]: '.format(j)
msg += '\n Association[{0}]: '.format(j)
if association == 0:
text += 'the rendered result'
msg += 'the rendered result'
elif (association >> 24) == 1:
idx = association & 0x00FFFFFF
text += 'codestream {0}'.format(idx)
msg += 'codestream {0}'
msg = msg.format(idx)
elif (association >> 24) == 2:
idx = association & 0x00FFFFFF
text += 'compositing layer {0}'.format(idx)
msg += 'compositing layer {0}'
msg = msg.format(idx)
else:
text += 'unrecognized'
lst.append(text)
body = '\n'.join(lst)
body = self._indent(body)
text = '\n'.join([title, body])
return text
msg += 'unrecognized'
return msg
def __repr__(self):
msg = 'glymur.jp2box.NumberListBox(associations={0})'
@ -2971,22 +2797,21 @@ class XMLBox(Jp2kBox):
return "glymur.jp2box.XMLBox(xml={0})".format(self.xml)
def __str__(self):
title = Jp2kBox.__str__(self)
msg = Jp2kBox.__str__(self)
if _printoptions['short'] is True:
return title
return msg
if _printoptions['xml'] is False:
return title
return msg
msg += '\n'
if self.xml is not None:
body = ET.tostring(self.xml,
encoding='utf-8',
pretty_print=True).decode('utf-8')
xmlstring = ET.tostring(self.xml,
encoding='utf-8',
pretty_print=True).decode('utf-8')
else:
body = 'None'
body = self._indent(body)
text = '\n'.join([title, body])
return text
xmlstring = 'None'
msg += self._indent(xmlstring)
return msg
def write(self, fptr):
"""Write an XML box to file.
@ -3093,19 +2918,13 @@ class UUIDListBox(Jp2kBox):
return msg
def __str__(self):
title = Jp2kBox.__str__(self)
msg = Jp2kBox.__str__(self)
if _printoptions['short'] is True:
return title
return msg
lst = []
for j, uuid_item in enumerate(self.ulst):
text = 'UUID[{0}]: {1}'.format(j, uuid_item)
lst.append(text)
body = '\n'.join(lst)
body = self._indent(body)
text = '\n'.join([title, body])
return text
msg += '\n UUID[{0}]: {1}'.format(j, uuid_item)
return msg
@classmethod
def parse(cls, fptr, offset, length):
@ -3251,21 +3070,20 @@ class DataEntryURLBox(Jp2kBox):
return msg
def __str__(self):
title = Jp2kBox.__str__(self)
msg = Jp2kBox.__str__(self)
if _printoptions['short'] is True:
return title
return msg
lst = ['Version: {0}',
'Flag: {1} {2} {3}',
'URL: "{4}"']
body = '\n'.join(lst)
body = body.format(self.version,
self.flag[0], self.flag[1], self.flag[2],
self.url)
body = self._indent(body)
msg += '\n '
text = '\n'.join([title, body])
return text
lines = ['Version: {0}',
'Flag: {1} {2} {3}',
'URL: "{4}"']
msg += '\n '.join(lines)
msg = msg.format(self.version,
self.flag[0], self.flag[1], self.flag[2],
self.url)
return msg
@classmethod
def parse(cls, fptr, offset, length):
@ -3404,44 +3222,38 @@ class UUIDBox(Jp2kBox):
return msg.format(repr(self.uuid), len(self.raw_data))
def __str__(self):
title = Jp2kBox.__str__(self)
msg = Jp2kBox.__str__(self)
if _printoptions['short'] is True:
return title
return msg
text = 'UUID: {0}'.format(self.uuid)
msg = '{0}\n UUID: {1}'.format(msg, self.uuid)
if self.uuid == UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'):
text += ' (XMP)'
msg += ' (XMP)'
elif self.uuid.bytes == b'JpgTiffExif->JP2':
text += ' (EXIF)'
msg += ' (EXIF)'
else:
text += ' (unknown)'
lst = [text]
msg += ' (unknown)'
if (((_printoptions['xml'] is False) and
(self.uuid == UUID('be7acfcb-97a9-42e8-9c71-999491e3afac')))):
# If it's an XMP UUID, don't print the XML contents.
pass
return msg
elif self.uuid == UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'):
line = 'UUID Data:\n{0}'
if self.uuid == UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'):
line = '\n UUID Data:\n{0}'
xmlstring = ET.tostring(self.data,
encoding='utf-8',
pretty_print=True).decode('utf-8').rstrip()
text = line.format(xmlstring)
lst.append(text)
pretty_print=True).decode('utf-8')
# indent it a bit
xmlstring = self._indent(xmlstring.rstrip())
msg += line.format(xmlstring)
elif self.uuid.bytes == b'JpgTiffExif->JP2':
text = 'UUID Data: {0}'.format(str(self.data))
lst.append(text)
msg += '\n UUID Data: {0}'.format(str(self.data))
else:
text = 'UUID Data: {0} bytes'.format(len(self.raw_data))
lst.append(text)
line = '\n UUID Data: {0} bytes'
msg += line.format(len(self.raw_data))
body = '\n'.join(lst)
body = self._indent(body)
text = '\n'.join([title, body])
return text
return msg
def write(self, fptr):
"""Write a UUID box to file.
@ -3477,7 +3289,6 @@ 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,

File diff suppressed because it is too large Load diff

View file

@ -1092,8 +1092,3 @@ 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]"""

View file

@ -618,17 +618,6 @@ 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')
@ -1099,6 +1088,8 @@ class TestJp2dump(unittest.TestCase):
def test_jp2_codestream_2(self):
"""Verify dumping with -c 2, print entire jp2 jacket, codestream."""
actual = self.run_jp2dump(['', '-c', '2', self.jp2file])
# shave off the non-main-header segments
expected = fixtures.nemo
self.assertEqual(actual, expected)
@ -1134,7 +1125,6 @@ class TestJp2dump(unittest.TestCase):
def test_suppress_xml(self):
"""Verify dumping with -x, suppress XML."""
self.maxDiff = None
actual = self.run_jp2dump(['', '-x', self.jp2file])
# shave off the XML and non-main-header segments