diff --git a/glymur/jp2box.py b/glymur/jp2box.py index ddcd19c..98b0e6a 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -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. @@ -1923,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. @@ -2014,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. @@ -2266,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): @@ -2577,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): @@ -2651,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): @@ -2719,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) @@ -2790,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})' @@ -2898,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. @@ -3020,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): @@ -3178,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): @@ -3331,46 +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') - # Remove any trailing newline - xmlstring = xmlstring.rstrip() - text = line.format(xmlstring) - lst.append(text) + # 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. diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 3633657..ee6eedc 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -363,11 +363,7 @@ class Jp2k(Jp2kBox): # 2.1 API self._cparams.rsiz = core.OPJ_PROFILE_CINEMA_4K - def _populate_cparams(self, img_array, mct=None, cratios=None, psnr=None, - cinema2k=None, cinema4k=None, irreversible=None, - cbsize=None, eph=None, grid_offset=None, modesw=None, - numres=None, prog=None, psizes=None, sop=None, - subsam=None, tilesize=None, colorspace=None): + def _populate_cparams(self, img_array, **kwargs): """Directs processing of write method arguments. Parameters @@ -377,14 +373,12 @@ class Jp2k(Jp2kBox): kwargs : dictionary non-image keyword inputs provided to write method """ - other_args = (mct, cratios, psnr, irreversible, cbsize, eph, - grid_offset, modesw, numres, prog, psizes, sop, subsam) - if (((cinema2k is not None or cinema4k is not None) and - (not all([arg is None for arg in other_args])))): + if ((('cinema2k' in kwargs or 'cinema4k' in kwargs) and + (len(set(kwargs)) > 1))): msg = "Cannot specify cinema2k/cinema4k along with other options." raise IOError(msg) - if cratios is not None and psnr is not None: + if 'cratios' in kwargs and 'psnr' in kwargs: msg = "Cannot specify cratios and psnr together." raise IOError(msg) @@ -408,81 +402,90 @@ class Jp2k(Jp2kBox): cparams.tcp_numlayers = 1 cparams.cp_disto_alloc = 1 - cparams.irreversible = 1 if irreversible else 0 + if 'irreversible' in kwargs and kwargs['irreversible'] is True: + cparams.irreversible = 1 - if cinema2k is not None: + if 'cinema2k' in kwargs: self._cparams = cparams - self._set_cinema_params('cinema2k', cinema2k) + self._set_cinema_params('cinema2k', kwargs['cinema2k']) return - if cinema4k is not None: + if 'cinema4k' in kwargs: self._cparams = cparams - self._set_cinema_params('cinema4k', cinema4k) + self._set_cinema_params('cinema4k', kwargs['cinema4k']) return - if cbsize is not None: - cparams.cblockw_init = cbsize[1] - cparams.cblockh_init = cbsize[0] + if 'cbsize' in kwargs: + cparams.cblockw_init = kwargs['cbsize'][1] + cparams.cblockh_init = kwargs['cbsize'][0] - if cratios is not None: - cparams.tcp_numlayers = len(cratios) - for j, cratio in enumerate(cratios): + if 'cratios' in kwargs: + cparams.tcp_numlayers = len(kwargs['cratios']) + for j, cratio in enumerate(kwargs['cratios']): cparams.tcp_rates[j] = cratio cparams.cp_disto_alloc = 1 - cparams.csty |= 0x02 if sop else 0 - cparams.csty |= 0x04 if eph else 0 + if 'eph' in kwargs: + cparams.csty |= 0x04 - if grid_offset is not None: - cparams.image_offset_x0 = grid_offset[1] - cparams.image_offset_y0 = grid_offset[0] + if 'grid_offset' in kwargs: + cparams.image_offset_x0 = kwargs['grid_offset'][1] + cparams.image_offset_y0 = kwargs['grid_offset'][0] - if modesw is not None: + if 'modesw' in kwargs: for shift in range(6): power_of_two = 1 << shift - if modesw & power_of_two: + if kwargs['modesw'] & power_of_two: cparams.mode |= power_of_two - if numres is not None: - cparams.numresolution = numres + if 'numres' in kwargs: + cparams.numresolution = kwargs['numres'] - if prog is not None: - cparams.prog_order = core.PROGRESSION_ORDER[prog.upper()] + if 'prog' in kwargs: + prog = kwargs['prog'].upper() + cparams.prog_order = core.PROGRESSION_ORDER[prog] - if psnr is not None: - cparams.tcp_numlayers = len(psnr) - for j, snr_layer in enumerate(psnr): + if 'psnr' in kwargs: + cparams.tcp_numlayers = len(kwargs['psnr']) + for j, snr_layer in enumerate(kwargs['psnr']): cparams.tcp_distoratio[j] = snr_layer cparams.cp_fixed_quality = 1 - if psizes is not None: - for j, (prch, prcw) in enumerate(psizes): + if 'psizes' in kwargs: + for j, (prch, prcw) in enumerate(kwargs['psizes']): cparams.prcw_init[j] = prcw cparams.prch_init[j] = prch cparams.csty |= 0x01 - cparams.res_spec = len(psizes) + cparams.res_spec = len(kwargs['psizes']) - if subsam is not None: - cparams.subsampling_dy = subsam[0] - cparams.subsampling_dx = subsam[1] + if 'sop' in kwargs: + cparams.csty |= 0x02 - if tilesize is not None: - cparams.cp_tdx = tilesize[1] - cparams.cp_tdy = tilesize[0] + if 'subsam' in kwargs: + cparams.subsampling_dy = kwargs['subsam'][0] + cparams.subsampling_dx = kwargs['subsam'][1] + + if 'tilesize' in kwargs: + cparams.cp_tdx = kwargs['tilesize'][1] + cparams.cp_tdy = kwargs['tilesize'][0] cparams.tile_size_on = opj2.TRUE - if mct is None: - # If the multi component transform was not specified, we infer - # that it should be used if the color space is RGB. - cparams.tcp_mct = 1 if self._colorspace == opj2.CLRSPC_SRGB else 0 - else: - if self._colorspace == opj2.CLRSPC_GRAY: + try: + mct = kwargs['mct'] + if mct and self._colorspace == opj2.CLRSPC_GRAY: msg = "Cannot specify usage of the multi component transform " msg += "if the colorspace is gray." raise IOError(msg) cparams.tcp_mct = 1 if mct else 0 + except KeyError: + # If the multi component transform was not specified, we infer + # that it should be used if the color space is RGB. + if self._colorspace == opj2.CLRSPC_SRGB: + cparams.tcp_mct = 1 + else: + cparams.tcp_mct = 0 - self._validate_compression_params(img_array, cparams, colorspace) + self._validate_compression_params(img_array, cparams, **kwargs) self._cparams = cparams @@ -573,26 +576,29 @@ class Jp2k(Jp2kBox): self.parse() - def _validate_j2k_colorspace(self, cparams, colorspace): + def _validate_compression_params(self, img_array, cparams, **kwargs): + """Check that the compression parameters are valid. + + Parameters + ---------- + img_array : ndarray + Image data to be written to file. + cparams : CompressionParametersType(ctypes.Structure) + Corresponds to cparameters_t type in openjp2 headers. """ - Cannot specify a colorspace with J2K. - """ - if cparams.codec_fmt == opj2.CODEC_J2K and colorspace is not None: + # Cannot specify a colorspace with J2K. + if cparams.codec_fmt == opj2.CODEC_J2K and 'colorspace' in kwargs: msg = 'Do not specify a colorspace when writing a raw ' msg += 'codestream.' raise IOError(msg) - def _validate_codeblock_size(self, cparams): - """ - Code block dimensions must satisfy certain restrictions. - - They must both be a power of 2 and the total area defined by the width - and height cannot be either too great or too small for the codec. - """ + # Code block size + code_block_specified = False if cparams.cblockw_init != 0 and cparams.cblockh_init != 0: # These fields ARE zero if uninitialized. width = cparams.cblockw_init height = cparams.cblockh_init + code_block_specified = True if height * width > 4096 or height < 4 or width < 4: msg = "Code block area cannot exceed 4096. " msg += "Code block height and width must be larger than 4." @@ -603,17 +609,7 @@ class Jp2k(Jp2kBox): msg += "must be powers of 2." raise IOError(msg.format(height, width)) - def _validate_precinct_size(self, cparams): - """ - Precinct dimensions must satisfy certain restrictions if specified. - - They must both be a power of 2 and must both be at least twice the - size of their codeblock size counterparts. - """ - code_block_specified = False - if cparams.cblockw_init != 0 and cparams.cblockh_init != 0: - code_block_specified = True - + # Precinct size if cparams.res_spec != 0: # precinct size was not specified if this field is zero. for j in range(cparams.res_spec): @@ -631,18 +627,11 @@ class Jp2k(Jp2kBox): msg += "must be powers of 2." raise IOError(msg.format(prch, prcw)) - def _validate_image_rank(self, img_array): - """ - Images must be either 2D or 3D. - """ + # What would the point of 1D images be? if img_array.ndim == 1 or img_array.ndim > 3: msg = "{0}D imagery is not allowed.".format(img_array.ndim) raise IOError(msg) - def _validate_v2_0_0_images(self, img_array): - """ - Version 2.0.0 is restricted to only the most common images. - """ if re.match("2.0.0", version.openjpeg_version) is not None: if (((img_array.ndim != 2) and (img_array.shape[2] != 1 and img_array.shape[2] != 3))): @@ -652,32 +641,11 @@ class Jp2k(Jp2kBox): msg += "release." raise IOError(msg) - def _validate_image_datatype(self, img_array): - """ - Only uint8 and uint16 images are currently supported. - """ if img_array.dtype != np.uint8 and img_array.dtype != np.uint16: msg = "Only uint8 and uint16 datatypes are currently supported " msg += "when writing." raise RuntimeError(msg) - def _validate_compression_params(self, img_array, cparams, colorspace): - """Check that the compression parameters are valid. - - Parameters - ---------- - img_array : ndarray - Image data to be written to file. - cparams : CompressionParametersType(ctypes.Structure) - Corresponds to cparameters_t type in openjp2 headers. - """ - self._validate_j2k_colorspace(cparams, colorspace) - self._validate_codeblock_size(cparams) - self._validate_precinct_size(cparams) - self._validate_image_rank(img_array) - self._validate_v2_0_0_images(img_array) - self._validate_image_datatype(img_array) - def _determine_colorspace(self, colorspace=None, **kwargs): """Determine the colorspace from the supplied inputs. @@ -948,47 +916,6 @@ class Jp2k(Jp2kBox): msg = "Partial write operations are currently not allowed." raise TypeError(msg) - def _remove_ellipsis(self, index, numrows, numcols, numbands): - """ - resolve the first ellipsis in the index so that it references the image - - Parameters - ---------- - index : tuple - tuple of index arguments, presumably one of them is the Ellipsis - numrows, numcols, numbands : int - image dimensions - - Returns - ------- - newindex : tuple - Same as index, except that the first Ellipsis is replaced with - a proper slice whose start and stop members are not None - """ - # Remove the first ellipsis we find. - rows = slice(0, numrows) - cols = slice(0, numcols) - bands = slice(0, numbands) - if index[0] is Ellipsis: - if len(index) == 2: - # jp2k[..., other_slice] - newindex = (rows, cols, index[1]) - else: - # jp2k[..., cols, bands] - newindex = (rows, index[1], index[2]) - elif index[1] is Ellipsis: - if len(index) == 2: - # jp2k[rows, ...] - newindex = (index[0], cols, bands) - else: - # jp2k[rows, ..., bands] - newindex = (index[0], cols, index[2]) - else: - # Assume that we don't have 4D imagery, of course. - newindex = (index[0], index[1], bands) - - return newindex - def __getitem__(self, pargs): """ Slicing protocol. @@ -1023,7 +950,23 @@ class Jp2k(Jp2kBox): pargs = (pargs, Ellipsis) if isinstance(pargs, tuple) and any(x is Ellipsis for x in pargs): - newindex = self._remove_ellipsis(pargs, numrows, numcols, numbands) + # Remove the first ellipsis we find. + rows = slice(0, numrows) + cols = slice(0, numcols) + bands = slice(0, numbands) + if pargs[0] is Ellipsis: + if len(pargs) == 2: + newindex = (rows, cols, pargs[1]) + else: + newindex = (rows, pargs[1], pargs[2]) + elif pargs[1] is Ellipsis: + if len(pargs) == 2: + newindex = (pargs[0], cols, bands) + else: + newindex = (pargs[0], cols, pargs[2]) + else: + # Assume that we don't have 4D imagery, of course. + newindex = (pargs[0], pargs[1], bands) # Run once again because it is possible that there's another # Ellipsis object in the 2nd or 3rd position. @@ -1336,11 +1279,12 @@ class Jp2k(Jp2kBox): infile += b'0' * nelts dparam.infile = infile - # Return raw codestream components instead of "interpolating" the - # colormap? - dparam.flags |= 1 if self.ignore_pclr_cmap_cdef else 0 + if self.ignore_pclr_cmap_cdef: + # Return raw codestream components. + dparam.flags |= 1 dparam.decod_format = self._codec_format + dparam.cp_layer = self._layer # Must check the specified rlevel against the maximum. @@ -1371,6 +1315,10 @@ class Jp2k(Jp2kBox): dparam.tile_index = tile dparam.nb_tile_to_decode = 1 + if self.ignore_pclr_cmap_cdef: + # Return raw codestream components. + dparam.flags |= 1 + self._dparams = dparam def read_bands(self, rlevel=0, layer=None, area=None, tile=None, diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index ad60f29..bccaa00 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -1125,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