From ed1ca36040a14d37eb80a0e4d30a57561c933b85 Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 9 Sep 2013 21:09:33 -0400 Subject: [PATCH 1/5] Added preliminary 1.5.1 write routine. --- glymur/jp2k.py | 213 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 207 insertions(+), 6 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index bd90dfe..154db4f 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -280,9 +280,6 @@ class Jp2k(Jp2kBox): Corresponds to cparameters_t type in openjp2 headers. colorspace : int Either CLRSPC_SRGB or CLRSPC_GRAY - mct : bool, optional - Specifies usage of the multi component transform. If not - specified, defaults to True if the colorspace is RGB. """ if 'cratios' in kwargs and 'psnr' in kwargs: @@ -380,11 +377,215 @@ class Jp2k(Jp2kBox): glymur.LibraryNotFoundError If glymur is unable to load the openjp2 library. """ - if opj2.OPENJP2 is None: - raise LibraryNotFoundError("You must have the openjp2 library " - "installed before using this " + if opj2.OPENJP2 is not None: + img = self._write_openjp2(img_array, verbose=verbose, **kwargs) + elif opj.OPENJPEG is not None: + img = self._write_openjpeg(img_array, verbose=verbose, **kwargs) + else: + raise LibraryNotFoundError("You must have version 1.5 of OpenJPEG " + "or more recent before using this " "functionality.") + 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)) + + 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 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)) + + if cratio is not None and psnr is not None: + msg = 'Psnr and cratio parameters cannot be specified together.' + raise(RuntimeError(msg)) + + + cparams = opj.cparameters_t() + + 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) + + # set image offset and reference grid + image.contents.x0 = cparams.image_offset_x0 + image.contents.y0 = cparams.image_offset_y0 + image.contents.x1 = image.contents.x0 + (numcols - 1) * cparams.subsampling_dx + 1 + image.contents.y1 = image.contents.y0 + (numrows - 1) * cparams.subsampling_dy + 1 + + # Stage the image data to the openjpeg data structure. + for k in range(0,numlayers): + layer = np.ascontiguousarray(data[:,:,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) + + event_mgr = opj.event_mgr_t(None, None, None) + opj.set_event_mgr(cparams, ctypes.byref(event_mgr), None) + + opj.setup_encoder(cinfo, ctypes.byref(cparams), image) + + # open a byte stream for writing + # allocate memory for all tiles + cio = opj.cio_open(cinfo) + + opj.encode(cinfo, cio, image) + pos = opj.cio_tell(cio) + + ss = ctypes.string_at(cio.contents.buffer, pos) + f = open(self.jp2k_file,'wb') + f.write(ss) + f.close() + opj.cio_close(cio); + + opj.destroy_compress(cinfo); + opj.image_destroy(image); + + self.parse() + + + def _write_openjp2(self, img_array, verbose=False, **kwargs): + """Write image data to a JP2/JPX/J2k file. Intended usage of the + various parameters follows that of OpenJPEG's opj_compress utility. + + This method can only be used to create JPEG 2000 images that can fit + in memory. + + Parameters + ---------- + img_array : ndarray + Image data to be written to file. + cbsize : tuple, optional + Code block size (DY, DX). + colorspace : str, optional + Either 'rgb' or 'gray'. + cratios : iterable + Compression ratios for successive layers. + eph : bool, optional + If true, write SOP marker after each header packet. + grid_offset : tuple, optional + Offset (DY, DX) of the origin of the image in the reference grid. + mct : bool, optional + Specifies usage of the multi component transform. If not + specified, defaults to True if the colorspace is RGB. + modesw : int, optional + Mode switch. + 1 = BYPASS(LAZY) + 2 = RESET + 4 = RESTART(TERMALL) + 8 = VSC + 16 = ERTERM(SEGTERM) + 32 = SEGMARK(SEGSYM) + numres : int, optional + Number of resolutions. + prog : str, optional + Progression order, one of "LRCP" "RLCP", "RPCL", "PCRL", "CPRL". + psnr : iterable, optional + Different PSNR for successive layers. + psizes : list, optional + List of precinct sizes. Each precinct size tuple is defined in + (height x width). + sop : bool, optional + If true, write SOP marker before each packet. + subsam : tuple, optional + Subsampling factors (dy, dx). + tilesize : tuple, optional + Numeric tuple specifying tile size in terms of (numrows, numcols), + not (X, Y). + verbose : bool, optional + Print informational messages produced by the OpenJPEG library. + + Raises + ------ + glymur.LibraryNotFoundError + If glymur is unable to load the openjp2 library. + """ cparams, colorspace = self._process_write_inputs(img_array, **kwargs) if img_array.ndim == 2: From d070cc414464ad6ff1c0894e3a936f5afec49b13 Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 10 Sep 2013 20:11:32 -0400 Subject: [PATCH 2/5] Filled in more of openjpeg write definitions. #111 --- glymur/jp2k.py | 4 +- glymur/lib/openjpeg.py | 426 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 419 insertions(+), 11 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 154db4f..bb2fe43 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -409,7 +409,7 @@ class Jp2k(Jp2kBox): raise(RuntimeError(msg)) - cparams = opj.cparameters_t() + cparams = opj.CompressionParametersType() opj.set_default_encoder_parameters(ctypes.byref(cparams)) @@ -506,7 +506,7 @@ class Jp2k(Jp2kBox): #cinfo = opj.create_compress(opj.codec_format[self.file_format]) cinfo = opj.create_compress(self.file_format) - event_mgr = opj.event_mgr_t(None, None, None) + event_mgr = opj.EventMgrType(None, None, None) opj.set_event_mgr(cparams, ctypes.byref(event_mgr), None) opj.setup_encoder(cinfo, ctypes.byref(cparams), image) diff --git a/glymur/lib/openjpeg.py b/glymur/lib/openjpeg.py index 704cf9c..6e7b858 100644 --- a/glymur/lib/openjpeg.py +++ b/glymur/lib/openjpeg.py @@ -6,10 +6,16 @@ import ctypes import sys +import numpy as np + from .config import glymur_config _, OPENJPEG = glymur_config() -PATH_LEN = 4096 # maximum allowed size for filenames +# Maximum number of tile parts expected by JPWL: increase at your will +JPWL_MAX_NO_TILESPECS = 16 + +J2K_MAXRLVLS = 33 # Number of maximum resolution level authorized +PATH_LEN = 4096 # maximum allowed size for filenames def version(): @@ -52,14 +58,8 @@ class CommonStructType(ctypes.Structure): ("mj2_handle", ctypes.c_void_p)] -class DecompressionInfoType(ctypes.Structure): - """This is for decompression contexts. - - Corresponds to dinfo_t type in openjpeg headers. - """ - pass - - +STREAM_READ = 0x0001 # The stream was opened for reading. +STREAM_WRITE = 0x0002 # The stream was opened for writing. class CioType(ctypes.Structure): """Byte input-output stream (CIO) @@ -80,6 +80,266 @@ class CioType(ctypes.Structure): ("bp", ctypes.c_char_p)] +class CompressionInfoType(CommonStructType): + """Common fields between JPEG-2000 compression and decompression contexts. + This is for compression contexts. Corresponds to common_struct_t. + """ + pass + + +class PocType(ctypes.Structure): + """Progression order changes.""" + _fields_ = [("resno", ctypes.c_int), + # Resolution num start, Component num start, given by POC + ("compno0", ctypes.c_int), + + # Layer num end,Resolution num end, Component num end, given by POC + ("layno1", ctypes.c_int), + ("resno1", ctypes.c_int), + ("compno1", ctypes.c_int), + + # Layer num start,Precinct num start, Precinct num end + ("layno0", ctypes.c_int), + ("precno0", ctypes.c_int), + ("precno1", ctypes.c_int), + + # Progression order enum + # OPJ_PROG_ORDER prg1,prg; + ("prg1", ctypes.c_int), + ("prg", ctypes.c_int), + + # Progression order string + # char progorder[5]; + ("progorder", ctypes.c_char * 5), + + # Tile number + # int tile; + ("tile", ctypes.c_int), + + # /** Start and end values for Tile width and height*/ + # int tx0,tx1,ty0,ty1; + ("tx0", ctypes.c_int), + ("tx1", ctypes.c_int), + ("ty0", ctypes.c_int), + ("ty1", ctypes.c_int), + + # /** Start value, initialised in pi_initialise_encode*/ + # int layS, resS, compS, prcS; + ("layS", ctypes.c_int), + ("resS", ctypes.c_int), + ("compS", ctypes.c_int), + ("prcS", ctypes.c_int), + + # /** End value, initialised in pi_initialise_encode */ + # int layE, resE, compE, prcE; + ("layE", ctypes.c_int), + ("resE", ctypes.c_int), + ("compE", ctypes.c_int), + ("prcE", ctypes.c_int), + + # Start and end values of Tile width and height, initialised in + # pi_initialise_encode int txS,txE,tyS,tyE,dx,dy; + ("txS", 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): + """Compression parameters. + + Corresponds to cparameters_t type in openjp2 headers. + """ + _fields_ = [ + # size of tile: + # tile_size_on = false (not in argument) or + # = true (in argument) + ("tile_size_on", ctypes.c_int), + + # XTOsiz, YTOsiz + ("cp_tx0", ctypes.c_int), + ("cp_ty0", ctypes.c_int), + + # XTsiz, YTsiz + ("cp_tdx", ctypes.c_int), + ("cp_tdy", ctypes.c_int), + + # allocation by rate/distortion + ("cp_disto_alloc", ctypes.c_int), + + # allocation by fixed layer + ("cp_fixed_alloc", ctypes.c_int), + + # add fixed_quality + ("cp_fixed_quality", ctypes.c_int), + + # fixed layer + ("cp_matrice", ctypes.c_void_p), + + # comment for coding + ("cp_comment", ctypes.c_char_p), + + # csty : coding style + ("csty", ctypes.c_int), + + # progression order (default OPJ_LRCP) + ("prog_order", ctypes.c_int), + + # progression order changes + ("poc", PocType * 32), + + # number of progression order changes (POC), default to 0 + ("numpocs", ctypes.c_uint), + + # number of layers + ("tcp_numlayers", ctypes.c_int), + + # rates of layers + ("tcp_rates", ctypes.c_float * 100), + + # different psnr for successive layers + ("tcp_distoratio", ctypes.c_float * 100), + + # number of resolutions + ("numresolution", ctypes.c_int), + + # initial code block width, default to 64 + ("cblockw_init", ctypes.c_int), + + # initial code block height, default to 64 + ("cblockh_init", ctypes.c_int), + + # mode switch (cblk_style) + ("mode", ctypes.c_int), + + # 1 : use the irreversible DWT 9-7 + # 0 : use lossless compression (default) + ("irreversible", ctypes.c_int), + + # region of interest: affected component in [0..3], -1 means no ROI + ("roi_compno", ctypes.c_int), + + # region of interest: upshift value + ("roi_shift", ctypes.c_int), + + # number of precinct size specifications + ("res_spec", ctypes.c_int), + + # initial precinct width + ("prcw_init", ctypes.c_int * J2K_MAXRLVLS), + + # initial precinct height + ("prch_init", ctypes.c_int * J2K_MAXRLVLS), + + # input file name + ("infile", ctypes.c_char * PATH_LEN), + + # output file name + ("outfile", ctypes.c_char * PATH_LEN), + + # DEPRECATED. + ("index_on", ctypes.c_int), + + # DEPRECATED. + ("index", ctypes.c_char * PATH_LEN), + + # subimage encoding: origin image offset in x direction + # subimage encoding: origin image offset in y direction + ("image_offset_x0", ctypes.c_int), + ("image_offset_y0", ctypes.c_int), + + # subsampling value for dx + # subsampling value for dy + ("subsampling_dx", ctypes.c_int), + ("subsampling_dy", ctypes.c_int), + + # input file format 0: PGX, 1: PxM, 2: BMP 3:TIF + # output file format 0: J2K, 1: JP2, 2: JPT + ("decod_format", ctypes.c_int), + ("cod_format", ctypes.c_int), + + # JPWL encoding parameters + # enables writing of EPC in MH, thus activating JPWL + ("jpwl_epc_on", ctypes.c_int), + + # error protection method for MH (0,1,16,32,37-128) + ("jpwl_hprot_mh", ctypes.c_int), + + # tile number of header protection specification (>=0) + ("jpwl_hprot_tph_tileno", ctypes.c_int * JPWL_MAX_NO_TILESPECS), + + # error protection methods for TPHs (0,1,16,32,37-128) + ("jpwl_hprot_tph", ctypes.c_int * JPWL_MAX_NO_TILESPECS), + + # tile number of packet protection specification (>=0) + ("jpwl_pprot_tileno", ctypes.c_int * JPWL_MAX_NO_TILESPECS), + + # packet number of packet protection specification (>=0) + ("jpwl_pprot_packno", ctypes.c_int * JPWL_MAX_NO_TILESPECS), + + # error protection methods for packets (0,1,16,32,37-128) + ("jpwl_pprot", ctypes.c_int * JPWL_MAX_NO_TILESPECS), + + # enables writing of ESD, (0=no/1/2 bytes) + ("jpwl_sens_size", ctypes.c_int), + + # sensitivity addressing size (0=auto/2/4 bytes) + ("jpwl_sens_addr", ctypes.c_int), + + # sensitivity range (0-3) + ("jpwl_sens_range", ctypes.c_int), + + # sensitivity method for MH (-1=no,0-7) + ("jpwl_sens_mh", ctypes.c_int), + + # tile number of sensitivity specification (>=0) + ("jpwl_sens_tph_tileno", ctypes.c_int * JPWL_MAX_NO_TILESPECS), + + # sensitivity methods for TPHs (-1=no,0-7) + ("jpwl_sens_tph", ctypes.c_int * JPWL_MAX_NO_TILESPECS), + + # Digital Cinema compliance 0-not compliant, 1-compliant + ("cp_cinema", ctypes.c_int), + + # Maximum rate for each component. + # If == 0, component size limitation is not considered + ("max_comp_size", ctypes.c_int), + + # Profile name + ("cp_rsiz", ctypes.c_int), + + # Tile part generation + ("tp_on", ctypes.c_uint8), + + # Flag for Tile part generation + ("tp_flag", ctypes.c_uint8), + + # MCT (multiple component transform) + ("tcp_mct", ctypes.c_uint8), + + # Enable JPIP indexing + ("jpip_on", ctypes.c_int)] + + +class DecompressionInfoType(ctypes.Structure): + """This is for decompression contexts. + + Corresponds to dinfo_t type in openjpeg headers. + """ + pass + + class DecompressionParametersType(ctypes.Structure): """Decompression parameters. @@ -111,6 +371,37 @@ class DecompressionParametersType(ctypes.Structure): _fields_.append(("flags", ctypes.c_uint)) +class ImageCmptparmType(ctypes.Structure): + """Component parameters structure used by the opj_image_create function. + """ + _fields_ = [ + # XRsiz: horizontal separation of a sample of ith component with + # respect to the reference grid + ("dx", ctypes.c_int), + + # YRsiz: vertical separation of a sample of ith component with + # respect to the reference grid */ + ("dy", ctypes.c_int), + + # data width, height + ("w", ctypes.c_int), + ("h", ctypes.c_int), + + # x component offset compared to the whole image + # y component offset compared to the whole image + ("x0", ctypes.c_int), + ("y0", ctypes.c_int), + + # precision + ('prec', ctypes.c_int), + + # imgae depth in bits + ('bpp', ctypes.c_int), + + # signed (1) / unsigned (0) + ('sgnd', ctypes.c_int)] + + class ImageCompType(ctypes.Structure): """Defines a single image component. @@ -166,6 +457,17 @@ def cio_close(cio): OPENJPEG.opj_cio_close(cio) +def create_compress(fmt): + """Wrapper for openjpeg library function opj_create_compress. + + Creates a J2K/JPT/JP2 compression structure. + """ + OPENJPEG.opj_create_compress.argtypes = [ctypes.c_int] + OPENJPEG.opj_create_compress.restype = ctypes.POINTER(CompressionInfoType) + cinfo = OPENJPEG.opj_create_compress(fmt) + return cinfo + + def create_decompress(fmt): """Wraps openjpeg library function opj_create_decompress. """ @@ -186,6 +488,39 @@ def decode(dinfo, cio): return image +def destroy_compress(cinfo): + """Wrapper for openjpeg library function opj_destroy_compress. + + Release resources for a compressor handle. + """ + argtypes = [ctypes.POINTER(CompressionInfoType)] + OPENJPEG.opj_destroy_compress.argtypes = argtypes + OPENJPEG.opj_destroy_compress(cinfo) + + +def encode(cinfo, cio, image): + """Wrapper for openjpeg library function opj_encode. + + Encodes an image into a JPEG-2000 codestream. + + Parameters + ---------- + cinfo : compression handle + + cio : output buffer stream + + image : image to encode + """ + argtypes = [ctypes.POINTER(CompressionInfoType), + ctypes.POINTER(CioType), + ctypes.POINTER(ImageType)] + OPENJPEG.opj_encode.argtypes = argtypes + OPENJPEG.opj_encode.restype = ctypes.c_int + status = OPENJPEG.opj_encode(cinfo, cio, image) + if not status: + raise RuntimeError("opj_encode failed") + + def destroy_decompress(dinfo): """Wraps openjpeg library function opj_destroy_decompress.""" argtypes = [ctypes.POINTER(DecompressionInfoType)] @@ -193,12 +528,76 @@ def destroy_decompress(dinfo): OPENJPEG.opj_destroy_decompress(dinfo) +def image_cmptparm_t_from_np(np_image): + """Return appropriate image_cmptparm_t based on given numpy array. + """ + try: + num_comps = np_image.shape[2] + except IndexError: + num_comps = 1 + + cmpt_parm_array_t = ImageCmptparmType * num_comps + tarr = cmpt_parm_array_t() + + if np_image.dtype == np.uint8: + prec = 8 + bpp = 8 + sgnd = 0 + elif np_image.dtype == np.int8: + prec = 8 + bpp = 8 + sgnd = 1 + elif np_image.dtype == np.uint16: + prec = 16 + bpp = 16 + sgnd = 0 + elif np_image.dtype == np.int16: + prec = 16 + bpp = 16 + sgnd = 1 + else: + raise(TypeError("unhandled")) + + for j in range(0, num_comps): + tarr[j].dx = 1 + tarr[j].dy = 1 + tarr[j].w = np_image.shape[1] + tarr[j].h = np_image.shape[0] + tarr[j].x0 = 0 + tarr[j].y0 = 0 + tarr[j].prec = prec + tarr[j].bpp = bpp + tarr[j].sgnd = sgnd + + return(tarr) + + +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.c_int] + OPENJPEG.opj_image_create.restype = ctypes.POINTER(ImageType) + + image = OPENJPEG.opj_image_create(len(cmptparms), cmptparms, cspace) + return(image) + + def image_destroy(image): """Wraps openjpeg library function opj_image_destroy.""" OPENJPEG.opj_image_destroy.argtypes = [ctypes.POINTER(ImageType)] OPENJPEG.opj_image_destroy(image) +def set_default_encoder_parameters(cparams_p): + """Wrapper for openjpeg library function opj_set_default_encoder_parameters. + """ + argtypes = [ctypes.POINTER(CompressionParametersType)] + OPENJPEG.opj_set_default_encoder_parameters.argtypes = argtypes + OPENJPEG.opj_set_default_encoder_parameters(cparams_p) + + def set_default_decoder_parameters(dparams_p): """Wrapper for opj_set_default_decoder_parameters. """ @@ -219,6 +618,15 @@ def set_event_mgr(dinfo, event_mgr, context=None): event_mgr, context) +def setup_encoder(cinfo, cparameters, image): + """Wrapper for openjpeg library function opj_setup_decoder.""" + argtypes = [ctypes.POINTER(CompressionInfoType), + ctypes.POINTER(CompressionParametersType), + ctypes.POINTER(ImageType)] + OPENJPEG.opj_setup_encoder.argtypes = argtypes + OPENJPEG.opj_setup_encoder(cinfo, cparameters, image) + + def setup_decoder(dinfo, dparams): """Wrapper for openjpeg library function opj_setup_decoder.""" argtypes = [ctypes.POINTER(DecompressionInfoType), From 516d3ca55b8f2f29ed6551211c13d5c9e9380928 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 11 Sep 2013 06:43:36 -0400 Subject: [PATCH 3/5] Don't bother trying to write with versions below 1.5. #111 --- glymur/test/test_jp2k.py | 3 +++ glymur/test/test_opj_suite_write.py | 8 +++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index edefb7f..8e4a74c 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -679,6 +679,9 @@ class TestJp2k15(unittest.TestCase): j2k.read(layer=1) @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + @unittest.skipIf(re.match(r"""1\.[01234]\.\d""", + OPENJPEG_VERSION) is not None, + "Writing only supported with openjpeg version 1.5+.") def test_2d_rgb(self): """RGB must have at least 3 components.""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 479cb1f..e187d34 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -10,6 +10,7 @@ suite. # pylint: disable=F0401 import os +import re import sys import tempfile @@ -19,15 +20,16 @@ else: import unittest from .fixtures import read_image, NO_READ_BACKEND, NO_READ_BACKEND_MSG -from .fixtures import OPJ_DATA_ROOT, opj_data_file +from .fixtures import OPJ_DATA_ROOT, OPENJPEG_VERSION, opj_data_file from glymur import Jp2k import glymur @unittest.skipIf(os.name == "nt", "no write support on windows, period") -@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None, - "Missing openjp2 library.") +@unittest.skipIf(re.match(r"""1\.[01234]\.\d""", + OPENJPEG_VERSION) is not None, + "Writing only supported with openjpeg version 1.5+.") @unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG) @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") From 5626f3f460f990bdf6347b2f5dc3ba2ca192683d Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 11 Sep 2013 20:29:47 -0400 Subject: [PATCH 4/5] Updated for OpenSUSE 12.3. Simplified the Fedora explanations. #111 --- docs/source/detailed_installation.rst | 56 +++++++++------------------ 1 file changed, 19 insertions(+), 37 deletions(-) diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index dab4c3f..879d2a1 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -80,60 +80,42 @@ MacPorts supplies both OpenJPEG 1.5.0 and OpenJPEG 2.0.0. Linux ----- +For the most part, you only need python and numpy to run glymur. In order to +run as many tests as possible, however, the following Python packages may also +need to be installed. + + * setuptools + * matplotlib + * pillow + * contextlib2 (python 2.7 only) + * mock (python 2.7 only) + +OpenSUSE 12.3 +''''''''''''' +Ships with Python 3.3 and 2.7. You should use pip to install Pillow. Fedora 19 ''''''''' -Fedora 18 ships with Python 3.3 and all the necessary RPMs are available to -run the maximum number of tests. - - * python3 - * python3-numpy - * python3-setuptools - * python3-matplotlib (for running tests) - * python3-matplotlib-tk (or whichever matplotlib backend you prefer) - * python3-pillow (for running tests) +Ships with Python 3.3 and 2.7. All packages available as RPMs. Fedora 18 ''''''''' -Fedora 18 ships with Python 3.3 and the following RPMs are available to -meet the minimal set of requirements for running glymur. - - * python3 - * python3-numpy - * python3-setuptools - -For running the maximal number of tests, you also need - - * python3-matplotlib - * python3-matplotlib-tk (or whichever matplotlib backend you prefer) - -Pillow is also needed in order to run the maximum number of tests, so -go ahead and install Pillow via pip since Pillow is not available -in Fedora 18 default repositories:: +Fedora 18 ships with Python 3.3 and 2.7. Most packages are available as +standard RPMS, but you should use pip to install Pillow as it is not available +in the Fedora 18 default repositories:: $ yum install python3-devel # pip needs this in order to compile Pillow $ yum install python3-pip $ pip-python3 install Pillow --user - $ export PYTHONPATH=$HOME/.local/lib/python3.3/site-packages:$PYTHONPATH Fedora 17 ''''''''' -Fedora 17 ships with Python 2.7 and OpenJPEG 1.4. You should have the -following RPMs installed. - - * python - * python-mock - * python-pip - * python-setuptools - * numpy - * matplotlib (optional) - -In addition, you must install contextlib2 and Pillow via pip. :: +Fedora 17 ships with Python 2.7 and OpenJPEG 1.4. You must install contextlib2 +and Pillow via pip. :: $ yum install python-devel # pip needs this in order to compile Pillow $ pip-python install Pillow --user $ pip-python install contextlib2 --user - $ export PYTHONPATH=$HOME/.local/lib/python2.7/site-packages:$PYTHONPATH Windows ------- From 5b401e1e2da5cb4aff6fdd39ef1af381525da152 Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 11 Sep 2013 20:30:54 -0400 Subject: [PATCH 5/5] Writing works with 1.5, two failures with 3.3. #111 --- glymur/jp2k.py | 133 +++++++++-------------------------------- glymur/lib/openjpeg.py | 58 +++++++++++------- 2 files changed, 64 insertions(+), 127 deletions(-) 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):