diff --git a/glymur/codestream.py b/glymur/codestream.py index 2f9d37a..6830fab 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -380,13 +380,13 @@ class Codestream(object): COD segment instance. """ offset = fptr.tell() - 2 - offset = fptr.tell() - 2 - read_buffer = fptr.read(3) - length, scod = struct.unpack('>HB', read_buffer) + read_buffer = fptr.read(2) + length, = struct.unpack('>H', read_buffer) - numbytes = offset + 2 + length - fptr.tell() - spcod = fptr.read(numbytes) + read_buffer = fptr.read(length - 2) + scod, = struct.unpack_from('>B', read_buffer, offset=0) + spcod = read_buffer[1:] spcod = np.frombuffer(spcod, dtype=np.uint8) if spcod[0] not in [LRCP, RLCP, RPCL, PCRL, CPRL]: msg = "Invalid progression order in COD segment: {0}." @@ -583,22 +583,21 @@ class Codestream(object): read_buffer = fptr.read(2) length, = struct.unpack('>H', read_buffer) + read_buffer = fptr.read(length - 2) if self._csiz > 256: - read_buffer = fptr.read(3) fmt = '>HB' - mantissa_exponent_buffer_length = length - 5 + mantissa_exponent_offset = 3 else: - read_buffer = fptr.read(2) fmt = '>BB' - mantissa_exponent_buffer_length = length - 4 - cqcc, sqcc = struct.unpack(fmt, read_buffer) + mantissa_exponent_offset = 2 + cqcc, sqcc = struct.unpack_from(fmt, read_buffer) if cqcc >= self._csiz: msg = "Invalid component number ({0}), " msg += "number of components is only {1}." msg = msg.format(cqcc, self._csiz) warnings.warn(msg) - spqcc = fptr.read(mantissa_exponent_buffer_length) + spqcc = read_buffer[mantissa_exponent_offset:] return QCCsegment(cqcc, sqcc, spqcc, length, offset) @@ -670,8 +669,8 @@ class Codestream(object): read_buffer = fptr.read(2) length, = struct.unpack('>H', read_buffer) - xy_buffer = fptr.read(36) - data = struct.unpack('>HIIIIIIIIH', xy_buffer) + read_buffer = fptr.read(length - 2) + data = struct.unpack_from('>HIIIIIIIIH', read_buffer) rsiz = data[0] if rsiz not in _KNOWN_PROFILES: @@ -685,9 +684,8 @@ class Codestream(object): # Csiz is the number of components Csiz = data[9] - component_buffer = fptr.read(Csiz * 3) - data = struct.unpack('>' + 'B' * len(component_buffer), - component_buffer) + data = struct.unpack_from('>' + 'B' * (length - 36 - 2), + read_buffer, offset=36) bitdepth = tuple(((x & 0x7f) + 1) 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) length, = struct.unpack('>H', read_buffer) - read_buffer = fptr.read(2) - ztlm, stlm = struct.unpack('>BB', read_buffer) + read_buffer = fptr.read(length - 2) + ztlm, stlm = struct.unpack_from('>BB', read_buffer) ttlm_st = (stlm >> 4) & 0x3 ptlm_sp = (stlm >> 6) & 0x1 @@ -815,7 +813,6 @@ class Codestream(object): else: ntiles = nbytes / (ttlm_st + (ptlm_sp + 1) * 2) - read_buffer = fptr.read(nbytes) if ttlm_st == 0: ttlm = None fmt = '' @@ -829,7 +826,8 @@ class Codestream(object): else: 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: ttlm = None ptlm = data diff --git a/glymur/jp2box.py b/glymur/jp2box.py index f62567d..22ef99d 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -43,7 +43,7 @@ _METHOD_DISPLAY = { ANY_ICC_PROFILE: 'any ICC profile', VENDOR_COLOR_METHOD: 'vendor color method'} -_factory = lambda x: '{0} (invalid)'.format(x) +_factory = lambda x: '{0} (invalid)'.format(x) _APPROX_DISPLAY = _Keydefaultdict(_factory, {1: 'accurately represents correct colorspace definition', 2: 'approximates correct colorspace definition, exceptional quality', @@ -125,7 +125,7 @@ class Jp2kBox(object): String to be indented. indent_level : str Number of spaces of indentation to add. - + Returns ------- indented_string : str @@ -407,15 +407,16 @@ class ColourSpecificationBox(Jp2kBox): ------- ColourSpecificationBox instance """ + num_bytes = offset + length - fptr.tell() + read_buffer = fptr.read(num_bytes) # Read the brand, minor version. - read_buffer = fptr.read(3) - (method, precedence, approximation) = struct.unpack('>BBB', - read_buffer) + (method, precedence, approximation) = struct.unpack_from('>BBB', + read_buffer, + offset=0) if method == 1: # enumerated colour space - read_buffer = fptr.read(4) - colorspace, = struct.unpack('>I', read_buffer) + colorspace, = struct.unpack_from('>I', read_buffer, offset=3) if colorspace not in _COLORSPACE_MAP_DISPLAY.keys(): msg = "Unrecognized colorspace: {0}".format(colorspace) warnings.warn(msg) @@ -424,14 +425,13 @@ class ColourSpecificationBox(Jp2kBox): else: # ICC profile colorspace = None - numbytes = offset + length - fptr.tell() - if numbytes < 128: + if (num_bytes - 3) < 128: msg = "ICC profile header is corrupt, length is " msg += "only {0} instead of 128." - warnings.warn(msg.format(numbytes), UserWarning) + warnings.warn(msg.format(num_bytes - 3), UserWarning) icc_profile = None else: - profile = _ICCProfile(fptr.read(numbytes)) + profile = _ICCProfile(read_buffer[3:]) icc_profile = profile.header return cls(method=method, @@ -659,12 +659,14 @@ class ChannelDefinitionBox(Jp2kBox): ------- ComponentDefinitionBox instance """ - # Read the number of components. - read_buffer = fptr.read(2) - num_components, = struct.unpack('>H', read_buffer) + num_bytes = offset + length - fptr.tell() + read_buffer = fptr.read(num_bytes) - read_buffer = fptr.read(num_components * 6) - data = struct.unpack('>' + 'HHH' * num_components, read_buffer) + # Read the number of components. + 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] channel_type = data[1:num_components * 6:3] association = data[2:num_components * 6:3] @@ -1103,7 +1105,7 @@ class DataReferenceBox(Jp2kBox): @classmethod def parse(cls, fptr, offset, length): - """Parse Label box. + """Parse data reference box. Parameters ---------- @@ -1234,19 +1236,17 @@ class FileTypeBox(Jp2kBox): ------- FileTypeBox instance """ - # Read the brand, minor version. - read_buffer = fptr.read(8) - (brand, minor_version) = struct.unpack('>4sI', read_buffer) + read_buffer = fptr.read(length - 8) + # Extract the brand, minor version. + (brand, minor_version) = struct.unpack_from('>4sI', read_buffer, 0) if sys.hexversion >= 0x030000: brand = brand.decode('utf-8') - # Read the compatibility list. Each entry has 4 bytes. - current_pos = fptr.tell() - num_bytes = (offset + length - current_pos) / 4 - read_buffer = fptr.read(int(num_bytes) * 4) + # Extract the compatibility list. Each entry has 4 bytes. + num_entries = int((length - 16)/ 4) compatibility_list = [] - for j in range(int(num_bytes)): - entry, = struct.unpack('>4s', read_buffer[4*j:4*(j+1)]) + for j in range(int(num_entries)): + entry, = struct.unpack_from('>4s', read_buffer, 8 + j * 4) if sys.hexversion >= 0x03000000: entry = entry.decode('utf-8') compatibility_list.append(entry) @@ -1348,11 +1348,13 @@ class FragmentListBox(Jp2kBox): ------- FragmentListBox instance """ - read_buffer = fptr.read(2) - num_fragments, = struct.unpack('>H', read_buffer) + num_bytes = offset + length - fptr.tell() + 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('>' + 'QIH' * num_fragments, read_buffer) + lst = struct.unpack_from('>' + 'QIH' * num_fragments, + read_buffer, + offset=2) frag_offset = lst[0::3] frag_len = lst[1::3] data_reference = lst[2::3] @@ -1885,11 +1887,11 @@ class PaletteBox(Jp2kBox): if all(b == bps[0] for b in bps): # All components are the same. Writing is straightforward. 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: - write_buffer = np.getbuffer(self.palette.astype(np.uint16)) + write_buffer = memoryview(self.palette.astype(np.uint16)) 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) else: # Not all the components are the same. More general, but much rarer @@ -1920,13 +1922,11 @@ class PaletteBox(Jp2kBox): ------- PaletteBox instance """ - # Get the size of the palette. 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(num_columns) - bps_signed = struct.unpack('>' + 'B' * num_columns, read_buffer) + read_buffer = fptr.read(ncols) + bps_signed = struct.unpack('>' + 'B' * ncols, read_buffer) bps = [((x & 0x7f) + 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 # be able to efficiently read it. if bps[0] <= 8: - nbytes_per_row = num_columns + nbytes_per_row = ncols dtype = np.uint8 elif bps[0] <= 16: - nbytes_per_row = 2 * num_columns + nbytes_per_row = 2 * ncols dtype = np.uint16 elif bps[0] <= 32: - nbytes_per_row = 3 * num_columns + nbytes_per_row = 3 * ncols 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.reshape(palette, (num_entries, num_columns)) + palette = np.reshape(palette, (nrows, ncols)) else: # 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. row_nbytes = sum([int(math.ceil(x/8.0)) for x in bps]) - read_buffer = fptr.read(num_entries * row_nbytes) - palette = np.zeros((num_entries, num_columns), dtype=np.int32) - for j in range(num_entries): + read_buffer = fptr.read(nrows * row_nbytes) + palette = np.zeros((nrows, ncols), dtype=np.int32) + for j in range(nrows): palette[j] = struct.unpack_from(fmt, read_buffer, offset=j * row_nbytes) @@ -2828,13 +2828,15 @@ class UUIDListBox(Jp2kBox): ------- UUIDListBox instance """ - read_buffer = fptr.read(2) - num_uuids, = struct.unpack('>H', read_buffer) + num_bytes = offset + length - fptr.tell() + read_buffer = fptr.read(num_bytes) + + num_uuids, = struct.unpack_from('>H', read_buffer) ulst = [] - for _ in range(num_uuids): - read_buffer = fptr.read(16) - ulst.append(uuid.UUID(bytes=read_buffer)) + for j in range(num_uuids): + uuid_buffer = read_buffer[2 + j * 16 : 2 + (j + 1) * 16] + ulst.append(uuid.UUID(bytes=uuid_buffer)) return cls(ulst, length=length, offset=offset) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 971c82d..8110c50 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -691,7 +691,7 @@ class Jp2k(Jp2kBox): msg = "Unable to locate the specified codestream." raise IOError(msg) 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. L = os.path.getsize(ifile.name) - ifile.tell() + 8 @@ -833,38 +833,12 @@ class Jp2k(Jp2kBox): """ self._subsampling_sanity_check() - # Must check the specified rlevel against the maximum. - 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) + dparameters = self._populate_dparam(rlevel, ignore_pclr_cmap_cdef) with ExitStack() as stack: 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 - infile = self.filename.encode() - nelts = opj.PATH_LEN - len(infile) - infile += b'0' * nelts - dparameters.infile = infile - dinfo = opj.create_decompress(dparameters.decod_format) event_mgr = opj.EventMgrType() @@ -931,8 +905,8 @@ class Jp2k(Jp2kBox): """ self._subsampling_sanity_check() - 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: if hasattr(opj2.OPENJP2, @@ -976,8 +950,8 @@ class Jp2k(Jp2kBox): return img_array - def _populate_dparam(self, layer, rlevel, area, tile, - ignore_pclr_cmap_cdef): + def _populate_dparam(self, rlevel, ignore_pclr_cmap_cdef, tile=None, + layer=None, area=None): """Populate decompression structure with appropriate input parameters. Parameters @@ -1000,7 +974,11 @@ class Jp2k(Jp2kBox): dparam : DecompressionParametersType (ctypes) 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() nelts = opj2.PATH_LEN - len(infile) @@ -1009,12 +987,22 @@ class Jp2k(Jp2kBox): dparam.decod_format = self._codec_format - dparam.cp_layer = layer + if layer is not None: + dparam.cp_layer = layer - if rlevel == -1: - # Get the lowest resolution thumbnail. + # Must check the specified rlevel against the maximum. + if rlevel != 0: + # Must check the specified rlevel against the maximum. 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 if area is not None: @@ -1088,7 +1076,8 @@ class Jp2k(Jp2kBox): "of OpenJP2 installed before using " "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: if hasattr(opj2.OPENJP2,