merge branch 'devel' of github.com:quintusdias/glymur into devel

This commit is contained in:
John Evans 2014-11-23 22:22:18 -05:00
commit 39cc8a845a
15 changed files with 302 additions and 278 deletions

View file

@ -11,6 +11,7 @@ import warnings
import lxml.etree as ET import lxml.etree as ET
def xml(raw_data): def xml(raw_data):
""" """
XMP data to be parsed as XML. XMP data to be parsed as XML.
@ -23,6 +24,7 @@ def xml(raw_data):
return ET.ElementTree(elt) return ET.ElementTree(elt)
def tiff_header(read_buffer): def tiff_header(read_buffer):
""" """
Interpret the uuid raw data as a tiff header. Interpret the uuid raw data as a tiff header.
@ -37,8 +39,8 @@ def tiff_header(read_buffer):
# big endian # big endian
endian = '>' endian = '>'
else: else:
msg = "The byte order indication in the TIFF header ({0}) is invalid. " msg = "The byte order indication in the TIFF header ({0}) is "
msg += "It should be either {1} or {2}." msg += "invalid. It should be either {1} or {2}."
msg = msg.format(read_buffer[6:8], bytes([73, 73]), bytes([77, 77])) msg = msg.format(read_buffer[6:8], bytes([73, 73]), bytes([77, 77]))
raise IOError(msg) raise IOError(msg)
@ -503,6 +505,3 @@ class _ExifInteroperabilityIfd(_Ifd):
def __init__(self, endian, read_buffer, offset): def __init__(self, endian, read_buffer, offset):
_Ifd.__init__(self, endian, read_buffer, offset) _Ifd.__init__(self, endian, read_buffer, offset)
self.post_process(self.tagnum2name) self.post_process(self.tagnum2name)

View file

@ -24,11 +24,10 @@ import warnings
import numpy as np import numpy as np
from .core import ( from .core import (LRCP, RLCP, RPCL, PCRL, CPRL,
LRCP, RLCP, RPCL, PCRL, CPRL, WAVELET_XFORM_9X7_IRREVERSIBLE,
WAVELET_XFORM_9X7_IRREVERSIBLE, WAVELET_XFORM_5X3_REVERSIBLE, WAVELET_XFORM_5X3_REVERSIBLE,
_Keydefaultdict _Keydefaultdict)
)
from .lib import openjp2 as opj2 from .lib import openjp2 as opj2
_factory = lambda x: '{0} (invalid)'.format(x) _factory = lambda x: '{0} (invalid)'.format(x)
@ -57,7 +56,7 @@ _CAPABILITIES_DISPLAY = _Keydefaultdict(_factory,
_PROFILE_0: '0', _PROFILE_0: '0',
_PROFILE_1: '1', _PROFILE_1: '1',
_PROFILE_3: 'Cinema 2K', _PROFILE_3: 'Cinema 2K',
_PROFILE_4: 'Cinema 4K'} ) _PROFILE_4: 'Cinema 4K'})
# Need a catch-all list of valid markers. # Need a catch-all list of valid markers.
# See table A-1 in ISO/IEC FCD15444-1. # See table A-1 in ISO/IEC FCD15444-1.
@ -703,7 +702,6 @@ class Codestream(object):
msg = "Invalid number of tiles ({0}).".format(numtiles) msg = "Invalid number of tiles ({0}).".format(numtiles)
warnings.warn(msg) warnings.warn(msg)
kwargs = {'rsiz': rsiz, kwargs = {'rsiz': rsiz,
'xysiz': xysiz, 'xysiz': xysiz,
'xyosiz': xyosiz, 'xyosiz': xyosiz,
@ -1614,6 +1612,7 @@ class SOCsegment(Segment):
msg = "glymur.codestream.SOCsegment()" msg = "glymur.codestream.SOCsegment()"
return msg 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

@ -8,28 +8,29 @@ import warnings
from . import Jp2k, set_printoptions, lib from . import Jp2k, set_printoptions, lib
def main(): def main():
""" """
Entry point for console script jp2dump. Entry point for console script jp2dump.
""" """
description='Print JPEG2000 metadata.' description = 'Print JPEG2000 metadata.'
parser = argparse.ArgumentParser(description=description) parser = argparse.ArgumentParser(description=description)
parser.add_argument('-x', '--noxml', parser.add_argument('-x', '--noxml',
help='Suppress XML.', help='Suppress XML.',
action='store_true') action='store_true')
parser.add_argument('-s', '--short', parser.add_argument('-s', '--short',
help='Only print box id, offset, and length.', help='Only print box id, offset, and length.',
action='store_true') action='store_true')
chelp = 'Level of codestream information. 0 suppressed all details, ' chelp = 'Level of codestream information. 0 suppressed all details, '
chelp += '1 prints headers, 2 prints the full codestream' chelp += '1 prints headers, 2 prints the full codestream'
parser.add_argument('-c', '--codestream', parser.add_argument('-c', '--codestream',
help=chelp, help=chelp,
nargs=1, nargs=1,
type=int, type=int,
default=[0]) default=[0])
parser.add_argument('filename') parser.add_argument('filename')
@ -58,7 +59,8 @@ def main():
# JP2 metadata can be extensive, so don't print any warnings until we # JP2 metadata can be extensive, so don't print any warnings until we
# are done with the metadata. # are done with the metadata.
jp2 = Jp2k(filename) jp2 = Jp2k(filename)
if jp2._codec_format == lib.openjp2.CODEC_J2K and codestream_level == 0: if (((jp2._codec_format == lib.openjp2.CODEC_J2K) and
(codestream_level == 0))):
print('File: {0}'.format(os.path.basename(filename))) print('File: {0}'.format(os.path.basename(filename)))
elif print_full_codestream: elif print_full_codestream:
print(jp2.get_codestream(header_only=False)) print(jp2.get_codestream(header_only=False))

View file

@ -4,6 +4,7 @@ import collections
import copy import copy
import lxml.etree as ET import lxml.etree as ET
class _Keydefaultdict(collections.defaultdict): class _Keydefaultdict(collections.defaultdict):
"""Unlisted keys help form their own error message. """Unlisted keys help form their own error message.
@ -121,12 +122,12 @@ ROMM_RGB = 21
_factory = lambda x: '{0} (unrecognized)'.format(x) _factory = lambda x: '{0} (unrecognized)'.format(x)
_COLORSPACE_MAP_DISPLAY = _Keydefaultdict(_factory, _COLORSPACE_MAP_DISPLAY = _Keydefaultdict(_factory,
{ CMYK: 'CMYK', {CMYK: 'CMYK',
SRGB: 'sRGB', SRGB: 'sRGB',
GREYSCALE: 'greyscale', GREYSCALE: 'greyscale',
YCC: 'YCC', YCC: 'YCC',
E_SRGB: 'e-sRGB', E_SRGB: 'e-sRGB',
ROMM_RGB: 'ROMM-RGB'} ) ROMM_RGB: 'ROMM-RGB'})
# enumerated color channel types # enumerated color channel types
COLOR = 0 COLOR = 0
@ -134,11 +135,11 @@ OPACITY = 1
PRE_MULTIPLIED_OPACITY = 2 PRE_MULTIPLIED_OPACITY = 2
_UNSPECIFIED = 65535 _UNSPECIFIED = 65535
_factory = lambda x: '{0} (invalid)'.format(x) _factory = lambda x: '{0} (invalid)'.format(x)
_COLOR_TYPE_MAP_DISPLAY = _Keydefaultdict(_factory, _dict = {COLOR: 'color',
{ COLOR: 'color', OPACITY: 'opacity',
OPACITY: 'opacity', PRE_MULTIPLIED_OPACITY: 'pre-multiplied opacity',
PRE_MULTIPLIED_OPACITY: 'pre-multiplied opacity', _UNSPECIFIED: 'unspecified'}
_UNSPECIFIED: 'unspecified'}) _COLOR_TYPE_MAP_DISPLAY = _Keydefaultdict(_factory, _dict)
# color channel definitions. # color channel definitions.
RED = 1 RED = 1
@ -153,4 +154,3 @@ _COLORSPACE = {SRGB: {"R": 1, "G": 2, "B": 3},
YCC: {"Y": 1, "Cb": 2, "Cr": 3}, YCC: {"Y": 1, "Cb": 2, "Cr": 3},
E_SRGB: {"R": 1, "G": 2, "B": 3}, E_SRGB: {"R": 1, "G": 2, "B": 3},
ROMM_RGB: {"R": 1, "G": 2, "B": 3}} ROMM_RGB: {"R": 1, "G": 2, "B": 3}}

View file

@ -29,13 +29,11 @@ import lxml.etree as ET
import numpy as np import numpy as np
from .codestream import Codestream from .codestream import Codestream
from .core import ( from .core import (_COLORSPACE_MAP_DISPLAY, _COLOR_TYPE_MAP_DISPLAY,
_COLORSPACE_MAP_DISPLAY, _COLOR_TYPE_MAP_DISPLAY, SRGB, GREYSCALE, YCC,
SRGB, GREYSCALE, YCC, ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE,
ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE, ANY_ICC_PROFILE, VENDOR_COLOR_METHOD,
ANY_ICC_PROFILE, VENDOR_COLOR_METHOD, _Keydefaultdict)
_Keydefaultdict
)
from . import _uuid_io from . import _uuid_io
@ -52,6 +50,7 @@ _APPROX_DISPLAY = _Keydefaultdict(_factory,
3: 'approximates correct colorspace definition, reasonable quality', 3: 'approximates correct colorspace definition, reasonable quality',
4: 'approximates correct colorspace definition, poor quality'}) 4: 'approximates correct colorspace definition, poor quality'})
class Jp2kBox(object): class Jp2kBox(object):
"""Superclass for JPEG 2000 boxes. """Superclass for JPEG 2000 boxes.
@ -109,7 +108,6 @@ class Jp2kBox(object):
msg += '\n' + self._indent(boxstr) msg += '\n' + self._indent(boxstr)
return msg return msg
def _indent(self, textstr, indent_level=4): def _indent(self, textstr, indent_level=4):
""" """
Indent a string. Indent a string.
@ -135,7 +133,6 @@ class Jp2kBox(object):
lst = [(' ' * indent_level + x) for x in textstr.split('\n')] lst = [(' ' * indent_level + x) for x in textstr.split('\n')]
return '\n'.join(lst) return '\n'.join(lst)
def _write_superbox(self, fptr, box_id): def _write_superbox(self, fptr, box_id):
"""Write a superbox. """Write a superbox.
@ -191,13 +188,14 @@ class Jp2kBox(object):
try: try:
box = parser(fptr, start, num_bytes) box = parser(fptr, start, num_bytes)
except ValueError as err: except ValueError as err:
msg = "Encountered an unrecoverable ValueError while parsing a {0} " msg = "Encountered an unrecoverable ValueError while parsing a "
msg += "box at byte offset {1}. The original error message was " msg += "{0} box at byte offset {1}. The original error message "
msg += "\"{2}\"" msg += "was \"{2}\""
msg = msg.format(_BOX_WITH_ID[box_id].longname, start, str(err)) msg = msg.format(_BOX_WITH_ID[box_id].longname, start, str(err))
warnings.warn(msg, UserWarning) warnings.warn(msg, UserWarning)
box = UnknownBox(box_id.decode('utf-8'), box = UnknownBox(box_id.decode('utf-8'),
length=num_bytes, offset=start, longname='Unknown') length=num_bytes,
offset=start, longname='Unknown')
return box return box
@ -299,6 +297,7 @@ class ColourSpecificationBox(Jp2kBox):
""" """
longname = 'Colour Specification' longname = 'Colour Specification'
box_id = 'colr' box_id = 'colr'
def __init__(self, method=ENUMERATED_COLORSPACE, precedence=0, def __init__(self, method=ENUMERATED_COLORSPACE, precedence=0,
approximation=0, colorspace=None, icc_profile=None, approximation=0, colorspace=None, icc_profile=None,
length=0, offset=-1): length=0, offset=-1):
@ -337,16 +336,16 @@ class ColourSpecificationBox(Jp2kBox):
if self.icc_profile is None: if self.icc_profile is None:
if self.colorspace not in [SRGB, GREYSCALE, YCC]: if self.colorspace not in [SRGB, GREYSCALE, YCC]:
msg = "Colorspace should correspond to one of SRGB, GREYSCALE, " msg = "Colorspace should correspond to one of SRGB, "
msg += "or YCC." msg += "GREYSCALE, or YCC."
self._dispatch_validation_error(msg, writing=True) self._dispatch_validation_error(msg, writing=True)
self._validate(writing=True) self._validate(writing=True)
def __repr__(self): def __repr__(self):
msg = "glymur.jp2box.ColourSpecificationBox(" msg = "glymur.jp2box.ColourSpecificationBox("
msg += "method={0}, precedence={1}, approximation={2}, colorspace={3}, " msg += "method={0}, precedence={1}, approximation={2}, "
msg += "colorspace={3}, "
msg += "icc_profile={4})" msg += "icc_profile={4})"
msg = msg.format(self.method, msg = msg.format(self.method,
self.precedence, self.precedence,
@ -357,7 +356,7 @@ class ColourSpecificationBox(Jp2kBox):
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
if _printoptions['short'] == True: if _printoptions['short'] is True:
return msg return msg
msg += '\n Method: {0}'.format(_METHOD_DISPLAY[self.method]) msg += '\n Method: {0}'.format(_METHOD_DISPLAY[self.method])
@ -619,10 +618,9 @@ class ChannelDefinitionBox(Jp2kBox):
msg += " 65535 - unspecified" msg += " 65535 - unspecified"
self._dispatch_validation_error(msg, writing=writing) self._dispatch_validation_error(msg, writing=writing)
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
if _printoptions['short'] == True: if _printoptions['short'] is True:
return msg return msg
for j in range(len(self.association)): for j in range(len(self.association)):
@ -842,7 +840,7 @@ class CompositingLayerHeaderBox(Jp2kBox):
List of boxes contained in this superbox. List of boxes contained in this superbox.
""" """
box_id = 'jplh' box_id = 'jplh'
longname='Compositing Layer Header' longname = 'Compositing Layer Header'
def __init__(self, box=None, length=0, offset=-1): def __init__(self, box=None, length=0, offset=-1):
Jp2kBox.__init__(self) Jp2kBox.__init__(self)
@ -931,7 +929,7 @@ class ComponentMappingBox(Jp2kBox):
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
if _printoptions['short'] == True: if _printoptions['short'] is True:
return msg return msg
for k in range(len(self.component_index)): for k in range(len(self.component_index)):
@ -1027,7 +1025,9 @@ class ContiguousCodestreamBox(Jp2kBox):
if self._filename is not None: if self._filename is not None:
with open(self._filename, 'rb') as fptr: with open(self._filename, 'rb') as fptr:
fptr.seek(self.main_header_offset) fptr.seek(self.main_header_offset)
main_header = Codestream(fptr, self._length, header_only=True) main_header = Codestream(fptr,
self._length,
header_only=True)
self._main_header = main_header self._main_header = main_header
return self._main_header return self._main_header
@ -1037,9 +1037,9 @@ class ContiguousCodestreamBox(Jp2kBox):
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
if _printoptions['short'] == True: if _printoptions['short'] is True:
return msg return msg
if _printoptions['codestream'] == False: if _printoptions['codestream'] is False:
return msg return msg
msg += '\n Main header:' msg += '\n Main header:'
@ -1118,7 +1118,8 @@ class DataReferenceBox(Jp2kBox):
"""Verify that the box obeys the specifications for writing. """Verify that the box obeys the specifications for writing.
""" """
if len(self.DR) == 0: if len(self.DR) == 0:
msg = "A data reference box cannot be empty when written to a file." msg = "A data reference box cannot be empty when written to a "
msg += "file."
self._dispatch_validation_error(msg, writing=True) self._dispatch_validation_error(msg, writing=True)
self._validate(writing=True) self._validate(writing=True)
@ -1145,7 +1146,7 @@ class DataReferenceBox(Jp2kBox):
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
if _printoptions['short'] == True: if _printoptions['short'] is True:
return msg return msg
for box in self.DR: for box in self.DR:
@ -1248,7 +1249,7 @@ class FileTypeBox(Jp2kBox):
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
if _printoptions['short'] == True: if _printoptions['short'] is True:
return msg return msg
lst = [msg, lst = [msg,
@ -1311,7 +1312,7 @@ class FileTypeBox(Jp2kBox):
brand = brand.decode('utf-8') brand = brand.decode('utf-8')
# Extract the compatibility list. Each entry has 4 bytes. # Extract the compatibility list. Each entry has 4 bytes.
num_entries = int((length - 16)/ 4) num_entries = int((length - 16) / 4)
compatibility_list = [] compatibility_list = []
for j in range(int(num_entries)): for j in range(int(num_entries)):
entry, = struct.unpack_from('>4s', read_buffer, 8 + j * 4) entry, = struct.unpack_from('>4s', read_buffer, 8 + j * 4)
@ -1374,7 +1375,7 @@ class FragmentListBox(Jp2kBox):
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
if _printoptions['short'] == True: if _printoptions['short'] is True:
return msg return msg
for j in range(len(self.fragment_offset)): for j in range(len(self.fragment_offset)):
@ -1458,7 +1459,10 @@ class FragmentTableBox(Jp2kBox):
def __repr__(self): def __repr__(self):
msg = "glymur.jp2box.FragmentTableBox(box={0})" msg = "glymur.jp2box.FragmentTableBox(box={0})"
msg = msg.format(None) if (len(self.box) == 0) else msg.format(self.box) if len(self.box) == 0:
msg = msg.format(None)
else:
msg = msg.format(self.box)
return msg return msg
def __str__(self): def __str__(self):
@ -1505,7 +1509,6 @@ class FragmentTableBox(Jp2kBox):
self._write_superbox(fptr, b'ftbl') self._write_superbox(fptr, b'ftbl')
class FreeBox(Jp2kBox): class FreeBox(Jp2kBox):
"""Container for JPX free box information. """Container for JPX free box information.
@ -1534,7 +1537,7 @@ class FreeBox(Jp2kBox):
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
if _printoptions['short'] == True: if _printoptions['short'] is True:
return msg return msg
return msg return msg
@ -1630,7 +1633,7 @@ class ImageHeaderBox(Jp2kBox):
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
if _printoptions['short'] == True: if _printoptions['short'] is True:
return msg return msg
msg = "{0}" msg = "{0}"
@ -1861,7 +1864,7 @@ class JPEG2000SignatureBox(Jp2kBox):
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
if _printoptions['short'] == True: if _printoptions['short'] is True:
return msg return msg
msg += '\n Signature: {0:02x}{1:02x}{2:02x}{3:02x}' msg += '\n Signature: {0:02x}{1:02x}{2:02x}{3:02x}'
@ -1950,7 +1953,7 @@ class PaletteBox(Jp2kBox):
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
if _printoptions['short'] == True: if _printoptions['short'] is True:
return msg return msg
msg += '\n Size: ({0} x {1})'.format(*self.palette.shape) msg += '\n Size: ({0} x {1})'.format(*self.palette.shape)
@ -2203,7 +2206,7 @@ class ReaderRequirementsBox(Jp2kBox):
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
if _printoptions['short'] == True: if _printoptions['short'] is True:
return msg return msg
msg += '\n Fully Understands Aspect Mask: 0x{0:x}'.format(self.fuam) msg += '\n Fully Understands Aspect Mask: 0x{0:x}'.format(self.fuam)
@ -2262,7 +2265,8 @@ class ReaderRequirementsBox(Jp2kBox):
standard_flag, standard_mask = data standard_flag, standard_mask = data
nflags = len(standard_flag) nflags = len(standard_flag)
vendor_offset = 1 + 2 * mask_length + 2 + (2 + mask_length) * nflags vendor_offset = 1 + 2 * mask_length + 2 \
+ (2 + mask_length) * nflags
data = _parse_vendor_features(read_buffer[vendor_offset:], data = _parse_vendor_features(read_buffer[vendor_offset:],
mask_length) mask_length)
vendor_feature, vendor_mask = data vendor_feature, vendor_mask = data
@ -2348,14 +2352,11 @@ def _parse_standard_flag(read_buffer, mask_length):
# from the buffer read from file. # from the buffer read from file.
mask_format = {1: 'B', 2: 'H', 4: 'I'}[mask_length] mask_format = {1: 'B', 2: 'H', 4: 'I'}[mask_length]
#read_buffer = fptr.read(2)
num_standard_flags, = struct.unpack_from('>H', read_buffer, offset=0) num_standard_flags, = struct.unpack_from('>H', read_buffer, offset=0)
# Read in standard flags and standard masks. Each standard flag should # Read in standard flags and standard masks. Each standard flag should
# be two bytes, but the standard mask flag is as long as specified by # be two bytes, but the standard mask flag is as long as specified by
# the mask length. # the mask length.
#read_buffer = fptr.read(num_standard_flags * (2 + mask_length))
fmt = '>' + ('H' + mask_format) * num_standard_flags fmt = '>' + ('H' + mask_format) * num_standard_flags
data = struct.unpack_from(fmt, read_buffer, offset=2) data = struct.unpack_from(fmt, read_buffer, offset=2)
@ -2386,7 +2387,6 @@ def _parse_vendor_features(read_buffer, mask_length):
# Each vendor feature consists of a 16-byte UUID plus a mask whose # Each vendor feature consists of a 16-byte UUID plus a mask whose
# length is specified by, you guessed it, "mask_length". # length is specified by, you guessed it, "mask_length".
entry_length = 16 + mask_length entry_length = 16 + mask_length
#read_buffer = fptr.read(num_vendor_features * entry_length)
vendor_feature = [] vendor_feature = []
vendor_mask = [] vendor_mask = []
for j in range(num_vendor_features): for j in range(num_vendor_features):
@ -2494,7 +2494,7 @@ class CaptureResolutionBox(Jp2kBox):
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
if _printoptions['short'] == True: if _printoptions['short'] is True:
return msg return msg
msg += '\n VCR: {0}'.format(self.vertical_resolution) msg += '\n VCR: {0}'.format(self.vertical_resolution)
@ -2560,7 +2560,7 @@ class DisplayResolutionBox(Jp2kBox):
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
if _printoptions['short'] == True: if _printoptions['short'] is True:
return msg return msg
msg += '\n VDR: {0}'.format(self.vertical_resolution) msg += '\n VDR: {0}'.format(self.vertical_resolution)
@ -2620,7 +2620,7 @@ class LabelBox(Jp2kBox):
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
if _printoptions['short'] == True: if _printoptions['short'] is True:
return msg return msg
msg += '\n Label: {0}'.format(self.label) msg += '\n Label: {0}'.format(self.label)
@ -2688,7 +2688,7 @@ class NumberListBox(Jp2kBox):
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
if _printoptions['short'] == True: if _printoptions['short'] is True:
return msg return msg
for j, association in enumerate(self.associations): for j, association in enumerate(self.associations):
@ -2738,7 +2738,8 @@ class NumberListBox(Jp2kBox):
def write(self, fptr): def write(self, fptr):
"""Write a NumberList box to file. """Write a NumberList box to file.
""" """
fptr.write(struct.pack('>I4s', len(self.associations) * 4 + 8, b'nlst')) fptr.write(struct.pack('>I4s',
len(self.associations) * 4 + 8, b'nlst'))
fmt = '>' + 'I' * len(self.associations) fmt = '>' + 'I' * len(self.associations)
write_buffer = struct.pack(fmt, *self.associations) write_buffer = struct.pack(fmt, *self.associations)
@ -2790,9 +2791,9 @@ class XMLBox(Jp2kBox):
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
if _printoptions['short'] == True: if _printoptions['short'] is True:
return msg return msg
if _printoptions['xml'] == False: if _printoptions['xml'] is False:
return msg return msg
msg += '\n' msg += '\n'
@ -2911,7 +2912,7 @@ class UUIDListBox(Jp2kBox):
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
if _printoptions['short'] == True: if _printoptions['short'] is True:
return msg return msg
for j, uuid_item in enumerate(self.ulst): for j, uuid_item in enumerate(self.ulst):
@ -2942,7 +2943,7 @@ class UUIDListBox(Jp2kBox):
ulst = [] ulst = []
for j in range(num_uuids): for j in range(num_uuids):
uuid_buffer = read_buffer[2 + j * 16 : 2 + (j + 1) * 16] uuid_buffer = read_buffer[2 + j * 16:2 + (j + 1) * 16]
ulst.append(uuid.UUID(bytes=uuid_buffer)) ulst.append(uuid.UUID(bytes=uuid_buffer))
return cls(ulst, length=length, offset=offset) return cls(ulst, length=length, offset=offset)
@ -3056,7 +3057,6 @@ class DataEntryURLBox(Jp2kBox):
fptr.write(write_buffer) fptr.write(write_buffer)
fptr.write(url) fptr.write(url)
def __repr__(self): def __repr__(self):
msg = "glymur.jp2box.DataEntryURLBox({0}, {1}, '{2}')" msg = "glymur.jp2box.DataEntryURLBox({0}, {1}, '{2}')"
msg = msg.format(self.version, self.flag, self.url) msg = msg.format(self.version, self.flag, self.url)
@ -3064,7 +3064,7 @@ class DataEntryURLBox(Jp2kBox):
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
if _printoptions['short'] == True: if _printoptions['short'] is True:
return msg return msg
msg += '\n ' msg += '\n '
@ -3216,7 +3216,7 @@ class UUIDBox(Jp2kBox):
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
if _printoptions['short'] == True: if _printoptions['short'] is True:
return msg return msg
msg = '{0}\n UUID: {1}'.format(msg, self.uuid) msg = '{0}\n UUID: {1}'.format(msg, self.uuid)
@ -3227,7 +3227,7 @@ class UUIDBox(Jp2kBox):
else: else:
msg += ' (unknown)' msg += ' (unknown)'
if (((_printoptions['xml'] == False) and if (((_printoptions['xml'] is False) and
(self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac')))): (self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac')))):
# If it's an XMP UUID, don't print the XML contents. # If it's an XMP UUID, don't print the XML contents.
return msg return msg
@ -3312,6 +3312,7 @@ _BOX_WITH_ID = {
_parseoptions = {'codestream': True} _parseoptions = {'codestream': True}
def set_parseoptions(codestream=True): def set_parseoptions(codestream=True):
"""Set parsing options. """Set parsing options.
@ -3336,6 +3337,7 @@ def set_parseoptions(codestream=True):
""" """
_parseoptions['codestream'] = codestream _parseoptions['codestream'] = codestream
def get_parseoptions(): def get_parseoptions():
"""Return the current parsing options. """Return the current parsing options.
@ -3356,6 +3358,7 @@ def get_parseoptions():
_printoptions = {'short': False, 'xml': True, 'codestream': True} _printoptions = {'short': False, 'xml': True, 'codestream': True}
def set_printoptions(**kwargs): def set_printoptions(**kwargs):
"""Set printing options. """Set printing options.
@ -3365,7 +3368,8 @@ def set_printoptions(**kwargs):
---------- ----------
short : bool, optional short : bool, optional
When True, only the box ID, offset, and length are displayed. Useful When True, only the box ID, offset, and length are displayed. Useful
for displaying only the basic structure or skeleton of a JPEG 2000 file. for displaying only the basic structure or skeleton of a JPEG 2000
file.
xml : bool, optional xml : bool, optional
When False, printing of the XML contents of any XML boxes or UUID XMP When False, printing of the XML contents of any XML boxes or UUID XMP
boxes is suppressed. boxes is suppressed.
@ -3388,6 +3392,7 @@ def set_printoptions(**kwargs):
raise TypeError('"{0}" not a valid keyword parameter.'.format(key)) raise TypeError('"{0}" not a valid keyword parameter.'.format(key))
_printoptions[key] = value _printoptions[key] = value
def get_printoptions(): def get_printoptions():
"""Return the current print options. """Return the current print options.
@ -3407,5 +3412,3 @@ def get_printoptions():
set_printoptions set_printoptions
""" """
return _printoptions return _printoptions

View file

@ -31,12 +31,12 @@ import numpy as np
from .codestream import Codestream from .codestream import Codestream
from . import core, version from . import core, version
from .jp2box import ( from .jp2box import (Jp2kBox, JPEG2000SignatureBox, FileTypeBox,
Jp2kBox, JPEG2000SignatureBox, FileTypeBox, JP2HeaderBox, JP2HeaderBox, ColourSpecificationBox,
ColourSpecificationBox, ContiguousCodestreamBox, ImageHeaderBox ContiguousCodestreamBox, ImageHeaderBox)
)
from .lib import openjpeg as opj, openjp2 as opj2, c as libc from .lib import openjpeg as opj, openjp2 as opj2, c as libc
class Jp2k(Jp2kBox): class Jp2k(Jp2kBox):
"""JPEG 2000 file. """JPEG 2000 file.
@ -138,7 +138,6 @@ class Jp2k(Jp2kBox):
not (X, Y) not (X, Y)
verbose : bool, optional verbose : bool, optional
print informational messages produced by the OpenJPEG library print informational messages produced by the OpenJPEG library
""" """
Jp2kBox.__init__(self) Jp2kBox.__init__(self)
self.filename = filename self.filename = filename
@ -176,8 +175,8 @@ class Jp2k(Jp2kBox):
@layer.setter @layer.setter
def layer(self, layer): def layer(self, layer):
if version.openjpeg_version_tuple[0] < 2: if version.openjpeg_version_tuple[0] < 2:
msg = "Layer property not supported unless the version of OpenJPEG " msg = "Layer property not supported unless the version of "
msg += "is 2.0 or higher." msg += "OpenJPEG is 2.0 or higher."
raise RuntimeError(msg) raise RuntimeError(msg)
self._layer = layer self._layer = layer
@ -361,8 +360,8 @@ class Jp2k(Jp2kBox):
kwargs : dictionary kwargs : dictionary
non-image keyword inputs provided to write method non-image keyword inputs provided to write method
""" """
if (('cinema2k' in kwargs or 'cinema4k' in kwargs) and if ((('cinema2k' in kwargs or 'cinema4k' in kwargs) and
(len(set(kwargs)) > 1)): (len(set(kwargs)) > 1))):
msg = "Cannot specify cinema2k/cinema4k along with other options." msg = "Cannot specify cinema2k/cinema4k along with other options."
raise IOError(msg) raise IOError(msg)
@ -484,9 +483,10 @@ class Jp2k(Jp2kBox):
This method can only be used to create JPEG 2000 images that can fit This method can only be used to create JPEG 2000 images that can fit
in memory. in memory.
""" """
if re.match("1.[0-4]", version.openjpeg_version) is not None: if re.match("0|1.[0-4]", version.openjpeg_version) is not None:
raise RuntimeError("You must have at least version 1.5 of OpenJPEG " msg = "You must have at least version 1.5 of OpenJPEG "
"in order to write images.") msg += "in order to write images."
raise RuntimeError(msg)
self._determine_colorspace(**kwargs) self._determine_colorspace(**kwargs)
self._populate_cparams(img_array, **kwargs) self._populate_cparams(img_array, **kwargs)
@ -518,9 +518,11 @@ class Jp2k(Jp2kBox):
image.contents.x0 = self._cparams.image_offset_x0 image.contents.x0 = self._cparams.image_offset_x0
image.contents.y0 = self._cparams.image_offset_y0 image.contents.y0 = self._cparams.image_offset_y0
image.contents.x1 = image.contents.x0 \ image.contents.x1 = image.contents.x0 \
+ (numcols - 1) * self._cparams.subsampling_dx + 1 + (numcols - 1) * self._cparams.subsampling_dx \
+ 1
image.contents.y1 = image.contents.y0 \ image.contents.y1 = image.contents.y0 \
+ (numrows - 1) * self._cparams.subsampling_dy + 1 + (numrows - 1) * self._cparams.subsampling_dy \
+ 1
# Stage the image data to the openjpeg data structure. # Stage the image data to the openjpeg data structure.
for k in range(0, numlayers): for k in range(0, numlayers):
@ -668,7 +670,6 @@ class Jp2k(Jp2kBox):
self._colorspace = COLORSPACE_MAP[colorspace.lower()] self._colorspace = COLORSPACE_MAP[colorspace.lower()]
def _write_openjp2(self, img_array, verbose=False): def _write_openjp2(self, img_array, verbose=False):
""" """
Write JPEG 2000 file using OpenJPEG 2.x interface. Write JPEG 2000 file using OpenJPEG 2.x interface.
@ -734,7 +735,8 @@ class Jp2k(Jp2kBox):
if not ((box.box_id == 'xml ') or if not ((box.box_id == 'xml ') or
(box.box_id == 'uuid' and (box.box_id == 'uuid' and
box.uuid == UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'))): box.uuid == UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'))):
msg = "Only XML boxes and XMP UUID boxes can currently be appended." msg = "Only XML boxes and XMP UUID boxes can currently be "
msg += "appended."
raise IOError(msg) raise IOError(msg)
# Check the last box. If the length field is zero, then rewrite # Check the last box. If the length field is zero, then rewrite
@ -891,9 +893,9 @@ class Jp2k(Jp2kBox):
Slicing protocol. Slicing protocol.
""" """
if ((isinstance(index, slice) and if ((isinstance(index, slice) and
(index.start == None and (index.start is None and
index.stop == None and index.stop is None and
index.step == None)) or (index is Ellipsis)): index.step is None)) or (index is Ellipsis)):
# Case of jp2[:] = data, i.e. write the entire image. # Case of jp2[:] = data, i.e. write the entire image.
# #
# Should have a slice object where start = stop = step = None # Should have a slice object where start = stop = step = None
@ -923,7 +925,9 @@ class Jp2k(Jp2kBox):
return self._read() return self._read()
if isinstance(pargs, slice): if isinstance(pargs, slice):
if pargs.start is None and pargs.stop is None and pargs.step is None: if (((pargs.start is None) and
(pargs.stop is None) and
(pargs.step is None))):
# Case of jp2[:] # Case of jp2[:]
return self._read() return self._read()
@ -971,7 +975,6 @@ class Jp2k(Jp2kBox):
# Reduce dimensionality in the scalar dimension. # Reduce dimensionality in the scalar dimension.
return np.squeeze(data, axis=idx) return np.squeeze(data, axis=idx)
# Assuming pargs is a tuple of slices from now on. # Assuming pargs is a tuple of slices from now on.
rows = pargs[0] rows = pargs[0]
cols = pargs[1] cols = pargs[1]
@ -996,8 +999,7 @@ class Jp2k(Jp2kBox):
raise IndexError(msg) raise IndexError(msg)
rlevel = np.int(np.round(np.log2(step))) rlevel = np.int(np.round(np.log2(step)))
area = ( area = (0 if rows.start is None else rows.start,
0 if rows.start is None else rows.start,
0 if cols.start is None else cols.start, 0 if cols.start is None else cols.start,
numrows if rows.stop is None else rows.stop, numrows if rows.stop is None else rows.stop,
numcols if cols.stop is None else cols.stop numcols if cols.stop is None else cols.stop
@ -1009,7 +1011,6 @@ class Jp2k(Jp2kBox):
# Ok, 3 arguments in pargs. # Ok, 3 arguments in pargs.
return data[:, :, bands] return data[:, :, bands]
def _read(self, **kwargs): def _read(self, **kwargs):
"""Read a JPEG 2000 image. """Read a JPEG 2000 image.
@ -1032,33 +1033,33 @@ class Jp2k(Jp2kBox):
def read(self, **kwargs): def read(self, **kwargs):
""" """
""" """
#Read a JPEG 2000 image. # Read a JPEG 2000 image.
# #
#Parameters # Parameters
#---------- # ----------
#rlevel : int, optional # rlevel : int, optional
# Factor by which to rlevel output resolution. Use -1 to get the # Factor by which to rlevel output resolution. Use -1 to get the
# lowest resolution thumbnail. This is the only keyword option # lowest resolution thumbnail. This is the only keyword option
# available to use when the OpenJPEG version is 1.5 or earlier. # available to use when the OpenJPEG version is 1.5 or earlier.
#layer : int, optional # layer : int, optional
# Number of quality layer to decode. # Number of quality layer to decode.
#area : tuple, optional # area : tuple, optional
# Specifies decoding image area, # Specifies decoding image area,
# (first_row, first_col, last_row, last_col) # (first_row, first_col, last_row, last_col)
#tile : int, optional # tile : int, optional
# Number of tile to decode. # Number of tile to decode.
#verbose : bool, optional # verbose : bool, optional
# Print informational messages produced by the OpenJPEG library. # Print informational messages produced by the OpenJPEG library.
# #
#Returns # Returns
#------- # -------
#img_array : ndarray # img_array : ndarray
# The image data. # The image data.
# #
#Raises # Raises
#------ # ------
#IOError # IOError
# If the image has differing subsample factors. # If the image has differing subsample factors.
if 'ignore_pclr_cmap_cdef' in kwargs: if 'ignore_pclr_cmap_cdef' in kwargs:
self.ignore_pclr_cmap_cdef = kwargs['ignore_pclr_cmap_cdef'] self.ignore_pclr_cmap_cdef = kwargs['ignore_pclr_cmap_cdef']
@ -1163,7 +1164,8 @@ class Jp2k(Jp2kBox):
return data return data
def _read_openjp2(self, rlevel=0, layer=None, area=None, tile=None, verbose=False): def _read_openjp2(self, rlevel=0, layer=None, area=None, tile=None,
verbose=False):
"""Read a JPEG 2000 image using libopenjp2. """Read a JPEG 2000 image using libopenjp2.
Parameters Parameters
@ -1573,6 +1575,7 @@ JP2_IDS = ['colr', 'cdef', 'cmap', 'jp2c', 'ftyp', 'ihdr', 'jp2h', 'jP ',
'pclr', 'res ', 'resc', 'resd', 'xml ', 'ulst', 'uinf', 'url ', 'pclr', 'res ', 'resc', 'resd', 'xml ', 'ulst', 'uinf', 'url ',
'uuid'] 'uuid']
def _validate_jp2_box_sequence(boxes): def _validate_jp2_box_sequence(boxes):
"""Run through series of tests for JP2 box legality. """Run through series of tests for JP2 box legality.
@ -1588,12 +1591,13 @@ def _validate_jp2_box_sequence(boxes):
count = _collect_box_count(boxes) count = _collect_box_count(boxes)
for box_id in count.keys(): for box_id in count.keys():
if box_id not in JP2_IDS: if box_id not in JP2_IDS:
msg = "The presence of a '{0}' box requires that the file type " msg = "The presence of a '{0}' box requires that the file "
msg += "brand be set to 'jpx '." msg += "type brand be set to 'jpx '."
raise IOError(msg.format(box_id)) raise IOError(msg.format(box_id))
_validate_jp2_colr(boxes) _validate_jp2_colr(boxes)
def _validate_jp2_colr(boxes): def _validate_jp2_colr(boxes):
""" """
Validate JP2 requirements on colour specification boxes. Validate JP2 requirements on colour specification boxes.
@ -1605,6 +1609,7 @@ def _validate_jp2_colr(boxes):
msg = "A JP2 colr box cannot have a non-zero approximation field." msg = "A JP2 colr box cannot have a non-zero approximation field."
raise IOError(msg) raise IOError(msg)
def _validate_jpx_box_sequence(boxes): def _validate_jpx_box_sequence(boxes):
"""Run through series of tests for JPX box legality.""" """Run through series of tests for JPX box legality."""
_validate_label(boxes) _validate_label(boxes)
@ -1613,6 +1618,7 @@ def _validate_jpx_box_sequence(boxes):
_validate_singletons(boxes) _validate_singletons(boxes)
_validate_top_level(boxes) _validate_top_level(boxes)
def _validate_signature_compatibility(boxes): def _validate_signature_compatibility(boxes):
"""Validate the file signature and compatibility status.""" """Validate the file signature and compatibility status."""
# Check for a bad sequence of boxes. # Check for a bad sequence of boxes.
@ -1700,6 +1706,8 @@ def _validate_channel_definition(jp2h, colr):
JP2H_CHILDREN = set(['bpcc', 'cdef', 'cmap', 'ihdr', 'pclr']) JP2H_CHILDREN = set(['bpcc', 'cdef', 'cmap', 'ihdr', 'pclr'])
def _check_jp2h_child_boxes(boxes, parent_box_name): def _check_jp2h_child_boxes(boxes, parent_box_name):
"""Certain boxes can only reside in the JP2 header.""" """Certain boxes can only reside in the JP2 header."""
box_ids = set([box.box_id for box in boxes]) box_ids = set([box.box_id for box in boxes])
@ -1727,6 +1735,7 @@ def _collect_box_count(boxes):
TOP_LEVEL_ONLY_BOXES = set(['dtbl']) TOP_LEVEL_ONLY_BOXES = set(['dtbl'])
def _check_superbox_for_top_levels(boxes): def _check_superbox_for_top_levels(boxes):
"""Several boxes can only occur at the top level.""" """Several boxes can only occur at the top level."""
# We are only looking at the boxes contained in a superbox, so if any of # We are only looking at the boxes contained in a superbox, so if any of
@ -1742,6 +1751,7 @@ def _check_superbox_for_top_levels(boxes):
if hasattr(box, 'box'): if hasattr(box, 'box'):
_check_superbox_for_top_levels(box.box) _check_superbox_for_top_levels(box.box)
def _validate_top_level(boxes): def _validate_top_level(boxes):
"""Several boxes can only occur at the top level.""" """Several boxes can only occur at the top level."""
# Add the counts in the superboxes. # Add the counts in the superboxes.
@ -1761,6 +1771,7 @@ def _validate_top_level(boxes):
msg += 'a fragment table box as well.' msg += 'a fragment table box as well.'
raise IOError(msg) raise IOError(msg)
def _validate_singletons(boxes): def _validate_singletons(boxes):
"""Several boxes can only occur once.""" """Several boxes can only occur once."""
count = _collect_box_count(boxes) count = _collect_box_count(boxes)
@ -1771,6 +1782,7 @@ def _validate_singletons(boxes):
JPX_IDS = ['asoc', 'nlst'] JPX_IDS = ['asoc', 'nlst']
def _validate_jpx_brand(boxes, brand): def _validate_jpx_brand(boxes, brand):
""" """
If there is a JPX box then the brand must be 'jpx '. If there is a JPX box then the brand must be 'jpx '.
@ -1785,6 +1797,7 @@ def _validate_jpx_brand(boxes, brand):
# Same set of checks on any child boxes. # Same set of checks on any child boxes.
_validate_jpx_brand(box.box, brand) _validate_jpx_brand(box.box, brand)
def _validate_jpx_compatibility(boxes, compatibility_list): def _validate_jpx_compatibility(boxes, compatibility_list):
""" """
If there is a JPX box then the compatibility list must also contain 'jpx '. If there is a JPX box then the compatibility list must also contain 'jpx '.
@ -1800,6 +1813,7 @@ def _validate_jpx_compatibility(boxes, compatibility_list):
# Same set of checks on any child boxes. # Same set of checks on any child boxes.
_validate_jpx_compatibility(box.box, compatibility_list) _validate_jpx_compatibility(box.box, compatibility_list)
def _validate_label(boxes): def _validate_label(boxes):
""" """
Label boxes can only be inside association, codestream headers, or Label boxes can only be inside association, codestream headers, or
@ -1816,6 +1830,7 @@ def _validate_label(boxes):
# Same set of checks on any child boxes. # Same set of checks on any child boxes.
_validate_label(box.box) _validate_label(box.box)
def extract_image_cube(image): def extract_image_cube(image):
"""Extract 3D image from openjpeg data structure. """Extract 3D image from openjpeg data structure.
""" """
@ -1871,7 +1886,6 @@ def extract_image_bands(image):
return data return data
# Setup the default callback handlers. See the callback functions subsection # Setup the default callback handlers. See the callback functions subsection
# in the ctypes section of the Python documentation for a solid explanation of # in the ctypes section of the Python documentation for a solid explanation of
# what's going on here. # what's going on here.

View file

@ -144,7 +144,7 @@ def glymur_config():
lst.append(load_openjpeg_library(libname)) lst.append(load_openjpeg_library(libname))
if all(handle is None for handle in lst): if all(handle is None for handle in lst):
msg = "Neither the openjp2 nor the openjpeg library could be loaded. " msg = "Neither the openjp2 nor the openjpeg library could be loaded. "
raise IOError(msg) warnings.warn(msg)
return tuple(lst) return tuple(lst)
def get_configdir(): def get_configdir():

View file

@ -13,6 +13,7 @@ from .config import glymur_config
OPENJP2, OPENJPEG = glymur_config() OPENJP2, OPENJPEG = glymur_config()
def version(): def version():
"""Wrapper for opj_version library routine.""" """Wrapper for opj_version library routine."""
try: try:
@ -50,13 +51,6 @@ JPWL_MAX_NO_TILESPECS = 16
TRUE = 1 TRUE = 1
FALSE = 0 FALSE = 0
#PROFILE = {'none': 0, # No profile
# 0: 1, # Profile 0
# 1: 2, # Profile 1
# 'part2': 0x8000, # At least one extension
# 'Cinema2K': 0x0003, # 2K cinema profile
# 'Cinema4K': 0x0004, # 4K cinema profile
# supported color spaces # supported color spaces
CLRSPC_UNKNOWN = -1 CLRSPC_UNKNOWN = -1
CLRSPC_UNSPECIFIED = 0 CLRSPC_UNSPECIFIED = 0
@ -548,7 +542,6 @@ class ImageType(ctypes.Structure):
return msg return msg
class ImageComptParmType(ctypes.Structure): class ImageComptParmType(ctypes.Structure):
"""Component parameters structure used by image_create function. """Component parameters structure used by image_create function.
@ -1317,6 +1310,7 @@ def _stream_create_default_file_stream_2p0(fptr, isa_read_stream):
stream = OPENJP2.opj_stream_create_default_file_stream(fptr, read_stream) stream = OPENJP2.opj_stream_create_default_file_stream(fptr, read_stream)
return stream return stream
def _stream_create_default_file_stream_2p1(fname, isa_read_stream): def _stream_create_default_file_stream_2p1(fname, isa_read_stream):
"""Wraps openjp2 library function opj_stream_create_default_vile_stream. """Wraps openjp2 library function opj_stream_create_default_vile_stream.

View file

@ -59,8 +59,10 @@ class CommonStructType(ctypes.Structure):
("mj2_handle", ctypes.c_void_p)] ("mj2_handle", ctypes.c_void_p)]
STREAM_READ = 0x0001 # The stream was opened for reading. STREAM_READ = 0x0001 # The stream was opened for reading.
STREAM_WRITE = 0x0002 # The stream was opened for writing. STREAM_WRITE = 0x0002 # The stream was opened for writing.
class CioType(ctypes.Structure): class CioType(ctypes.Structure):
"""Byte input-output stream (CIO) """Byte input-output stream (CIO)
@ -91,70 +93,57 @@ class CompressionInfoType(CommonStructType):
class PocType(ctypes.Structure): class PocType(ctypes.Structure):
"""Progression order changes.""" """Progression order changes."""
_fields_ = [("resno", ctypes.c_int), _fields_ = [("resno", ctypes.c_int),
# Resolution num start, Component num start, given by POC # Resolution num start, Component num start, given by POC
("compno0", ctypes.c_int), ("compno0", ctypes.c_int),
# Layer num end,Resolution num end, Component num end, given by POC # Layer num end,Resolution num end, Component num end, given
("layno1", ctypes.c_int), # by POC
("resno1", ctypes.c_int), ("layno1", ctypes.c_int),
("compno1", ctypes.c_int), ("resno1", ctypes.c_int),
("compno1", ctypes.c_int),
# Layer num start,Precinct num start, Precinct num end # Layer num start,Precinct num start, Precinct num end
("layno0", ctypes.c_int), ("layno0", ctypes.c_int),
("precno0", ctypes.c_int), ("precno0", ctypes.c_int),
("precno1", ctypes.c_int), ("precno1", ctypes.c_int),
# Progression order enum # Progression order enum
# OPJ_PROG_ORDER prg1,prg; # OPJ_PROG_ORDER prg1,prg;
("prg1", ctypes.c_int), ("prg1", ctypes.c_int),
("prg", ctypes.c_int), ("prg", ctypes.c_int),
# Progression order string # Progression order string
# char progorder[5]; # char progorder[5];
("progorder", ctypes.c_char * 5), ("progorder", ctypes.c_char * 5),
# Tile number # Tile number
# int tile; # int tile;
("tile", ctypes.c_int), ("tile", ctypes.c_int),
# /** Start and end values for Tile width and height*/ ("tx0", ctypes.c_int),
# int tx0,tx1,ty0,ty1; ("tx1", ctypes.c_int),
("tx0", ctypes.c_int), ("ty0", ctypes.c_int),
("tx1", ctypes.c_int), ("ty1", ctypes.c_int),
("ty0", ctypes.c_int), ("layS", ctypes.c_int),
("ty1", ctypes.c_int), ("resS", ctypes.c_int),
("compS", ctypes.c_int),
# /** Start value, initialised in pi_initialise_encode*/ ("prcS", ctypes.c_int),
# int layS, resS, compS, prcS; ("layE", ctypes.c_int),
("layS", ctypes.c_int), ("resE", ctypes.c_int),
("resS", ctypes.c_int), ("compE", ctypes.c_int),
("compS", ctypes.c_int), ("prcE", ctypes.c_int),
("prcS", ctypes.c_int), ("txS", ctypes.c_int),
("txE", ctypes.c_int),
# /** End value, initialised in pi_initialise_encode */ ("tyS", ctypes.c_int),
# int layE, resE, compE, prcE; ("tyE", ctypes.c_int),
("layE", ctypes.c_int), ("dx", ctypes.c_int),
("resE", ctypes.c_int), ("dy", ctypes.c_int),
("compE", ctypes.c_int), ("lay_t", ctypes.c_int),
("prcE", ctypes.c_int), ("res_t", ctypes.c_int),
("comp_t", ctypes.c_int),
# Start and end values of Tile width and height, initialised in ("prc_t", ctypes.c_int),
# pi_initialise_encode int txS,txE,tyS,tyE,dx,dy; ("tx0_t", ctypes.c_int),
("txS", ctypes.c_int), ("ty0_t", ctypes.c_int)]
("txE", ctypes.c_int),
("tyS", ctypes.c_int),
("tyE", ctypes.c_int),
("dx", ctypes.c_int),
("dy", ctypes.c_int),
# Temporary values for Tile parts, initialised in pi_create_encode
# int lay_t, res_t, comp_t, prc_t,tx0_t,ty0_t;
("lay_t", ctypes.c_int),
("res_t", ctypes.c_int),
("comp_t", ctypes.c_int),
("prc_t", ctypes.c_int),
("tx0_t", ctypes.c_int),
("ty0_t", ctypes.c_int)]
class CompressionParametersType(ctypes.Structure): class CompressionParametersType(ctypes.Structure):
@ -375,48 +364,47 @@ class DecompressionParametersType(ctypes.Structure):
class ImageComptParmType(ctypes.Structure): class ImageComptParmType(ctypes.Structure):
"""Component parameters structure used by the opj_image_create function. """Component parameters structure used by the opj_image_create function.
""" """
_fields_ = [ _fields_ = [# XRsiz: horizontal separation of a sample of ith component
# XRsiz: horizontal separation of a sample of ith component with # with respect to the reference grid
# respect to the reference grid ("dx", ctypes.c_int),
("dx", ctypes.c_int),
# YRsiz: vertical separation of a sample of ith component with # YRsiz: vertical separation of a sample of ith component with
# respect to the reference grid */ # respect to the reference grid */
("dy", ctypes.c_int), ("dy", ctypes.c_int),
# data width, height # data width, height
("w", ctypes.c_int), ("w", ctypes.c_int),
("h", ctypes.c_int), ("h", ctypes.c_int),
# x component offset compared to the whole image # x component offset compared to the whole image
# y component offset compared to the whole image # y component offset compared to the whole image
("x0", ctypes.c_int), ("x0", ctypes.c_int),
("y0", ctypes.c_int), ("y0", ctypes.c_int),
# precision # precision
('prec', ctypes.c_int), ('prec', ctypes.c_int),
# image depth in bits # image depth in bits
('bpp', ctypes.c_int), ('bpp', ctypes.c_int),
# signed (1) / unsigned (0) # signed (1) / unsigned (0)
('sgnd', ctypes.c_int)] ('sgnd', ctypes.c_int)]
class ImageCompType(ctypes.Structure): class ImageCompType(ctypes.Structure):
"""Defines a single image component. """ """Defines a single image component. """
_fields_ = [("dx", ctypes.c_int), _fields_ = [("dx", ctypes.c_int),
("dy", ctypes.c_int), ("dy", ctypes.c_int),
("w", ctypes.c_int), ("w", ctypes.c_int),
("h", ctypes.c_int), ("h", ctypes.c_int),
("x0", ctypes.c_int), ("x0", ctypes.c_int),
("y0", ctypes.c_int), ("y0", ctypes.c_int),
("prec", ctypes.c_int), ("prec", ctypes.c_int),
("bpp", ctypes.c_int), ("bpp", ctypes.c_int),
("sgnd", ctypes.c_int), ("sgnd", ctypes.c_int),
("resno_decoded", ctypes.c_int), ("resno_decoded", ctypes.c_int),
("factor", ctypes.c_int), ("factor", ctypes.c_int),
("data", ctypes.POINTER(ctypes.c_int))] ("data", ctypes.POINTER(ctypes.c_int))]
class ImageType(ctypes.Structure): class ImageType(ctypes.Structure):
@ -468,6 +456,7 @@ def cio_tell(cio):
pos = OPENJPEG.cio_tell(cio) pos = OPENJPEG.cio_tell(cio)
return pos return pos
def create_compress(fmt): def create_compress(fmt):
"""Wrapper for openjpeg library function opj_create_compress. """Wrapper for openjpeg library function opj_create_compress.
@ -585,9 +574,8 @@ def image_cmptparm_t_from_np(np_image):
def image_create(cmptparms, cspace): def image_create(cmptparms, cspace):
"""Wrapper for openjpeg library function opj_image_create. """Wrapper for openjpeg library function opj_image_create.
""" """
OPENJPEG.opj_image_create.argtypes = [ctypes.c_int, lst = [ctypes.c_int, ctypes.POINTER(ImageComptParmType), ctypes.c_int]
ctypes.POINTER(ImageComptParmType), OPENJPEG.opj_image_create.argtypes = lst
ctypes.c_int]
OPENJPEG.opj_image_create.restype = ctypes.POINTER(ImageType) OPENJPEG.opj_image_create.restype = ctypes.POINTER(ImageType)
image = OPENJPEG.opj_image_create(len(cmptparms), cmptparms, cspace) image = OPENJPEG.opj_image_create(len(cmptparms), cmptparms, cspace)

View file

@ -17,7 +17,7 @@ import glymur
from . import fixtures from . import fixtures
@unittest.skipIf(sys.hexversion < 0x03000000, "do not care about 2.7 here") @unittest.skipIf(sys.hexversion < 0x03000000, "do not care about 2.7 here")
@unittest.skipIf(re.match('1|2.0', glymur.version.openjpeg_version), @unittest.skipIf(re.match('0|1|2.0', glymur.version.openjpeg_version),
"Requires openjpeg 2.1.0 or higher") "Requires openjpeg 2.1.0 or higher")
class TestPrintingOpenjp2(unittest.TestCase): class TestPrintingOpenjp2(unittest.TestCase):
"""Tests for verifying how printing works on openjp2 library structures.""" """Tests for verifying how printing works on openjp2 library structures."""

View file

@ -13,6 +13,14 @@ import six
import glymur import glymur
# If openjpeg is not installed, many tests cannot be run.
if glymur.version.openjpeg_version == '0.0.0':
OPENJPEG_NOT_AVAILABLE = True
OPENJPEG_NOT_AVAILABLE_MSG = 'OpenJPEG library not installed'
else:
OPENJPEG_NOT_AVAILABLE = False
OPENJPEG_NOT_AVAILABLE_MSG = None
# Some versions of "six" on python3 cause problems when verifying warnings. # Some versions of "six" on python3 cause problems when verifying warnings.
# Only use when the version is 1.7 or higher. # Only use when the version is 1.7 or higher.
# And moreover, we only test using the 3.x infrastructure, never on 2.x. # And moreover, we only test using the 3.x infrastructure, never on 2.x.

View file

@ -65,6 +65,8 @@ class TestCallbacks(unittest.TestCase):
expected = '[INFO] tile number 1 / 1' expected = '[INFO] tile number 1 / 1'
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
@unittest.skipIf(glymur.version.openjpeg_version[0] == '0',
"Missing openjpeg/openjp2 library.")
def test_info_callbacks_on_read(self): def test_info_callbacks_on_read(self):
"""stdio output when info callback handler is enabled""" """stdio output when info callback handler is enabled"""

View file

@ -114,7 +114,7 @@ class TestDataEntryURL(unittest.TestCase):
self.assertEqual(url + chr(0), read_url) self.assertEqual(url + chr(0), read_url)
@unittest.skipIf(re.match(r'''(1|2.0.0)''', @unittest.skipIf(re.match(r'''0|1|2.0.0''',
glymur.version.openjpeg_version) is not None, glymur.version.openjpeg_version) is not None,
"Not supported until 2.1") "Not supported until 2.1")
@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG)

View file

@ -36,6 +36,7 @@ from glymur.version import openjpeg_version
from .fixtures import HAS_PYTHON_XMP_TOOLKIT from .fixtures import HAS_PYTHON_XMP_TOOLKIT
from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG
from .fixtures import OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG
if HAS_PYTHON_XMP_TOOLKIT: if HAS_PYTHON_XMP_TOOLKIT:
import libxmp import libxmp
@ -76,6 +77,7 @@ class SliceProtocolBase(unittest.TestCase):
self.j2k_data_r1 = self.j2k[::2, ::2] self.j2k_data_r1 = self.j2k[::2, ::2]
self.j2k_data_r5 = self.j2k[::32, ::32] self.j2k_data_r5 = self.j2k[::32, ::32]
@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG)
@unittest.skipIf(re.match("1.5|2", glymur.version.openjpeg_version) is None, @unittest.skipIf(re.match("1.5|2", glymur.version.openjpeg_version) is None,
"Must have openjpeg 1.5 or higher to run") "Must have openjpeg 1.5 or higher to run")
@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG)
@ -143,6 +145,7 @@ class TestSliceProtocolBaseWrite(SliceProtocolBase):
j[:25, :45, :] = self.j2k_data[:25, :25, :] j[:25, :45, :] = self.j2k_data[:25, :25, :]
@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG)
class TestSliceProtocolRead(SliceProtocolBase): class TestSliceProtocolRead(SliceProtocolBase):
def test_resolution_strides_cannot_differ(self): def test_resolution_strides_cannot_differ(self):
@ -251,6 +254,7 @@ class TestJp2k(unittest.TestCase):
def tearDown(self): def tearDown(self):
pass pass
@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG)
@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG)
def test_warn_if_using_read_method(self): def test_warn_if_using_read_method(self):
"""Should warn if deprecated read method is called""" """Should warn if deprecated read method is called"""
@ -313,6 +317,7 @@ class TestJp2k(unittest.TestCase):
actdata = j2[:] actdata = j2[:]
self.assertTrue(fixtures.mse(actdata[0], expdata[0]) < 0.38) self.assertTrue(fixtures.mse(actdata[0], expdata[0]) < 0.38)
@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG)
@unittest.skipIf(re.match('1.[0-4]', openjpeg_version) is not None, @unittest.skipIf(re.match('1.[0-4]', openjpeg_version) is not None,
"Not supported with OpenJPEG {0}".format(openjpeg_version)) "Not supported with OpenJPEG {0}".format(openjpeg_version))
@unittest.skipIf(re.match('1.5.(1|2)', openjpeg_version) is not None, @unittest.skipIf(re.match('1.5.(1|2)', openjpeg_version) is not None,
@ -345,6 +350,7 @@ class TestJp2k(unittest.TestCase):
self.assertEqual(newjp2.filename, self.j2kfile) self.assertEqual(newjp2.filename, self.j2kfile)
self.assertEqual(len(newjp2.box), 0) self.assertEqual(len(newjp2.box), 0)
@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG)
@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG)
def test_rlevel_max_backwards_compatibility(self): def test_rlevel_max_backwards_compatibility(self):
""" """
@ -367,6 +373,7 @@ class TestJp2k(unittest.TestCase):
np.testing.assert_array_equal(thumbnail1, thumbnail2) np.testing.assert_array_equal(thumbnail1, thumbnail2)
self.assertEqual(thumbnail1.shape, (25, 15, 3)) self.assertEqual(thumbnail1.shape, (25, 15, 3))
@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG)
def test_rlevel_too_high(self): def test_rlevel_too_high(self):
"""Should error out appropriately if reduce level too high""" """Should error out appropriately if reduce level too high"""
j = Jp2k(self.jp2file) j = Jp2k(self.jp2file)
@ -518,12 +525,14 @@ class TestJp2k(unittest.TestCase):
self.assertEqual(new_jp2.box[j].length, self.assertEqual(new_jp2.box[j].length,
baseline_jp2.box[j].length) baseline_jp2.box[j].length)
@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG)
def test_basic_jp2(self): def test_basic_jp2(self):
"""Just a very basic test that reading a JP2 file does not error out. """Just a very basic test that reading a JP2 file does not error out.
""" """
j2k = Jp2k(self.jp2file) j2k = Jp2k(self.jp2file)
j2k[::2, ::2] j2k[::2, ::2]
@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG)
def test_basic_j2k(self): def test_basic_j2k(self):
"""This test is only useful when openjp2 is not available """This test is only useful when openjp2 is not available
and OPJ_DATA_ROOT is not set. We need at least one and OPJ_DATA_ROOT is not set. We need at least one
@ -648,6 +657,7 @@ class TestJp2k(unittest.TestCase):
creator_tool = xmp.get_property(libxmp.consts.XMP_NS_XMP, 'CreatorTool') creator_tool = xmp.get_property(libxmp.consts.XMP_NS_XMP, 'CreatorTool')
self.assertEqual(creator_tool, 'Google') self.assertEqual(creator_tool, 'Google')
@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG)
@unittest.skipIf(re.match(r'''(1|2.0.0)''', @unittest.skipIf(re.match(r'''(1|2.0.0)''',
glymur.version.openjpeg_version) is not None, glymur.version.openjpeg_version) is not None,
"Not supported until 2.0.1") "Not supported until 2.0.1")
@ -664,6 +674,7 @@ class TestJp2k(unittest.TestCase):
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
glymur.Jp2k(self.jp2file).read_bands() glymur.Jp2k(self.jp2file).read_bands()
@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG)
@unittest.skipIf(re.match('1.[0-4]', openjpeg_version) is not None, @unittest.skipIf(re.match('1.[0-4]', openjpeg_version) is not None,
"Not supported with OpenJPEG {0}".format(openjpeg_version)) "Not supported with OpenJPEG {0}".format(openjpeg_version))
@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG)
@ -869,6 +880,7 @@ class TestJp2k_1_x(unittest.TestCase):
@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG)
@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG)
class Test_2p0_official(unittest.TestCase): class Test_2p0_official(unittest.TestCase):
"""Tests specific to v2.0.0""" """Tests specific to v2.0.0"""
@ -962,6 +974,7 @@ class TestJp2k_2_0(unittest.TestCase):
self.assertEqual(jasoc.box[3].box[1].box_id, 'xml ') self.assertEqual(jasoc.box[3].box[1].box_id, 'xml ')
@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG)
@unittest.skipIf(re.match(r'''(1|2.0.0)''', @unittest.skipIf(re.match(r'''(1|2.0.0)''',
glymur.version.openjpeg_version) is not None, glymur.version.openjpeg_version) is not None,
"Not to be run until unless 2.0.1 or higher is present") "Not to be run until unless 2.0.1 or higher is present")

View file

@ -41,10 +41,12 @@ from glymur.jp2box import FileTypeBox, ImageHeaderBox, ColourSpecificationBox
from .fixtures import ( from .fixtures import (
OPJ_DATA_ROOT, MetadataBase, OPJ_DATA_ROOT, MetadataBase,
WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG, WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG,
mse, peak_tolerance, read_pgx, opj_data_file mse, peak_tolerance, read_pgx, opj_data_file,
OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG
) )
@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG)
@unittest.skipIf(OPJ_DATA_ROOT is None, @unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set") "OPJ_DATA_ROOT environment variable not set")
class TestSuite(unittest.TestCase): class TestSuite(unittest.TestCase):
@ -482,7 +484,7 @@ class TestSuiteWarns(MetadataBase):
@unittest.skipIf(OPJ_DATA_ROOT is None, @unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set") "OPJ_DATA_ROOT environment variable not set")
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1, @unittest.skipIf(glymur.version.openjpeg_version_tuple[0] != 2,
"Feature not supported in glymur until openjpeg 2.0") "Feature not supported in glymur until openjpeg 2.0")
class TestSuiteBands(unittest.TestCase): class TestSuiteBands(unittest.TestCase):
""" """
@ -577,7 +579,7 @@ class TestSuiteBands(unittest.TestCase):
@unittest.skipIf(OPJ_DATA_ROOT is None, @unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set") "OPJ_DATA_ROOT environment variable not set")
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1, @unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2,
"Tests not passing until 2.0") "Tests not passing until 2.0")
class TestSuite2point0(unittest.TestCase): class TestSuite2point0(unittest.TestCase):
"""Runs tests introduced in version 2.0 or that pass only in 2.0""" """Runs tests introduced in version 2.0 or that pass only in 2.0"""
@ -641,7 +643,7 @@ class TestSuite2point0(unittest.TestCase):
@unittest.skipIf(OPJ_DATA_ROOT is None, @unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set") "OPJ_DATA_ROOT environment variable not set")
@unittest.skipIf(re.match(r'''(1|2.0.0)''', @unittest.skipIf(re.match(r'''0|1|2.0.0''',
glymur.version.openjpeg_version) is not None, glymur.version.openjpeg_version) is not None,
"Only supported in 2.0.1 or higher") "Only supported in 2.0.1 or higher")
class TestSuite2point1(unittest.TestCase): class TestSuite2point1(unittest.TestCase):
@ -798,7 +800,7 @@ class TestSuite2point1(unittest.TestCase):
@unittest.skipIf(OPJ_DATA_ROOT is None, @unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set") "OPJ_DATA_ROOT environment variable not set")
@unittest.skipIf(re.match(r'''(1|2.0.0)''', @unittest.skipIf(re.match(r'''0|1|2.0.0''',
glymur.version.openjpeg_version) is not None, glymur.version.openjpeg_version) is not None,
"Only supported in 2.0.1 or higher") "Only supported in 2.0.1 or higher")
class TestReadArea(unittest.TestCase): class TestReadArea(unittest.TestCase):