Merge branch 'issue138'

This commit is contained in:
jevans 2013-10-28 21:54:15 -04:00
commit 170f99ce3f
17 changed files with 738 additions and 864 deletions

View file

@ -1,19 +1,26 @@
language: python
python:
- "2.6"
- "2.7"
- "3.3"
before_install:
- sudo apt-get update -qq
- sudo apt-get install -qq libopenjpeg2
- sudo apt-get install -qq python-numpy
- sudo apt-get install -qq python3-numpy
- wget http://openjpeg.googlecode.com/files/openjpeg-1.5.0-Linux-x86_64.tar.gz
- sudo tar -xvf openjpeg-1.5.0-Linux-x86_64.tar.gz --strip-components=1 -C /
# command to install dependencies
install: "pip install -r travis-requirements.txt --use-mirrors"
install:
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install --use-mirrors contextlib2 mock ordereddict unittest2; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors contextlib2 mock; fi
- if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install --use-mirrors numpy; fi
# command to run tests
script: "python -m unittest discover"
script:
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then unit2 discover; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then python -m unittest discover; fi
- if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then python -m unittest discover; fi
notifications:
email: "john.g.evans.ne@gmail.com"

View file

@ -1,9 +1,17 @@
Oct 28, 2013 - v0.5.7 Fixed bad import error message when libopenjpeg library
not installed on mac.
Oct 13, 2013 - v0.5.6 Fixed handling of non-ascii chars in XML boxes. Fixed
some docstring errors in jp2box module.
Oct 03, 2013 - v0.5.5 Fixed pip install error introduced in 0.5.0.
Sep 24, 2013 - v0.5.4 Fixed test error restricted to v2.0.
Sep 24, 2013 - v0.5.3 Removed a duplicated channel definition test in
test_jp2box that could cause a segfault in 1.3 if not properly skipped.
Sep 23, 2013 - v0.5.2 Fixed some teests that have been failing since 0.5.
Sep 23, 2013 - v0.5.2 Fixed some tests that have been failing since 0.5.
under various edge cases.
Sep 19, 2013 - v0.5.1 Added more resiliency to XML box parsing. Fixed tests

View file

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

View file

@ -7,13 +7,13 @@ Most users won't need to read this! You've been warned...
Glymur Configuration
''''''''''''''''''''''
The default glymur installation process relies upon OpenJPEG version
1.X being properly installed on your system. If you have version 1.5 you can
The default glymur installation process relies upon OpenJPEG
being properly installed on your system. If you have version 1.5 you can
both read and write JPEG 2000 files, but you may wish to install version 2.0
or the 2.0+ version from OpenJPEG's development trunk for better performance.
If you do that, you should compile it as a shared library (named *openjp2*
instead of *openjpeg*) from the developmental source that you can retrieve
via subversion. As of this time of writing, svn revision 2345 works.
via subversion. As of this time of writing, svn revision 2347 works.
You should also download the test data for the purpose of configuring
and running OpenJPEG's test suite, check their instructions for all this.
You should set the **OPJ_DATA_ROOT** environment variable for the purpose
@ -25,16 +25,17 @@ of running Glymur's test suite. ::
Glymur uses ctypes to access the openjp2/openjpeg libraries,
and because ctypes accesses libraries in a platform-dependent manner, it is
recommended that you create a configuration file to help Glymur properly find
the openjpeg or openjp2 libraries (linux users don't need to bother if you are
using OpenJPEG as provided by your package manager). The configuration
format is the same as used by Python's configparser module, i.e. ::
the openjpeg or openjp2 libraries (linux users don't need to bother with this
if you are using OpenJPEG as provided by your package manager). The
configuration format is the same as used by Python's configparser module,
i.e. ::
[library]
openjp2: /opt/openjp2-svn/lib/libopenjp2.so
This assumes, of course, that you've installed OpenJPEG into
/opt/openjp2-svn on a linux system. The location of the configuration file
is platform-dependent (of course). If you use either linux or mac, the path
can vary as well (of course). If you use either linux or mac, the path
to the configuration file would normally be ::
$HOME/.config/glymur/glymurrc
@ -70,7 +71,8 @@ packages/RPMs/ports/whatever without going through pip.
Mac OS X
--------
All the necessary packages are available to use glymur with Python 2.6, 2.7,
and 3.3 via MacPorts. You should install the following set of ports:
and 3.3 via MacPorts. For python 3.3, you should install the following set of
ports:
* python33
* py33-numpy

View file

@ -3,7 +3,7 @@ How do I...?
------------
read the lowest resolution thumbnail?
... read the lowest resolution thumbnail?
=====================================
Printing the Jp2k object should reveal the number of resolutions (look in the
COD segment section), but you can take a shortcut by supplying -1 as the
@ -14,7 +14,7 @@ resolution level. ::
>>> j = glymur.Jp2k(file)
>>> thumbnail = j.read(rlevel=-1)
display metadata?
... display metadata?
=================
There are two ways. From the unix command line, the script *jp2dump* is
available. ::
@ -34,7 +34,7 @@ codestream box, only the main header is printed. It is possible to print
>>> print(j.get_codestream())
add XML metadata?
... add XML metadata?
=================
You can append any number of XML boxes to a JP2 file (not to a raw codestream).
Consider the following XML file `data.xml` : ::
@ -66,7 +66,7 @@ The **append** method can add an XML box as shown below::
>>> jp2.append(xmlbox)
>>> print(jp2)
add metadata in a more general fashion?
... add metadata in a more general fashion?
=======================================
An existing raw codestream (or JP2 file) can be wrapped (re-wrapped) in a
user-defined set of JP2 boxes. To get just a minimal JP2 jacket on the
@ -157,18 +157,18 @@ to add metadata, you should keep in mind that **wrap** produces a new JP2 file,
while **append** modifies an existing file and is currently limited to XML
boxes.
create an image with an alpha layer?
... create an image with an alpha layer?
====================================
OpenJPEG can create JP2 files with more than 3 components (requires
the development version), but by default, any extra components are
the development version of OpenJPEG), but by default, any extra components are
not described as such. In order to do so, we need to rewrap such
an image in a set of boxes that includes a channel definition box.
This example is based on SciPy example code found at
http://scipy-lectures.github.io/advanced/image_processing/#basic-manipulations .
Instead of a circular mask, however, we'll make it an ellipse since the source
image isn't square.
Instead of a circular mask we'll make it an ellipse since the source
image isn't square. ::
>>> import numpy as np
>>> import glymur

View file

@ -4,9 +4,8 @@ Glymur: a Python interface for JPEG 2000
**Glymur** is an interface to the OpenJPEG library
which allows one to read and write JPEG 2000 files from within Python.
Glymur supports both reading and writing of JPEG 2000 images. Writing
JPEG 2000 images is currently limited to images that can fit in memory,
however.
Glymur supports both reading and writing of JPEG 2000 images, but writing
JPEG 2000 images is currently limited to images that can fit in memory
Of particular focus is retrieval of metadata. Reading Exif UUIDs is supported,
as is reading XMP UUIDs as the XMP data packet is just XML. There is
@ -24,11 +23,10 @@ only supported with the 1.5 or better, however, and the trunk/development
version is strongly recommended. For more information about OpenJPEG,
please consult http://www.openjpeg.org.
If you use MacPorts on the mac or if you have a sufficiently recent
version of Linux, your package manager should already provide you
with a version of OpenJPEG 1.X with which glymur can already use
for read-only purposes. If your platform is windows, I suggest
using the windows installers provided to you by the OpenJPEG
If you use MacPorts or if you have a sufficiently recent version of
Linux, your package manager should already provide you with a version of
OpenJPEG 1.X which glymur can already use. If your platform is windows,
I suggest using the windows installers provided to you by the OpenJPEG
folks at https://code.google.com/p/openjpeg/downloads/list .
Glymur Installation
@ -47,13 +45,5 @@ line, so you should adjust your **$PATH**
to take advantage of it. For example, if you install with pip's
`--user` option on linux ::
$ export PYTHONPATH=$HOME/.local/lib/python3.3/site-packages
$ export PATH=$HOME/.local/bin:$PATH
You can run the tests from within python as follows::
>>> import glymur
>>> glymur.runtests()
Many tests are currently skipped, but the The important thing is whether or
not any tests fail.

View file

@ -568,7 +568,7 @@ class CodestreamHeaderBox(Jp2kBox):
Returns
-------
AssociationBox instance
CodestreamHeaderBox instance
"""
box = CodestreamHeaderBox(length=length, offset=offset)
@ -627,7 +627,7 @@ class CompositingLayerHeaderBox(Jp2kBox):
Returns
-------
AssociationBox instance
CompositingLayerHeaderBox instance
"""
box = CompositingLayerHeaderBox(length=length, offset=offset)
@ -638,7 +638,7 @@ class CompositingLayerHeaderBox(Jp2kBox):
class ComponentMappingBox(Jp2kBox):
"""Container for channel identification information.
"""Container for component mapping information.
Attributes
----------
@ -1637,7 +1637,7 @@ class CaptureResolutionBox(Jp2kBox):
@staticmethod
def parse(fptr, offset, length):
"""Parse Resolution box.
"""Parse CaptureResolutionBox.
Parameters
----------
@ -1694,7 +1694,7 @@ class DisplayResolutionBox(Jp2kBox):
@staticmethod
def parse(fptr, offset, length):
"""Parse Resolution box.
"""Parse display resolution box.
Parameters
----------
@ -1870,10 +1870,8 @@ class XMLBox(Jp2kBox):
# Strip out any trailing nulls, as they can foul up XML parsing.
text = text.rstrip(chr(0))
# Scan for the start of the xml declaration.
try:
elt = ET.fromstring(text)
elt = ET.fromstring(text.encode('utf-8'))
xml = ET.ElementTree(elt)
except ParseError as parse_error:
msg = 'A problem was encountered while parsing an XML box:'
@ -2047,7 +2045,7 @@ class DataEntryURLBox(Jp2kBox):
@staticmethod
def parse(fptr, offset, length):
"""Parse Data Entry URL box.
"""Parse data entry URL box.
Parameters
----------
@ -2749,9 +2747,18 @@ def _pretty_print_xml(xml, level=0):
"""
xml = copy.deepcopy(xml)
_indent(xml.getroot(), level=level)
xmltext = ET.tostring(xml.getroot()).decode('utf-8')
xmltext = ET.tostring(xml.getroot(), encoding='utf-8').decode('utf-8')
# Indent it a bit.
lst = [(' ' + x) for x in xmltext.split('\n')]
xml = '\n'.join(lst)
return '\n{0}'.format(xml)
try:
xml = '\n'.join(lst)
return '\n{0}'.format(xml)
except UnicodeEncodeError:
# This can happen on python 2.x if the character set contains certain
# non-ascii characters. Just print out the corresponding xml char
# entities instead.
xml = u'\n'.join(lst)
text = u'\n{0}'.format(xml)
text = text.encode('ascii', 'xmlcharrefreplace')
return text

View file

@ -474,41 +474,37 @@ class Jp2k(Jp2kBox):
comptparms = _populate_comptparms(img_array, cparams)
image = opj2.image_create(comptparms, colorspace)
_populate_image_struct(cparams, image, img_array)
codec = opj2.create_compress(cparams.codec_fmt)
info_handler = _INFO_CALLBACK if verbose else None
opj2.set_info_handler(codec, info_handler)
opj2.set_warning_handler(codec, _WARNING_CALLBACK)
opj2.set_error_handler(codec, _ERROR_CALLBACK)
opj2.setup_encoder(codec, cparams, image)
if _OPENJP2_IS_OFFICIAL_V2:
fptr = libc.fopen(self.filename, 'wb')
strm = opj2.stream_create_default_file_stream(fptr, False)
else:
# This routine introduced in 2.0 devel series.
strm = opj2.stream_create_default_file_stream_v3(self.filename,
False)
# Start to clean up after ourselves.
opj2.start_compress(codec, image, strm)
opj2.encode(codec, strm)
opj2.end_compress(codec, strm)
if _OPENJP2_IS_OFFICIAL_V2:
opj2.stream_destroy(strm)
libc.fclose(fptr)
else:
# This routine introduced in 2.0 devel series.
opj2.stream_destroy_v3(strm)
opj2.destroy_codec(codec)
opj2.image_destroy(image)
with ExitStack() as stack:
image = opj2.image_create(comptparms, colorspace)
stack.callback(opj2.image_destroy, image)
_populate_image_struct(cparams, image, img_array)
codec = opj2.create_compress(cparams.codec_fmt)
stack.callback(opj2.destroy_codec, codec)
info_handler = _INFO_CALLBACK if verbose else None
opj2.set_info_handler(codec, info_handler)
opj2.set_warning_handler(codec, _WARNING_CALLBACK)
opj2.set_error_handler(codec, _ERROR_CALLBACK)
opj2.setup_encoder(codec, cparams, image)
if _OPENJP2_IS_OFFICIAL_V2:
fptr = libc.fopen(self.filename, 'wb')
strm = opj2.stream_create_default_file_stream(fptr, False)
stack.callback(opj2.stream_destroy, strm)
stack.callback(libc.fclose, fptr)
else:
# This routine introduced in 2.0 devel series.
strm = opj2.stream_create_default_file_stream_v3(self.filename,
False)
stack.callback(opj2.stream_destroy_v3, strm)
opj2.start_compress(codec, image, strm)
opj2.encode(codec, strm)
opj2.end_compress(codec, strm)
# Refresh the metadata.
self.parse()

View file

@ -43,37 +43,62 @@ def glymurrc_fname():
return None
def load_openjpeg(libopenjpeg_path):
def load_openjpeg(path):
"""Load the openjpeg library, falling back on defaults if necessary.
"""
if libopenjpeg_path is None:
if path is None:
# Let ctypes try to find it.
libopenjpeg_path = find_library('openjpeg')
path = find_library('openjpeg')
# If we could not find it, then look in some likely locations.
if libopenjpeg_path is None:
# If we could not find it, then look in some likely locations on mac
# and win.
if path is None:
if platform.system() == 'Darwin':
# MacPorts
path = '/opt/local/lib/libopenjpeg.dylib'
if os.path.exists(path):
libopenjpeg_path = path
elif os.name == 'nt':
path = os.path.join('C:\\', 'Program files', 'OpenJPEG 1.5',
'bin', 'openjpeg.dll')
if os.path.exists(path):
libopenjpeg_path = path
else:
# No sense trying further on Linux
return None
return load_library_handle(path)
def load_openjp2(path):
"""Load the openjp2 library, falling back on defaults if necessary.
"""
if path is None:
# No help from the config file, try to find it ourselves.
path = find_library('openjp2')
if path is None:
if platform.system() == 'Darwin':
# MacPorts
path = '/opt/local/lib/libopenjp2.dylib'
elif os.name == 'nt':
path = os.path.join('C:\\', 'Program files', 'OpenJPEG 2.0',
'bin', 'openjp2.dll')
return load_library_handle(path)
def load_library_handle(path):
"""Load the library, return the ctypes handle."""
if path is None:
return None
try:
if os.name == "nt":
openjpeg_lib = ctypes.windll.LoadLibrary(libopenjpeg_path)
opj_lib = ctypes.windll.LoadLibrary(path)
else:
openjpeg_lib = ctypes.CDLL(libopenjpeg_path)
except OSError:
openjpeg_lib = None
opj_lib = ctypes.CDLL(path)
except (TypeError, OSError):
msg = '"Library {0}" could not be loaded. Operating in degraded mode.'
msg = msg.format(path)
warnings.warn(msg, UserWarning)
opj_lib = None
return openjpeg_lib
return opj_lib
def read_config_file():
@ -98,41 +123,6 @@ def read_config_file():
return lib
def load_openjp2(libopenjp2_path):
"""Load the openjp2 library, falling back on defaults if necessary.
"""
if libopenjp2_path is None:
# No help from the config file, try to find it ourselves.
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:
return None
try:
if os.name == "nt":
openjp2_lib = ctypes.windll.LoadLibrary(libopenjp2_path)
else:
openjp2_lib = ctypes.CDLL(libopenjp2_path)
except (TypeError, OSError):
msg = '"Library {0}" could not be loaded. Operating in degraded mode.'
msg = msg.format(libopenjp2_path)
warnings.warn(msg, UserWarning)
openjp2_lib = None
return openjp2_lib
def glymur_config():
"""Try to ascertain locations of openjp2, openjpeg libraries.
"""

View file

@ -553,119 +553,6 @@ class CodestreamInfoV2(ctypes.Structure):
# information regarding tiles inside of image
("tile_info", ctypes.POINTER(TileInfoV2))]
# Restrict the input and output argument types for each function used in the
# API.
if OPENJP2 is not None:
OPENJP2.opj_create_compress.restype = CODEC_TYPE
OPENJP2.opj_create_compress.argtypes = [CODEC_FORMAT_TYPE]
OPENJP2.opj_create_decompress.argtypes = [CODEC_FORMAT_TYPE]
OPENJP2.opj_create_decompress.restype = CODEC_TYPE
ARGTYPES = [CODEC_TYPE, STREAM_TYPE_P, ctypes.POINTER(ImageType)]
OPENJP2.opj_decode.argtypes = ARGTYPES
ARGTYPES = [CODEC_TYPE, ctypes.c_uint32,
ctypes.POINTER(ctypes.c_uint8),
ctypes.c_uint32,
STREAM_TYPE_P]
OPENJP2.opj_decode_tile_data.argtypes = ARGTYPES
ARGTYPES = [ctypes.POINTER(ctypes.POINTER(CodestreamInfoV2))]
OPENJP2.opj_destroy_cstr_info.argtypes = ARGTYPES
OPENJP2.opj_destroy_cstr_info.restype = ctypes.c_void_p
ARGTYPES = [CODEC_TYPE, STREAM_TYPE_P]
OPENJP2.opj_encode.argtypes = ARGTYPES
OPENJP2.opj_get_cstr_info.argtypes = [CODEC_TYPE]
OPENJP2.opj_get_cstr_info.restype = ctypes.POINTER(CodestreamInfoV2)
ARGTYPES = [CODEC_TYPE,
STREAM_TYPE_P,
ctypes.POINTER(ImageType),
ctypes.c_uint32]
OPENJP2.opj_get_decoded_tile.argtypes = ARGTYPES
ARGTYPES = [ctypes.c_uint32,
ctypes.POINTER(ImageComptParmType),
COLOR_SPACE_TYPE]
OPENJP2.opj_image_create.argtypes = ARGTYPES
OPENJP2.opj_image_create.restype = ctypes.POINTER(ImageType)
ARGTYPES = [ctypes.c_uint32,
ctypes.POINTER(ImageComptParmType),
COLOR_SPACE_TYPE]
OPENJP2.opj_image_tile_create.argtypes = ARGTYPES
OPENJP2.opj_image_tile_create.restype = ctypes.POINTER(ImageType)
OPENJP2.opj_image_destroy.argtypes = [ctypes.POINTER(ImageType)]
ARGTYPES = [STREAM_TYPE_P, CODEC_TYPE,
ctypes.POINTER(ctypes.POINTER(ImageType))]
OPENJP2.opj_read_header.argtypes = ARGTYPES
ARGTYPES = [CODEC_TYPE,
STREAM_TYPE_P,
ctypes.POINTER(ctypes.c_uint32),
ctypes.POINTER(ctypes.c_uint32),
ctypes.POINTER(ctypes.c_int32),
ctypes.POINTER(ctypes.c_int32),
ctypes.POINTER(ctypes.c_int32),
ctypes.POINTER(ctypes.c_int32),
ctypes.POINTER(ctypes.c_uint32),
ctypes.POINTER(BOOL_TYPE)]
OPENJP2.opj_read_tile_header.argtypes = ARGTYPES
ARGTYPES = [CODEC_TYPE, ctypes.POINTER(ImageType), ctypes.c_int32,
ctypes.c_int32, ctypes.c_int32, ctypes.c_int32]
OPENJP2.opj_set_decode_area.argtypes = ARGTYPES
ARGTYPES = [ctypes.POINTER(CompressionParametersType)]
OPENJP2.opj_set_default_encoder_parameters.argtypes = ARGTYPES
ARGTYPES = [ctypes.POINTER(DecompressionParametersType)]
OPENJP2.opj_set_default_decoder_parameters.argtypes = ARGTYPES
ARGTYPES = [CODEC_TYPE, ctypes.c_void_p, ctypes.c_void_p]
OPENJP2.opj_set_error_handler.argtypes = ARGTYPES
OPENJP2.opj_set_info_handler.argtypes = ARGTYPES
OPENJP2.opj_set_warning_handler.argtypes = ARGTYPES
ARGTYPES = [CODEC_TYPE, ctypes.POINTER(DecompressionParametersType)]
OPENJP2.opj_setup_decoder.argtypes = ARGTYPES
ARGTYPES = [CODEC_TYPE,
ctypes.POINTER(CompressionParametersType),
ctypes.POINTER(ImageType)]
OPENJP2.opj_setup_encoder.argtypes = ARGTYPES
if hasattr(OPENJP2, 'opj_stream_create_default_file_stream_v3'):
ARGTYPES = [ctypes.c_char_p, ctypes.c_int32]
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]
OPENJP2.opj_start_compress.argtypes = ARGTYPES
OPENJP2.opj_end_compress.argtypes = [CODEC_TYPE, STREAM_TYPE_P]
OPENJP2.opj_end_decompress.argtypes = [CODEC_TYPE, STREAM_TYPE_P]
OPENJP2.opj_destroy_codec.argtypes = [CODEC_TYPE]
ARGTYPES = [CODEC_TYPE,
ctypes.c_uint32,
ctypes.POINTER(ctypes.c_uint8),
ctypes.c_uint32,
STREAM_TYPE_P]
OPENJP2.opj_write_tile.argtypes = ARGTYPES
def check_error(status):
"""Set a generic function as the restype attribute of all OpenJPEG
@ -684,19 +571,6 @@ def check_error(status):
else:
raise IOError("OpenJPEG function failure.")
# These library functions all return an error status. Circumvent that and
# force them to raise an exception.
FCNS = ['opj_decode', 'opj_decode_tile_data', 'opj_end_compress',
'opj_encode', 'opj_end_decompress', 'opj_get_decoded_tile',
'opj_read_header', 'opj_read_tile_header', 'opj_set_decode_area',
'opj_set_error_handler', 'opj_set_info_handler',
'opj_set_warning_handler',
'opj_setup_decoder', 'opj_setup_encoder', 'opj_start_compress',
'opj_write_tile']
if OPENJP2 is not None:
for fcn in FCNS:
setattr(getattr(OPENJP2, fcn), 'restype', check_error)
def create_compress(codec_format):
"""Creates a J2K/JP2 compress structure.
@ -712,6 +586,9 @@ def create_compress(codec_format):
-------
codec : Reference to CODEC_TYPE instance.
"""
OPENJP2.opj_create_compress.restype = CODEC_TYPE
OPENJP2.opj_create_compress.argtypes = [CODEC_FORMAT_TYPE]
codec = OPENJP2.opj_create_compress(codec_format)
return codec
@ -735,6 +612,10 @@ def decode(codec, stream, image):
RuntimeError
If the OpenJPEG library routine opj_decode fails.
"""
OPENJP2.opj_decode.argtypes = [CODEC_TYPE, STREAM_TYPE_P,
ctypes.POINTER(ImageType)]
OPENJP2.opj_decode.restype = check_error
OPENJP2.opj_decode(codec, stream, image)
@ -761,13 +642,19 @@ def decode_tile_data(codec, tidx, data, data_size, stream):
RuntimeError
If the OpenJPEG library routine opj_decode fails.
"""
OPENJP2.opj_decode_tile_data.argtypes = [CODEC_TYPE,
ctypes.c_uint32,
ctypes.POINTER(ctypes.c_uint8),
ctypes.c_uint32,
STREAM_TYPE_P]
OPENJP2.opj_decode_tile_data.restype = check_error
datap = data.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8))
OPENJP2.opj_decode_tile_data(codec,
ctypes.c_uint32(tidx),
datap,
ctypes.c_uint32(data_size),
stream)
return codec
def create_decompress(codec_format):
@ -784,6 +671,9 @@ def create_decompress(codec_format):
-------
codec : Reference to CODEC_TYPE instance.
"""
OPENJP2.opj_create_decompress.argtypes = [CODEC_FORMAT_TYPE]
OPENJP2.opj_create_decompress.restype = CODEC_TYPE
codec = OPENJP2.opj_create_decompress(codec_format)
return codec
@ -798,6 +688,8 @@ def destroy_codec(codec):
codec : CODEC_TYPE
Decompressor handle to destroy.
"""
OPENJP2.opj_destroy_codec.argtypes = [CODEC_TYPE]
OPENJP2.opj_destroy_codec.restype = ctypes.c_void_p
OPENJP2.opj_destroy_codec(codec)
@ -818,6 +710,9 @@ def encode(codec, stream):
RuntimeError
If the OpenJPEG library routine opj_encode fails.
"""
OPENJP2.opj_encode.argtypes = [CODEC_TYPE, STREAM_TYPE_P]
OPENJP2.opj_encode.restype = check_error
OPENJP2.opj_encode(codec, stream)
@ -836,6 +731,9 @@ def get_cstr_info(codec):
cstr_info_p : CodestreamInfoV2
Reference to codestream information.
"""
OPENJP2.opj_get_cstr_info.argtypes = [CODEC_TYPE]
OPENJP2.opj_get_cstr_info.restype = ctypes.POINTER(CodestreamInfoV2)
cstr_info_p = OPENJP2.opj_get_cstr_info(codec)
return cstr_info_p
@ -861,6 +759,12 @@ def get_decoded_tile(codec, stream, imagep, tile_index):
RuntimeError
If the OpenJPEG library routine opj_get_decoded_tile fails.
"""
OPENJP2.opj_get_decoded_tile.argtypes = [CODEC_TYPE,
STREAM_TYPE_P,
ctypes.POINTER(ImageType),
ctypes.c_uint32]
OPENJP2.opj_get_decoded_tile.restype = check_error
OPENJP2.opj_get_decoded_tile(codec, stream, imagep, tile_index)
@ -874,6 +778,10 @@ def destroy_cstr_info(cstr_info_p):
cstr_info_p : CodestreamInfoV2 pointer
Pointer to codestream info structure.
"""
ARGTYPES = [ctypes.POINTER(ctypes.POINTER(CodestreamInfoV2))]
OPENJP2.opj_destroy_cstr_info.argtypes = ARGTYPES
OPENJP2.opj_destroy_cstr_info.restype = ctypes.c_void_p
OPENJP2.opj_destroy_cstr_info(ctypes.byref(cstr_info_p))
@ -894,6 +802,8 @@ def end_compress(codec, stream):
RuntimeError
If the OpenJPEG library routine opj_end_compress fails.
"""
OPENJP2.opj_end_compress.argtypes = [CODEC_TYPE, STREAM_TYPE_P]
OPENJP2.opj_end_compress.restype = check_error
OPENJP2.opj_end_compress(codec, stream)
@ -914,6 +824,8 @@ def end_decompress(codec, stream):
RuntimeError
If the OpenJPEG library routine opj_end_decompress fails.
"""
OPENJP2.opj_end_decompress.argtypes = [CODEC_TYPE, STREAM_TYPE_P]
OPENJP2.opj_end_decompress.restype = check_error
OPENJP2.opj_end_decompress(codec, stream)
@ -927,6 +839,9 @@ def image_destroy(image):
image : ImageType pointer
Image resource to be disposed.
"""
OPENJP2.opj_image_destroy.argtypes = [ctypes.POINTER(ImageType)]
OPENJP2.opj_image_destroy.restype = ctypes.c_void_p
OPENJP2.opj_image_destroy(image)
@ -947,6 +862,11 @@ def image_create(comptparms, clrspc):
image : ImageType
Reference to ImageType instance.
"""
OPENJP2.opj_image_create.argtypes = [ctypes.c_uint32,
ctypes.POINTER(ImageComptParmType),
COLOR_SPACE_TYPE]
OPENJP2.opj_image_create.restype = ctypes.POINTER(ImageType)
image = OPENJP2.opj_image_create(len(comptparms),
comptparms,
clrspc)
@ -970,6 +890,12 @@ def image_tile_create(comptparms, clrspc):
image : ImageType
Reference to ImageType instance.
"""
ARGTYPES = [ctypes.c_uint32,
ctypes.POINTER(ImageComptParmType),
COLOR_SPACE_TYPE]
OPENJP2.opj_image_tile_create.argtypes = ARGTYPES
OPENJP2.opj_image_tile_create.restype = ctypes.POINTER(ImageType)
image = OPENJP2.opj_image_tile_create(len(comptparms),
comptparms,
clrspc)
@ -998,6 +924,11 @@ def read_header(stream, codec):
RuntimeError
If the OpenJPEG library routine opj_read_header fails.
"""
ARGTYPES = [STREAM_TYPE_P, CODEC_TYPE,
ctypes.POINTER(ctypes.POINTER(ImageType))]
OPENJP2.opj_read_header.argtypes = ARGTYPES
OPENJP2.opj_read_header.restype = check_error
imagep = ctypes.POINTER(ImageType)()
OPENJP2.opj_read_header(stream, codec, ctypes.byref(imagep))
return imagep
@ -1035,6 +966,19 @@ def read_tile_header(codec, stream):
RuntimeError
If the OpenJPEG library routine opj_read_tile_header fails.
"""
ARGTYPES = [CODEC_TYPE,
STREAM_TYPE_P,
ctypes.POINTER(ctypes.c_uint32),
ctypes.POINTER(ctypes.c_uint32),
ctypes.POINTER(ctypes.c_int32),
ctypes.POINTER(ctypes.c_int32),
ctypes.POINTER(ctypes.c_int32),
ctypes.POINTER(ctypes.c_int32),
ctypes.POINTER(ctypes.c_uint32),
ctypes.POINTER(BOOL_TYPE)]
OPENJP2.opj_read_tile_header.argtypes = ARGTYPES
OPENJP2.opj_read_tile_header.restype = check_error
tile_index = ctypes.c_uint32()
data_size = ctypes.c_uint32()
col0 = ctypes.c_int32()
@ -1086,6 +1030,14 @@ def set_decode_area(codec, image, start_x=0, start_y=0, end_x=0, end_y=0):
RuntimeError
If the OpenJPEG library routine opj_set_decode_area fails.
"""
OPENJP2.opj_set_decode_area.argtypes = [CODEC_TYPE,
ctypes.POINTER(ImageType),
ctypes.c_int32,
ctypes.c_int32,
ctypes.c_int32,
ctypes.c_int32]
OPENJP2.opj_set_decode_area.restype = check_error
OPENJP2.opj_set_decode_area(codec, image,
ctypes.c_int32(start_x),
ctypes.c_int32(start_y),
@ -1103,6 +1055,10 @@ def set_default_decoder_parameters():
dparam : DecompressionParametersType
Decompression parameters.
"""
ARGTYPES = [ctypes.POINTER(DecompressionParametersType)]
OPENJP2.opj_set_default_decoder_parameters.argtypes = ARGTYPES
OPENJP2.opj_set_default_decoder_parameters.restype = ctypes.c_void_p
dparams = DecompressionParametersType()
OPENJP2.opj_set_default_decoder_parameters(ctypes.byref(dparams))
return dparams
@ -1138,6 +1094,10 @@ def set_default_encoder_parameters():
cparameters : CompressionParametersType
Compression parameters.
"""
ARGTYPES = [ctypes.POINTER(CompressionParametersType)]
OPENJP2.opj_set_default_encoder_parameters.argtypes = ARGTYPES
OPENJP2.opj_set_default_encoder_parameters.restype = ctypes.c_void_p
cparams = CompressionParametersType()
OPENJP2.opj_set_default_encoder_parameters(ctypes.byref(cparams))
return cparams
@ -1162,6 +1122,10 @@ def set_error_handler(codec, handler, data=None):
RuntimeError
If the OpenJPEG library routine opj_set_error_handler fails.
"""
OPENJP2.opj_set_error_handler.argtypes = [CODEC_TYPE,
ctypes.c_void_p,
ctypes.c_void_p]
OPENJP2.opj_set_error_handler.restype = check_error
OPENJP2.opj_set_error_handler(codec, handler, data)
@ -1184,6 +1148,10 @@ def set_info_handler(codec, handler, data=None):
RuntimeError
If the OpenJPEG library routine opj_set_info_handler fails.
"""
OPENJP2.opj_set_info_handler.argtypes = [CODEC_TYPE,
ctypes.c_void_p,
ctypes.c_void_p]
OPENJP2.opj_set_info_handler.restype = check_error
OPENJP2.opj_set_info_handler(codec, handler, data)
@ -1206,6 +1174,11 @@ def set_warning_handler(codec, handler, data=None):
RuntimeError
If the OpenJPEG library routine opj_set_warning_handler fails.
"""
OPENJP2.opj_set_warning_handler.argtypes = [CODEC_TYPE,
ctypes.c_void_p,
ctypes.c_void_p]
OPENJP2.opj_set_warning_handler.restype = check_error
OPENJP2.opj_set_warning_handler(codec, handler, data)
@ -1226,6 +1199,10 @@ def setup_decoder(codec, dparams):
RuntimeError
If the OpenJPEG library routine opj_setup_decoder fails.
"""
ARGTYPES = [CODEC_TYPE, ctypes.POINTER(DecompressionParametersType)]
OPENJP2.opj_setup_decoder.argtypes = ARGTYPES
OPENJP2.opj_setup_decoder.restype = check_error
OPENJP2.opj_setup_decoder(codec, ctypes.byref(dparams))
@ -1249,6 +1226,11 @@ def setup_encoder(codec, cparams, image):
RuntimeError
If the OpenJPEG library routine opj_setup_encoder fails.
"""
ARGTYPES = [CODEC_TYPE,
ctypes.POINTER(CompressionParametersType),
ctypes.POINTER(ImageType)]
OPENJP2.opj_setup_encoder.argtypes = ARGTYPES
OPENJP2.opj_setup_encoder.restype = check_error
OPENJP2.opj_setup_encoder(codec, ctypes.byref(cparams), image)
@ -1271,6 +1253,11 @@ def start_compress(codec, image, stream):
RuntimeError
If the OpenJPEG library routine opj_start_compress fails.
"""
OPENJP2.opj_start_compress.argtypes = [CODEC_TYPE,
ctypes.POINTER(ImageType),
STREAM_TYPE_P]
OPENJP2.opj_start_compress.restype = check_error
OPENJP2.opj_start_compress(codec, image, stream)
@ -1292,6 +1279,9 @@ def stream_create_default_file_stream(fptr, isa_read_stream):
stream : stream_t
An OpenJPEG file stream.
"""
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
read_stream = 1 if isa_read_stream else 0
stream = OPENJP2.opj_stream_create_default_file_stream(fptr, read_stream)
return stream
@ -1315,6 +1305,9 @@ def stream_create_default_file_stream_v3(fname, isa_read_stream):
stream : stream_t
An OpenJPEG file stream.
"""
ARGTYPES = [ctypes.c_char_p, ctypes.c_int32]
OPENJP2.opj_stream_create_default_file_stream_v3.argtypes = ARGTYPES
OPENJP2.opj_stream_create_default_file_stream_v3.restype = STREAM_TYPE_P
read_stream = 1 if isa_read_stream else 0
file_argument = ctypes.c_char_p(fname.encode())
stream = OPENJP2.opj_stream_create_default_file_stream_v3(file_argument,
@ -1332,6 +1325,8 @@ def stream_destroy(stream):
stream : STREAM_TYPE_P
The file stream.
"""
OPENJP2.opj_stream_destroy.argtypes = [STREAM_TYPE_P]
OPENJP2.opj_stream_destroy.restype = ctypes.c_void_p
OPENJP2.opj_stream_destroy(stream)
@ -1345,6 +1340,8 @@ def stream_destroy_v3(stream):
stream : STREAM_TYPE_P
The file stream.
"""
OPENJP2.opj_stream_destroy_v3.argtypes = [STREAM_TYPE_P]
OPENJP2.opj_stream_destroy_v3.restype = ctypes.c_void_p
OPENJP2.opj_stream_destroy_v3(stream)
@ -1371,6 +1368,13 @@ def write_tile(codec, tile_index, data, data_size, stream):
RuntimeError
If the OpenJPEG library routine opj_write_tile fails.
"""
OPENJP2.opj_write_tile.argtypes = [CODEC_TYPE,
ctypes.c_uint32,
ctypes.POINTER(ctypes.c_uint8),
ctypes.c_uint32,
STREAM_TYPE_P]
OPENJP2.opj_write_tile.restype = check_error
datap = data.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8))
OPENJP2.opj_write_tile(codec,
ctypes.c_uint32(int(tile_index)),

View file

@ -124,7 +124,7 @@ class TestConfig(unittest.TestCase):
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_write_without_library(self):
"""Don't have openjp2 library? Must error out.
"""Don't have openjpeg libraries? Must error out.
"""
data = glymur.Jp2k(self.j2kfile).read()
with patch('glymur.lib.openjp2.OPENJP2', new=None):

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
"""
Test suite specifically targeting JP2 box layout.
"""
@ -23,6 +24,16 @@ import tempfile
import warnings
import xml.etree.cElementTree as ET
if sys.hexversion < 0x03000000:
from StringIO import StringIO
else:
from io import StringIO
if sys.hexversion <= 0x03030000:
from mock import patch
else:
from unittest.mock import patch
if sys.hexversion < 0x02070000:
import unittest2 as unittest
else:
@ -94,8 +105,6 @@ class TestXML(unittest.TestCase):
with self.assertRaises((IOError, OSError)):
glymur.jp2box.XMLBox(filename=self.xmlfile, xml=xml_object)
@unittest.skipIf(os.name == "nt",
"Problems using NamedTemporaryFile on windows.")
def test_basic_xml(self):
"""Should be able to write a basic XMLBox"""
j2k = Jp2k(self.j2kfile)
@ -116,8 +125,6 @@ class TestXML(unittest.TestCase):
self.assertEqual(ET.tostring(jp2.box[3].xml.getroot()),
b'<data>0</data>')
@unittest.skipIf(os.name == "nt",
"Problems using NamedTemporaryFile on windows.")
def test_xml_from_file(self):
"""Must be able to create an XML box from an XML file."""
j2k = Jp2k(self.j2kfile)
@ -141,6 +148,28 @@ class TestXML(unittest.TestCase):
self.assertEqual(neighbor.attrib['name'], 'Malaysia')
self.assertEqual(neighbor.attrib['direction'], 'N')
def test_utf8_xml(self):
"""Should be able to write/read an XMLBox with utf-8 encoding."""
# 'Россия' is 'Russia' in Cyrillic, not that it matters.
xml = u"""<?xml version="1.0" encoding="utf-8"?>
<country>Россия</country>"""
with tempfile.NamedTemporaryFile(suffix=".xml") as xmlfile:
xmlfile.write(xml.encode('utf-8'))
xmlfile.flush()
j2k = glymur.Jp2k(self.j2kfile)
with tempfile.NamedTemporaryFile(suffix=".jp2") as jfile:
jp2 = j2k.wrap(jfile.name)
xmlbox = glymur.jp2box.XMLBox(filename=xmlfile.name)
jp2.append(xmlbox)
box_xml = jp2.box[-1].xml.getroot()
box_xml_str = ET.tostring(box_xml,
encoding='utf-8').decode('utf-8')
self.assertEqual(box_xml_str,
u'<country>Россия</country>')
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
class TestJp2kBadXmlFile(unittest.TestCase):
@ -179,7 +208,6 @@ class TestJp2kBadXmlFile(unittest.TestCase):
def setUp(self):
self.jp2file = glymur.data.nemo()
self.j2kfile = glymur.data.goodstuff()
def tearDown(self):
pass

View file

@ -55,598 +55,11 @@ def load_tests(loader, tests, ignore):
return tests
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2 or
OPENJP2_IS_V2_OFFICIAL,
"Missing openjp2 library version 2.0+.")
class TestJp2k_2_1(unittest.TestCase):
"""Test suite for version 2.0+ of openjpeg software"""
def setUp(self):
self.jp2file = glymur.data.nemo()
self.j2kfile = glymur.data.goodstuff()
def tearDown(self):
pass
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_grey_with_extra_component(self):
"""version 2.0 cannot write gray + extra"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
data = np.zeros((128, 128, 2), dtype=np.uint8)
j.write(data)
self.assertEqual(j.box[2].box[0].height, 128)
self.assertEqual(j.box[2].box[0].width, 128)
self.assertEqual(j.box[2].box[0].num_components, 2)
self.assertEqual(j.box[2].box[1].colorspace,
glymur.core.GREYSCALE)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_rgb_with_extra_component(self):
"""v2.0+ should be able to write extra components"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
data = np.zeros((128, 128, 4), dtype=np.uint8)
j.write(data)
self.assertEqual(j.box[2].box[0].height, 128)
self.assertEqual(j.box[2].box[0].width, 128)
self.assertEqual(j.box[2].box[0].num_components, 4)
self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_openjpeg_library_message(self):
"""Verify the error message produced by the openjpeg library"""
# This will confirm that the error callback mechanism is working.
with open(self.jp2file, 'rb') as fptr:
data = fptr.read()
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
# Codestream starts at byte 3127. SIZ marker at 3137.
# COD marker at 3186. Subsampling at 3180.
tfile.write(data[0:3179])
# Make the DY bytes of the SIZ segment zero. That means that
# a subsampling factor is zero, which is illegal.
tfile.write(b'\x00')
tfile.write(data[3180:3182])
tfile.write(b'\x00')
tfile.write(data[3184:3186])
tfile.write(b'\x00')
tfile.write(data[3186:])
tfile.flush()
with warnings.catch_warnings():
warnings.simplefilter("ignore")
j = Jp2k(tfile.name)
regexp = re.compile(r'''OpenJPEG\slibrary\serror:\s+
Invalid\svalues\sfor\scomp\s=\s0\s+
:\sdx=1\sdy=0''', re.VERBOSE)
if sys.hexversion < 0x03020000:
with self.assertRaisesRegexp((IOError, OSError), regexp):
j.read(rlevel=1)
else:
with self.assertRaisesRegex((IOError, OSError), regexp):
j.read(rlevel=1)
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2,
"Not tested for 1.x")
class TestJp2k_2_0(unittest.TestCase):
"""Test suite requiring at least version 2.0"""
def setUp(self):
self.jp2file = glymur.data.nemo()
self.j2kfile = glymur.data.goodstuff()
def tearDown(self):
pass
@unittest.skipIf(not OPENJP2_IS_V2_OFFICIAL,
"Behavior is specific to 2.0 official.")
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_extra_components_on_v2(self):
"""must error out in 1.x with extra components."""
# Extra components seems to require 2.0+. Verify that we error out.
with self.assertRaises((IOError, OSError)):
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
data = np.zeros((128, 128, 4), dtype=np.uint8)
j.write(data)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_unrecognized_jp2_clrspace(self):
"""We only allow RGB and GRAYSCALE. Should error out with others"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
with self.assertRaises(IOError):
data = np.zeros((128, 128, 3), dtype=np.uint8)
j.write(data, colorspace='cmyk')
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_asoc_label_box(self):
"""Test asoc and label box"""
# Construct a fake file with an asoc and a label box, as
# OpenJPEG doesn't have such a file.
data = Jp2k(self.jp2file).read(rlevel=1)
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
j.write(data)
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2:
# Offset of the codestream is where we start.
read_buffer = tfile.read(77)
tfile2.write(read_buffer)
# read the rest of the file, it's the codestream.
codestream = tfile.read()
# Write the asoc superbox.
# Length = 36, id is 'asoc'.
write_buffer = struct.pack('>I4s', int(56), b'asoc')
tfile2.write(write_buffer)
# Write the contained label box
write_buffer = struct.pack('>I4s', int(13), b'lbl ')
tfile2.write(write_buffer)
tfile2.write('label'.encode())
# Write the xml box
# Length = 36, id is 'xml '.
write_buffer = struct.pack('>I4s', int(35), b'xml ')
tfile2.write(write_buffer)
write_buffer = '<test>this is a test</test>'
write_buffer = write_buffer.encode()
tfile2.write(write_buffer)
# Now append the codestream.
tfile2.write(codestream)
tfile2.flush()
jasoc = Jp2k(tfile2.name)
self.assertEqual(jasoc.box[3].box_id, 'asoc')
self.assertEqual(jasoc.box[3].box[0].box_id, 'lbl ')
self.assertEqual(jasoc.box[3].box[0].label, 'label')
self.assertEqual(jasoc.box[3].box[1].box_id, 'xml ')
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
"Missing openjp2 library.")
class TestJp2k_1_x(unittest.TestCase):
"""Test suite for versions up to 1.5.1 of openjpeg but no further"""
def setUp(self):
self.jp2file = glymur.data.nemo()
self.j2kfile = glymur.data.goodstuff()
def tearDown(self):
pass
def test_rlevel_max(self):
"""Verify that rlevel=-1 gets us the lowest resolution image"""
j = Jp2k(self.j2kfile)
thumbnail1 = j.read(rlevel=-1)
thumbnail2 = j.read(rlevel=5)
np.testing.assert_array_equal(thumbnail1, thumbnail2)
self.assertEqual(thumbnail1.shape, (25, 15, 3))
def test_bad_area_parameter(self):
"""Should error out appropriately if given a bad area parameter."""
j = Jp2k(self.jp2file)
with self.assertRaises(IOError):
# Start corner must be >= 0
j.read(area=(-1, -1, 1, 1))
with self.assertRaises(IOError):
# End corner must be > 0
j.read(area=(10, 10, 0, 0))
with self.assertRaises(IOError):
# End corner must be >= start corner
j.read(area=(10, 10, 8, 8))
def test_rlevel_too_high(self):
"""Should error out appropriately if reduce level too high"""
j = Jp2k(self.jp2file)
with self.assertRaises(IOError):
j.read(rlevel=6)
def test_jp2_boxes(self):
"""Verify the boxes of a JP2 file. Basic jp2 test."""
jp2k = Jp2k(self.jp2file)
# top-level boxes
self.assertEqual(len(jp2k.box), 6)
self.assertEqual(jp2k.box[0].box_id, 'jP ')
self.assertEqual(jp2k.box[0].offset, 0)
self.assertEqual(jp2k.box[0].length, 12)
self.assertEqual(jp2k.box[0].longname, 'JPEG 2000 Signature')
self.assertEqual(jp2k.box[1].box_id, 'ftyp')
self.assertEqual(jp2k.box[1].offset, 12)
self.assertEqual(jp2k.box[1].length, 20)
self.assertEqual(jp2k.box[1].longname, 'File Type')
self.assertEqual(jp2k.box[2].box_id, 'jp2h')
self.assertEqual(jp2k.box[2].offset, 32)
self.assertEqual(jp2k.box[2].length, 45)
self.assertEqual(jp2k.box[2].longname, 'JP2 Header')
self.assertEqual(jp2k.box[3].box_id, 'uuid')
self.assertEqual(jp2k.box[3].offset, 77)
self.assertEqual(jp2k.box[3].length, 638)
self.assertEqual(jp2k.box[4].box_id, 'uuid')
self.assertEqual(jp2k.box[4].offset, 715)
self.assertEqual(jp2k.box[4].length, 2412)
self.assertEqual(jp2k.box[5].box_id, 'jp2c')
self.assertEqual(jp2k.box[5].offset, 3127)
self.assertEqual(jp2k.box[5].length, 1132296)
# jp2h super box
self.assertEqual(len(jp2k.box[2].box), 2)
self.assertEqual(jp2k.box[2].box[0].box_id, 'ihdr')
self.assertEqual(jp2k.box[2].box[0].offset, 40)
self.assertEqual(jp2k.box[2].box[0].length, 22)
self.assertEqual(jp2k.box[2].box[0].longname, 'Image Header')
self.assertEqual(jp2k.box[2].box[0].height, 1456)
self.assertEqual(jp2k.box[2].box[0].width, 2592)
self.assertEqual(jp2k.box[2].box[0].num_components, 3)
self.assertEqual(jp2k.box[2].box[0].bits_per_component, 8)
self.assertEqual(jp2k.box[2].box[0].signed, False)
self.assertEqual(jp2k.box[2].box[0].compression, 7)
self.assertEqual(jp2k.box[2].box[0].colorspace_unknown, False)
self.assertEqual(jp2k.box[2].box[0].ip_provided, False)
self.assertEqual(jp2k.box[2].box[1].box_id, 'colr')
self.assertEqual(jp2k.box[2].box[1].offset, 62)
self.assertEqual(jp2k.box[2].box[1].length, 15)
self.assertEqual(jp2k.box[2].box[1].longname, 'Colour Specification')
self.assertEqual(jp2k.box[2].box[1].precedence, 0)
self.assertEqual(jp2k.box[2].box[1].approximation, 0)
self.assertEqual(jp2k.box[2].box[1].colorspace, glymur.core.SRGB)
self.assertIsNone(jp2k.box[2].box[1].icc_profile)
def test_j2k_box(self):
"""A J2K/J2C file must not have any boxes."""
# Verify that a J2K file has no boxes.
jp2k = Jp2k(self.j2kfile)
self.assertEqual(len(jp2k.box), 0)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_64bit_xl_field(self):
"""XL field should be supported"""
# Verify that boxes with the XL field are properly read.
# Don't have such a file on hand, so we create one. Copy our example
# file, but making the codestream have a 64-bit XL field.
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
with open(self.jp2file, 'rb') as ifile:
# Everything up until the jp2c box.
write_buffer = ifile.read(3127)
tfile.write(write_buffer)
# The L field must be 1 in order to signal the presence of the
# XL field. The actual length of the jp2c box increased by 8
# (8 bytes for the XL field).
length = 1
typ = b'jp2c'
xlen = 1133427 + 8
write_buffer = struct.pack('>I4sQ', int(length), typ, xlen)
tfile.write(write_buffer)
# Get the rest of the input file (minus the 8 bytes for L and
# T.
ifile.seek(8, 1)
write_buffer = ifile.read()
tfile.write(write_buffer)
tfile.flush()
jp2k = Jp2k(tfile.name)
self.assertEqual(jp2k.box[5].box_id, 'jp2c')
self.assertEqual(jp2k.box[5].offset, 3127)
self.assertEqual(jp2k.box[5].length, 1133427 + 8)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_length_field_is_zero(self):
"""L=0 (length field in box header) is allowed"""
# Verify that boxes with the L field as zero are correctly read.
# This should only happen in the last box of a JPEG 2000 file.
# Our example image has its last box at byte 588458.
baseline_jp2 = Jp2k(self.jp2file)
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
with open(self.jp2file, 'rb') as ifile:
# Everything up until the jp2c box.
write_buffer = ifile.read(588458)
tfile.write(write_buffer)
length = 0
typ = b'uuid'
write_buffer = struct.pack('>I4s', int(length), typ)
tfile.write(write_buffer)
# Get the rest of the input file (minus the 8 bytes for L and
# T.
ifile.seek(8, 1)
write_buffer = ifile.read()
tfile.write(write_buffer)
tfile.flush()
new_jp2 = Jp2k(tfile.name)
# The top level boxes in each file should match.
for j in range(len(baseline_jp2.box)):
self.assertEqual(new_jp2.box[j].box_id,
baseline_jp2.box[j].box_id)
self.assertEqual(new_jp2.box[j].offset,
baseline_jp2.box[j].offset)
self.assertEqual(new_jp2.box[j].length,
baseline_jp2.box[j].length)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_cblkh_different_than_width(self):
"""Verify that we can set a code block size where height does not equal
width.
"""
data = np.zeros((128, 128), dtype=np.uint8)
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
# The code block dimensions are given as rows x columns.
j.write(data, cbsize=(16, 32))
codestream = j.get_codestream()
# Code block size is reported as XY in the codestream.
self.assertEqual(tuple(codestream.segment[2].spcod[5:7]), (3, 2))
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_too_many_dimensions(self):
"""OpenJP2 only allows 2D or 3D images."""
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
with self.assertRaises(IOError):
data = np.zeros((128, 128, 2, 2), dtype=np.uint8)
j.write(data)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_2d_rgb(self):
"""RGB must have at least 3 components."""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
with self.assertRaises(IOError):
data = np.zeros((128, 128, 2), dtype=np.uint8)
j.write(data, colorspace='rgb')
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_colorspace_with_j2k(self):
"""Specifying a colorspace with J2K does not make sense"""
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
with self.assertRaises(IOError):
data = np.zeros((128, 128, 3), dtype=np.uint8)
j.write(data, colorspace='rgb')
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_specify_rgb(self):
"""specify RGB explicitly"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
data = np.zeros((128, 128, 3), dtype=np.uint8)
j.write(data, colorspace='rgb')
self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_specify_gray(self):
"""test gray explicitly specified (that's GRAY, not GREY)"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
data = np.zeros((128, 128), dtype=np.uint8)
j.write(data, colorspace='gray')
self.assertEqual(j.box[2].box[1].colorspace,
glymur.core.GREYSCALE)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_specify_grey(self):
"""test grey explicitly specified"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
data = np.zeros((128, 128), dtype=np.uint8)
j.write(data, colorspace='grey')
self.assertEqual(j.box[2].box[1].colorspace,
glymur.core.GREYSCALE)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_grey_with_two_extra_comps(self):
"""should be able to write gray + two extra components"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
data = np.zeros((128, 128, 3), dtype=np.uint8)
j.write(data, colorspace='gray')
self.assertEqual(j.box[2].box[0].height, 128)
self.assertEqual(j.box[2].box[0].width, 128)
self.assertEqual(j.box[2].box[0].num_components, 3)
self.assertEqual(j.box[2].box[1].colorspace,
glymur.core.GREYSCALE)
def test_specify_ycc(self):
"""Should reject YCC"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
with self.assertRaises(IOError):
data = np.zeros((128, 128, 3), dtype=np.uint8)
j.write(data, colorspace='ycc')
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_write_with_jp2_in_caps(self):
"""should be able to write with JP2 suffix."""
j2k = Jp2k(self.j2kfile)
expdata = j2k.read()
with tempfile.NamedTemporaryFile(suffix='.JP2') as tfile:
ofile = Jp2k(tfile.name, 'wb')
ofile.write(expdata)
actdata = ofile.read()
np.testing.assert_array_equal(actdata, expdata)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_write_srgb_without_mct(self):
"""should be able to write RGB without specifying mct"""
j2k = Jp2k(self.j2kfile)
expdata = j2k.read()
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
ofile = Jp2k(tfile.name, 'wb')
ofile.write(expdata, mct=False)
actdata = ofile.read()
np.testing.assert_array_equal(actdata, expdata)
codestream = ofile.get_codestream()
self.assertEqual(codestream.segment[2].spcod[3], 0) # no mct
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_write_grayscale_with_mct(self):
"""MCT usage makes no sense for grayscale images."""
j2k = Jp2k(self.j2kfile)
expdata = j2k.read()
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
ofile = Jp2k(tfile.name, 'wb')
with self.assertRaises(IOError):
ofile.write(expdata[:, :, 0], mct=True)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_write_cprl(self):
"""Must be able to write a CPRL progression order file"""
# Issue 17
j = Jp2k(self.jp2file)
expdata = j.read(rlevel=1)
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
ofile = Jp2k(tfile.name, 'wb')
ofile.write(expdata, prog='CPRL')
actdata = ofile.read()
np.testing.assert_array_equal(actdata, expdata)
codestream = ofile.get_codestream()
self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL)
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is not None,
"Don't bother if openjp2 is present.")
@unittest.skipIf(glymur.lib.openjpeg.OPENJPEG is None,
"Missing openjpeg library.")
class TestJp2k15(unittest.TestCase):
"""Test suite for openjpeg 1.x, not appropriate for 2.x"""
def setUp(self):
self.jp2file = glymur.data.nemo()
self.j2kfile = glymur.data.goodstuff()
def tearDown(self):
pass
def test_area(self):
"""Area option not allowed for 1.5.1.
"""
j2k = Jp2k(self.j2kfile)
with self.assertRaises(TypeError):
j2k.read(area=(0, 0, 100, 100))
def test_tile(self):
"""tile option not allowed for 1.5.1.
"""
j2k = Jp2k(self.j2kfile)
with self.assertRaises(TypeError):
j2k.read(tile=0)
def test_layer(self):
"""layer option not allowed for 1.5.1.
"""
j2k = Jp2k(self.j2kfile)
with self.assertRaises(TypeError):
j2k.read(layer=1)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
@unittest.skipIf(glymur.version.openjpeg_version_tuple[1] < 5,
"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:
j = Jp2k(tfile.name, 'wb')
with self.assertRaises(IOError):
data = np.zeros((128, 128, 2), dtype=np.uint8)
j.write(data, colorspace='rgb')
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_xml_with_trailing_nulls(self):
"""ElementTree doesn't like trailing null chars after valid XML text"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
with open(self.jp2file, 'rb') as ifile:
# Everything up until the jp2c box.
write_buffer = ifile.read(77)
tfile.write(write_buffer)
# Write the xml box
# Length = 36, id is 'xml '.
write_buffer = struct.pack('>I4s', int(36), b'xml ')
tfile.write(write_buffer)
write_buffer = '<test>this is a test</test>' + chr(0)
write_buffer = write_buffer.encode()
tfile.write(write_buffer)
# Get the rest of the input file.
write_buffer = ifile.read()
tfile.write(write_buffer)
tfile.flush()
jp2k = Jp2k(tfile.name)
self.assertEqual(jp2k.box[3].box_id, 'xml ')
self.assertEqual(jp2k.box[3].offset, 77)
self.assertEqual(jp2k.box[3].length, 36)
self.assertEqual(ET.tostring(jp2k.box[3].xml.getroot()),
b'<test>this is a test</test>')
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2,
"Segfault on official v1.x series.")
def test_openjpeg_library_message(self):
"""Verify the error message produced by the openjpeg library"""
# This will confirm that the error callback mechanism is working.
with open(self.jp2file, 'rb') as fptr:
data = fptr.read()
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
# Codestream starts at byte 3127. SIZ marker at 3137.
# COD marker at 3186. Subsampling at 3180.
tfile.write(data[0:3179])
# Make the DY bytes of the SIZ segment zero. That means that
# a subsampling factor is zero, which is illegal.
tfile.write(b'\x00')
tfile.write(data[3180:3182])
tfile.write(b'\x00')
tfile.write(data[3184:3186])
tfile.write(b'\x00')
tfile.write(data[3186:])
tfile.flush()
with warnings.catch_warnings():
warnings.simplefilter("ignore")
j = Jp2k(tfile.name)
regexp = re.compile(r'''OpenJPEG\slibrary\serror:\s+
Invalid\svalues\sfor\scomp\s=\s0\s+
:\sdx=1\sdy=0''', re.VERBOSE)
if sys.hexversion < 0x03020000:
with self.assertRaisesRegexp((IOError, OSError), regexp):
j.read(rlevel=1)
else:
with self.assertRaisesRegex((IOError, OSError), regexp):
j.read(rlevel=1)
class TestJp2k(unittest.TestCase):
"""Test suite for openjpeg software starting at 1.3"""
# These tests should be run by just about all configuration.
def setUp(self):
self.jp2file = glymur.data.nemo()
self.j2kfile = glymur.data.goodstuff()
@ -985,5 +398,368 @@ class TestJp2k(unittest.TestCase):
self.assertFalse('Make' in exif['Image'].keys())
@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version),
"Requires at least version 1.5")
class TestJp2k_write(unittest.TestCase):
"""Write tests, can be run by versions 1.5+"""
def setUp(self):
self.jp2file = glymur.data.nemo()
self.j2kfile = glymur.data.goodstuff()
def tearDown(self):
pass
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_cblkh_different_than_width(self):
"""Verify that we can set a code block size where height does not equal
width.
"""
data = np.zeros((128, 128), dtype=np.uint8)
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
# The code block dimensions are given as rows x columns.
j.write(data, cbsize=(16, 32))
codestream = j.get_codestream()
# Code block size is reported as XY in the codestream.
self.assertEqual(tuple(codestream.segment[2].spcod[5:7]), (3, 2))
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_too_many_dimensions(self):
"""OpenJP2 only allows 2D or 3D images."""
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
with self.assertRaises(IOError):
data = np.zeros((128, 128, 2, 2), dtype=np.uint8)
j.write(data)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_2d_rgb(self):
"""RGB must have at least 3 components."""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
with self.assertRaises(IOError):
data = np.zeros((128, 128, 2), dtype=np.uint8)
j.write(data, colorspace='rgb')
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_colorspace_with_j2k(self):
"""Specifying a colorspace with J2K does not make sense"""
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
with self.assertRaises(IOError):
data = np.zeros((128, 128, 3), dtype=np.uint8)
j.write(data, colorspace='rgb')
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_specify_rgb(self):
"""specify RGB explicitly"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
data = np.zeros((128, 128, 3), dtype=np.uint8)
j.write(data, colorspace='rgb')
self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_specify_gray(self):
"""test gray explicitly specified (that's GRAY, not GREY)"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
data = np.zeros((128, 128), dtype=np.uint8)
j.write(data, colorspace='gray')
self.assertEqual(j.box[2].box[1].colorspace,
glymur.core.GREYSCALE)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_specify_grey(self):
"""test grey explicitly specified"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
data = np.zeros((128, 128), dtype=np.uint8)
j.write(data, colorspace='grey')
self.assertEqual(j.box[2].box[1].colorspace,
glymur.core.GREYSCALE)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_grey_with_two_extra_comps(self):
"""should be able to write gray + two extra components"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
data = np.zeros((128, 128, 3), dtype=np.uint8)
j.write(data, colorspace='gray')
self.assertEqual(j.box[2].box[0].height, 128)
self.assertEqual(j.box[2].box[0].width, 128)
self.assertEqual(j.box[2].box[0].num_components, 3)
self.assertEqual(j.box[2].box[1].colorspace,
glymur.core.GREYSCALE)
def test_specify_ycc(self):
"""Should reject YCC"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
with self.assertRaises(IOError):
data = np.zeros((128, 128, 3), dtype=np.uint8)
j.write(data, colorspace='ycc')
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_write_with_jp2_in_caps(self):
"""should be able to write with JP2 suffix."""
j2k = Jp2k(self.j2kfile)
expdata = j2k.read()
with tempfile.NamedTemporaryFile(suffix='.JP2') as tfile:
ofile = Jp2k(tfile.name, 'wb')
ofile.write(expdata)
actdata = ofile.read()
np.testing.assert_array_equal(actdata, expdata)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_write_srgb_without_mct(self):
"""should be able to write RGB without specifying mct"""
j2k = Jp2k(self.j2kfile)
expdata = j2k.read()
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
ofile = Jp2k(tfile.name, 'wb')
ofile.write(expdata, mct=False)
actdata = ofile.read()
np.testing.assert_array_equal(actdata, expdata)
codestream = ofile.get_codestream()
self.assertEqual(codestream.segment[2].spcod[3], 0) # no mct
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_write_grayscale_with_mct(self):
"""MCT usage makes no sense for grayscale images."""
j2k = Jp2k(self.j2kfile)
expdata = j2k.read()
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
ofile = Jp2k(tfile.name, 'wb')
with self.assertRaises(IOError):
ofile.write(expdata[:, :, 0], mct=True)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_write_cprl(self):
"""Must be able to write a CPRL progression order file"""
# Issue 17
j = Jp2k(self.jp2file)
expdata = j.read(rlevel=1)
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
ofile = Jp2k(tfile.name, 'wb')
ofile.write(expdata, prog='CPRL')
actdata = ofile.read()
np.testing.assert_array_equal(actdata, expdata)
codestream = ofile.get_codestream()
self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL)
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] >= 2,
"Negative tests only for version 1.x")
class TestJp2k_1_x(unittest.TestCase):
"""Test suite for openjpeg 1.x, not appropriate for 2.x"""
def setUp(self):
self.jp2file = glymur.data.nemo()
self.j2kfile = glymur.data.goodstuff()
def tearDown(self):
pass
def test_area(self):
"""Area option not allowed for 1.x.
"""
j2k = Jp2k(self.j2kfile)
with self.assertRaises(TypeError):
j2k.read(area=(0, 0, 100, 100))
def test_tile(self):
"""tile option not allowed for 1.x.
"""
j2k = Jp2k(self.j2kfile)
with self.assertRaises(TypeError):
j2k.read(tile=0)
def test_layer(self):
"""layer option not allowed for 1.x.
"""
j2k = Jp2k(self.j2kfile)
with self.assertRaises(TypeError):
j2k.read(layer=1)
@unittest.skipIf(not OPENJP2_IS_V2_OFFICIAL,
"Tests only to be run on 2.0 official.")
class TestJp2k_2_0_official(unittest.TestCase):
"""Test suite to only be run on v2.0 official."""
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_extra_components_on_v2(self):
"""Can only write 4 components on 2.0+, should error out otherwise."""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
data = np.zeros((128, 128, 4), dtype=np.uint8)
with self.assertRaises(IOError):
j.write(data)
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2,
"Requires as least v2.0")
class TestJp2k_2_0(unittest.TestCase):
"""Test suite requiring at least version 2.0"""
def setUp(self):
self.jp2file = glymur.data.nemo()
self.j2kfile = glymur.data.goodstuff()
def tearDown(self):
pass
def test_bad_area_parameter(self):
"""Should error out appropriately if given a bad area parameter."""
j = Jp2k(self.jp2file)
with self.assertRaises(IOError):
# Start corner must be >= 0
j.read(area=(-1, -1, 1, 1))
with self.assertRaises(IOError):
# End corner must be > 0
j.read(area=(10, 10, 0, 0))
with self.assertRaises(IOError):
# End corner must be >= start corner
j.read(area=(10, 10, 8, 8))
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_unrecognized_jp2_clrspace(self):
"""We only allow RGB and GRAYSCALE. Should error out with others"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
with self.assertRaises(IOError):
data = np.zeros((128, 128, 3), dtype=np.uint8)
j.write(data, colorspace='cmyk')
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_asoc_label_box(self):
"""Test asoc and label box"""
# Construct a fake file with an asoc and a label box, as
# OpenJPEG doesn't have such a file.
data = Jp2k(self.jp2file).read(rlevel=1)
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
j.write(data)
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2:
# Offset of the codestream is where we start.
read_buffer = tfile.read(77)
tfile2.write(read_buffer)
# read the rest of the file, it's the codestream.
codestream = tfile.read()
# Write the asoc superbox.
# Length = 36, id is 'asoc'.
write_buffer = struct.pack('>I4s', int(56), b'asoc')
tfile2.write(write_buffer)
# Write the contained label box
write_buffer = struct.pack('>I4s', int(13), b'lbl ')
tfile2.write(write_buffer)
tfile2.write('label'.encode())
# Write the xml box
# Length = 36, id is 'xml '.
write_buffer = struct.pack('>I4s', int(35), b'xml ')
tfile2.write(write_buffer)
write_buffer = '<test>this is a test</test>'
write_buffer = write_buffer.encode()
tfile2.write(write_buffer)
# Now append the codestream.
tfile2.write(codestream)
tfile2.flush()
jasoc = Jp2k(tfile2.name)
self.assertEqual(jasoc.box[3].box_id, 'asoc')
self.assertEqual(jasoc.box[3].box[0].box_id, 'lbl ')
self.assertEqual(jasoc.box[3].box[0].label, 'label')
self.assertEqual(jasoc.box[3].box[1].box_id, 'xml ')
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2 or
OPENJP2_IS_V2_OFFICIAL,
"Missing openjp2 library version 2.0+.")
class TestJp2k_2_1(unittest.TestCase):
"""Only to be run in 2.0+."""
def setUp(self):
self.jp2file = glymur.data.nemo()
self.j2kfile = glymur.data.goodstuff()
def tearDown(self):
pass
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_grey_with_extra_component(self):
"""version 2.0 cannot write gray + extra"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
data = np.zeros((128, 128, 2), dtype=np.uint8)
j.write(data)
self.assertEqual(j.box[2].box[0].height, 128)
self.assertEqual(j.box[2].box[0].width, 128)
self.assertEqual(j.box[2].box[0].num_components, 2)
self.assertEqual(j.box[2].box[1].colorspace,
glymur.core.GREYSCALE)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_rgb_with_extra_component(self):
"""v2.0+ should be able to write extra components"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
data = np.zeros((128, 128, 4), dtype=np.uint8)
j.write(data)
self.assertEqual(j.box[2].box[0].height, 128)
self.assertEqual(j.box[2].box[0].width, 128)
self.assertEqual(j.box[2].box[0].num_components, 4)
self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_openjpeg_library_message(self):
"""Verify the error message produced by the openjpeg library"""
# This will confirm that the error callback mechanism is working.
with open(self.jp2file, 'rb') as fptr:
data = fptr.read()
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
# Codestream starts at byte 3127. SIZ marker at 3137.
# COD marker at 3186. Subsampling at 3180.
tfile.write(data[0:3179])
# Make the DY bytes of the SIZ segment zero. That means that
# a subsampling factor is zero, which is illegal.
tfile.write(b'\x00')
tfile.write(data[3180:3182])
tfile.write(b'\x00')
tfile.write(data[3184:3186])
tfile.write(b'\x00')
tfile.write(data[3186:])
tfile.flush()
with warnings.catch_warnings():
warnings.simplefilter("ignore")
j = Jp2k(tfile.name)
regexp = re.compile(r'''OpenJPEG\slibrary\serror:\s+
Invalid\svalues\sfor\scomp\s=\s0\s+
:\sdx=1\sdy=0''', re.VERBOSE)
if sys.hexversion < 0x03020000:
with self.assertRaisesRegexp((IOError, OSError), regexp):
j.read(rlevel=1)
else:
with self.assertRaisesRegex((IOError, OSError), regexp):
j.read(rlevel=1)
if __name__ == "__main__":
unittest.main()

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
"""Test suite for printing.
"""
# C0302: don't care too much about having too many lines in a test module
@ -15,6 +16,7 @@ import struct
import sys
import tempfile
import warnings
from xml.etree import cElementTree as ET
if sys.hexversion < 0x02070000:
import unittest2 as unittest
@ -730,6 +732,65 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(sys.hexversion < 0x03000000,
"Only trusting python3 for printing non-ascii chars")
def test_xml_latin1(self):
"""Should be able to print an XMLBox with utf-8 encoding (latin1)."""
# Seems to be inconsistencies between different versions of python2.x
# as to what gets printed.
#
# 2.7.5 (fedora 19) prints xml entities.
# 2.7.3 seems to want to print hex escapes.
text = u"""<?xml version="1.0" encoding="utf-8"?>
<flow>Strömung</flow>"""
if sys.hexversion < 0x03000000:
xml = ET.parse(StringIO(text.encode('utf-8')))
else:
xml = ET.parse(StringIO(text))
xmlbox = glymur.jp2box.XMLBox(xml=xml)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(xmlbox)
actual = fake_out.getvalue().strip()
if sys.hexversion < 0x03000000:
lines = ["XML Box (xml ) @ (-1, 0)",
" <flow>Str\xc3\xb6mung</flow>"]
else:
lines = ["XML Box (xml ) @ (-1, 0)",
" <flow>Strömung</flow>"]
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(sys.hexversion < 0x03000000,
"Only trusting python3 for printing non-ascii chars")
def test_xml_cyrrilic(self):
"""Should be able to print an XMLBox with utf-8 encoding (cyrrillic)."""
# Seems to be inconsistencies between different versions of python2.x
# as to what gets printed.
#
# 2.7.5 (fedora 19) prints xml entities.
# 2.7.3 seems to want to print hex escapes.
text = u"""<?xml version="1.0" encoding="utf-8"?>
<country>Россия</country>"""
if sys.hexversion < 0x03000000:
xml = ET.parse(StringIO(text.encode('utf-8')))
else:
xml = ET.parse(StringIO(text))
xmlbox = glymur.jp2box.XMLBox(xml=xml)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(xmlbox)
actual = fake_out.getvalue().strip()
if sys.hexversion < 0x03000000:
lines = ["XML Box (xml ) @ (-1, 0)",
" <country>&#1056;&#1086;&#1089;&#1089;&#1080;&#1103;</country>"]
else:
lines = ["XML Box (xml ) @ (-1, 0)",
" <country>Россия</country>"]
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_channel_definition(self):

View file

@ -13,7 +13,9 @@ from distutils.version import LooseVersion
from .lib import openjpeg as opj
from .lib import openjp2 as opj2
version = "0.5.4"
# Do not change the format of this next line! Doing so risks breaking
# setup.py
version = "0.5.7"
_sv = LooseVersion(version)
version_tuple = _sv.version

View file

@ -1,4 +1,6 @@
from setuptools import setup, find_packages
import os
import re
import sys
kwargs = {'name': 'Glymur',
@ -38,7 +40,11 @@ clssfrs = ["Programming Language :: Python",
"Topic :: Software Development :: Libraries :: Python Modules"]
kwargs['classifiers'] = clssfrs
import glymur
kwargs['version'] = glymur.version.version
# Get the version string. Cannot do this by importing glymur!
version_file = os.path.join('glymur', 'version.py')
with open('glymur/version.py', 'rt') as fptr:
contents = fptr.read()
match = re.search('version\s*=\s*"(?P<version>\d*.\d*.\d*.*)"\n', contents)
kwargs['version'] = match.group('version')
setup(**kwargs)

View file

@ -1,3 +0,0 @@
numpy
contextlib2>=0.4.0
mock>=1.0.1