Merge branch 'issue130' into devel

This commit is contained in:
John Evans 2014-04-04 12:20:09 -04:00
commit 0c8b11b722
3 changed files with 99 additions and 110 deletions

View file

@ -380,13 +380,13 @@ class Codestream(object):
COD segment instance. COD segment instance.
""" """
offset = fptr.tell() - 2 offset = fptr.tell() - 2
offset = fptr.tell() - 2
read_buffer = fptr.read(3) read_buffer = fptr.read(2)
length, scod = struct.unpack('>HB', read_buffer) length, = struct.unpack('>H', read_buffer)
numbytes = offset + 2 + length - fptr.tell() read_buffer = fptr.read(length - 2)
spcod = fptr.read(numbytes) scod, = struct.unpack_from('>B', read_buffer, offset=0)
spcod = read_buffer[1:]
spcod = np.frombuffer(spcod, dtype=np.uint8) spcod = np.frombuffer(spcod, dtype=np.uint8)
if spcod[0] not in [LRCP, RLCP, RPCL, PCRL, CPRL]: if spcod[0] not in [LRCP, RLCP, RPCL, PCRL, CPRL]:
msg = "Invalid progression order in COD segment: {0}." msg = "Invalid progression order in COD segment: {0}."
@ -583,22 +583,21 @@ class Codestream(object):
read_buffer = fptr.read(2) read_buffer = fptr.read(2)
length, = struct.unpack('>H', read_buffer) length, = struct.unpack('>H', read_buffer)
read_buffer = fptr.read(length - 2)
if self._csiz > 256: if self._csiz > 256:
read_buffer = fptr.read(3)
fmt = '>HB' fmt = '>HB'
mantissa_exponent_buffer_length = length - 5 mantissa_exponent_offset = 3
else: else:
read_buffer = fptr.read(2)
fmt = '>BB' fmt = '>BB'
mantissa_exponent_buffer_length = length - 4 mantissa_exponent_offset = 2
cqcc, sqcc = struct.unpack(fmt, read_buffer) cqcc, sqcc = struct.unpack_from(fmt, read_buffer)
if cqcc >= self._csiz: if cqcc >= self._csiz:
msg = "Invalid component number ({0}), " msg = "Invalid component number ({0}), "
msg += "number of components is only {1}." msg += "number of components is only {1}."
msg = msg.format(cqcc, self._csiz) msg = msg.format(cqcc, self._csiz)
warnings.warn(msg) warnings.warn(msg)
spqcc = fptr.read(mantissa_exponent_buffer_length) spqcc = read_buffer[mantissa_exponent_offset:]
return QCCsegment(cqcc, sqcc, spqcc, length, offset) return QCCsegment(cqcc, sqcc, spqcc, length, offset)
@ -670,8 +669,8 @@ class Codestream(object):
read_buffer = fptr.read(2) read_buffer = fptr.read(2)
length, = struct.unpack('>H', read_buffer) length, = struct.unpack('>H', read_buffer)
xy_buffer = fptr.read(36) read_buffer = fptr.read(length - 2)
data = struct.unpack('>HIIIIIIIIH', xy_buffer) data = struct.unpack_from('>HIIIIIIIIH', read_buffer)
rsiz = data[0] rsiz = data[0]
if rsiz not in _KNOWN_PROFILES: if rsiz not in _KNOWN_PROFILES:
@ -685,9 +684,8 @@ class Codestream(object):
# Csiz is the number of components # Csiz is the number of components
Csiz = data[9] Csiz = data[9]
component_buffer = fptr.read(Csiz * 3) data = struct.unpack_from('>' + 'B' * (length - 36 - 2),
data = struct.unpack('>' + 'B' * len(component_buffer), read_buffer, offset=36)
component_buffer)
bitdepth = tuple(((x & 0x7f) + 1) for x in data[0::3]) bitdepth = tuple(((x & 0x7f) + 1) for x in data[0::3])
signed = tuple(((x & 0x80) > 0) for x in data[0::3]) signed = tuple(((x & 0x80) > 0) for x in data[0::3])
@ -804,8 +802,8 @@ class Codestream(object):
read_buffer = fptr.read(2) read_buffer = fptr.read(2)
length, = struct.unpack('>H', read_buffer) length, = struct.unpack('>H', read_buffer)
read_buffer = fptr.read(2) read_buffer = fptr.read(length - 2)
ztlm, stlm = struct.unpack('>BB', read_buffer) ztlm, stlm = struct.unpack_from('>BB', read_buffer)
ttlm_st = (stlm >> 4) & 0x3 ttlm_st = (stlm >> 4) & 0x3
ptlm_sp = (stlm >> 6) & 0x1 ptlm_sp = (stlm >> 6) & 0x1
@ -815,7 +813,6 @@ class Codestream(object):
else: else:
ntiles = nbytes / (ttlm_st + (ptlm_sp + 1) * 2) ntiles = nbytes / (ttlm_st + (ptlm_sp + 1) * 2)
read_buffer = fptr.read(nbytes)
if ttlm_st == 0: if ttlm_st == 0:
ttlm = None ttlm = None
fmt = '' fmt = ''
@ -829,7 +826,8 @@ class Codestream(object):
else: else:
fmt += 'I' fmt += 'I'
data = struct.unpack('>' + fmt * int(ntiles), read_buffer) data = struct.unpack_from('>' + fmt * int(ntiles), read_buffer,
offset=2)
if ttlm_st == 0: if ttlm_st == 0:
ttlm = None ttlm = None
ptlm = data ptlm = data

View file

@ -43,7 +43,7 @@ _METHOD_DISPLAY = {
ANY_ICC_PROFILE: 'any ICC profile', ANY_ICC_PROFILE: 'any ICC profile',
VENDOR_COLOR_METHOD: 'vendor color method'} VENDOR_COLOR_METHOD: 'vendor color method'}
_factory = lambda x: '{0} (invalid)'.format(x) _factory = lambda x: '{0} (invalid)'.format(x)
_APPROX_DISPLAY = _Keydefaultdict(_factory, _APPROX_DISPLAY = _Keydefaultdict(_factory,
{1: 'accurately represents correct colorspace definition', {1: 'accurately represents correct colorspace definition',
2: 'approximates correct colorspace definition, exceptional quality', 2: 'approximates correct colorspace definition, exceptional quality',
@ -125,7 +125,7 @@ class Jp2kBox(object):
String to be indented. String to be indented.
indent_level : str indent_level : str
Number of spaces of indentation to add. Number of spaces of indentation to add.
Returns Returns
------- -------
indented_string : str indented_string : str
@ -407,15 +407,16 @@ class ColourSpecificationBox(Jp2kBox):
------- -------
ColourSpecificationBox instance ColourSpecificationBox instance
""" """
num_bytes = offset + length - fptr.tell()
read_buffer = fptr.read(num_bytes)
# Read the brand, minor version. # Read the brand, minor version.
read_buffer = fptr.read(3) (method, precedence, approximation) = struct.unpack_from('>BBB',
(method, precedence, approximation) = struct.unpack('>BBB', read_buffer,
read_buffer) offset=0)
if method == 1: if method == 1:
# enumerated colour space # enumerated colour space
read_buffer = fptr.read(4) colorspace, = struct.unpack_from('>I', read_buffer, offset=3)
colorspace, = struct.unpack('>I', read_buffer)
if colorspace not in _COLORSPACE_MAP_DISPLAY.keys(): if colorspace not in _COLORSPACE_MAP_DISPLAY.keys():
msg = "Unrecognized colorspace: {0}".format(colorspace) msg = "Unrecognized colorspace: {0}".format(colorspace)
warnings.warn(msg) warnings.warn(msg)
@ -424,14 +425,13 @@ class ColourSpecificationBox(Jp2kBox):
else: else:
# ICC profile # ICC profile
colorspace = None colorspace = None
numbytes = offset + length - fptr.tell() if (num_bytes - 3) < 128:
if numbytes < 128:
msg = "ICC profile header is corrupt, length is " msg = "ICC profile header is corrupt, length is "
msg += "only {0} instead of 128." msg += "only {0} instead of 128."
warnings.warn(msg.format(numbytes), UserWarning) warnings.warn(msg.format(num_bytes - 3), UserWarning)
icc_profile = None icc_profile = None
else: else:
profile = _ICCProfile(fptr.read(numbytes)) profile = _ICCProfile(read_buffer[3:])
icc_profile = profile.header icc_profile = profile.header
return cls(method=method, return cls(method=method,
@ -659,12 +659,14 @@ class ChannelDefinitionBox(Jp2kBox):
------- -------
ComponentDefinitionBox instance ComponentDefinitionBox instance
""" """
# Read the number of components. num_bytes = offset + length - fptr.tell()
read_buffer = fptr.read(2) read_buffer = fptr.read(num_bytes)
num_components, = struct.unpack('>H', read_buffer)
read_buffer = fptr.read(num_components * 6) # Read the number of components.
data = struct.unpack('>' + 'HHH' * num_components, read_buffer) num_components, = struct.unpack_from('>H', read_buffer)
data = struct.unpack_from('>' + 'HHH' * num_components, read_buffer,
offset=2)
index = data[0:num_components * 6:3] index = data[0:num_components * 6:3]
channel_type = data[1:num_components * 6:3] channel_type = data[1:num_components * 6:3]
association = data[2:num_components * 6:3] association = data[2:num_components * 6:3]
@ -1103,7 +1105,7 @@ class DataReferenceBox(Jp2kBox):
@classmethod @classmethod
def parse(cls, fptr, offset, length): def parse(cls, fptr, offset, length):
"""Parse Label box. """Parse data reference box.
Parameters Parameters
---------- ----------
@ -1234,19 +1236,17 @@ class FileTypeBox(Jp2kBox):
------- -------
FileTypeBox instance FileTypeBox instance
""" """
# Read the brand, minor version. read_buffer = fptr.read(length - 8)
read_buffer = fptr.read(8) # Extract the brand, minor version.
(brand, minor_version) = struct.unpack('>4sI', read_buffer) (brand, minor_version) = struct.unpack_from('>4sI', read_buffer, 0)
if sys.hexversion >= 0x030000: if sys.hexversion >= 0x030000:
brand = brand.decode('utf-8') brand = brand.decode('utf-8')
# Read the compatibility list. Each entry has 4 bytes. # Extract the compatibility list. Each entry has 4 bytes.
current_pos = fptr.tell() num_entries = int((length - 16)/ 4)
num_bytes = (offset + length - current_pos) / 4
read_buffer = fptr.read(int(num_bytes) * 4)
compatibility_list = [] compatibility_list = []
for j in range(int(num_bytes)): for j in range(int(num_entries)):
entry, = struct.unpack('>4s', read_buffer[4*j:4*(j+1)]) entry, = struct.unpack_from('>4s', read_buffer, 8 + j * 4)
if sys.hexversion >= 0x03000000: if sys.hexversion >= 0x03000000:
entry = entry.decode('utf-8') entry = entry.decode('utf-8')
compatibility_list.append(entry) compatibility_list.append(entry)
@ -1348,11 +1348,13 @@ class FragmentListBox(Jp2kBox):
------- -------
FragmentListBox instance FragmentListBox instance
""" """
read_buffer = fptr.read(2) num_bytes = offset + length - fptr.tell()
num_fragments, = struct.unpack('>H', read_buffer) read_buffer = fptr.read(num_bytes)
num_fragments, = struct.unpack_from('>H', read_buffer, offset=0)
read_buffer = fptr.read(num_fragments * 14) lst = struct.unpack_from('>' + 'QIH' * num_fragments,
lst = struct.unpack('>' + 'QIH' * num_fragments, read_buffer) read_buffer,
offset=2)
frag_offset = lst[0::3] frag_offset = lst[0::3]
frag_len = lst[1::3] frag_len = lst[1::3]
data_reference = lst[2::3] data_reference = lst[2::3]
@ -1885,11 +1887,11 @@ class PaletteBox(Jp2kBox):
if all(b == bps[0] for b in bps): if all(b == bps[0] for b in bps):
# All components are the same. Writing is straightforward. # All components are the same. Writing is straightforward.
if self.bits_per_component[0] <= 8: if self.bits_per_component[0] <= 8:
write_buffer = np.getbuffer(self.palette.astype(np.uint8)) write_buffer = memoryview(self.palette.astype(np.uint8))
elif self.bits_per_component[0] <= 16: elif self.bits_per_component[0] <= 16:
write_buffer = np.getbuffer(self.palette.astype(np.uint16)) write_buffer = memoryview(self.palette.astype(np.uint16))
elif self.bits_per_component[0] <= 32: elif self.bits_per_component[0] <= 32:
write_buffer = np.getbuffer(self.palette.astype(np.uint32)) write_buffer = memoryview(self.palette.astype(np.uint32))
fptr.write(write_buffer) fptr.write(write_buffer)
else: else:
# Not all the components are the same. More general, but much rarer # Not all the components are the same. More general, but much rarer
@ -1920,13 +1922,11 @@ class PaletteBox(Jp2kBox):
------- -------
PaletteBox instance PaletteBox instance
""" """
# Get the size of the palette.
read_buffer = fptr.read(3) read_buffer = fptr.read(3)
(num_entries, num_columns) = struct.unpack('>HB', read_buffer) (nrows, ncols) = struct.unpack('>HB', read_buffer)
# Need to determine bps and signed or not read_buffer = fptr.read(ncols)
read_buffer = fptr.read(num_columns) bps_signed = struct.unpack('>' + 'B' * ncols, read_buffer)
bps_signed = struct.unpack('>' + 'B' * num_columns, read_buffer)
bps = [((x & 0x7f) + 1) for x in bps_signed] bps = [((x & 0x7f) + 1) for x in bps_signed]
signed = [((x & 0x80) > 1) for x in bps_signed] signed = [((x & 0x80) > 1) for x in bps_signed]
@ -1934,18 +1934,18 @@ class PaletteBox(Jp2kBox):
# Ok the palette has the same datatype for all columns. We should # Ok the palette has the same datatype for all columns. We should
# be able to efficiently read it. # be able to efficiently read it.
if bps[0] <= 8: if bps[0] <= 8:
nbytes_per_row = num_columns nbytes_per_row = ncols
dtype = np.uint8 dtype = np.uint8
elif bps[0] <= 16: elif bps[0] <= 16:
nbytes_per_row = 2 * num_columns nbytes_per_row = 2 * ncols
dtype = np.uint16 dtype = np.uint16
elif bps[0] <= 32: elif bps[0] <= 32:
nbytes_per_row = 3 * num_columns nbytes_per_row = 3 * ncols
dtype = np.uint32 dtype = np.uint32
read_buffer = fptr.read(num_entries * nbytes_per_row) read_buffer = fptr.read(nrows * nbytes_per_row)
palette = np.frombuffer(read_buffer, dtype=dtype) palette = np.frombuffer(read_buffer, dtype=dtype)
palette = np.reshape(palette, (num_entries, num_columns)) palette = np.reshape(palette, (nrows, ncols))
else: else:
# General case where the columns may not be the same width. # General case where the columns may not be the same width.
@ -1962,9 +1962,9 @@ class PaletteBox(Jp2kBox):
# That means a list comprehension does this in one shot. # That means a list comprehension does this in one shot.
row_nbytes = sum([int(math.ceil(x/8.0)) for x in bps]) row_nbytes = sum([int(math.ceil(x/8.0)) for x in bps])
read_buffer = fptr.read(num_entries * row_nbytes) read_buffer = fptr.read(nrows * row_nbytes)
palette = np.zeros((num_entries, num_columns), dtype=np.int32) palette = np.zeros((nrows, ncols), dtype=np.int32)
for j in range(num_entries): for j in range(nrows):
palette[j] = struct.unpack_from(fmt, read_buffer, palette[j] = struct.unpack_from(fmt, read_buffer,
offset=j * row_nbytes) offset=j * row_nbytes)
@ -2828,13 +2828,15 @@ class UUIDListBox(Jp2kBox):
------- -------
UUIDListBox instance UUIDListBox instance
""" """
read_buffer = fptr.read(2) num_bytes = offset + length - fptr.tell()
num_uuids, = struct.unpack('>H', read_buffer) read_buffer = fptr.read(num_bytes)
num_uuids, = struct.unpack_from('>H', read_buffer)
ulst = [] ulst = []
for _ in range(num_uuids): for j in range(num_uuids):
read_buffer = fptr.read(16) uuid_buffer = read_buffer[2 + j * 16 : 2 + (j + 1) * 16]
ulst.append(uuid.UUID(bytes=read_buffer)) ulst.append(uuid.UUID(bytes=uuid_buffer))
return cls(ulst, length=length, offset=offset) return cls(ulst, length=length, offset=offset)

View file

@ -691,7 +691,7 @@ class Jp2k(Jp2kBox):
msg = "Unable to locate the specified codestream." msg = "Unable to locate the specified codestream."
raise IOError(msg) raise IOError(msg)
if L == 0: if L == 0:
# The length of the box is presumed to last until the end of # The length of the box is presumed to last until the end of
# the file. Compute the effective length of the box. # the file. Compute the effective length of the box.
L = os.path.getsize(ifile.name) - ifile.tell() + 8 L = os.path.getsize(ifile.name) - ifile.tell() + 8
@ -833,38 +833,12 @@ class Jp2k(Jp2kBox):
""" """
self._subsampling_sanity_check() self._subsampling_sanity_check()
# Must check the specified rlevel against the maximum. dparameters = self._populate_dparam(rlevel, ignore_pclr_cmap_cdef)
if rlevel != 0:
# Must check the specified rlevel against the maximum.
codestream = self.get_codestream()
max_rlevel = codestream.segment[2].spcod[4]
if rlevel == -1:
# -1 is shorthand for the largest rlevel
rlevel = max_rlevel
elif rlevel < -1 or rlevel > max_rlevel:
msg = "rlevel must be in the range [-1, {0}] for this image."
msg = msg.format(max_rlevel)
raise IOError(msg)
with ExitStack() as stack: with ExitStack() as stack:
try: try:
# Set decoding parameters.
# TODO: look to refactor, use _populate_dparam
dparameters = opj.DecompressionParametersType()
opj.set_default_decoder_parameters(ctypes.byref(dparameters))
if ignore_pclr_cmap_cdef is True:
# Return raw codestream components.
dparameters.flags |= 1
dparameters.cp_reduce = rlevel
dparameters.decod_format = self._codec_format dparameters.decod_format = self._codec_format
infile = self.filename.encode()
nelts = opj.PATH_LEN - len(infile)
infile += b'0' * nelts
dparameters.infile = infile
dinfo = opj.create_decompress(dparameters.decod_format) dinfo = opj.create_decompress(dparameters.decod_format)
event_mgr = opj.EventMgrType() event_mgr = opj.EventMgrType()
@ -931,8 +905,8 @@ class Jp2k(Jp2kBox):
""" """
self._subsampling_sanity_check() self._subsampling_sanity_check()
dparam = self._populate_dparam(layer, rlevel, area, tile, dparam = self._populate_dparam(rlevel, ignore_pclr_cmap_cdef,
ignore_pclr_cmap_cdef) layer=layer, tile=tile, area=area)
with ExitStack() as stack: with ExitStack() as stack:
if hasattr(opj2.OPENJP2, if hasattr(opj2.OPENJP2,
@ -976,8 +950,8 @@ class Jp2k(Jp2kBox):
return img_array return img_array
def _populate_dparam(self, layer, rlevel, area, tile, def _populate_dparam(self, rlevel, ignore_pclr_cmap_cdef, tile=None,
ignore_pclr_cmap_cdef): layer=None, area=None):
"""Populate decompression structure with appropriate input parameters. """Populate decompression structure with appropriate input parameters.
Parameters Parameters
@ -1000,7 +974,11 @@ class Jp2k(Jp2kBox):
dparam : DecompressionParametersType (ctypes) dparam : DecompressionParametersType (ctypes)
Corresponds to openjp2 decompression parameters structure. Corresponds to openjp2 decompression parameters structure.
""" """
dparam = opj2.set_default_decoder_parameters() if opj2.OPENJP2 is not None:
dparam = opj2.set_default_decoder_parameters()
else:
dparam = opj.DecompressionParametersType()
opj.set_default_decoder_parameters(ctypes.byref(dparam))
infile = self.filename.encode() infile = self.filename.encode()
nelts = opj2.PATH_LEN - len(infile) nelts = opj2.PATH_LEN - len(infile)
@ -1009,12 +987,22 @@ class Jp2k(Jp2kBox):
dparam.decod_format = self._codec_format dparam.decod_format = self._codec_format
dparam.cp_layer = layer if layer is not None:
dparam.cp_layer = layer
if rlevel == -1: # Must check the specified rlevel against the maximum.
# Get the lowest resolution thumbnail. if rlevel != 0:
# Must check the specified rlevel against the maximum.
codestream = self.get_codestream() codestream = self.get_codestream()
rlevel = codestream.segment[2].spcod[4] max_rlevel = codestream.segment[2].spcod[4]
if rlevel == -1:
# -1 is shorthand for the largest rlevel
rlevel = max_rlevel
elif rlevel < -1 or rlevel > max_rlevel:
msg = "rlevel must be in the range [-1, {0}] for this image."
msg = msg.format(max_rlevel)
raise IOError(msg)
dparam.cp_reduce = rlevel dparam.cp_reduce = rlevel
if area is not None: if area is not None:
@ -1088,7 +1076,8 @@ class Jp2k(Jp2kBox):
"of OpenJP2 installed before using " "of OpenJP2 installed before using "
"this functionality.") "this functionality.")
dparam = self._populate_dparam(layer, rlevel, area, tile, ignore_pclr_cmap_cdef) dparam = self._populate_dparam(rlevel, ignore_pclr_cmap_cdef,
layer=layer, tile=tile, area=area)
with ExitStack() as stack: with ExitStack() as stack:
if hasattr(opj2.OPENJP2, if hasattr(opj2.OPENJP2,