diff --git a/glymur/jp2k.py b/glymur/jp2k.py index bb2fe43..413179b 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -15,14 +15,14 @@ else: import ctypes import math import os +import re import struct import warnings import numpy as np from .codestream import Codestream -from .core import SRGB -from .core import GREYSCALE +from .core import SRGB, GREYSCALE from .core import PROGRESSION_ORDER from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE from .jp2box import Jp2kBox @@ -33,6 +33,13 @@ from .lib import openjpeg as opj from .lib import openjp2 as opj2 from .lib import c as libc +if opj.OPENJPEG is None and opj2.OPENJP2 is None: + OPENJPEG_VERSION = '0.0.0' +elif opj2.OPENJP2 is None: + OPENJPEG_VERSION = opj.version() +else: + OPENJPEG_VERSION = opj2.version() + class Jp2k(Jp2kBox): """JPEG 2000 file. @@ -185,7 +192,10 @@ class Jp2k(Jp2kBox): cparams : CompressionParametersType(ctypes.Structure) Corresponds to cparameters_t type in openjp2 headers. """ - cparams = opj2.set_default_encoder_parameters() + if re.match(r"""1\.\d\.\d""", OPENJPEG_VERSION): + cparams = opj.set_default_encoder_parameters() + else: + cparams = opj2.set_default_encoder_parameters() outfile = self.filename.encode() num_pad_bytes = opj2.PATH_LEN - len(outfile) @@ -389,99 +399,18 @@ class Jp2k(Jp2kBox): def _write_openjpeg(self, img_array, verbose=False, **kwargs): """ """ - if codeblock is not None: - if (np.prod(codeblock) > 4096) or (np.any(np.array(codeblock) < 4)) or (np.any(np.array(codeblock) > 1024)): - msg = 'Size of code block error. ' - msg += 'Restriction: width*height <= 4096, ' - msg += '4 <= width, height <= 1024' - raise(RuntimeError(msg)) + cparams, colorspace = self._process_write_inputs(img_array, **kwargs) - if cratio is not None and len(cratio) >= opj.MAX_NUM_LAYERS: - msg = 'Maximum number of layers is %d.' % opj.MAX_NUM_LAYERS - raise(RuntimeError(msg)) + if img_array.ndim == 2: + # Force the image to be 3D. Just makes things easier later on. + numrows, numcols = img_array.shape + img_array = img_array.reshape(numrows, numcols, 1) - if psnr is not None and len(psnr) >= opj.MAX_NUM_LAYERS: - msg = 'Maximum number of layers is %d.' % opj.MAX_NUM_LAYERS - raise(RuntimeError(msg)) + comptparms = _populate_comptparms(img_array, cparams) - if cratio is not None and psnr is not None: - msg = 'Psnr and cratio parameters cannot be specified together.' - raise(RuntimeError(msg)) + image = opj.image_create(comptparms, colorspace) - - cparams = opj.CompressionParametersType() - - opj.set_default_encoder_parameters(ctypes.byref(cparams)) - - # Set default to lossless until we know otherwise. - cparams.tcp_rates[0] = 0 - cparams.tcp_numlayers = 1 - cparams.cp_disto_alloc = 1 - - outfile = self.jp2k_file - nelts = opj.OPJ_PATH_LEN - len(outfile) - outfile += b'0'*nelts - cparams.outfile = outfile - - numrows = data.shape[0] - numcols = data.shape[1] - numlayers = data.shape[2] - - if codeblock is not None: - cparams.cblockw_init = codeblock[0] - cparams.cblockh_init = codeblock[1] - - if comment is None: - comment = 'Created by OpenJPEG version %s' % opj.version() - cparams.cp_comment = ctypes.c_char_p(comment) - - - if cratio is not None: - cparams.tcp_numlayers = len(cratio) - for j in range(0,len(cratio)): - cparams.tcp_rates[j] = cratio[j] - cparams.cp_disto_alloc = 1 - - if eph: - cparams.csty = cparams.csty | 0x04 - - if modeswitch is not None: - cparams.mode = modeswitch - - if numres is not None: - cparams.numresolution = numres - - if origin is not None: - cparams.image_offset_x0 = origin[0] - cparams.image_offset_y0 = origin[1] - - if progorder is not None: - cparams.prog_order = opj.progression_order[progorder] - - if precinct is not None: - cparams.csty = cparams.csty | 0x01 - cparams.res_spec = len(precinct) - for j in range(0,len(precinct)): - cparams.prcw_init[j] = precinct[j][0] - cparams.prch_init[j] = precinct[j][1] - - if psnr is not None: - cparams.tcp_numlayers = len(psnr) - for j in range(0,len(psnr)): - cparams.tcp_distoratio[j] = psnr[j] - cparams.cp_fixed_quality = 1 - - if sop: - cparams.csty = cparams.csty | 0x02 - - if tile is not None: - cparams.tile_size_on = 1 - cparams.cp_tdx = tile[0] - cparams.cp_tdy = tile[1] - - # comment = what? - cmptparms = opj.image_cmptparm_t_from_np(data) - image = opj.image_create(cmptparms) + numrows, numcols, numlayers = img_array.shape # set image offset and reference grid image.contents.x0 = cparams.image_offset_x0 @@ -491,23 +420,16 @@ class Jp2k(Jp2kBox): # Stage the image data to the openjpeg data structure. for k in range(0,numlayers): - layer = np.ascontiguousarray(data[:,:,k], dtype=np.int32) + layer = np.ascontiguousarray(img_array[:,:,k], dtype=np.int32) dest = image.contents.comps[k].data src = layer.ctypes.data ctypes.memmove(dest, src, layer.nbytes) - # set multi-component transform? - if image.contents.numcomps == 3: - cparams.tcp_mct = chr(1) - else: - cparams.tcp_mct = chr(0) - # set encode format - #cinfo = opj.create_compress(opj.codec_format[self.file_format]) - cinfo = opj.create_compress(self.file_format) + cinfo = opj.create_compress(cparams.codec_fmt) event_mgr = opj.EventMgrType(None, None, None) - opj.set_event_mgr(cparams, ctypes.byref(event_mgr), None) + #opj.set_event_mgr(cparams, ctypes.byref(event_mgr), None) opj.setup_encoder(cinfo, ctypes.byref(cparams), image) @@ -519,7 +441,7 @@ class Jp2k(Jp2kBox): pos = opj.cio_tell(cio) ss = ctypes.string_at(cio.contents.buffer, pos) - f = open(self.jp2k_file,'wb') + f = open(self.filename,'wb') f.write(ss) f.close() opj.cio_close(cio); @@ -1408,7 +1330,10 @@ def _populate_comptparms(img_array, cparams): comp_prec = 16 numrows, numcols, num_comps = img_array.shape - comptparms = (opj2.ImageComptParmType * num_comps)() + if re.match(r"""1\.\d\.\d""", OPENJPEG_VERSION): + comptparms = (opj.ImageComptParmType * num_comps)() + else: + comptparms = (opj2.ImageComptParmType * num_comps)() for j in range(num_comps): comptparms[j].dx = cparams.subsampling_dx comptparms[j].dy = cparams.subsampling_dy diff --git a/glymur/lib/openjpeg.py b/glymur/lib/openjpeg.py index 6e7b858..3ea424a 100644 --- a/glymur/lib/openjpeg.py +++ b/glymur/lib/openjpeg.py @@ -371,7 +371,7 @@ class DecompressionParametersType(ctypes.Structure): _fields_.append(("flags", ctypes.c_uint)) -class ImageCmptparmType(ctypes.Structure): +class ImageComptParmType(ctypes.Structure): """Component parameters structure used by the opj_image_create function. """ _fields_ = [ @@ -395,7 +395,7 @@ class ImageCmptparmType(ctypes.Structure): # precision ('prec', ctypes.c_int), - # imgae depth in bits + # image depth in bits ('bpp', ctypes.c_int), # signed (1) / unsigned (0) @@ -403,22 +403,19 @@ class ImageCmptparmType(ctypes.Structure): class ImageCompType(ctypes.Structure): - """Defines a single image component. - - Corresponds to image_comp_t type in openjpeg. - """ - _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))] + """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 ImageType(ctypes.Structure): @@ -437,16 +434,22 @@ class ImageType(ctypes.Structure): ("icc_profile_len", ctypes.c_int)] -def cio_open(cinfo, src): +def cio_open(cinfo, src=None): """Wrapper for openjpeg library function opj_cio_open.""" argtypes = [ctypes.POINTER(CommonStructType), ctypes.c_char_p, ctypes.c_int] OPENJPEG.opj_cio_open.argtypes = argtypes OPENJPEG.opj_cio_open.restype = ctypes.POINTER(CioType) + if src is None: + length = 0 + else: + length = len(src) + cio = OPENJPEG.opj_cio_open(ctypes.cast(cinfo, ctypes.POINTER(CommonStructType)), - src, len(src)) + src, + length) return cio @@ -457,6 +460,13 @@ def cio_close(cio): OPENJPEG.opj_cio_close(cio) +def cio_tell(cio): + """Get position in byte stream.""" + OPENJPEG.cio_tell.argtypes = [ctypes.POINTER(CioType)] + OPENJPEG.cio_tell.restype = ctypes.c_int + pos = OPENJPEG.cio_tell(cio) + return pos + def create_compress(fmt): """Wrapper for openjpeg library function opj_create_compress. @@ -576,7 +586,7 @@ def image_create(cmptparms, cspace): """Wrapper for openjpeg library function opj_image_create. """ OPENJPEG.opj_image_create.argtypes = [ctypes.c_int, - ctypes.POINTER(ImageCmptparmType), + ctypes.POINTER(ImageComptParmType), ctypes.c_int] OPENJPEG.opj_image_create.restype = ctypes.POINTER(ImageType) @@ -590,12 +600,14 @@ def image_destroy(image): OPENJPEG.opj_image_destroy(image) -def set_default_encoder_parameters(cparams_p): +def set_default_encoder_parameters(): """Wrapper for openjpeg library function opj_set_default_encoder_parameters. """ + cparams = CompressionParametersType() argtypes = [ctypes.POINTER(CompressionParametersType)] OPENJPEG.opj_set_default_encoder_parameters.argtypes = argtypes - OPENJPEG.opj_set_default_encoder_parameters(cparams_p) + OPENJPEG.opj_set_default_encoder_parameters(ctypes.byref(cparams)) + return cparams def set_default_decoder_parameters(dparams_p):