Merge branch 'release-0.3.0'

This commit is contained in:
jevans 2013-07-31 18:52:32 -04:00
commit d7635bdfef
22 changed files with 458 additions and 252 deletions

View file

@ -1,3 +1,5 @@
Jul 31, 2013 - v0.3.0 Added support for official 2.0.0.
Jul 27, 2013 - v0.2.8 Fixed inconsistency regarding configuration Jul 27, 2013 - v0.2.8 Fixed inconsistency regarding configuration
file directory on windows (issue91). file directory on windows (issue91).

View file

@ -78,7 +78,7 @@ copyright = u'2013, John Evans'
# The short X.Y version. # The short X.Y version.
version = '0.2' version = '0.2'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '0.2.8' release = '0.3.0'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.

View file

@ -9,13 +9,14 @@ Glymur Configuration
The default glymur installation process relies upon OpenJPEG version The default glymur installation process relies upon OpenJPEG version
1.X being properly installed on your system. This will, however, only 1.X being properly installed on your system. This will, however, only
give you you basic read capabilities, so if you wish to take advantage give you you basic read capabilities, so if you wish to take advantage
of more of glymur's features, you should compile OpenJPEG as a shared of more of glymur's features, you should install version 2.0 or
library (named *openjp2* instead of *openjpeg*) from the developmental compile OpenJPEG as a shared library (named *openjp2* instead of
source that you can retrieve via subversion. As of this time of writing, *openjpeg*) from the developmental source that you can retrieve via
svn revision 2345 works. You should also download the test data for subversion. As of this time of writing, svn revision 2345 works.
the purpose of configuring and running OpenJPEG's test suite, check You should also download the test data for the purpose of configuring
their instructions for all this. You should set the **OPJ_DATA_ROOT** and running OpenJPEG's test suite, check their instructions for all
environment variable for the purpose of running Glymur's test suite. :: this. You should set the **OPJ_DATA_ROOT** environment variable
for the purpose of running Glymur's test suite. ::
$ svn co http://openjpeg.googlecode.com/svn/data $ svn co http://openjpeg.googlecode.com/svn/data
$ export OPJ_DATA_ROOT=`pwd`/data $ export OPJ_DATA_ROOT=`pwd`/data
@ -40,8 +41,8 @@ but if you have **$XDG_CONFIG_HOME** defined, the path will be ::
$XDG_CONFIG_HOME/glymur/glymurrc $XDG_CONFIG_HOME/glymur/glymurrc
On windows, the path to the configuration file can be determined by starting up Python On windows, the path to the configuration file can be determined
and typing :: by starting up Python and typing ::
import os import os
os.path.join(os.path.expanduser('~'), 'glymur', 'glymurrc') os.path.join(os.path.expanduser('~'), 'glymur', 'glymurrc')
@ -75,9 +76,7 @@ MacPorts. You should install the following set of ports:
* py33-matplotlib (optional, for running certain tests) * py33-matplotlib (optional, for running certain tests)
* py33-Pillow (optional, for running certain tests) * py33-Pillow (optional, for running certain tests)
MacPorts supplies both OpenJPEG 1.5.0 and OpenJPEG 2.0.0. As previously MacPorts supplies both OpenJPEG 1.5.0 and OpenJPEG 2.0.0.
mentioned, the 2.0.0 official release is not supported (although the 2.0+
development version via SVN *is* supported).
Linux Linux
----- -----
@ -138,12 +137,14 @@ In addition, you must install contextlib2 and Pillow via pip. ::
Windows Windows
------- -------
I would recommend using WinPython, but Python(xy) also seems to work. WinPython 3.3 32-bit WinPython 2.7.5 seems to work with OpenJPEG 1.X, 2.0, and the
should work with no additional installations required, but 2.7 versions still require development version, but still requires contextlib2 and mock to be
contextlib2 and mock to be installed via pip. installed via pip. WinPython 3.3.2, however, seems to have trouble
with OpenJPEG 2.0, so I would suggest using the development version
there (I'm unwilling to spend ANY more time trying to figure out what
the problem is there).
Glymur has been tested far less extensively on Windows than on the other 64-bit windows is completely untested.
platforms.
''''''' '''''''

View file

@ -122,10 +122,10 @@ and add it after the JP2 header box, but before the codestream box ::
Create an image with an alpha layer? Create an image with an alpha layer?
==================================== ====================================
OpenJPEG can create JP2 files with more than 3 components, but by default, any OpenJPEG can create JP2 files with more than 3 components (requires
extra components are not described as such. In order to do so, the development version), but by default, any extra components are
we need to rewrap such an image in a set of boxes that includes a channel not described as such. In order to do so, we need to rewrap such
definition box. an image in a set of boxes that includes a channel definition box.
This example is based on SciPy example code found at This example is based on SciPy example code found at
http://scipy-lectures.github.io/advanced/image_processing/#basic-manipulations . http://scipy-lectures.github.io/advanced/image_processing/#basic-manipulations .

View file

@ -14,22 +14,21 @@ some very limited support for reading JPX metadata. For instance,
**asoc** and **labl** boxes are recognized, so GMLJP2 metadata can **asoc** and **labl** boxes are recognized, so GMLJP2 metadata can
be retrieved from such JPX files. be retrieved from such JPX files.
Glymur works on Python 2.6, 2.7, and 3.3. Python 3.3 is strongly recommended. Glymur works on Python 2.6, 2.7, and 3.3.
OpenJPEG Installation OpenJPEG Installation
===================== =====================
OpenJPEG should be version 1.4, 1.5, or the trunk/development Glymur will read JPEG 2000 images with versions 1.3, 1.4, 1.5, 2.0,
version of OpenJPEG. The official 2.0.0 release or versions earlier than 1.3.0 and the trunk/development version of OpenJPEG. Writing images is
are not supported. Furthermore, the 1.X versions of OpenJPEG are only supported with the 2.0 series, however, and the trunk/development
currently only utilized for read-only purposes. In order to write JPEG 2000 version is strongly recommended. For more information about OpenJPEG,
images, you must compile the the trunk/development version. For more information please consult http://www.openjpeg.org.
about OpenJPEG, please consult http://www.openjpeg.org.
If you use MacPorts on the mac or if you have a sufficiently recent If you use MacPorts on the mac or if you have a sufficiently recent
version of Linux, your package manager should already provide you version of Linux, your package manager should already provide you
with a version of OpenJPEG 1.X with which glymur can already use with a version of OpenJPEG 1.X with which glymur can already use
for read-only purposes. If your platform is windows, I suggest for read-only purposes. If your platform is windows, I suggest
using the 1.5.1 windows installer provided to you by the OpenJPEG using the windows installers provided to you by the OpenJPEG
folks at https://code.google.com/p/openjpeg/downloads/list . folks at https://code.google.com/p/openjpeg/downloads/list .
Glymur Installation Glymur Installation
@ -57,5 +56,5 @@ You can run the tests from within python as follows::
>>> glymur.runtests() >>> glymur.runtests()
Many tests are currently skipped; in fact most of them are skipped if you Many tests are currently skipped; in fact most of them are skipped if you
are relying on OpenJPEG 1.4 or 1.5. The important thing, though, is whether or are relying on OpenJPEG 1.X. The important thing, though, is whether or
not any tests fail. not any tests fail.

View file

@ -1,11 +1,17 @@
------------
Known Issues
------------
* WinPython 3.3.2 and OpenJPEG 2.0 don't seem to want to play well together. If you do not need write support, just use OpenJPEG 1.5 instead. If you do need write support, try the development version of OpenJPEG.
------- -------
Roadmap Roadmap
------- -------
Here's an incomplete list of what I'd like to focus on in the near future. Here's an incomplete list of what I'd like to focus on in the future.
* continue to monitor upstream changes in the openjp2 library * continue to monitor upstream changes in the openjp2 library
* investigate using CFFI or cython instead of ctypes to wrap openjp2 * investigate using CFFI or cython instead of ctypes to wrap openjp2
* eventually expose the openjp2 API * investigate adding write support for UUID/XMP boxes (potentially a big project)
* investigate JPIP * investigate JPIP (likely to be an even bigger project)

View file

@ -636,79 +636,6 @@ class CompositingLayerHeaderBox(Jp2kBox):
return box return box
class JP2HeaderBox(Jp2kBox):
"""Container for JP2 header box information.
Attributes
----------
box_id : str
4-character identifier for the box.
length : int
length of the box in bytes.
offset : int
offset of the box from the start of the file.
longname : str
more verbose description of the box.
box : list
List of boxes contained in this superbox.
"""
def __init__(self, length=0, offset=-1):
Jp2kBox.__init__(self, box_id='jp2h', longname='JP2 Header')
self.length = length
self.offset = offset
self.box = []
def __str__(self):
msg = Jp2kBox.__str__(self)
for box in self.box:
boxstr = str(box)
# Add indentation.
strs = [('\n ' + x) for x in boxstr.split('\n')]
msg += ''.join(strs)
return msg
def write(self, fptr):
"""Write a JP2 Header box to file.
"""
# Write the contained boxes, then come back and write the length.
orig_pos = fptr.tell()
fptr.write(struct.pack('>I', 0))
fptr.write('jp2h'.encode())
for box in self.box:
box.write(fptr)
end_pos = fptr.tell()
fptr.seek(orig_pos)
fptr.write(struct.pack('>I', end_pos - orig_pos))
fptr.seek(end_pos)
@staticmethod
def parse(fptr, offset, length):
"""Parse JPEG 2000 header box.
Parameters
----------
fptr : file
Open file object.
offset : int
Start position of box in bytes.
length : int
Length of the box in bytes.
Returns
-------
JP2HeaderBox instance
"""
box = JP2HeaderBox(length=length, offset=offset)
# The JP2 header box is a superbox, so go ahead and parse its child
# boxes.
box.box = box.parse_superbox(fptr)
return box
class ComponentMappingBox(Jp2kBox): class ComponentMappingBox(Jp2kBox):
"""Container for channel identification information. """Container for channel identification information.

View file

@ -32,6 +32,15 @@ from .jp2box import ImageHeaderBox
from .jp2box import ColourSpecificationBox from .jp2box import ColourSpecificationBox
from .lib import _openjpeg as _opj from .lib import _openjpeg as _opj
from .lib import _openjp2 as _opj2 from .lib import _openjp2 as _opj2
from .lib import c as _libc
# Need to known if openjp2 library is the officially release v2.0.0 or not.
_OPENJP2_IS_OFFICIAL_V2 = False
if _opj2.OPENJP2 is not None:
if _opj2.version() == '2.0.0':
if not hasattr(_opj2.OPENJP2,
'opj_stream_create_default_file_stream_v3'):
_OPENJP2_IS_OFFICIAL_V2 = True
_COLORSPACE_MAP = {'rgb': _opj2.CLRSPC_SRGB, _COLORSPACE_MAP = {'rgb': _opj2.CLRSPC_SRGB,
'gray': _opj2.CLRSPC_GRAY, 'gray': _opj2.CLRSPC_GRAY,
@ -178,6 +187,94 @@ class Jp2k(Jp2kBox):
msg += "profile if the file type box brand is 'jp2 '." msg += "profile if the file type box brand is 'jp2 '."
warnings.warn(msg) warnings.warn(msg)
def _validate_write_parameters(self, img_array, code_block_size,
precinct_sizes, cratios, psnr, colorspace,
codec_fmt):
"""Check that the input parameters to the write function are valid.
Parameters
----------
img_array : ndarray
Image data to be written to file.
code_block_size : tuple
Code block size (DY, DX).
precinct_sizes : list
List of precinct sizes. Each precinct size tuple is defined in
(height x width).
cratios : iterable
Compression ratios for successive layers.
psnr : iterable
Different PSNR for successive layers.
mct : bool
Specifies usage of the multi component transform. If not
specified, defaults to True if the colorspace is RGB.
colorspace : str, optional
Either 'rgb' or 'gray'.
codec_fmt : int
Are we writing a JP2 file or a J2K file?
"""
# Validate code block size and precinct sizes.
if code_block_size is not None:
width = code_block_size[1]
height = code_block_size[0]
if height * width > 4096 or height < 4 or width < 4:
msg = "Code block area cannot exceed 4096. "
msg += "Code block height and width must be larger than 4."
raise IOError(msg)
if ((math.log(height, 2) != math.floor(math.log(height, 2)) or
math.log(width, 2) != math.floor(math.log(width, 2)))):
msg = "Bad code block size ({0}, {1}), "
msg += "must be powers of 2."
raise IOError(msg.format(height, width))
if precinct_sizes is not None:
for j, (prch, prcw) in enumerate(precinct_sizes):
if j == 0 and code_block_size is not None:
cblkh, cblkw = code_block_size
if cblkh * 2 > prch or cblkw * 2 > prcw:
msg = "Highest Resolution precinct size must be at "
msg += "least twice that of the code block dimensions."
raise IOError(msg)
if ((math.log(prch, 2) != math.floor(math.log(prch, 2)) or
math.log(prcw, 2) != math.floor(math.log(prcw, 2)))):
msg = "Bad precinct sizes ({0}, {1}), "
msg += "must be powers of 2."
raise IOError(msg.format(prch, prcw))
if cratios is not None and psnr is not None:
msg = "Cannot specify cratios and psnr together."
raise IOError(msg)
# What would the point of 1D images be?
if img_array.ndim == 1 or img_array.ndim > 3:
msg = "{0}D imagery is not allowed.".format(img_array.ndim)
raise IOError(msg)
if _OPENJP2_IS_OFFICIAL_V2:
if (((img_array.ndim != 2) and
(img_array.shape[2] != 1 and img_array.shape[2] != 3))):
msg = "Writing images is restricted to single-channel "
msg += "greyscale images or three-channel RGB images when "
msg += "the OpenJPEG library version is the official 2.0.0 "
msg += "release."
raise IOError(msg)
if colorspace is not None:
if codec_fmt == _opj2.CODEC_J2K:
msg = 'Do not specify a colorspace when writing a raw '
msg += 'codestream.'
raise IOError(msg)
if colorspace.lower() not in ('rgb', 'grey', 'gray'):
msg = 'Invalid colorspace "{0}"'.format(colorspace)
raise IOError(msg)
elif colorspace.lower() == 'rgb' and img_array.shape[2] < 3:
msg = 'RGB colorspace requires at least 3 components.'
raise IOError(msg)
if img_array.dtype != np.uint8 and img_array.dtype != np.uint16:
msg = "Only uint8 and uint16 images are currently supported."
raise RuntimeError(msg)
# pylint: disable-msg=W0221 # pylint: disable-msg=W0221
def write(self, img_array, cratios=None, eph=False, psnr=None, numres=None, def write(self, img_array, cratios=None, eph=False, psnr=None, numres=None,
cbsize=None, psizes=None, grid_offset=None, sop=False, cbsize=None, psizes=None, grid_offset=None, sop=False,
@ -202,7 +299,7 @@ class Jp2k(Jp2kBox):
Code block size (DY, DX). Code block size (DY, DX).
colorspace : str, optional colorspace : str, optional
Either 'rgb' or 'gray'. Either 'rgb' or 'gray'.
cratios : sequence, optional cratios : iterable
Compression ratios for successive layers. Compression ratios for successive layers.
eph : bool, optional eph : bool, optional
If true, write SOP marker after each header packet. If true, write SOP marker after each header packet.
@ -223,7 +320,7 @@ class Jp2k(Jp2kBox):
Number of resolutions. Number of resolutions.
prog : str, optional prog : str, optional
Progression order, one of "LRCP" "RLCP", "RPCL", "PCRL", "CPRL". Progression order, one of "LRCP" "RLCP", "RPCL", "PCRL", "CPRL".
psnr : list, optional psnr : iterable, optional
Different PSNR for successive layers. Different PSNR for successive layers.
psizes : list, optional psizes : list, optional
List of precinct sizes. Each precinct size tuple is defined in List of precinct sizes. Each precinct size tuple is defined in
@ -255,9 +352,17 @@ class Jp2k(Jp2kBox):
If glymur is unable to load the openjp2 library. If glymur is unable to load the openjp2 library.
""" """
if _opj2.OPENJP2 is None: if _opj2.OPENJP2 is None:
raise LibraryNotFoundError("You must have the development version " raise LibraryNotFoundError("You must have the openjp2 library "
"of OpenJP2 installed before using " "installed before using this "
"this functionality.") "functionality.")
if self.filename[-4:].lower() == '.jp2':
codec_fmt = _opj2.CODEC_JP2
else:
codec_fmt = _opj2.CODEC_J2K
self._validate_write_parameters(img_array, cbsize, psizes, cratios,
psnr, colorspace, codec_fmt)
cparams = _opj2.set_default_encoder_parameters() cparams = _opj2.set_default_encoder_parameters()
@ -266,11 +371,6 @@ class Jp2k(Jp2kBox):
outfile += b'0' * num_pad_bytes outfile += b'0' * num_pad_bytes
cparams.outfile = outfile cparams.outfile = outfile
if self.filename[-4:].lower() == '.jp2':
codec_fmt = _opj2.CODEC_JP2
else:
codec_fmt = _opj2.CODEC_J2K
cparams.cod_format = codec_fmt cparams.cod_format = codec_fmt
# Set defaults to lossless to begin. # Set defaults to lossless to begin.
@ -281,15 +381,6 @@ class Jp2k(Jp2kBox):
if cbsize is not None: if cbsize is not None:
width = cbsize[1] width = cbsize[1]
height = cbsize[0] height = cbsize[0]
if height * width > 4096 or height < 4 or width < 4:
msg = "Code block area cannot exceed 4096. "
msg += "Code block height and width must be larger than 4."
raise RuntimeError(msg)
if ((math.log(height, 2) != math.floor(math.log(height, 2)) or
math.log(width, 2) != math.floor(math.log(width, 2)))):
msg = "Bad code block size ({0}, {1}), "
msg += "must be powers of 2."
raise IOError(msg.format(height, width))
cparams.cblockw_init = width cparams.cblockw_init = width
cparams.cblockh_init = height cparams.cblockh_init = height
@ -327,18 +418,6 @@ class Jp2k(Jp2kBox):
if psizes is not None: if psizes is not None:
for j, (prch, prcw) in enumerate(psizes): for j, (prch, prcw) in enumerate(psizes):
if j == 0 and cbsize is not None:
cblkh, cblkw = cbsize
if cblkh * 2 > prch or cblkw * 2 > prcw:
msg = "Highest Resolution precinct size must be at "
msg += "least twice that of the code block dimensions."
raise IOError(msg)
if ((math.log(prch, 2) != math.floor(math.log(prch, 2)) or
math.log(prcw, 2) != math.floor(math.log(prcw, 2)))):
msg = "Bad precinct sizes ({0}, {1}), "
msg += "must be powers of 2."
raise IOError(msg.format(prch, prcw))
cparams.prcw_init[j] = prcw cparams.prcw_init[j] = prcw
cparams.prch_init[j] = prch cparams.prch_init[j] = prch
cparams.csty |= 0x01 cparams.csty |= 0x01
@ -356,47 +435,38 @@ class Jp2k(Jp2kBox):
cparams.cp_tdy = tilesize[0] cparams.cp_tdy = tilesize[0]
cparams.tile_size_on = _opj2.TRUE cparams.tile_size_on = _opj2.TRUE
if cratios is not None and psnr is not None:
msg = "Cannot specify cratios and psnr together."
raise RuntimeError(msg)
if img_array.ndim == 2: if img_array.ndim == 2:
# Force it to be 3D. Just makes things easier later on.
numrows, numcols = img_array.shape numrows, numcols = img_array.shape
img_array = img_array.reshape(numrows, numcols, 1) img_array = img_array.reshape(numrows, numcols, 1)
elif img_array.ndim == 3:
pass
else:
msg = "{0}D imagery is not allowed.".format(img_array.ndim)
raise IOError(msg)
numrows, numcols, num_comps = img_array.shape numrows, numcols, num_comps = img_array.shape
if colorspace is None: if colorspace is None:
# Must infer the colorspace from the image dimensions.
if img_array.shape[2] == 1 or img_array.shape[2] == 2: if img_array.shape[2] == 1 or img_array.shape[2] == 2:
# A single channel image or an image with two channels is going
# to be greyscale.
colorspace = _opj2.CLRSPC_GRAY colorspace = _opj2.CLRSPC_GRAY
else: else:
# No YCC unless specifically told to do so. # Anything else must be RGB, right?
colorspace = _opj2.CLRSPC_SRGB colorspace = _opj2.CLRSPC_SRGB
else: else:
if codec_fmt == _opj2.CODEC_J2K: # Turn the colorspace from a string to the enumerated value that
raise IOError('Do not specify a colorspace with J2K.') # the library expects.
colorspace = colorspace.lower() colorspace = _COLORSPACE_MAP[colorspace.lower()]
if colorspace not in ('rgb', 'grey', 'gray'):
msg = 'Invalid colorspace "{0}"'.format(colorspace)
raise IOError(msg)
elif colorspace == 'rgb' and img_array.shape[2] < 3:
msg = 'RGB colorspace requires at least 3 components.'
raise IOError(msg)
else:
colorspace = _COLORSPACE_MAP[colorspace]
if mct is None: if mct is None:
# If the multi component transform was not specified, we infer
# that it should be used if the color space is RGB.
if colorspace == _opj2.CLRSPC_SRGB: if colorspace == _opj2.CLRSPC_SRGB:
cparams.tcp_mct = 1 cparams.tcp_mct = 1
else: else:
cparams.tcp_mct = 0 cparams.tcp_mct = 0
else: else:
if mct and colorspace == _opj2.CLRSPC_GRAY: if mct and colorspace == _opj2.CLRSPC_GRAY:
# Cannot check for this in the validate routine, as we need
# to know what the target colorspace has been determined to be.
msg = "Cannot specify usage of the multi component transform " msg = "Cannot specify usage of the multi component transform "
msg += "if the colorspace is gray." msg += "if the colorspace is gray."
raise IOError(msg) raise IOError(msg)
@ -404,10 +474,9 @@ class Jp2k(Jp2kBox):
if img_array.dtype == np.uint8: if img_array.dtype == np.uint8:
comp_prec = 8 comp_prec = 8
elif img_array.dtype == np.uint16:
comp_prec = 16
else: else:
raise RuntimeError("unhandled datatype") # We already know it cannot be anything else than uint16.
comp_prec = 16
comptparms = (_opj2.ImageComptParmType * num_comps)() comptparms = (_opj2.ImageComptParmType * num_comps)()
for j in range(num_comps): for j in range(num_comps):
@ -448,14 +517,29 @@ class Jp2k(Jp2kBox):
_opj2.set_warning_handler(codec, _WARNING_CALLBACK) _opj2.set_warning_handler(codec, _WARNING_CALLBACK)
_opj2.set_error_handler(codec, _ERROR_CALLBACK) _opj2.set_error_handler(codec, _ERROR_CALLBACK)
_opj2.setup_encoder(codec, cparams, image) _opj2.setup_encoder(codec, cparams, image)
strm = _opj2.stream_create_default_file_stream_v3(self.filename, False)
if _OPENJP2_IS_OFFICIAL_V2:
fptr = _libc.fopen(self.filename, 'wb')
strm = _opj2.stream_create_default_file_stream(fptr, False)
else:
strm = _opj2.stream_create_default_file_stream_v3(self.filename,
False)
# Start to clean up after ourselves.
_opj2.start_compress(codec, image, strm) _opj2.start_compress(codec, image, strm)
_opj2.encode(codec, strm) _opj2.encode(codec, strm)
_opj2.end_compress(codec, strm) _opj2.end_compress(codec, strm)
_opj2.stream_destroy_v3(strm)
if _OPENJP2_IS_OFFICIAL_V2:
_opj2.stream_destroy(strm)
_libc.fclose(fptr)
else:
_opj2.stream_destroy_v3(strm)
_opj2.destroy_codec(codec) _opj2.destroy_codec(codec)
_opj2.image_destroy(image) _opj2.image_destroy(image)
# Refresh the metadata.
self.parse() self.parse()
def wrap(self, filename, boxes=None): def wrap(self, filename, boxes=None):
@ -882,9 +966,15 @@ class Jp2k(Jp2kBox):
dparam.nb_tile_to_decode = 1 dparam.nb_tile_to_decode = 1
with ExitStack() as stack: with ExitStack() as stack:
stream = _opj2.stream_create_default_file_stream_v3(self.filename, if hasattr(_opj2.OPENJP2, 'opj_stream_create_default_file_stream_v3'):
True) stream = _opj2.stream_create_default_file_stream_v3(self.filename,
stack.callback(_opj2.stream_destroy_v3, stream) True)
stack.callback(_opj2.stream_destroy_v3, stream)
else:
fptr = _libc.fopen(self.filename, 'rb')
stack.callback(_libc.fclose, fptr)
stream = _opj2.stream_create_default_file_stream(fptr, True)
stack.callback(_opj2.stream_destroy, stream)
codec = _opj2.create_decompress(self._codec_format) codec = _opj2.create_decompress(self._codec_format)
stack.callback(_opj2.destroy_codec, codec) stack.callback(_opj2.destroy_codec, codec)

View file

@ -1,3 +1,4 @@
"""This package organizes individual libraries employed by glymur.""" """This package organizes individual libraries employed by glymur."""
from . import openjp2 as _openjp2 from . import openjp2 as _openjp2
from . import openjpeg as _openjpeg from . import openjpeg as _openjpeg
from . import c

46
glymur/lib/c.py Normal file
View file

@ -0,0 +1,46 @@
"""
Wraps fopen and fclose functions in libc.
"""
import ctypes
import ctypes.util
LIBC_PATH = ctypes.util.find_library('c')
C_LIB = ctypes.CDLL(LIBC_PATH)
def fopen(filename, mode):
"""Opens the file with the specified mode.
Parameters
----------
filename : str
Path to filename.
mode : str
Specifies how the file is to be opened.
Returns
-------
fptr : ctypes.c_void_p
File pointer.
"""
C_LIB.fopen.restype = ctypes.c_void_p
C_LIB.fopen.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
fptr = C_LIB.fopen(ctypes.c_char_p(filename.encode()),
ctypes.c_char_p(mode.encode()))
return fptr
def fclose(fptr):
"""Closes a file stream.
Parameters
----------
fptr : ctypes.c_void_p
File pointer.
"""
C_LIB.fclose.argtypes = [ctypes.c_void_p]
C_LIB.fclose.restype = ctypes.c_int32
status = C_LIB.fclose(fptr)
if status != 0:
raise IOError("Unable to close file.")

View file

@ -75,8 +75,6 @@ def load_openjpeg(libopenjpeg_path):
def read_config_file(): def read_config_file():
""" """
We expect to not find openjp2 on the system path since the only version
that we currently care about is still in the svn trunk at openjpeg.org.
We must use a configuration file that the user must write. We must use a configuration file that the user must write.
""" """
lib = {'openjp2': None, 'openjpeg': None} lib = {'openjp2': None, 'openjpeg': None}
@ -104,6 +102,17 @@ def load_openjp2(libopenjp2_path):
# No help from the config file, try to find it ourselves. # No help from the config file, try to find it ourselves.
libopenjp2_path = find_library('openjp2') libopenjp2_path = find_library('openjp2')
if libopenjp2_path is None:
if platform.system() == 'Darwin':
path = '/opt/local/lib/libopenjp2.dylib'
if os.path.exists(path):
libopenjp2_path = path
elif os.name == 'nt':
path = os.path.join('C:\\', 'Program files', 'OpenJPEG 2.0',
'bin', 'openjp2.dll')
if os.path.exists(path):
libopenjp2_path = path
if libopenjp2_path is None: if libopenjp2_path is None:
return None return None
@ -145,7 +154,7 @@ def get_configdir():
if 'HOME' in os.environ and os.name != 'nt': if 'HOME' in os.environ and os.name != 'nt':
# HOME is set by WinPython to something unusual, so we don't # HOME is set by WinPython to something unusual, so we don't
# necessarily want that. # necessarily want that.
return os.path.join(os.environ['HOME'], '.config', 'glymur') return os.path.join(os.environ['HOME'], '.config', 'glymur')
# Last stand. Should handle windows... others? # Last stand. Should handle windows... others?

View file

@ -5,6 +5,7 @@ Wraps individual functions in openjp2 library.
# pylint: disable=C0302,R0903 # pylint: disable=C0302,R0903
import ctypes import ctypes
import sys
from .config import glymur_config from .config import glymur_config
OPENJP2, OPENJPEG = glymur_config() OPENJP2, OPENJPEG = glymur_config()
@ -639,9 +640,16 @@ if OPENJP2 is not None:
ctypes.POINTER(ImageType)] ctypes.POINTER(ImageType)]
OPENJP2.opj_setup_encoder.argtypes = ARGTYPES OPENJP2.opj_setup_encoder.argtypes = ARGTYPES
ARGTYPES = [ctypes.c_char_p, ctypes.c_int32] if hasattr(OPENJP2, 'opj_stream_create_default_file_stream_v3'):
OPENJP2.opj_stream_create_default_file_stream_v3.argtypes = ARGTYPES ARGTYPES = [ctypes.c_char_p, ctypes.c_int32]
OPENJP2.opj_stream_create_default_file_stream_v3.restype = STREAM_TYPE_P OPENJP2.opj_stream_create_default_file_stream_v3.argtypes = ARGTYPES
OPENJP2.opj_stream_create_default_file_stream_v3.restype = STREAM_TYPE_P
OPENJP2.opj_stream_destroy_v3.argtypes = [STREAM_TYPE_P]
else:
ARGTYPES = [ctypes.c_void_p, ctypes.c_int32]
OPENJP2.opj_stream_create_default_file_stream.argtypes = ARGTYPES
OPENJP2.opj_stream_create_default_file_stream.restype = STREAM_TYPE_P
OPENJP2.opj_stream_destroy.argtypes = [STREAM_TYPE_P]
ARGTYPES = [CODEC_TYPE, ctypes.POINTER(ImageType), STREAM_TYPE_P] ARGTYPES = [CODEC_TYPE, ctypes.POINTER(ImageType), STREAM_TYPE_P]
OPENJP2.opj_start_compress.argtypes = ARGTYPES OPENJP2.opj_start_compress.argtypes = ARGTYPES
@ -649,7 +657,6 @@ if OPENJP2 is not None:
OPENJP2.opj_end_compress.argtypes = [CODEC_TYPE, STREAM_TYPE_P] OPENJP2.opj_end_compress.argtypes = [CODEC_TYPE, STREAM_TYPE_P]
OPENJP2.opj_end_decompress.argtypes = [CODEC_TYPE, STREAM_TYPE_P] OPENJP2.opj_end_decompress.argtypes = [CODEC_TYPE, STREAM_TYPE_P]
OPENJP2.opj_stream_destroy_v3.argtypes = [STREAM_TYPE_P]
OPENJP2.opj_destroy_codec.argtypes = [CODEC_TYPE] OPENJP2.opj_destroy_codec.argtypes = [CODEC_TYPE]
ARGTYPES = [CODEC_TYPE, ARGTYPES = [CODEC_TYPE,
@ -1267,16 +1274,17 @@ def start_compress(codec, image, stream):
OPENJP2.opj_start_compress(codec, image, stream) OPENJP2.opj_start_compress(codec, image, stream)
def stream_create_default_file_stream_v3(fname, a_read_stream): def stream_create_default_file_stream(fptr, isa_read_stream):
"""Wraps openjp2 library function opj_stream_create_default_vile_stream_v3. """Wraps openjp2 library function opj_stream_create_default_vile_stream.
Sets the stream to be a file stream. Sets the stream to be a file stream. This is valid only for version 2.0.0
of OpenJPEG.
Parameters Parameters
---------- ----------
fname : str fptr : ctypes.c_void_p
Specifies a file. Corresponds to C file pointer. Must be obtained from libc.fopen.
a_read_stream: bool isa_read_stream: bool
True (read) or False (write) True (read) or False (write)
Returns Returns
@ -1284,16 +1292,52 @@ def stream_create_default_file_stream_v3(fname, a_read_stream):
stream : stream_t stream : stream_t
An OpenJPEG file stream. An OpenJPEG file stream.
""" """
read_stream = 1 if a_read_stream else 0 read_stream = 1 if isa_read_stream else 0
stream = OPENJP2.opj_stream_create_default_file_stream(fptr, read_stream)
return stream
def stream_create_default_file_stream_v3(fname, isa_read_stream):
"""Wraps openjp2 library function opj_stream_create_default_vile_stream_v3.
Sets the stream to be a file stream. This function is only valid for the
trunk/development 2.0+ version of the openjp2 library.
Parameters
----------
fname : str
Specifies a file.
isa_read_stream: bool
True (read) or False (write)
Returns
-------
stream : stream_t
An OpenJPEG file stream.
"""
read_stream = 1 if isa_read_stream else 0
file_argument = ctypes.c_char_p(fname.encode()) file_argument = ctypes.c_char_p(fname.encode())
stream = OPENJP2.opj_stream_create_default_file_stream_v3(file_argument, stream = OPENJP2.opj_stream_create_default_file_stream_v3(file_argument,
read_stream) read_stream)
return stream return stream
def stream_destroy_v3(stream): def stream_destroy(stream):
"""Wraps openjp2 library function opj_stream_destroy. """Wraps openjp2 library function opj_stream_destroy.
Destroys the stream created by create_stream.
Parameters
----------
stream : STREAM_TYPE_P
The file stream.
"""
OPENJP2.opj_stream_destroy(stream)
def stream_destroy_v3(stream):
"""Wraps openjp2 library function opj_stream_destroy_v3.
Destroys the stream created by create_stream_v3. Destroys the stream created by create_stream_v3.
Parameters Parameters
@ -1339,3 +1383,13 @@ def set_error_message(msg):
"""The openjpeg error handler has recorded an error message.""" """The openjpeg error handler has recorded an error message."""
global ERROR_MSG_LST global ERROR_MSG_LST
ERROR_MSG_LST.append(msg) ERROR_MSG_LST.append(msg)
def version():
"""Wrapper for opj_version library routine."""
OPENJP2.opj_version.restype = ctypes.c_char_p
library_version = OPENJP2.opj_version()
if sys.hexversion >= 0x03000000:
return library_version.decode('utf-8')
else:
return library_version

View file

@ -28,9 +28,6 @@ else:
# Does not really matter. But version should not be called if there is no # Does not really matter. But version should not be called if there is no
# OpenJPEG library found. # OpenJPEG library found.
_MINOR = 0 _MINOR = 0
# Redefine version so that we can use it.
def version():
return '0.0.0'
class EventMgrType(ctypes.Structure): class EventMgrType(ctypes.Structure):

View file

@ -16,10 +16,17 @@ import numpy as np
import glymur import glymur
OPENJP2_IS_V2_OFFICIAL = False
if glymur.lib.openjp2.OPENJP2 is not None:
if not hasattr(glymur.lib.openjp2.OPENJP2,
'opj_stream_create_default_file_stream_v3'):
OPENJP2_IS_V2_OFFICIAL = True
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
@unittest.skipIf(glymur.lib._openjp2.OPENJP2 is None, @unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
"Missing openjp2 library.") "Missing openjp2 library.")
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL, "API followed here specific to V2.0+")
class TestOpenJP2(unittest.TestCase): class TestOpenJP2(unittest.TestCase):
def setUp(self): def setUp(self):

View file

@ -3,6 +3,22 @@ import sys
import numpy as np import numpy as np
import glymur
# Need to know the openjpeg version. If openjpeg is not installed, we use
# '0.0.0'
OPENJPEG_VERSION = '0.0.0'
if glymur.lib.openjpeg.OPENJPEG is not None:
OPENJPEG_VERSION = glymur.lib.openjpeg.version()
# Need to know of the libopenjp2 version is the official 2.0.0 release and NOT
# the 2.0+ development version.
OPENJP2_IS_V2_OFFICIAL = False
if glymur.lib.openjp2.OPENJP2 is not None:
if not hasattr(glymur.lib.openjp2.OPENJP2,
'opj_stream_create_default_file_stream_v3'):
OPENJP2_IS_V2_OFFICIAL = True
def mse(amat, bmat): def mse(amat, bmat):
"""Mean Square Error""" """Mean Square Error"""

View file

@ -6,16 +6,18 @@ import sys
import tempfile import tempfile
import warnings import warnings
if sys.hexversion < 0x03000000:
from StringIO import StringIO
else:
from io import StringIO
if sys.hexversion < 0x02070000: if sys.hexversion < 0x02070000:
import unittest2 as unittest import unittest2 as unittest
else: else:
import unittest import unittest
if sys.hexversion <= 0x03030000:
from mock import patch
from StringIO import StringIO
else:
from unittest.mock import patch
from io import StringIO
import glymur import glymur
@ -24,15 +26,11 @@ import glymur
class TestCallbacks(unittest.TestCase): class TestCallbacks(unittest.TestCase):
def setUp(self): def setUp(self):
# Save sys.stdout.
self.stdout = sys.stdout
sys.stdout = StringIO()
self.jp2file = glymur.data.nemo() self.jp2file = glymur.data.nemo()
self.j2kfile = glymur.data.goodstuff() self.j2kfile = glymur.data.goodstuff()
def tearDown(self): def tearDown(self):
# Restore stdout. pass
sys.stdout = self.stdout
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_info_callback_on_write(self): def test_info_callback_on_write(self):
@ -43,8 +41,9 @@ class TestCallbacks(unittest.TestCase):
tiledata = j.read(tile=0) tiledata = j.read(tile=0)
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = glymur.Jp2k(tfile.name, 'wb') j = glymur.Jp2k(tfile.name, 'wb')
j.write(tiledata, verbose=True) with patch('sys.stdout', new=StringIO()) as fake_out:
actual = sys.stdout.getvalue().strip() j.write(tiledata, verbose=True)
actual = fake_out.getvalue().strip()
expected = '[INFO] tile number 1 / 1' expected = '[INFO] tile number 1 / 1'
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
@ -52,8 +51,9 @@ class TestCallbacks(unittest.TestCase):
# Verify that we get the expected stdio output when our internal info # Verify that we get the expected stdio output when our internal info
# callback handler is enabled. # callback handler is enabled.
j = glymur.Jp2k(self.j2kfile) j = glymur.Jp2k(self.j2kfile)
d = j.read(rlevel=1, verbose=True, area=(0, 0, 200, 150)) with patch('sys.stdout', new=StringIO()) as fake_out:
actual = sys.stdout.getvalue().strip() d = j.read(rlevel=1, verbose=True, area=(0, 0, 200, 150))
actual = fake_out.getvalue().strip()
lines = ['[INFO] Start to read j2k main header (0).', lines = ['[INFO] Start to read j2k main header (0).',
'[INFO] Main header has been correctly decoded.', '[INFO] Main header has been correctly decoded.',
@ -66,53 +66,41 @@ class TestCallbacks(unittest.TestCase):
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
@unittest.skipIf(glymur.lib.openjp2.OPENJPEG is None, @unittest.skipIf(glymur.lib.openjpeg.OPENJPEG is None,
"Missing openjpeg library.") "Missing openjpeg library.")
class TestCallbacks15(unittest.TestCase): class TestCallbacks15(unittest.TestCase):
"""This test suite is for OpenJPEG 1.5.1 properties. """This test suite is for OpenJPEG 1.5.1 properties.
""" """
@classmethod
def setUpClass(cls):
# Monkey patch the package so as to use OPENJPEG instead of OPENJP2
cls.openjp2 = glymur.lib.openjp2.OPENJP2
glymur.lib.openjp2.OPENJP2 = None
@classmethod
def tearDownClass(cls):
# Restore OPENJP2
glymur.lib.openjp2.OPENJP2 = cls.openjp2
def setUp(self): def setUp(self):
# Save sys.stdout.
self.stdout = sys.stdout
sys.stdout = StringIO()
self.jp2file = glymur.data.nemo() self.jp2file = glymur.data.nemo()
self.j2kfile = glymur.data.goodstuff() self.j2kfile = glymur.data.goodstuff()
def tearDown(self): def tearDown(self):
# Restore stdout. pass
sys.stdout = self.stdout
def test_info_callbacks_on_read(self): def test_info_callbacks_on_read(self):
# Verify that we get the expected stdio output when our internal info # Verify that we get the expected stdio output when our internal info
# callback handler is enabled. # callback handler is enabled.
j = glymur.Jp2k(self.j2kfile) with patch('glymur.lib.openjp2.OPENJP2', new=None):
d = j.read(rlevel=1, verbose=True) # Force to use OPENJPEG instead of OPENJP2.
actual = sys.stdout.getvalue().strip() j = glymur.Jp2k(self.j2kfile)
with patch('sys.stdout', new=StringIO()) as fake_out:
regex = re.compile(r"""\[INFO\]\stile\s1\sof\s1\s+ d = j.read(rlevel=1, verbose=True)
\[INFO\]\s-\stiers-1\stook\s actual = fake_out.getvalue().strip()
[0-9]+\.[0-9]+\ss\s+
\[INFO\]\s-\sdwt\stook\s regex = re.compile(r"""\[INFO\]\stile\s1\sof\s1\s+
(-){0,1}[0-9]+\.[0-9]+\ss\s+ \[INFO\]\s-\stiers-1\stook\s
\[INFO\]\s-\stile\sdecoded\sin\s [0-9]+\.[0-9]+\ss\s+
[0-9]+\.[0-9]+\ss""", \[INFO\]\s-\sdwt\stook\s
re.VERBOSE) (-){0,1}[0-9]+\.[0-9]+\ss\s+
if sys.hexversion <= 0x03020000: \[INFO\]\s-\stile\sdecoded\sin\s
self.assertRegexpMatches(actual, regex) [0-9]+\.[0-9]+\ss""",
else: re.VERBOSE)
self.assertRegex(actual, regex) if sys.hexversion <= 0x03020000:
self.assertRegexpMatches(actual, regex)
else:
self.assertRegex(actual, regex)
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -19,6 +19,8 @@ from glymur.jp2box import *
from glymur.core import COLOR, OPACITY from glymur.core import COLOR, OPACITY
from glymur.core import RED, GREEN, BLUE, GREY, WHOLE_IMAGE from glymur.core import RED, GREEN, BLUE, GREY, WHOLE_IMAGE
from .fixtures import OPENJP2_IS_V2_OFFICIAL
try: try:
format_corpus_data_root = os.environ['FORMAT_CORPUS_DATA_ROOT'] format_corpus_data_root = os.environ['FORMAT_CORPUS_DATA_ROOT']
except KeyError: except KeyError:
@ -34,6 +36,8 @@ def load_tests(loader, tests, ignore):
return tests return tests
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL,
"Requires v2.0.0+ in order to run.")
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None, @unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
"Missing openjp2 library.") "Missing openjp2 library.")

View file

@ -26,6 +26,8 @@ import glymur
from glymur import Jp2k from glymur import Jp2k
from glymur.lib import openjp2 as opj2 from glymur.lib import openjp2 as opj2
from .fixtures import OPENJP2_IS_V2_OFFICIAL
try: try:
data_root = os.environ['OPJ_DATA_ROOT'] data_root = os.environ['OPJ_DATA_ROOT']
except KeyError: except KeyError:
@ -477,6 +479,8 @@ class TestJp2k(unittest.TestCase):
self.assertEqual(j.box[2].box[1].colorspace, self.assertEqual(j.box[2].box[1].colorspace,
glymur.core.GREYSCALE) glymur.core.GREYSCALE)
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL,
"Does not seem to work on official v2.0.0 release.")
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_grey_with_extra_component(self): def test_grey_with_extra_component(self):
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
@ -501,6 +505,8 @@ class TestJp2k(unittest.TestCase):
self.assertEqual(j.box[2].box[1].colorspace, self.assertEqual(j.box[2].box[1].colorspace,
glymur.core.GREYSCALE) glymur.core.GREYSCALE)
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL,
"Does not seem to work on official v2.0.0 release.")
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_rgb_with_extra_component(self): def test_rgb_with_extra_component(self):
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
@ -512,6 +518,17 @@ class TestJp2k(unittest.TestCase):
self.assertEqual(j.box[2].box[0].num_components, 4) self.assertEqual(j.box[2].box[0].num_components, 4)
self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB) self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB)
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL is False,
"Test is specific for v2.0.0 release")
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_extra_components_on_v2_official(self):
# Extra components seems to require 2.0+. Verify that we error out.
with self.assertRaises(IOError):
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
data = np.zeros((128, 128, 4), dtype=np.uint8)
j.write(data)
def test_specify_ycc(self): def test_specify_ycc(self):
# We don't support writing YCC at the moment. # We don't support writing YCC at the moment.
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
@ -655,6 +672,8 @@ class TestJp2k(unittest.TestCase):
self.assertEqual(jasoc.box[3].box[1].box_id, 'xml ') self.assertEqual(jasoc.box[3].box[1].box_id, 'xml ')
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL,
"Segfault on official v2.0.0 release.")
def test_openjpeg_library_message(self): def test_openjpeg_library_message(self):
# Verify the error message produced by the openjpeg library. # Verify the error message produced by the openjpeg library.
# This will confirm that the error callback mechanism is working. # This will confirm that the error callback mechanism is working.

View file

@ -30,6 +30,9 @@ import numpy as np
from glymur import Jp2k from glymur import Jp2k
import glymur import glymur
from .fixtures import OPENJPEG_VERSION
from .fixtures import OPENJP2_IS_V2_OFFICIAL
from .fixtures import * from .fixtures import *
try: try:
@ -986,6 +989,8 @@ class TestSuite(unittest.TestCase):
data = Jp2k(jfile).read() data = Jp2k(jfile).read()
self.assertTrue(True) self.assertTrue(True)
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL,
"Test known to fail in v2.0.0 official")
def test_NR_DEC_text_GBR_jp2_29_decode(self): def test_NR_DEC_text_GBR_jp2_29_decode(self):
jfile = os.path.join(data_root, jfile = os.path.join(data_root,
'input/nonregression/text_GBR.jp2') 'input/nonregression/text_GBR.jp2')
@ -1002,12 +1007,16 @@ class TestSuite(unittest.TestCase):
data = Jp2k(jfile).read() data = Jp2k(jfile).read()
self.assertTrue(True) self.assertTrue(True)
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL,
"Test known to fail in v2.0.0 official")
def test_NR_DEC_kodak_2layers_lrcp_j2c_31_decode(self): def test_NR_DEC_kodak_2layers_lrcp_j2c_31_decode(self):
jfile = os.path.join(data_root, jfile = os.path.join(data_root,
'input/nonregression/kodak_2layers_lrcp.j2c') 'input/nonregression/kodak_2layers_lrcp.j2c')
data = Jp2k(jfile).read() data = Jp2k(jfile).read()
self.assertTrue(True) self.assertTrue(True)
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL,
"Test known to fail in v2.0.0 official")
def test_NR_DEC_kodak_2layers_lrcp_j2c_32_decode(self): def test_NR_DEC_kodak_2layers_lrcp_j2c_32_decode(self):
jfile = os.path.join(data_root, jfile = os.path.join(data_root,
'input/nonregression/kodak_2layers_lrcp.j2c') 'input/nonregression/kodak_2layers_lrcp.j2c')
@ -1020,6 +1029,8 @@ class TestSuite(unittest.TestCase):
data = Jp2k(jfile).read() data = Jp2k(jfile).read()
self.assertTrue(True) self.assertTrue(True)
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL,
"Test known to fail in v2.0.0 official")
def test_NR_DEC_mem_b2ace68c_1381_jp2_34_decode(self): def test_NR_DEC_mem_b2ace68c_1381_jp2_34_decode(self):
jfile = os.path.join(data_root, jfile = os.path.join(data_root,
'input/nonregression/mem-b2ace68c-1381.jp2') 'input/nonregression/mem-b2ace68c-1381.jp2')
@ -1030,6 +1041,8 @@ class TestSuite(unittest.TestCase):
data = j.read() data = j.read()
self.assertTrue(True) self.assertTrue(True)
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL,
"Test known to fail in v2.0.0 official")
def test_NR_DEC_mem_b2b86b74_2753_jp2_35_decode(self): def test_NR_DEC_mem_b2b86b74_2753_jp2_35_decode(self):
jfile = os.path.join(data_root, jfile = os.path.join(data_root,
'input/nonregression/mem-b2b86b74-2753.jp2') 'input/nonregression/mem-b2b86b74-2753.jp2')
@ -1045,6 +1058,8 @@ class TestSuite(unittest.TestCase):
with self.assertRaises(IOError): with self.assertRaises(IOError):
data = j.read() data = j.read()
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL,
"Test not in done in v2.0.0 official")
def test_NR_DEC_jp2_36_decode(self): def test_NR_DEC_jp2_36_decode(self):
lst = ('input', lst = ('input',
'nonregression', 'nonregression',
@ -1056,6 +1071,8 @@ class TestSuite(unittest.TestCase):
with self.assertRaises(IOError): with self.assertRaises(IOError):
data = j.read() data = j.read()
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL,
"Test not in done in v2.0.0 official")
def test_NR_DEC_gdal_fuzzer_check_number_of_tiles_jp2_38_decode(self): def test_NR_DEC_gdal_fuzzer_check_number_of_tiles_jp2_38_decode(self):
relpath = 'input/nonregression/gdal_fuzzer_check_number_of_tiles.jp2' relpath = 'input/nonregression/gdal_fuzzer_check_number_of_tiles.jp2'
jfile = os.path.join(data_root, relpath) jfile = os.path.join(data_root, relpath)
@ -1065,6 +1082,8 @@ class TestSuite(unittest.TestCase):
with self.assertRaises(IOError): with self.assertRaises(IOError):
data = j.read() data = j.read()
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL,
"Test not in done in v2.0.0 official")
def test_NR_DEC_gdal_fuzzer_check_comp_dx_dy_jp2_39_decode(self): def test_NR_DEC_gdal_fuzzer_check_comp_dx_dy_jp2_39_decode(self):
relpath = 'input/nonregression/gdal_fuzzer_check_comp_dx_dy.jp2' relpath = 'input/nonregression/gdal_fuzzer_check_comp_dx_dy.jp2'
jfile = os.path.join(data_root, relpath) jfile = os.path.join(data_root, relpath)
@ -1079,6 +1098,8 @@ class TestSuite(unittest.TestCase):
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
data = Jp2k(jfile).read() data = Jp2k(jfile).read()
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL,
"Test not in done in v2.0.0 official")
@unittest.skipIf(sys.hexversion < 0x03020000, @unittest.skipIf(sys.hexversion < 0x03020000,
"Uses features introduced in 3.2.") "Uses features introduced in 3.2.")
def test_NR_DEC_issue188_beach_64bitsbox_jp2_41_decode(self): def test_NR_DEC_issue188_beach_64bitsbox_jp2_41_decode(self):
@ -1089,6 +1110,8 @@ class TestSuite(unittest.TestCase):
with self.assertWarns(UserWarning) as cw: with self.assertWarns(UserWarning) as cw:
data = Jp2k(jfile).read() data = Jp2k(jfile).read()
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL,
"Test not in done in v2.0.0 official")
def test_NR_DEC_issue206_image_000_jp2_42_decode(self): def test_NR_DEC_issue206_image_000_jp2_42_decode(self):
jfile = os.path.join(data_root, jfile = os.path.join(data_root,
'input/nonregression/issue206_image-000.jp2') 'input/nonregression/issue206_image-000.jp2')
@ -7779,7 +7802,8 @@ class TestSuite15(unittest.TestCase):
jfile = os.path.join(data_root, 'input/conformance/file9.jp2') jfile = os.path.join(data_root, 'input/conformance/file9.jp2')
jp2k = Jp2k(jfile) jp2k = Jp2k(jfile)
jpdata = jp2k.read() jpdata = jp2k.read()
if glymur.lib.openjpeg.version().startswith('1.3'): if re.match('[01]\.3', OPENJPEG_VERSION):
# Version 1.3 reads in the image as the palette indices.
self.assertEqual(jpdata.shape, (512, 768)) self.assertEqual(jpdata.shape, (512, 768))
else: else:
self.assertEqual(jpdata.shape, (512, 768, 3)) self.assertEqual(jpdata.shape, (512, 768, 3))
@ -7818,7 +7842,7 @@ class TestSuite15(unittest.TestCase):
data = jp2.read() data = jp2.read()
self.assertTrue(True) self.assertTrue(True)
@unittest.skipIf(int(glymur.lib.openjpeg.version().split('.')[1]) < 5, @unittest.skipIf(re.match('[01]\.[34]', OPENJPEG_VERSION),
"Segfaults openjpeg 1.4 and earlier.") "Segfaults openjpeg 1.4 and earlier.")
def test_NR_DEC_broken2_jp2_5_decode(self): def test_NR_DEC_broken2_jp2_5_decode(self):
# Null pointer access # Null pointer access
@ -7841,7 +7865,7 @@ class TestSuite15(unittest.TestCase):
with self.assertRaises(ValueError) as ce: with self.assertRaises(ValueError) as ce:
d = j.read() d = j.read()
@unittest.skipIf(int(glymur.lib.openjpeg.version().split('.')[1]) < 5, @unittest.skipIf(re.match('[01]\.[34]', OPENJPEG_VERSION),
"Segfaults openjpeg 1.4 and earlier.") "Segfaults openjpeg 1.4 and earlier.")
def test_NR_DEC_broken4_jp2_7_decode(self): def test_NR_DEC_broken4_jp2_7_decode(self):
# Null pointer access # Null pointer access

View file

@ -70,7 +70,7 @@ class TestSuiteNegative(unittest.TestCase):
data = read_image(infile) data = read_image(infile)
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb') j = Jp2k(tfile.name, 'wb')
with self.assertRaises(RuntimeError): with self.assertRaises(IOError):
j.write(data, psnr=[30, 35, 40], cratios=[2, 3, 4]) j.write(data, psnr=[30, 35, 40], cratios=[2, 3, 4])
def test_NR_MarkerIsNotCompliant_j2k_dump(self): def test_NR_MarkerIsNotCompliant_j2k_dump(self):
@ -109,13 +109,13 @@ class TestSuiteNegative(unittest.TestCase):
j = Jp2k(tfile.name, 'wb') j = Jp2k(tfile.name, 'wb')
# opj_compress doesn't allow code block area to exceed 4096. # opj_compress doesn't allow code block area to exceed 4096.
with self.assertRaises(RuntimeError) as cr: with self.assertRaises(IOError) as cr:
j.write(data, cbsize=(256, 256)) j.write(data, cbsize=(256, 256))
# opj_compress doesn't allow either dimension to be less than 4. # opj_compress doesn't allow either dimension to be less than 4.
with self.assertRaises(RuntimeError) as cr: with self.assertRaises(IOError) as cr:
j.write(data, cbsize=(2048, 2)) j.write(data, cbsize=(2048, 2))
with self.assertRaises(RuntimeError) as cr: with self.assertRaises(IOError) as cr:
j.write(data, cbsize=(2, 2048)) j.write(data, cbsize=(2, 2048))
@unittest.skipIf(sys.hexversion < 0x03020000, @unittest.skipIf(sys.hexversion < 0x03020000,

View file

@ -1,35 +1,51 @@
| OS | Python | Python | Python | Notes | | OS | Python | Python | Python | Notes |
| | 2.6 | 2.7 | 3.3 | | | | 2.6 | 2.7 | 3.3 | |
+-----------+--------+--------+--------+--------------------------------------+ +-----------+--------+--------+--------+--------------------------------------+
| Windows | | X | | Python(xy) with OpenJPEG 1.5.1 and | | Windows | | X | | WinPython with OpenJPEG 1.5.1 and |
| | | | | OpenJPEG svn. 285 of 448 tests | | | | | | OpenJPEG 2.0.0. 267 of 455 tests |
| | | | | pass. |
+-----------+--------+--------+--------+--------------------------------------+
| Windows | | | X | WinPython with OpenJPEG 1.5.1 and |
| | | | | OpenJPEG svn. 307 of 455 tests |
| | | | | pass. | | | | | | pass. |
+-----------+--------+--------+--------+--------------------------------------+ +-----------+--------+--------+--------+--------------------------------------+
| Mac | X | | | MacPorts with both OpenJPEG 1.5.1 | | Mac | X | | | MacPorts with both OpenJPEG 1.5.1 |
| 10.6.8 | | | | and OpenJPEG svn. 354 of 455 tests | | 10.6.8 | | | | and OpenJPEG svn. 354 of 456 tests |
| | | | | should pass. |
+-----------+--------+--------+--------+--------------------------------------+
| Mac | X | | | MacPorts with both OpenJPEG 1.5.1 |
| 10.6.8 | | | | and OpenJPEG 2.0. 315 of 456 tests |
| | | | | should pass. | | | | | | should pass. |
+-----------+--------+--------+--------+--------------------------------------+ +-----------+--------+--------+--------+--------------------------------------+
| Mac | | X | | MacPorts with both OpenJPEG 1.5.1 | | Mac | | X | | MacPorts with both OpenJPEG 1.5.1 |
| 10.6.8 | | | | and OpenJPEG svn. 379 of 460 tests | | 10.6.8 | | | | and OpenJPEG svn. 379 of 461 tests |
| | | | | should pass. |
+-----------+--------+--------+--------+--------------------------------------+
| Mac | | X | | MacPorts with both OpenJPEG 1.5.1 |
| 10.6.8 | | | | and OpenJPEG 2.0. 340 of 461 tests |
| | | | | should pass. | | | | | | should pass. |
+-----------+--------+--------+--------+--------------------------------------+ +-----------+--------+--------+--------+--------------------------------------+
| Mac | | | X | MacPorts with both OpenJPEG 1.5.1 | | Mac | | | X | MacPorts with both OpenJPEG 1.5.1 |
| 10.6.8 | | | | and OpenJPEG svn. 407 of 460 | | 10.6.8 | | | | and OpenJPEG svn. 407 of 461 |
| | | | | tests should pass. |
+-----------+--------+--------+--------+--------------------------------------+
| Mac | | | X | MacPorts with both OpenJPEG 1.5.1 |
| 10.6.8 | | | | and OpenJPEG 2.0. 355 of 461 |
| | | | | tests should pass. | | | | | | tests should pass. |
+-----------+--------+--------+-----------------------------------------------+ +-----------+--------+--------+-----------------------------------------------+
| Fedora 19 | | | X | Ships with 1.5.1, openjp2 built too. | | Fedora 19 | | | X | Ships with 1.5.1, openjp2 svn built, |
| | | | | 402 of 455 tests should pass. | | | | | | too. 407 of 461 tests should pass. |
+-----------+--------+--------+--------+--------------------------------------+ +-----------+--------+--------+--------+--------------------------------------+
| Fedora 18 | | | X | Ships with 1.5.1. 169 of 449 tests | | Fedora 18 | | X | | Ships with 1.5.1. 173 of 456 tests |
| | | | | should pass. | | | | | | should pass. |
+-----------+--------+--------+--------+--------------------------------------+ +-----------+--------+--------+--------+--------------------------------------+
| Fedora 17 | | X | | Ships with 1.4.0. 166 of 450 tests | | Fedora 17 | | X | | Ships with 1.4.0. 171 of 456 tests |
| | | | | should pass. | | | | | | should pass. |
+-----------+--------+--------+--------+--------------------------------------+ +-----------+--------+--------+--------+--------------------------------------+
| CentOS | X | | | Ships with 1.3.0. 169 of 455 tests | | CentOS | X | | | Ships with 1.3.0. 169 of 456 tests |
| 6.3 | | | | should pass. | | 6.3 | | | | should pass. |
+-----------+--------+--------+--------+--------------------------------------+ +-----------+--------+--------+--------+--------------------------------------+
| Raspberry | | X | | Ships with 1.3.0. 171 of 455 tests | | Raspberry | | X | | Ships with 1.3.0. 171 of 456 tests |
| Pi | | | | should pass. | | Pi | | | | should pass. |
| Debian 7 | | | | | | Debian 7 | | | | |
+-----------+--------+--------+--------+--------------------------------------+ +-----------+--------+--------+--------+--------------------------------------+

View file

@ -2,7 +2,7 @@ from setuptools import setup, find_packages
import sys import sys
kwargs = {'name': 'Glymur', kwargs = {'name': 'Glymur',
'version': '0.2.8', 'version': '0.3.0',
'description': 'Tools for accessing JPEG2000 files', 'description': 'Tools for accessing JPEG2000 files',
'long_description': open('README.md').read(), 'long_description': open('README.md').read(),
'author': 'John Evans', 'author': 'John Evans',