From e071a96f68178e46addc3d44215094152ed65edc Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 3 Jul 2013 11:04:47 -0400 Subject: [PATCH] Reading goodstuff on 64-bit linux works, but nemo is oddly truncated. --- glymur/jp2k.py | 67 ++++++++++++++++++- glymur/lib/openjpeg.py | 126 +++++++++++++++++++++++++++++++++++- glymur/test/test_jp2k_15.py | 4 ++ 3 files changed, 193 insertions(+), 4 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 4219218..4a62021 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -614,7 +614,72 @@ class Jp2k(Jp2kBox): IOError If the image has differing subsample factors. """ - raise NotImplementedError("not there yet") + + # Set decoding parameters. + dparameters = opj.dparameters_t() + opj._set_default_decoder_parameters(ctypes.byref(dparameters)) + dparameters.cp_reduce = reduce + 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) + opj._setup_decoder(dinfo, dparameters) + + with open(self.filename, 'rb') as fp: + src = fp.read() + + cio = opj._cio_open(dinfo, src) + image = opj._decode(dinfo, cio) + ncomps = image.contents.numcomps + + component = image.contents.comps[0] + if component.sgnd: + if component.prec <= 8: + dtype = np.int8 + elif component.prec <= 16: + dtype = np.int16 + else: + raise RuntimeError("Unhandled precision, datatype") + else: + if component.prec <= 8: + dtype = np.uint8 + elif component.prec <= 16: + dtype = np.uint16 + else: + raise RuntimeError("Unhandled precision, datatype") + + nrows = image.contents.comps[0].h + ncols = image.contents.comps[0].w + ncomps = image.contents.numcomps + data = np.zeros((nrows, ncols, ncomps), dtype) + + for k in range(image.contents.numcomps): + component = image.contents.comps[k] + nrows = component.h + ncols = component.w + + if nrows == 0 or ncols == 0: + # Letting this situation continue would segfault + # Python. + msg = "Component {0} has dimensions {1} x {2}" + msg = msg.format(k, nrows, ncols) + raise IOError(msg) + + addr = ctypes.addressof(component.data.contents) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + x = np.ctypeslib.as_array( (ctypes.c_int32 * nrows * ncols).from_address(addr)) + data[:, :, k] = np.reshape(x.astype(dtype), (nrows, ncols)) + + opj._cio_close(cio) + opj._destroy_decompress(dinfo) + opj._image_destroy(image) + + return data def _read_openjp2(self, reduce=0, layer=0, area=None, tile=None, verbose=False): diff --git a/glymur/lib/openjpeg.py b/glymur/lib/openjpeg.py index d2fceb1..4063a17 100644 --- a/glymur/lib/openjpeg.py +++ b/glymur/lib/openjpeg.py @@ -14,7 +14,50 @@ else: elif platform.system() == 'Linux': _OPENJPEG = ctypes.CDLL(find_library('openjpeg')) -OPJ_PATH_LEN = 4096 # maximum allowed size for filenames +_PATH_LEN = 4096 # maximum allowed size for filenames + + +class event_mgr_t(ctypes.Structure): + """Message handler object. + """ + _fields_ = [("error_handler", ctypes.c_void_p), + ("warning_handler", ctypes.c_void_p), + ("info_handler", ctypes.c_void_p)] + +class common_struct_t(ctypes.Structure): + """Common fields between JPEG 2000 compression and decompression contextx. + """ + _fields_ = [("event_mgr", ctypes.POINTER(event_mgr_t)), + ("client_data", ctypes.c_void_p), + ("is_decompressor", ctypes.c_bool), + ("codec_format", ctypes.c_int), + ("j2k_handle", ctypes.c_void_p), + ("jp2_handle", ctypes.c_void_p), + ("mj2_handle", ctypes.c_void_p)] + + +class dinfo_t(ctypes.Structure): + """Common fields between JPEG 2000 compression and decompression contextx. + This is for decompression contexts. + """ + pass + + +class cio_t(ctypes.Structure): + _fields_ = [# codec context + ("cinfo", ctypes.POINTER(common_struct_t)), + # STREAM_READ or STREAM_WRITE + ("openmode", ctypes.c_int), + # pointer to start of buffer + ("buffer", ctypes.POINTER(ctypes.c_char)), + # buffer size in bytes + ("length", ctypes.c_int), + # pointer to start of stream + ("start", ctypes.c_char_p), + # pointer to end of stream + ("end", ctypes.c_char_p), + # pointer to current position + ("bp", ctypes.c_char_p)] class dparameters_t(ctypes.Structure): @@ -23,9 +66,9 @@ class dparameters_t(ctypes.Structure): # cp_layer: the maximum number of quality layers to decode ("cp_layer", ctypes.c_int), # infile: input file name - ("infile", ctypes.c_char * OPJ_PATH_LEN), + ("infile", ctypes.c_char * _PATH_LEN), # outfile: output file name - ("outfile", ctypes.c_char * OPJ_PATH_LEN), + ("outfile", ctypes.c_char * _PATH_LEN), # decod_format: input file format 0: J2K, 1: JP2, 2: JPT ("decod_format", ctypes.c_int), # cod_format: output file format 0: PGX, 1: PxM, 2: BMP @@ -42,6 +85,77 @@ class dparameters_t(ctypes.Structure): ("flags", ctypes.c_uint)] +class image_comp_t(ctypes.Structure): + """Defines a single image component. """ + _fields_ = [("dx", ctypes.c_int), + ("dy", ctypes.c_int), + ("w", ctypes.c_int), + ("h", ctypes.c_int), + ("x0", ctypes.c_int), + ("y0", ctypes.c_int), + ("prec", ctypes.c_int), + ("bpp", ctypes.c_int), + ("sgnd", ctypes.c_int), + ("resno_decoded", ctypes.c_int), + ("factor", ctypes.c_int), + ("data", ctypes.POINTER(ctypes.c_int))] + + +class image_t(ctypes.Structure): + """Defines image data and characteristics.""" + _fields_ = [("x0", ctypes.c_int), + ("y0", ctypes.c_int), + ("x1", ctypes.c_int), + ("y1", ctypes.c_int), + ("numcomps", ctypes.c_int), + ("color_space", ctypes.c_int), + ("comps", ctypes.POINTER(image_comp_t)), + ("icc_profile_buf", ctypes.c_char_p), + ("icc_profile_len", ctypes.c_int)] + +def _cio_open(cinfo, src): + """Wrapper for openjpeg library function opj_cio_open.""" + argtypes = [ctypes.POINTER(common_struct_t), ctypes.c_char_p, ctypes.c_int] + _OPENJPEG.opj_cio_open.argtypes = argtypes + _OPENJPEG.opj_cio_open.restype = ctypes.POINTER(cio_t) + + cio = _OPENJPEG.opj_cio_open(ctypes.cast(cinfo, ctypes.POINTER(common_struct_t)), + src, len(src)) + return cio + +def _cio_close(cio): + """Wraps openjpeg library function cio_close. + """ + _OPENJPEG.opj_cio_close.argtypes = [ctypes.POINTER(cio_t)] + _OPENJPEG.opj_cio_close(cio) + +def _create_decompress(fmt): + """Wraps openjpeg library function opj_create_decompress. + """ + _OPENJPEG.opj_create_decompress.argtypes = [ctypes.c_int] + _OPENJPEG.opj_create_decompress.restype = ctypes.POINTER(dinfo_t) + dinfo = _OPENJPEG.opj_create_decompress(fmt) + return dinfo + +def _decode(dinfo, cio): + """Wrapper for opj_decode. + """ + argtypes = [ctypes.POINTER(dinfo_t), ctypes.POINTER(cio_t)] + _OPENJPEG.opj_decode.argtypes = argtypes + _OPENJPEG.opj_decode.restype = ctypes.POINTER(image_t) + image = _OPENJPEG.opj_decode(dinfo, cio) + return image + +def _destroy_decompress(dinfo): + """Wraps openjpeg library function opj_destroy_decompress.""" + _OPENJPEG.opj_destroy_decompress.argtypes = [ctypes.POINTER(dinfo_t)] + _OPENJPEG.opj_destroy_decompress(dinfo) + +def _image_destroy(image): + """Wraps openjpeg library function opj_image_destroy.""" + _OPENJPEG.opj_image_destroy.argtypes = [ctypes.POINTER(image_t)] + _OPENJPEG.opj_image_destroy(image) + def _set_default_decoder_parameters(dparams_p): """Wrapper for opj_set_default_decoder_parameters. """ @@ -50,6 +164,12 @@ def _set_default_decoder_parameters(dparams_p): _OPENJPEG.opj_set_default_decoder_parameters(dparams_p) +def _setup_decoder(dinfo, dparams): + """Wrapper for openjpeg library function opj_setup_decoder.""" + argtypes = [ctypes.POINTER(dinfo_t), ctypes.POINTER(dparameters_t)] + _OPENJPEG.opj_setup_decoder.argtypes = argtypes + _OPENJPEG.opj_setup_decoder(dinfo, dparams) + def _version(): """Wrapper for opj_version library routine.""" _OPENJPEG.opj_version.restype = ctypes.c_char_p diff --git a/glymur/test/test_jp2k_15.py b/glymur/test/test_jp2k_15.py index ef73183..eed72a3 100644 --- a/glymur/test/test_jp2k_15.py +++ b/glymur/test/test_jp2k_15.py @@ -42,6 +42,10 @@ class TestJp2k15(unittest.TestCase): # Verbose option not allowed for 1.5.1. self.assertTrue(False) + def test_differing_subsampling(self): + # Only images with same subsampling is allowed. + self.assertTrue(False) + if __name__ == "__main__": unittest.main()