Merge branch 'release-0.5.6'

This commit is contained in:
John Evans 2013-10-13 12:31:00 -04:00
commit b7edcd5b43
36 changed files with 6069 additions and 5254 deletions

26
.travis.yml Normal file
View file

@ -0,0 +1,26 @@
language: python
python:
- "2.6"
- "2.7"
- "3.3"
before_install:
- sudo apt-get update -qq
- sudo apt-get install -qq python-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:
- 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:
- 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,3 +1,32 @@
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 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
that failed if OPJ_DATA_ROOT not set.
Sep 16, 2013 - v0.5.0 Added write support for 1.5.x. Added version module.
Aug 21, 2013 - v0.4.1 Fixed segfault with openjpeg 1.x when rlevel=-1
Aug 18, 2013 - v0.4.0 Added append method.
Aug 15, 2013 - v0.3.2 Fixed test bug where missing Pillow package caused test
failures.
Aug 14, 2013 - v0.3.1 Exposed mantissa, exponent, and guard_bits fields in QCC
and QCD segments. Exposed layers and code_block_size in COD segment.
Exposed precinct_size in COC segment.
Jul 31, 2013 - v0.3.0 Added support for official 2.0.0.
Jul 27, 2013 - v0.2.8 Fixed inconsistency regarding configuration

View file

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

View file

@ -1,43 +1,47 @@
----------------------------------
Advanced Installation Instructions
----------------------------------
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. This will, however, only
give you you basic read capabilities, so if you wish to take advantage
of more of glymur's features, you should install version 2.0 or
compile OpenJPEG 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.
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 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 of running Glymur's test suite. ::
and running OpenJPEG's test suite, check their instructions for all 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
$ export OPJ_DATA_ROOT=`pwd`/data
Glymur uses ctypes (for the moment) to access the openjp2 library, and
because ctypes access libraries in a platform-dependent manner, it is
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 openjp2 library. 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
but if you have **$XDG_CONFIG_HOME** defined, the path will be ::
but if you have the **XDG_CONFIG_HOME** environment variable defined,
the path will be ::
$XDG_CONFIG_HOME/glymur/glymurrc
@ -52,12 +56,11 @@ You may also include a line for the version 1.x openjpeg library if you have it
installed in a non-standard place, i.e. ::
[library]
openjp2: /opt/openjp2-svn/lib/libopenjp2.so
openjpeg: /not/the/usual/location/lib/libopenjpeg.so
'''''''''''''''''''''''''''''''''''''''''''
Package Management Suggestions for Testing
'''''''''''''''''''''''''''''''''''''''''''
''''''''''''''''''''''''''''''
Package Management Suggestions
''''''''''''''''''''''''''''''
You only need to read this section if you want detailed
platform-specific instructions on running as many tests as possible or wish to
@ -67,8 +70,9 @@ packages/RPMs/ports/whatever without going through pip.
Mac OS X
--------
All the necessary packages are available to use glymur with Python 3.3 via
MacPorts. You should install the following set of ports:
All the necessary packages are available to use glymur with Python 2.6, 2.7,
and 3.3 via MacPorts. For python 3.3, you should install the following set of
ports:
* python33
* py33-numpy
@ -80,71 +84,38 @@ MacPorts supplies both OpenJPEG 1.5.0 and OpenJPEG 2.0.0.
Linux
-----
For the most part, you only need python and numpy to run glymur, so on
just about all distributions you are already set to go (and you don't
need to mess around with a configuration file, as the openjpeg shared
libraries are found in the usual places thanks to your package manager).
In order to run as many tests as possible, however, the following Python
packages may also need to be installed. Consult your package manager
documentation or use pip.
Fedora 19
'''''''''
Fedora 18 ships with Python 3.3 and all the necessary RPMs are available to
run the maximum number of tests.
* setuptools
* matplotlib
* pillow
* contextlib2 (python 2.6, 2.7 only)
* mock (python 2.6, 2.7 only)
* ordereddict (python 2.6 only)
* python3
* python3-numpy
* python3-setuptools
* python3-matplotlib (for running tests)
* python3-matplotlib-tk (or whichever matplotlib backend you prefer)
* python3-pillow (for running tests)
Fedora 18
'''''''''
Fedora 18 ships with Python 3.3 and the following RPMs are available to
meet the minimal set of requirements for running glymur.
* python3
* python3-numpy
* python3-setuptools
For running the maximal number of tests, you also need
* python3-matplotlib
* python3-matplotlib-tk (or whichever matplotlib backend you prefer)
Pillow is also needed in order to run the maximum number of tests, so
go ahead and install Pillow via pip since Pillow is not available
in Fedora 18 default repositories::
$ yum install python3-devel # pip needs this in order to compile Pillow
$ yum install python3-pip
$ pip-python3 install Pillow --user
$ export PYTHONPATH=$HOME/.local/lib/python3.3/site-packages:$PYTHONPATH
Fedora 17
'''''''''
Fedora 17 ships with Python 2.7 and OpenJPEG 1.4. You should have the
following RPMs installed.
* python
* python-mock
* python-pip
* python-setuptools
* numpy
* matplotlib (optional)
In addition, you must install contextlib2 and Pillow via pip. ::
$ yum install python-devel # pip needs this in order to compile Pillow
$ pip-python install Pillow --user
$ pip-python install contextlib2 --user
$ export PYTHONPATH=$HOME/.local/lib/python2.7/site-packages:$PYTHONPATH
Glymur's been tested on the following linux platforms without any unexpected
difficulties:
* OpenSUSE 12.3
* Fedora 17, 18, 19
* Raspian
* Travis CI (currently Ubuntu 12.04?)
* CentOS 6.4
Windows
-------
32-bit WinPython 2.7.5 seems to work with OpenJPEG 1.X, 2.0, and the
development version, but still requires contextlib2 and mock to be
32-bit WinPython 2.7.5 seemed to work with OpenJPEG 1.X, 2.0, and the
development version, but still required contextlib2 and mock to be
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).
64-bit windows is completely untested.
with OpenJPEG 2.0, so I would suggest using the development version with
that configuration. I no longer have any access to a windows machine,
so I cannot currently offer much guidance here.
'''''''
@ -154,8 +125,8 @@ Testing
There are two environment variables you may wish to set before running the
tests.
* **OPJ_DATA_ROOT** - points to directory for OpenJPEG test data
* **FORMAT_CORPUS_ROOT** - points to directory for format-corpus repository (see https://github.com/openplanets/format-corpus if you wish, but you really don't need to bother with this)
* **OPJ_DATA_ROOT** - points to directory for OpenJPEG test data (see above)
* **FORMAT_CORPUS_DATA_ROOT** - points to directory for format-corpus repository (see https://github.com/openplanets/format-corpus if you wish, but you really don't need to bother with this)
Setting these two environment variables is not required, as any tests using
either of them will be skipped.
@ -174,7 +145,7 @@ or from the command line. ::
Quite a few tests are currently skipped. These include tests whose
OpenJPEG counterparts are already failing, and others which do pass but
still produce heaps of output on stderr. Rather than let this swamp
the signal (that most of the tests are actually passing), they've been
the signal (that most of those tests are actually passing), they've been
filtered out for now. There are also more skipped tests on Python 2.7
than on Python 3.3. The important part is whether or not any test
errors are reported at the end.

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,8 +34,40 @@ 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` : ::
<?xml version="1.0"?>
<info>
<locality>
<city>Boston</city>
<snowfall>24.9 inches</snowfall>
</locality>
<locality>
<city>Portland</city>
<snowfall>31.9 inches</snowfall>
</locality>
<locality>
<city>New York City</city>
<snowfall>11.4 inches</snowfall>
</locality>
</info>
The **append** method can add an XML box as shown below::
>>> import shutil
>>> import glymur
>>> shutil.copyfile(glymur.data.nemo(), 'myfile.jp2')
>>> from xml.etree import cElementTree as ET
>>> jp2 = glymur.Jp2k('myfile.jp2')
>>> xmlbox = glymur.jp2box.XMLBox(filename='data.xml')
>>> jp2.append(xmlbox)
>>> print(jp2)
... 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
codestream provided by `goodstuff.j2k` (a file consisting of a raw codestream),
@ -75,8 +107,8 @@ two additional boxes (image header and color specification) contained in the
JP2 header superbox.
XML boxes are not in the minimal set of box requirements for the JP2 format, so
in order to add an XML box into the mix, we'll need to specify all of the
boxes. If you already have a JP2 jacket in place, you can just reuse it,
in order to add an XML box into the mix before the codestream box, we'll need to
re-specify all of the boxes. If you already have a JP2 jacket in place, you can just reuse that,
though. Take the following example content in an XML file `favorites.xml` : ::
<?xml version="1.0"?>
@ -84,10 +116,11 @@ though. Take the following example content in an XML file `favorites.xml` : ::
<category>Light Ale</category>
</favorite_things>
and add it after the JP2 header box, but before the codestream box ::
In order to add the XML after the JP2 header box, but before the codestream box,
the following will work. ::
>>> boxes = jp2.box # The box attribute is the list of JP2 boxes
>>> xmlbox = glymur.jp2box.XMLBox(file='favorites.xml')
>>> xmlbox = glymur.jp2box.XMLBox(filename='favorites.xml')
>>> boxes.insert(3, xmlbox)
>>> jp2_xml = jp2.wrap("newfile_with_xml.jp2", boxes=boxes)
>>> print(jp2_xml)
@ -119,18 +152,23 @@ and add it after the JP2 header box, but before the codestream box ::
. (truncated)
.
Create an image with an alpha layer?
As to the question of which method you should use, **append** or **wrap**,
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?
====================================
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
@ -181,7 +219,7 @@ Here's how the Preview application on the mac shows the RGBA image.
.. image:: goodstuff_alpha.png
Work with XMP UUIDs?
work with XMP UUIDs?
====================
The example JP2 file shipped with glymur has an XMP UUID. ::

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
@ -20,15 +19,14 @@ OpenJPEG Installation
=====================
Glymur will read JPEG 2000 images with versions 1.3, 1.4, 1.5, 2.0,
and the trunk/development version of OpenJPEG. Writing images is
only supported with the 2.0 series, however, and the trunk/development
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,14 +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; in fact most of them are skipped if you
are relying on OpenJPEG 1.X. The important thing, though, is whether or
not any tests fail.

View file

@ -1,9 +1,3 @@
------------
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
-------

View file

@ -2,12 +2,17 @@
"""
import sys
from glymur import version
__version__ = version.version
from .jp2k import Jp2k
from .jp2dump import jp2dump
from . import data
# unittest2 only in python-2.6 (pylint/python2.7 issue)
# pylint: disable=F0401
def runtests():
"""Discover and run all tests for the glymur package.
"""

File diff suppressed because it is too large Load diff

View file

@ -24,6 +24,7 @@ import uuid
import warnings
import xml.etree.cElementTree as ET
if sys.hexversion < 0x02070000:
# pylint: disable=F0401,E0611
from ordereddict import OrderedDict
from xml.etree.cElementTree import XMLParserError as ParseError
else:
@ -361,7 +362,8 @@ class _ICCProfile(object):
header['Connection Space'] = data
data = struct.unpack('>HHHHHH', self._raw_buffer[24:36])
header['Datetime'] = datetime.datetime(*data)
header['Datetime'] = datetime.datetime(data[0], data[1], data[2],
data[3], data[4], data[5])
header['File Signature'] = read_buffer[36:40].decode('utf-8')
if read_buffer[40:44] == b'\x00\x00\x00\x00':
header['Platform'] = 'unrecognized'
@ -369,10 +371,9 @@ class _ICCProfile(object):
header['Platform'] = read_buffer[40:44].decode('utf-8')
fval, = struct.unpack('>I', read_buffer[44:48])
flags = 'embedded, ' if fval & 0x01 else 'not embedded, '
flags += 'cannot ' if fval & 0x02 else 'can '
flags += 'be used independently'
header['Flags'] = flags
flags = "{0}embedded, {1} be used independently"
header['Flags'] = flags.format('' if fval & 0x01 else 'not ',
'cannot' if fval & 0x02 else 'can')
header['Device Manufacturer'] = read_buffer[48:52].decode('utf-8')
if read_buffer[52:56] == b'\x00\x00\x00\x00':
@ -382,11 +383,11 @@ class _ICCProfile(object):
header['Device Model'] = device_model
val, = struct.unpack('>Q', read_buffer[56:64])
attr = 'transparency, ' if val & 0x01 else 'reflective, '
attr += 'matte, ' if val & 0x02 else 'glossy, '
attr += 'negative ' if val & 0x04 else 'positive '
attr += 'media polarity, '
attr += 'black and white media' if val & 0x08 else 'color media'
attr = "{0}, {1}, {2} media polarity, {3} media"
attr = attr.format('transparency' if val & 0x01 else 'reflective',
'matte' if val & 0x02 else 'glossy',
'negative' if val & 0x04 else 'positive',
'black and white' if val & 0x08 else 'color')
header['Device Attributes'] = attr
rval, = struct.unpack('>I', read_buffer[64:68])
@ -567,7 +568,7 @@ class CodestreamHeaderBox(Jp2kBox):
Returns
-------
AssociationBox instance
CodestreamHeaderBox instance
"""
box = CodestreamHeaderBox(length=length, offset=offset)
@ -626,7 +627,7 @@ class CompositingLayerHeaderBox(Jp2kBox):
Returns
-------
AssociationBox instance
CompositingLayerHeaderBox instance
"""
box = CompositingLayerHeaderBox(length=length, offset=offset)
@ -637,7 +638,7 @@ class CompositingLayerHeaderBox(Jp2kBox):
class ComponentMappingBox(Jp2kBox):
"""Container for channel identification information.
"""Container for component mapping information.
Attributes
----------
@ -1237,43 +1238,68 @@ class PaletteBox(Jp2kBox):
bps = [((x & 0x07f) + 1) for x in data]
signed = [((x & 0x80) > 1) for x in data]
# Each palette component is padded out to the next largest byte.
# That means a list comprehension does this in one shot.
row_nbytes = sum([int(math.ceil(x/8.0)) for x in bps])
# Form the format string so that we can intelligently unpack the
# colormap. We have to do this because it is possible that the
# colormap columns could have different datatypes.
#
# This means that we store the palette as a list of 1D arrays,
# which reverses the usual indexing scheme.
palette = []
fmt = '>'
row_nbytes = 0
for j in range(num_columns):
if bps[j] <= 8:
fmt += 'B'
row_nbytes += 1
palette.append(np.zeros(num_entries, dtype=np.uint8))
elif bps[j] <= 16:
fmt += 'H'
row_nbytes += 2
palette.append(np.zeros(num_entries, dtype=np.uint16))
elif bps[j] <= 32:
fmt += 'I'
row_nbytes += 4
palette.append(np.zeros(num_entries, dtype=np.uint32))
else:
msg = 'Unsupported palette bitdepth (%d).'.format(bps[j])
raise IOError(msg)
read_buffer = fptr.read(num_entries * row_nbytes)
palette = _buffer2palette(read_buffer, num_entries, num_columns, bps)
for j in range(num_entries):
row_buffer = read_buffer[(row_nbytes * j):(row_nbytes * (j + 1))]
row = struct.unpack(fmt, row_buffer)
for k in range(num_columns):
palette[k][j] = row[k]
box = PaletteBox(palette, bps, signed, length=length,
offset=offset)
box = PaletteBox(palette, bps, signed, length=length, offset=offset)
return box
def _buffer2palette(read_buffer, num_rows, num_cols, bps):
"""Construct the palette from the buffer read from file.
Parameters
----------
read_buffer : iterable
Byte array of palette information read from file.
num_rows, num_cols : int
Size of palette.
bps : iterable
Bits per sample for each channel.
Returns
-------
palette : list of 1D arrays
Each 1D array corresponds to a channel.
"""
row_nbytes = 0
palette = []
fmt = '>'
for j in range(num_cols):
if bps[j] <= 8:
row_nbytes += 1
fmt += 'B'
palette.append(np.zeros(num_rows, dtype=np.uint8))
elif bps[j] <= 16:
row_nbytes += 2
fmt += 'H'
palette.append(np.zeros(num_rows, dtype=np.uint16))
elif bps[j] <= 32:
row_nbytes += 4
fmt += 'I'
palette.append(np.zeros(num_rows, dtype=np.uint32))
else:
msg = 'Unsupported palette bitdepth (%d).'.format(bps[j])
raise IOError(msg)
for j in range(num_rows):
row_buffer = read_buffer[(row_nbytes * j):(row_nbytes * (j + 1))]
row = struct.unpack(fmt, row_buffer)
for k in range(num_cols):
palette[k][j] = row[k]
return palette
# Map rreq codes to display text.
_READER_REQUIREMENTS_DISPLAY = {
0: 'File not completely understood',
@ -1434,52 +1460,18 @@ class ReaderRequirementsBox(Jp2kBox):
"""
read_buffer = fptr.read(1)
mask_length, = struct.unpack('>B', read_buffer)
if mask_length == 1:
mask_format = 'B'
elif mask_length == 2:
mask_format = 'H'
elif mask_length == 4:
mask_format = 'I'
else:
msg = 'Unhandled reader requirements box mask length (%d).'
msg %= mask_length
raise RuntimeError(msg)
# Fully Understands Aspect Mask
# Decodes Completely Mask
read_buffer = fptr.read(2 * mask_length)
data = struct.unpack('>' + mask_format * 2, read_buffer)
fuam = data[0]
dcm = data[1]
read_buffer = fptr.read(2)
num_standard_flags, = struct.unpack('>H', read_buffer)
# The mask length tells us the format string to use when unpacking
# from the buffer read from file.
mask_format = {1: 'B', 2: 'H', 4: 'I'}[mask_length]
fuam, dcm = struct.unpack('>' + mask_format * 2, read_buffer)
# Read in standard flags and standard masks. Each standard flag should
# be two bytes, but the standard mask flag is as long as specified by
# the mask length.
read_buffer = fptr.read(num_standard_flags * (2 + mask_length))
data = struct.unpack('>' + ('H' + mask_format) * num_standard_flags,
read_buffer)
standard_flag = data[0:num_standard_flags * 2:2]
standard_mask = data[1:num_standard_flags * 2:2]
# Vendor features
read_buffer = fptr.read(2)
num_vendor_features, = struct.unpack('>H', read_buffer)
# Each vendor feature consists of a 16-byte UUID plus a mask whose
# length is specified by, you guessed it, "mask_length".
entry_length = 16 + mask_length
read_buffer = fptr.read(num_vendor_features * entry_length)
vendor_feature = []
vendor_mask = []
for j in range(num_vendor_features):
ubuffer = read_buffer[j * entry_length:(j + 1) * entry_length]
vendor_feature.append(uuid.UUID(bytes=ubuffer[0:16]))
vmask = struct.unpack('>' + mask_format, ubuffer[16:])
vendor_mask.append(vmask)
standard_flag, standard_mask = _parse_standard_flag(fptr, mask_length)
vendor_feature, vendor_mask = _parse_vendor_features(fptr, mask_length)
box = ReaderRequirementsBox(fuam, dcm, standard_flag, standard_mask,
vendor_feature, vendor_mask,
@ -1487,6 +1479,74 @@ class ReaderRequirementsBox(Jp2kBox):
return box
def _parse_standard_flag(fptr, mask_length):
"""Construct standard flag, standard mask data from the file.
Specifically working on Reader Requirements box.
Parameters
----------
fptr : file object
File object for JP2K file.
mask_length : int
Length of standard mask flag
"""
# The mask length tells us the format string to use when unpacking
# from the buffer read from file.
mask_format = {1: 'B', 2: 'H', 4: 'I'}[mask_length]
read_buffer = fptr.read(2)
num_standard_flags, = struct.unpack('>H', read_buffer)
# Read in standard flags and standard masks. Each standard flag should
# be two bytes, but the standard mask flag is as long as specified by
# the mask length.
read_buffer = fptr.read(num_standard_flags * (2 + mask_length))
fmt = '>' + ('H' + mask_format) * num_standard_flags
data = struct.unpack(fmt, read_buffer)
standard_flag = data[0:num_standard_flags * 2:2]
standard_mask = data[1:num_standard_flags * 2:2]
return standard_flag, standard_mask
def _parse_vendor_features(fptr, mask_length):
"""Construct vendor features, vendor mask data from the file.
Specifically working on Reader Requirements box.
Parameters
----------
fptr : file object
File object for JP2K file.
mask_length : int
Length of vendor mask flag
"""
# The mask length tells us the format string to use when unpacking
# from the buffer read from file.
mask_format = {1: 'B', 2: 'H', 4: 'I'}[mask_length]
read_buffer = fptr.read(2)
num_vendor_features, = struct.unpack('>H', read_buffer)
# Each vendor feature consists of a 16-byte UUID plus a mask whose
# length is specified by, you guessed it, "mask_length".
entry_length = 16 + mask_length
read_buffer = fptr.read(num_vendor_features * entry_length)
vendor_feature = []
vendor_mask = []
for j in range(num_vendor_features):
ubuffer = read_buffer[j * entry_length:(j + 1) * entry_length]
vendor_feature.append(uuid.UUID(bytes=ubuffer[0:16]))
vmask = struct.unpack('>' + mask_format, ubuffer[16:])
vendor_mask.append(vmask)
return vendor_feature, vendor_mask
class ResolutionBox(Jp2kBox):
"""Container for Resolution superbox information.
@ -1577,7 +1637,7 @@ class CaptureResolutionBox(Jp2kBox):
@staticmethod
def parse(fptr, offset, length):
"""Parse Resolution box.
"""Parse CaptureResolutionBox.
Parameters
----------
@ -1634,7 +1694,7 @@ class DisplayResolutionBox(Jp2kBox):
@staticmethod
def parse(fptr, offset, length):
"""Parse Resolution box.
"""Parse display resolution box.
Parameters
----------
@ -1789,16 +1849,33 @@ class XMLBox(Jp2kBox):
"""
num_bytes = offset + length - fptr.tell()
read_buffer = fptr.read(num_bytes)
text = read_buffer.decode('utf-8')
try:
text = read_buffer.decode('utf-8')
except UnicodeDecodeError as ude:
# Possibly bad string of bytes to begin with.
# Try to search for <?xml and go from there.
decl_start = read_buffer.find(b'<?xml')
if decl_start > -1:
text = read_buffer[decl_start:].decode('utf-8')
else:
raise
# Strip out any trailing nulls.
text = text.rstrip('\0')
# Let the user know that the XML box was problematic.
msg = 'A UnicodeDecodeError was encountered parsing an XML box at '
msg += 'byte position {0} ({1}), but the XML was still recovered.'
msg = msg.format(offset, ude.reason)
warnings.warn(msg, UserWarning)
# Strip out any trailing nulls, as they can foul up XML parsing.
text = text.rstrip(chr(0))
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: "{0}"'
msg = 'A problem was encountered while parsing an XML box:'
msg += '\n\n\t"{0}"\n\nNo XML was retrieved.'
msg = msg.format(str(parse_error))
warnings.warn(msg, UserWarning)
xml = None
@ -1968,7 +2045,7 @@ class DataEntryURLBox(Jp2kBox):
@staticmethod
def parse(fptr, offset, length):
"""Parse Data Entry URL box.
"""Parse data entry URL box.
Parameters
----------
@ -2670,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

@ -1,6 +1,7 @@
"""
Entry point for jp2dump script.
"""
import warnings
from .jp2k import Jp2k
@ -15,8 +16,21 @@ def jp2dump(filename, codestream=False):
codestream : optional, logical scalar
Whether or not to dump codestream contents.
"""
j = Jp2k(filename)
if codestream:
print(j.get_codestream(header_only=False))
else:
print(j)
with warnings.catch_warnings(record=True) as wctx:
# JP2 metadata can be extensive, so don't print any warnings until we
# are done with the metadata.
j = Jp2k(filename)
if codestream:
print(j.get_codestream(header_only=False))
else:
print(j)
# Re-emit any warnings that may have been suppressed.
if len(wctx) > 0:
print("\n")
for warning in wctx:
print("{0}:{1}: {2}: {3}".format(warning.filename,
warning.lineno,
warning.category.__name__,
warning.message))

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,6 +1,9 @@
"""
Configure glymur to use installed libraries if possible.
"""
# configparser is new in python3 (pylint/python-2.7)
# pylint: disable=F0401
import ctypes
from ctypes.util import find_library
import os

View file

@ -2,7 +2,7 @@
Wraps individual functions in openjp2 library.
"""
# pylint: disable=C0302,R0903
# pylint: disable=C0302,R0903,W0201
import ctypes
import sys
@ -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)),
@ -1381,7 +1385,6 @@ def write_tile(codec, tile_index, data, data_size, stream):
def set_error_message(msg):
"""The openjpeg error handler has recorded an error message."""
global ERROR_MSG_LST
ERROR_MSG_LST.append(msg)

View file

@ -6,10 +6,16 @@
import ctypes
import sys
import numpy as np
from .config import glymur_config
_, OPENJPEG = glymur_config()
PATH_LEN = 4096 # maximum allowed size for filenames
# Maximum number of tile parts expected by JPWL: increase at your will
JPWL_MAX_NO_TILESPECS = 16
J2K_MAXRLVLS = 33 # Number of maximum resolution level authorized
PATH_LEN = 4096 # maximum allowed size for filenames
def version():
@ -52,14 +58,8 @@ class CommonStructType(ctypes.Structure):
("mj2_handle", ctypes.c_void_p)]
class DecompressionInfoType(ctypes.Structure):
"""This is for decompression contexts.
Corresponds to dinfo_t type in openjpeg headers.
"""
pass
STREAM_READ = 0x0001 # The stream was opened for reading.
STREAM_WRITE = 0x0002 # The stream was opened for writing.
class CioType(ctypes.Structure):
"""Byte input-output stream (CIO)
@ -80,6 +80,266 @@ class CioType(ctypes.Structure):
("bp", ctypes.c_char_p)]
class CompressionInfoType(CommonStructType):
"""Common fields between JPEG-2000 compression and decompression contexts.
This is for compression contexts. Corresponds to common_struct_t.
"""
pass
class PocType(ctypes.Structure):
"""Progression order changes."""
_fields_ = [("resno", ctypes.c_int),
# Resolution num start, Component num start, given by POC
("compno0", ctypes.c_int),
# Layer num end,Resolution num end, Component num end, given by POC
("layno1", ctypes.c_int),
("resno1", ctypes.c_int),
("compno1", ctypes.c_int),
# Layer num start,Precinct num start, Precinct num end
("layno0", ctypes.c_int),
("precno0", ctypes.c_int),
("precno1", ctypes.c_int),
# Progression order enum
# OPJ_PROG_ORDER prg1,prg;
("prg1", ctypes.c_int),
("prg", ctypes.c_int),
# Progression order string
# char progorder[5];
("progorder", ctypes.c_char * 5),
# Tile number
# int tile;
("tile", ctypes.c_int),
# /** Start and end values for Tile width and height*/
# int tx0,tx1,ty0,ty1;
("tx0", ctypes.c_int),
("tx1", ctypes.c_int),
("ty0", ctypes.c_int),
("ty1", ctypes.c_int),
# /** Start value, initialised in pi_initialise_encode*/
# int layS, resS, compS, prcS;
("layS", ctypes.c_int),
("resS", ctypes.c_int),
("compS", ctypes.c_int),
("prcS", ctypes.c_int),
# /** End value, initialised in pi_initialise_encode */
# int layE, resE, compE, prcE;
("layE", ctypes.c_int),
("resE", ctypes.c_int),
("compE", ctypes.c_int),
("prcE", ctypes.c_int),
# Start and end values of Tile width and height, initialised in
# pi_initialise_encode int txS,txE,tyS,tyE,dx,dy;
("txS", ctypes.c_int),
("txE", ctypes.c_int),
("tyS", ctypes.c_int),
("tyE", ctypes.c_int),
("dx", ctypes.c_int),
("dy", ctypes.c_int),
# Temporary values for Tile parts, initialised in pi_create_encode
# int lay_t, res_t, comp_t, prc_t,tx0_t,ty0_t;
("lay_t", ctypes.c_int),
("res_t", ctypes.c_int),
("comp_t", ctypes.c_int),
("prc_t", ctypes.c_int),
("tx0_t", ctypes.c_int),
("ty0_t", ctypes.c_int)]
class CompressionParametersType(ctypes.Structure):
"""Compression parameters.
Corresponds to cparameters_t type in openjp2 headers.
"""
_fields_ = [
# size of tile:
# tile_size_on = false (not in argument) or
# = true (in argument)
("tile_size_on", ctypes.c_int),
# XTOsiz, YTOsiz
("cp_tx0", ctypes.c_int),
("cp_ty0", ctypes.c_int),
# XTsiz, YTsiz
("cp_tdx", ctypes.c_int),
("cp_tdy", ctypes.c_int),
# allocation by rate/distortion
("cp_disto_alloc", ctypes.c_int),
# allocation by fixed layer
("cp_fixed_alloc", ctypes.c_int),
# add fixed_quality
("cp_fixed_quality", ctypes.c_int),
# fixed layer
("cp_matrice", ctypes.c_void_p),
# comment for coding
("cp_comment", ctypes.c_char_p),
# csty : coding style
("csty", ctypes.c_int),
# progression order (default OPJ_LRCP)
("prog_order", ctypes.c_int),
# progression order changes
("poc", PocType * 32),
# number of progression order changes (POC), default to 0
("numpocs", ctypes.c_uint),
# number of layers
("tcp_numlayers", ctypes.c_int),
# rates of layers
("tcp_rates", ctypes.c_float * 100),
# different psnr for successive layers
("tcp_distoratio", ctypes.c_float * 100),
# number of resolutions
("numresolution", ctypes.c_int),
# initial code block width, default to 64
("cblockw_init", ctypes.c_int),
# initial code block height, default to 64
("cblockh_init", ctypes.c_int),
# mode switch (cblk_style)
("mode", ctypes.c_int),
# 1 : use the irreversible DWT 9-7
# 0 : use lossless compression (default)
("irreversible", ctypes.c_int),
# region of interest: affected component in [0..3], -1 means no ROI
("roi_compno", ctypes.c_int),
# region of interest: upshift value
("roi_shift", ctypes.c_int),
# number of precinct size specifications
("res_spec", ctypes.c_int),
# initial precinct width
("prcw_init", ctypes.c_int * J2K_MAXRLVLS),
# initial precinct height
("prch_init", ctypes.c_int * J2K_MAXRLVLS),
# input file name
("infile", ctypes.c_char * PATH_LEN),
# output file name
("outfile", ctypes.c_char * PATH_LEN),
# DEPRECATED.
("index_on", ctypes.c_int),
# DEPRECATED.
("index", ctypes.c_char * PATH_LEN),
# subimage encoding: origin image offset in x direction
# subimage encoding: origin image offset in y direction
("image_offset_x0", ctypes.c_int),
("image_offset_y0", ctypes.c_int),
# subsampling value for dx
# subsampling value for dy
("subsampling_dx", ctypes.c_int),
("subsampling_dy", ctypes.c_int),
# input file format 0: PGX, 1: PxM, 2: BMP 3:TIF
# output file format 0: J2K, 1: JP2, 2: JPT
("decod_format", ctypes.c_int),
("cod_format", ctypes.c_int),
# JPWL encoding parameters
# enables writing of EPC in MH, thus activating JPWL
("jpwl_epc_on", ctypes.c_int),
# error protection method for MH (0,1,16,32,37-128)
("jpwl_hprot_mh", ctypes.c_int),
# tile number of header protection specification (>=0)
("jpwl_hprot_tph_tileno", ctypes.c_int * JPWL_MAX_NO_TILESPECS),
# error protection methods for TPHs (0,1,16,32,37-128)
("jpwl_hprot_tph", ctypes.c_int * JPWL_MAX_NO_TILESPECS),
# tile number of packet protection specification (>=0)
("jpwl_pprot_tileno", ctypes.c_int * JPWL_MAX_NO_TILESPECS),
# packet number of packet protection specification (>=0)
("jpwl_pprot_packno", ctypes.c_int * JPWL_MAX_NO_TILESPECS),
# error protection methods for packets (0,1,16,32,37-128)
("jpwl_pprot", ctypes.c_int * JPWL_MAX_NO_TILESPECS),
# enables writing of ESD, (0=no/1/2 bytes)
("jpwl_sens_size", ctypes.c_int),
# sensitivity addressing size (0=auto/2/4 bytes)
("jpwl_sens_addr", ctypes.c_int),
# sensitivity range (0-3)
("jpwl_sens_range", ctypes.c_int),
# sensitivity method for MH (-1=no,0-7)
("jpwl_sens_mh", ctypes.c_int),
# tile number of sensitivity specification (>=0)
("jpwl_sens_tph_tileno", ctypes.c_int * JPWL_MAX_NO_TILESPECS),
# sensitivity methods for TPHs (-1=no,0-7)
("jpwl_sens_tph", ctypes.c_int * JPWL_MAX_NO_TILESPECS),
# Digital Cinema compliance 0-not compliant, 1-compliant
("cp_cinema", ctypes.c_int),
# Maximum rate for each component.
# If == 0, component size limitation is not considered
("max_comp_size", ctypes.c_int),
# Profile name
("cp_rsiz", ctypes.c_int),
# Tile part generation
("tp_on", ctypes.c_uint8),
# Flag for Tile part generation
("tp_flag", ctypes.c_uint8),
# MCT (multiple component transform)
("tcp_mct", ctypes.c_uint8),
# Enable JPIP indexing
("jpip_on", ctypes.c_int)]
class DecompressionInfoType(ctypes.Structure):
"""This is for decompression contexts.
Corresponds to dinfo_t type in openjpeg headers.
"""
pass
class DecompressionParametersType(ctypes.Structure):
"""Decompression parameters.
@ -111,23 +371,51 @@ class DecompressionParametersType(ctypes.Structure):
_fields_.append(("flags", ctypes.c_uint))
class ImageCompType(ctypes.Structure):
"""Defines a single image component.
Corresponds to image_comp_t type in openjpeg.
class ImageComptParmType(ctypes.Structure):
"""Component parameters structure used by the opj_image_create function.
"""
_fields_ = [("dx", ctypes.c_int),
("dy", ctypes.c_int),
("w", ctypes.c_int),
("h", ctypes.c_int),
("x0", ctypes.c_int),
("y0", ctypes.c_int),
("prec", ctypes.c_int),
("bpp", ctypes.c_int),
("sgnd", ctypes.c_int),
("resno_decoded", ctypes.c_int),
("factor", ctypes.c_int),
("data", ctypes.POINTER(ctypes.c_int))]
_fields_ = [
# XRsiz: horizontal separation of a sample of ith component with
# respect to the reference grid
("dx", ctypes.c_int),
# YRsiz: vertical separation of a sample of ith component with
# respect to the reference grid */
("dy", ctypes.c_int),
# data width, height
("w", ctypes.c_int),
("h", ctypes.c_int),
# x component offset compared to the whole image
# y component offset compared to the whole image
("x0", ctypes.c_int),
("y0", ctypes.c_int),
# precision
('prec', ctypes.c_int),
# image depth in bits
('bpp', ctypes.c_int),
# signed (1) / unsigned (0)
('sgnd', ctypes.c_int)]
class ImageCompType(ctypes.Structure):
"""Defines a single image component. """
_fields_ = [("dx", ctypes.c_int),
("dy", ctypes.c_int),
("w", ctypes.c_int),
("h", ctypes.c_int),
("x0", ctypes.c_int),
("y0", ctypes.c_int),
("prec", ctypes.c_int),
("bpp", ctypes.c_int),
("sgnd", ctypes.c_int),
("resno_decoded", ctypes.c_int),
("factor", ctypes.c_int),
("data", ctypes.POINTER(ctypes.c_int))]
class ImageType(ctypes.Structure):
@ -146,16 +434,22 @@ class ImageType(ctypes.Structure):
("icc_profile_len", ctypes.c_int)]
def cio_open(cinfo, src):
def cio_open(cinfo, src=None):
"""Wrapper for openjpeg library function opj_cio_open."""
argtypes = [ctypes.POINTER(CommonStructType), ctypes.c_char_p,
ctypes.c_int]
OPENJPEG.opj_cio_open.argtypes = argtypes
OPENJPEG.opj_cio_open.restype = ctypes.POINTER(CioType)
if src is None:
length = 0
else:
length = len(src)
cio = OPENJPEG.opj_cio_open(ctypes.cast(cinfo,
ctypes.POINTER(CommonStructType)),
src, len(src))
src,
length)
return cio
@ -166,6 +460,24 @@ def cio_close(cio):
OPENJPEG.opj_cio_close(cio)
def cio_tell(cio):
"""Get position in byte stream."""
OPENJPEG.cio_tell.argtypes = [ctypes.POINTER(CioType)]
OPENJPEG.cio_tell.restype = ctypes.c_int
pos = OPENJPEG.cio_tell(cio)
return pos
def create_compress(fmt):
"""Wrapper for openjpeg library function opj_create_compress.
Creates a J2K/JPT/JP2 compression structure.
"""
OPENJPEG.opj_create_compress.argtypes = [ctypes.c_int]
OPENJPEG.opj_create_compress.restype = ctypes.POINTER(CompressionInfoType)
cinfo = OPENJPEG.opj_create_compress(fmt)
return cinfo
def create_decompress(fmt):
"""Wraps openjpeg library function opj_create_decompress.
"""
@ -186,6 +498,38 @@ def decode(dinfo, cio):
return image
def destroy_compress(cinfo):
"""Wrapper for openjpeg library function opj_destroy_compress.
Release resources for a compressor handle.
"""
argtypes = [ctypes.POINTER(CompressionInfoType)]
OPENJPEG.opj_destroy_compress.argtypes = argtypes
OPENJPEG.opj_destroy_compress(cinfo)
def encode(cinfo, cio, image):
"""Wrapper for openjpeg library function opj_encode.
Encodes an image into a JPEG-2000 codestream.
Parameters
----------
cinfo : compression handle
cio : output buffer stream
image : image to encode
"""
argtypes = [ctypes.POINTER(CompressionInfoType),
ctypes.POINTER(CioType),
ctypes.POINTER(ImageType)]
OPENJPEG.opj_encode.argtypes = argtypes
OPENJPEG.opj_encode.restype = ctypes.c_int
status = OPENJPEG.opj_encode(cinfo, cio, image)
return status
def destroy_decompress(dinfo):
"""Wraps openjpeg library function opj_destroy_decompress."""
argtypes = [ctypes.POINTER(DecompressionInfoType)]
@ -193,12 +537,78 @@ def destroy_decompress(dinfo):
OPENJPEG.opj_destroy_decompress(dinfo)
def image_cmptparm_t_from_np(np_image):
"""Return appropriate image_cmptparm_t based on given numpy array.
"""
try:
num_comps = np_image.shape[2]
except IndexError:
num_comps = 1
cmpt_parm_array_t = ImageCmptparmType * num_comps
tarr = cmpt_parm_array_t()
if np_image.dtype == np.uint8:
prec = 8
bpp = 8
sgnd = 0
elif np_image.dtype == np.int8:
prec = 8
bpp = 8
sgnd = 1
elif np_image.dtype == np.uint16:
prec = 16
bpp = 16
sgnd = 0
elif np_image.dtype == np.int16:
prec = 16
bpp = 16
sgnd = 1
else:
raise(TypeError("unhandled"))
for j in range(0, num_comps):
tarr[j].dx = 1
tarr[j].dy = 1
tarr[j].w = np_image.shape[1]
tarr[j].h = np_image.shape[0]
tarr[j].x0 = 0
tarr[j].y0 = 0
tarr[j].prec = prec
tarr[j].bpp = bpp
tarr[j].sgnd = sgnd
return(tarr)
def image_create(cmptparms, cspace):
"""Wrapper for openjpeg library function opj_image_create.
"""
OPENJPEG.opj_image_create.argtypes = [ctypes.c_int,
ctypes.POINTER(ImageComptParmType),
ctypes.c_int]
OPENJPEG.opj_image_create.restype = ctypes.POINTER(ImageType)
image = OPENJPEG.opj_image_create(len(cmptparms), cmptparms, cspace)
return(image)
def image_destroy(image):
"""Wraps openjpeg library function opj_image_destroy."""
OPENJPEG.opj_image_destroy.argtypes = [ctypes.POINTER(ImageType)]
OPENJPEG.opj_image_destroy(image)
def set_default_encoder_parameters():
"""Wrapper for openjpeg library function opj_set_default_encoder_parameters.
"""
cparams = CompressionParametersType()
argtypes = [ctypes.POINTER(CompressionParametersType)]
OPENJPEG.opj_set_default_encoder_parameters.argtypes = argtypes
OPENJPEG.opj_set_default_encoder_parameters(ctypes.byref(cparams))
return cparams
def set_default_decoder_parameters(dparams_p):
"""Wrapper for opj_set_default_decoder_parameters.
"""
@ -219,6 +629,15 @@ def set_event_mgr(dinfo, event_mgr, context=None):
event_mgr, context)
def setup_encoder(cinfo, cparameters, image):
"""Wrapper for openjpeg library function opj_setup_decoder."""
argtypes = [ctypes.POINTER(CompressionInfoType),
ctypes.POINTER(CompressionParametersType),
ctypes.POINTER(ImageType)]
OPENJPEG.opj_setup_encoder.argtypes = argtypes
OPENJPEG.opj_setup_encoder(cinfo, cparameters, image)
def setup_decoder(dinfo, dparams):
"""Wrapper for openjpeg library function opj_setup_decoder."""
argtypes = [ctypes.POINTER(DecompressionInfoType),

View file

@ -1 +1,3 @@
#from .test_openjp2 import TestOpenJP2 as openjp2
"""
Test suite for openjp2, openjpeg low-level functionality.
"""

View file

@ -1,9 +1,14 @@
#pylint: disable-all
import doctest
"""
Tests for libopenjp2 wrapping functions.
"""
# R0904: Seems like pylint is fooled in this situation
# W0142: using kwargs is ok in this context
# pylint: disable=R0904,W0142
# unittest2 is python-2.6 only (pylint/python-2.7)
# pylint: disable=F0401
import os
import pkg_resources
import shutil
import struct
import sys
import tempfile
@ -15,28 +20,28 @@ else:
import numpy as np
import glymur
from glymur.lib import openjp2
OPENJP2_IS_V2_OFFICIAL = False
if glymur.lib.openjp2.OPENJP2 is not None:
if not hasattr(glymur.lib.openjp2.OPENJP2,
if openjp2.OPENJP2 is not None:
if not hasattr(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(glymur.lib.openjp2.OPENJP2 is None,
@unittest.skipIf(openjp2.OPENJP2 is None,
"Missing openjp2 library.")
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL, "API followed here specific to V2.0+")
class TestOpenJP2(unittest.TestCase):
"""Test openjp2 library functionality.
def setUp(self):
pass
Some tests correspond to those in the openjpeg test suite.
"""
def tearDown(self):
pass
def test_set_default_encoder_parameters(self):
cparams = glymur.lib._openjp2.set_default_encoder_parameters()
def test_default_encoder_parameters(self):
"""Ensure that the encoder structure is clean upon init."""
cparams = openjp2.set_default_encoder_parameters()
self.assertEqual(cparams.res_spec, 0)
self.assertEqual(cparams.cblockw_init, 64)
@ -52,8 +57,9 @@ class TestOpenJP2(unittest.TestCase):
self.assertEqual(cparams.irreversible, 0)
def test_set_default_decoder_parameters(self):
dparams = glymur.lib._openjp2.set_default_decoder_parameters()
def test_default_decoder_parameters(self):
"""Tests that the structure is clean upon initialization"""
dparams = openjp2.set_default_decoder_parameters()
self.assertEqual(dparams.DA_x0, 0)
self.assertEqual(dparams.DA_y0, 0)
@ -61,35 +67,34 @@ class TestOpenJP2(unittest.TestCase):
self.assertEqual(dparams.DA_y1, 0)
def tile_macro(self, codec, stream, imagep, tidx):
# called only by j2k_random_tile_access
glymur.lib._openjp2.get_decoded_tile(codec, stream, imagep, tidx)
"""called only by j2k_random_tile_access"""
openjp2.get_decoded_tile(codec, stream, imagep, tidx)
for j in range(imagep.contents.numcomps):
self.assertIsNotNone(imagep.contents.comps[j].data)
def j2k_random_tile_access(self, filename, codec_format=None):
# called by the test_rtaX methods
dparam = glymur.lib._openjp2.set_default_decoder_parameters()
"""fixture called by the test_rtaX methods"""
dparam = openjp2.set_default_decoder_parameters()
infile = filename.encode()
nelts = glymur.lib._openjp2.PATH_LEN - len(infile)
nelts = openjp2.PATH_LEN - len(infile)
infile += b'0' * nelts
dparam.infile = infile
dparam.decod_format = codec_format
codec = glymur.lib._openjp2.create_decompress(codec_format)
codec = openjp2.create_decompress(codec_format)
glymur.lib._openjp2.set_info_handler(codec, None)
glymur.lib._openjp2.set_warning_handler(codec, None)
glymur.lib._openjp2.set_error_handler(codec, None)
openjp2.set_info_handler(codec, None)
openjp2.set_warning_handler(codec, None)
openjp2.set_error_handler(codec, None)
x = (filename, True)
stream = glymur.lib._openjp2.stream_create_default_file_stream_v3(*x)
stream = openjp2.stream_create_default_file_stream_v3(filename, True)
glymur.lib._openjp2.setup_decoder(codec, dparam)
image = glymur.lib._openjp2.read_header(stream, codec)
openjp2.setup_decoder(codec, dparam)
image = openjp2.read_header(stream, codec)
cstr_info = glymur.lib._openjp2.get_cstr_info(codec)
cstr_info = openjp2.get_cstr_info(codec)
tile_ul = 0
tile_ur = cstr_info.contents.tw - 1
@ -101,159 +106,39 @@ class TestOpenJP2(unittest.TestCase):
self.tile_macro(codec, stream, image, tile_lr)
self.tile_macro(codec, stream, image, tile_ll)
glymur.lib._openjp2.destroy_cstr_info(cstr_info)
openjp2.destroy_cstr_info(cstr_info)
glymur.lib._openjp2.end_decompress(codec, stream)
glymur.lib._openjp2.destroy_codec(codec)
glymur.lib._openjp2.stream_destroy_v3(stream)
glymur.lib._openjp2.image_destroy(image)
def tile_decoder(self, x0=None, y0=None, x1=None, y1=None, filename=None,
codec_format=None):
x = (filename, True)
stream = glymur.lib._openjp2.stream_create_default_file_stream_v3(*x)
dparam = glymur.lib._openjp2.set_default_decoder_parameters()
dparam.decod_format = codec_format
# Do not use layer decoding limitation.
dparam.cp_layer = 0
# do not use resolution reductions.
dparam.cp_reduce = 0
codec = glymur.lib._openjp2.create_decompress(codec_format)
glymur.lib._openjp2.set_info_handler(codec, None)
glymur.lib._openjp2.set_warning_handler(codec, None)
glymur.lib._openjp2.set_error_handler(codec, None)
glymur.lib._openjp2.setup_decoder(codec, dparam)
image = glymur.lib._openjp2.read_header(stream, codec)
glymur.lib._openjp2.set_decode_area(codec, image, x0, y0, x1, y1)
data = np.zeros((1150, 2048, 3), dtype=np.uint8)
while True:
rargs = glymur.lib._openjp2.read_tile_header(codec, stream)
tidx = rargs[0]
sz = rargs[1]
go_on = rargs[-1]
if not go_on:
break
glymur.lib._openjp2.decode_tile_data(codec, tidx, data, sz, stream)
glymur.lib._openjp2.end_decompress(codec, stream)
glymur.lib._openjp2.destroy_codec(codec)
glymur.lib._openjp2.stream_destroy_v3(stream)
glymur.lib._openjp2.image_destroy(image)
def tile_encoder(self, num_comps=None, tile_width=None, tile_height=None,
filename=None, codec=None, comp_prec=None,
image_width=None, image_height=None,
irreversible=None):
num_tiles = (image_width / tile_width) * (image_height / tile_height)
tile_size = tile_width * tile_height * num_comps * comp_prec / 8
data = np.random.random((tile_height, tile_width, num_comps))
data = (data * 255).astype(np.uint8)
l_param = glymur.lib._openjp2.set_default_encoder_parameters()
l_param.tcp_numlayers = 1
l_param.cp_fixed_quality = 1
l_param.tcp_distoratio[0] = 20
# position of the tile grid aligned with the image
l_param.cp_tx0 = 0
l_param.cp_ty0 = 0
# tile size, we are using tile based encoding
l_param.tile_size_on = 1
l_param.cp_tdx = tile_width
l_param.cp_tdy = tile_height
# use irreversible encoding
l_param.irreversible = irreversible
l_param.numresolution = 6
l_param.prog_order = glymur.core.LRCP
l_params = (glymur.lib._openjp2.ImageComptParmType * num_comps)()
for j in range(num_comps):
l_params[j].dx = 1
l_params[j].dy = 1
l_params[j].h = image_height
l_params[j].w = image_width
l_params[j].sgnd = 0
l_params[j].prec = comp_prec
l_params[j].x0 = 0
l_params[j].y0 = 0
codec = glymur.lib._openjp2.create_compress(codec)
glymur.lib._openjp2.set_info_handler(codec, None)
glymur.lib._openjp2.set_warning_handler(codec, None)
glymur.lib._openjp2.set_error_handler(codec, None)
cspace = glymur.lib._openjp2.CLRSPC_SRGB
l_image = glymur.lib._openjp2.image_tile_create(l_params, cspace)
l_image.contents.x0 = 0
l_image.contents.y0 = 0
l_image.contents.x1 = image_width
l_image.contents.y1 = image_height
l_image.contents.color_space = glymur.lib._openjp2.CLRSPC_SRGB
glymur.lib._openjp2.setup_encoder(codec, l_param, l_image)
x = (filename, False)
stream = glymur.lib._openjp2.stream_create_default_file_stream_v3(*x)
glymur.lib._openjp2.start_compress(codec, l_image, stream)
for j in np.arange(num_tiles):
glymur.lib._openjp2.write_tile(codec, j, data, tile_size, stream)
glymur.lib._openjp2.end_compress(codec, stream)
glymur.lib._openjp2.stream_destroy_v3(stream)
glymur.lib._openjp2.destroy_codec(codec)
glymur.lib._openjp2.image_destroy(l_image)
def tte0_setup(self, filename):
kwargs = {'filename': filename,
'codec': glymur.lib._openjp2.CODEC_J2K,
'comp_prec': 8,
'irreversible': 1,
'num_comps': 3,
'image_height': 200,
'image_width': 200,
'tile_height': 100,
'tile_width': 100}
self.tile_encoder(**kwargs)
openjp2.end_decompress(codec, stream)
openjp2.destroy_codec(codec)
openjp2.stream_destroy_v3(stream)
openjp2.image_destroy(image)
def test_tte0(self):
# Runs test designated tte0 in OpenJPEG test suite.
"""Runs test designated tte0 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
self.tte0_setup(tfile.name)
ttx0_setup(tfile.name)
self.assertTrue(True)
def test_ttd0(self):
# Runs test designated ttd0 in OpenJPEG test suite.
"""Runs test designated ttd0 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
# Produce the tte0 output file for ttd0 input.
self.tte0_setup(tfile.name)
ttx0_setup(tfile.name)
kwargs = {'x0': 0,
'y0': 0,
'x1': 1000,
'y1': 1000,
'filename': tfile.name,
'codec_format': glymur.lib._openjp2.CODEC_J2K}
self.tile_decoder(**kwargs)
'codec_format': openjp2.CODEC_J2K}
tile_decoder(**kwargs)
self.assertTrue(True)
def tte1_setup(self, filename):
def xtx1_setup(self, filename):
"""Runs tests tte1, rta1."""
kwargs = {'filename': filename,
'codec': glymur.lib._openjp2.CODEC_J2K,
'codec': openjp2.CODEC_J2K,
'comp_prec': 8,
'irreversible': 1,
'num_comps': 3,
@ -261,149 +146,298 @@ class TestOpenJP2(unittest.TestCase):
'image_width': 256,
'tile_height': 128,
'tile_width': 128}
self.tile_encoder(**kwargs)
tile_encoder(**kwargs)
self.assertTrue(True)
def test_tte1(self):
"""Runs test designated tte1 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
# Runs test designated tte1 in OpenJPEG test suite.
self.tte1_setup(tfile.name)
self.xtx1_setup(tfile.name)
def test_ttd1(self):
# Runs test designated ttd1 in OpenJPEG test suite.
"""Runs test designated ttd1 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
# Produce the tte0 output file for ttd0 input.
self.tte1_setup(tfile.name)
self.xtx1_setup(tfile.name)
kwargs = {'x0': 0,
'y0': 0,
'x1': 128,
'y1': 128,
'filename': tfile.name,
'codec_format': glymur.lib._openjp2.CODEC_J2K}
self.tile_decoder(**kwargs)
'codec_format': openjp2.CODEC_J2K}
tile_decoder(**kwargs)
self.assertTrue(True)
def test_rta1(self):
"""Runs test designated rta1 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
# Runs test designated rta1 in OpenJPEG test suite.
self.tte1_setup(tfile.name)
self.xtx1_setup(tfile.name)
kwargs = {'codec_format': glymur.lib._openjp2.CODEC_J2K}
self.j2k_random_tile_access(tfile.name, **kwargs)
def tte2_setup(self, filename):
kwargs = {'filename': filename,
'codec': glymur.lib._openjp2.CODEC_JP2,
'comp_prec': 8,
'irreversible': 1,
'num_comps': 3,
'image_height': 256,
'image_width': 256,
'tile_height': 128,
'tile_width': 128}
self.tile_encoder(**kwargs)
codec_format = openjp2.CODEC_J2K
self.j2k_random_tile_access(tfile.name, codec_format)
self.assertTrue(True)
def test_tte2(self):
# Runs test designated tte2 in OpenJPEG test suite.
"""Runs test designated tte2 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
self.tte2_setup(tfile.name)
xtx2_setup(tfile.name)
self.assertTrue(True)
def test_ttd2(self):
# Runs test designated ttd2 in OpenJPEG test suite.
"""Runs test designated ttd2 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
# Produce the tte0 output file for ttd0 input.
self.tte2_setup(tfile.name)
xtx2_setup(tfile.name)
kwargs = {'x0': 0,
'y0': 0,
'x1': 128,
'y1': 128,
'filename': tfile.name,
'codec_format': glymur.lib._openjp2.CODEC_JP2}
self.tile_decoder(**kwargs)
'codec_format': openjp2.CODEC_JP2}
tile_decoder(**kwargs)
self.assertTrue(True)
def test_rta2(self):
"""Runs test designated rta2 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
# Runs test designated rta2 in OpenJPEG test suite.
self.tte2_setup(tfile.name)
xtx2_setup(tfile.name)
kwargs = {'codec_format': glymur.lib._openjp2.CODEC_JP2}
self.j2k_random_tile_access(tfile.name, **kwargs)
def tte3_setup(self, filename):
kwargs = {'filename': filename,
'codec': glymur.lib._openjp2.CODEC_J2K,
'comp_prec': 8,
'irreversible': 1,
'num_comps': 1,
'image_height': 256,
'image_width': 256,
'tile_height': 128,
'tile_width': 128}
self.tile_encoder(**kwargs)
codec_format = openjp2.CODEC_JP2
self.j2k_random_tile_access(tfile.name, codec_format)
def test_tte3(self):
"""Runs test designated tte3 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
# Runs test designated tte3 in OpenJPEG test suite.
self.tte3_setup(tfile.name)
xtx3_setup(tfile.name)
self.assertTrue(True)
def test_rta3(self):
# Runs test designated rta3 in OpenJPEG test suite.
"""Runs test designated rta3 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
self.tte3_setup(tfile.name)
xtx3_setup(tfile.name)
kwargs = {'codec_format': glymur.lib._openjp2.CODEC_J2K}
self.j2k_random_tile_access(tfile.name, **kwargs)
def tte4_setup(self, filename):
kwargs = {'filename': filename,
'codec': glymur.lib._openjp2.CODEC_J2K,
'comp_prec': 8,
'irreversible': 0,
'num_comps': 1,
'image_height': 256,
'image_width': 256,
'tile_height': 128,
'tile_width': 128}
self.tile_encoder(**kwargs)
codec_format = openjp2.CODEC_J2K
self.j2k_random_tile_access(tfile.name, codec_format)
self.assertTrue(True)
def test_tte4(self):
# Runs test designated tte4 in OpenJPEG test suite.
"""Runs test designated tte4 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
self.tte4_setup(tfile.name)
xtx4_setup(tfile.name)
self.assertTrue(True)
def test_rta4(self):
# Runs test designated rta4 in OpenJPEG test suite.
"""Runs test designated rta4 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
self.tte4_setup(tfile.name)
xtx4_setup(tfile.name)
kwargs = {'codec_format': glymur.lib._openjp2.CODEC_J2K}
self.j2k_random_tile_access(tfile.name, **kwargs)
def tte5_setup(self, filename):
kwargs = {'filename': filename,
'codec': glymur.lib._openjp2.CODEC_J2K,
'comp_prec': 8,
'irreversible': 0,
'num_comps': 1,
'image_height': 512,
'image_width': 512,
'tile_height': 256,
'tile_width': 256}
self.tile_encoder(**kwargs)
codec_format = openjp2.CODEC_J2K
self.j2k_random_tile_access(tfile.name, codec_format)
def test_tte5(self):
# Runs test designated tte5 in OpenJPEG test suite.
"""Runs test designated tte5 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
self.tte5_setup(tfile.name)
xtx5_setup(tfile.name)
self.assertTrue(True)
def test_rta5(self):
# Runs test designated rta5 in OpenJPEG test suite.
"""Runs test designated rta5 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
self.tte5_setup(tfile.name)
xtx5_setup(tfile.name)
kwargs = {'codec_format': glymur.lib._openjp2.CODEC_J2K}
self.j2k_random_tile_access(tfile.name, **kwargs)
codec_format = openjp2.CODEC_J2K
self.j2k_random_tile_access(tfile.name, codec_format)
#def tile_encoder(num_comps=None, tile_width=None, tile_height=None,
# filename=None, codec=None, comp_prec=None,
# image_width=None, image_height=None,
# irreversible=None):
def tile_encoder(**kwargs):
"""Fixture used by many tests."""
num_tiles = ((kwargs['image_width'] / kwargs['tile_width']) *
(kwargs['image_height'] / kwargs['tile_height']))
tile_size = ((kwargs['tile_width'] * kwargs['tile_height']) *
(kwargs['num_comps'] * kwargs['comp_prec'] / 8))
data = np.random.random((kwargs['tile_height'],
kwargs['tile_width'],
kwargs['num_comps']))
data = (data * 255).astype(np.uint8)
l_param = openjp2.set_default_encoder_parameters()
l_param.tcp_numlayers = 1
l_param.cp_fixed_quality = 1
l_param.tcp_distoratio[0] = 20
# position of the tile grid aligned with the image
l_param.cp_tx0 = 0
l_param.cp_ty0 = 0
# tile size, we are using tile based encoding
l_param.tile_size_on = 1
l_param.cp_tdx = kwargs['tile_width']
l_param.cp_tdy = kwargs['tile_height']
# use irreversible encoding
l_param.irreversible = kwargs['irreversible']
l_param.numresolution = 6
l_param.prog_order = glymur.core.LRCP
l_params = (openjp2.ImageComptParmType * kwargs['num_comps'])()
for j in range(kwargs['num_comps']):
l_params[j].dx = 1
l_params[j].dy = 1
l_params[j].h = kwargs['image_height']
l_params[j].w = kwargs['image_width']
l_params[j].sgnd = 0
l_params[j].prec = kwargs['comp_prec']
l_params[j].x0 = 0
l_params[j].y0 = 0
codec = openjp2.create_compress(kwargs['codec'])
openjp2.set_info_handler(codec, None)
openjp2.set_warning_handler(codec, None)
openjp2.set_error_handler(codec, None)
cspace = openjp2.CLRSPC_SRGB
l_image = openjp2.image_tile_create(l_params, cspace)
l_image.contents.x0 = 0
l_image.contents.y0 = 0
l_image.contents.x1 = kwargs['image_width']
l_image.contents.y1 = kwargs['image_height']
l_image.contents.color_space = openjp2.CLRSPC_SRGB
openjp2.setup_encoder(codec, l_param, l_image)
stream = openjp2.stream_create_default_file_stream_v3(kwargs['filename'],
False)
openjp2.start_compress(codec, l_image, stream)
for j in np.arange(num_tiles):
openjp2.write_tile(codec, j, data, tile_size, stream)
openjp2.end_compress(codec, stream)
openjp2.stream_destroy_v3(stream)
openjp2.destroy_codec(codec)
openjp2.image_destroy(l_image)
def tile_decoder(**kwargs):
"""Fixture called with various configurations by many tests.
Reads a tile. That's all it does.
"""
stream = openjp2.stream_create_default_file_stream_v3(kwargs['filename'],
True)
dparam = openjp2.set_default_decoder_parameters()
dparam.decod_format = kwargs['codec_format']
# Do not use layer decoding limitation.
dparam.cp_layer = 0
# do not use resolution reductions.
dparam.cp_reduce = 0
codec = openjp2.create_decompress(kwargs['codec_format'])
openjp2.set_info_handler(codec, None)
openjp2.set_warning_handler(codec, None)
openjp2.set_error_handler(codec, None)
openjp2.setup_decoder(codec, dparam)
image = openjp2.read_header(stream, codec)
openjp2.set_decode_area(codec, image,
kwargs['x0'], kwargs['y0'],
kwargs['x1'], kwargs['y1'])
data = np.zeros((1150, 2048, 3), dtype=np.uint8)
while True:
rargs = openjp2.read_tile_header(codec, stream)
tidx = rargs[0]
size = rargs[1]
go_on = rargs[-1]
if not go_on:
break
openjp2.decode_tile_data(codec, tidx, data, size, stream)
openjp2.end_decompress(codec, stream)
openjp2.destroy_codec(codec)
openjp2.stream_destroy_v3(stream)
openjp2.image_destroy(image)
def ttx0_setup(filename):
"""Runs tests tte0, tte0."""
kwargs = {'filename': filename,
'codec': openjp2.CODEC_J2K,
'comp_prec': 8,
'irreversible': 1,
'num_comps': 3,
'image_height': 200,
'image_width': 200,
'tile_height': 100,
'tile_width': 100}
tile_encoder(**kwargs)
def xtx2_setup(filename):
"""Runs tests rta2, tte2, ttd2."""
kwargs = {'filename': filename,
'codec': openjp2.CODEC_JP2,
'comp_prec': 8,
'irreversible': 1,
'num_comps': 3,
'image_height': 256,
'image_width': 256,
'tile_height': 128,
'tile_width': 128}
tile_encoder(**kwargs)
def xtx3_setup(filename):
"""Runs tests tte3, rta3."""
kwargs = {'filename': filename,
'codec': openjp2.CODEC_J2K,
'comp_prec': 8,
'irreversible': 1,
'num_comps': 1,
'image_height': 256,
'image_width': 256,
'tile_height': 128,
'tile_width': 128}
tile_encoder(**kwargs)
def xtx4_setup(filename):
"""Runs tests rta4, tte4."""
kwargs = {'filename': filename,
'codec': openjp2.CODEC_J2K,
'comp_prec': 8,
'irreversible': 0,
'num_comps': 1,
'image_height': 256,
'image_width': 256,
'tile_height': 128,
'tile_width': 128}
tile_encoder(**kwargs)
def xtx5_setup(filename):
"""Runs tests rta5, tte5."""
kwargs = {'filename': filename,
'codec': openjp2.CODEC_J2K,
'comp_prec': 8,
'irreversible': 0,
'num_comps': 1,
'image_height': 512,
'image_width': 512,
'tile_height': 256,
'tile_width': 256}
tile_encoder(**kwargs)
if __name__ == "__main__":
unittest.main()

View file

@ -1,4 +1,11 @@
#pylint: disable-all
"""
Tests for OpenJPEG module.
"""
# unittest2 is python2.6 only (pylint/python-2.7)
# pylint: disable=F0401
# pylint: disable=E1101,R0904
import ctypes
import re
import sys
@ -10,43 +17,38 @@ else:
import glymur
@unittest.skipIf(glymur.lib._openjpeg.OPENJPEG is None,
@unittest.skipIf(glymur.lib.openjpeg.OPENJPEG is None,
"Missing openjpeg library.")
class TestOpenJPEG(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
"""Test suite for openjpeg functions we choose to expose."""
def test_version(self):
version = glymur.lib._openjpeg.version()
"""Only versions 1.3, 1.4, and 1.5 are supported."""
version = glymur.lib.openjpeg.version()
regex = re.compile('1.[345].[0-9]')
if sys.hexversion <= 0x03020000:
self.assertRegexpMatches(version, regex)
else:
self.assertRegex(version, regex)
def test_set_default_decoder_parameters(self):
# Verify that we properly set the default decode parameters.
version = glymur.lib._openjpeg.version()
def test_default_decoder_parameters(self):
"""Verify that we properly set the default decode parameters."""
version = glymur.lib.openjpeg.version()
minor = int(version.split('.')[1])
dp = glymur.lib._openjpeg.DecompressionParametersType()
glymur.lib._openjpeg.set_default_decoder_parameters(ctypes.byref(dp))
dcp = glymur.lib.openjpeg.DecompressionParametersType()
glymur.lib.openjpeg.set_default_decoder_parameters(ctypes.byref(dcp))
self.assertEqual(dp.cp_reduce, 0)
self.assertEqual(dp.cp_layer, 0)
self.assertEqual(dp.infile, b'')
self.assertEqual(dp.outfile, b'')
self.assertEqual(dp.decod_format, -1)
self.assertEqual(dp.cod_format, -1)
self.assertEqual(dp.jpwl_correct, 0)
self.assertEqual(dp.jpwl_exp_comps, 0)
self.assertEqual(dp.jpwl_max_tiles, 0)
self.assertEqual(dp.cp_limit_decoding, 0)
self.assertEqual(dcp.cp_reduce, 0)
self.assertEqual(dcp.cp_layer, 0)
self.assertEqual(dcp.infile, b'')
self.assertEqual(dcp.outfile, b'')
self.assertEqual(dcp.decod_format, -1)
self.assertEqual(dcp.cod_format, -1)
self.assertEqual(dcp.jpwl_correct, 0)
self.assertEqual(dcp.jpwl_exp_comps, 0)
self.assertEqual(dcp.jpwl_max_tiles, 0)
self.assertEqual(dcp.cp_limit_decoding, 0)
if minor > 4:
# Introduced in 1.5.x
self.assertEqual(dp.flags, 0)
self.assertEqual(dcp.flags, 0)

View file

@ -0,0 +1,3 @@
"""
Test suite for glymur high-level functionality.
"""

View file

@ -1,15 +1,15 @@
"""
Test fixtures common to more than one test point.
"""
import os
import re
import sys
import warnings
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.
@ -20,6 +20,47 @@ if glymur.lib.openjp2.OPENJP2 is not None:
OPENJP2_IS_V2_OFFICIAL = True
NO_READ_BACKEND_MSG = "Matplotlib with the PIL backend must be available in "
NO_READ_BACKEND_MSG += "order to run the tests in this suite."
try:
OPJ_DATA_ROOT = os.environ['OPJ_DATA_ROOT']
except KeyError:
OPJ_DATA_ROOT = None
except:
raise
def opj_data_file(relative_file_name):
"""Compact way of forming a full filename from OpenJPEG's test suite."""
jfile = os.path.join(OPJ_DATA_ROOT, relative_file_name)
return jfile
try:
from matplotlib.pyplot import imread
# The whole point of trying to import PIL is to determine if it's there
# or not. We won't use it directly.
# pylint: disable=F0401,W0611
import PIL
NO_READ_BACKEND = False
except ImportError:
NO_READ_BACKEND = True
def read_image(infile):
"""Read image using matplotlib backend.
Hopefully PIL(low) is installed as matplotlib's backend. It issues
warnings which we do not care about, so suppress them.
"""
with warnings.catch_warnings():
warnings.simplefilter("ignore")
data = imread(infile)
return data
def mse(amat, bmat):
"""Mean Square Error"""
diff = amat.astype(np.double) - bmat.astype(np.double)
@ -36,28 +77,10 @@ def peak_tolerance(amat, bmat):
def read_pgx(pgx_file):
"""Helper function for reading the PGX comparison files.
Open the file in ascii mode and read the header line.
Will look something like
PG ML + 8 128 128
PG%[ \t]%c%c%[ \t+-]%d%[ \t]%d%[ \t]%d"
"""
header = ''
with open(pgx_file, 'rb') as fptr:
while True:
char = fptr.read(1)
if char[0] == 10 or char == '\n':
pos = fptr.tell()
break
else:
if sys.hexversion < 0x03000000:
header += char
else:
header += chr(char[0])
header, pos = read_pgx_header(pgx_file)
header = header.rstrip()
tokens = re.split('\s', header)
tokens = re.split(r'\s', header)
if (tokens[1][0] == 'M') and (sys.byteorder == 'little'):
swapbytes = True
@ -81,6 +104,29 @@ def read_pgx(pgx_file):
nrows = int(tokens[4])
ncols = int(tokens[3])
dtype = determine_pgx_datatype(signed, bitdepth)
shape = [nrows, ncols]
# Reopen the file in binary mode and seek to the start of the binary
# data
with open(pgx_file, 'rb') as fptr:
fptr.seek(pos)
data = np.fromfile(file=fptr, dtype=dtype).reshape(shape)
return(data.byteswap(swapbytes))
def determine_pgx_datatype(signed, bitdepth):
"""Determine the datatype of the PGX file.
Parameters
----------
signed : bool
True if the datatype is signed, false otherwise
bitdepth : int
How many bits are used to make up an image plane. Should be 8 or 16.
"""
if signed:
if bitdepth <= 8:
dtype = np.int8
@ -96,12 +142,28 @@ def read_pgx(pgx_file):
else:
raise RuntimeError("unhandled bitdepth")
shape = [nrows, ncols]
return dtype
# Reopen the file in binary mode and seek to the start of the binary
# data
def read_pgx_header(pgx_file):
"""Open the file in ascii mode (not really) and read the header line.
Will look something like
PG ML + 8 128 128
PG%[ \t]%c%c%[ \t+-]%d%[ \t]%d%[ \t]%d"
"""
header = ''
with open(pgx_file, 'rb') as fptr:
fptr.seek(pos)
data = np.fromfile(file=fptr, dtype=dtype).reshape(shape)
while True:
char = fptr.read(1)
if char[0] == 10 or char == '\n':
pos = fptr.tell()
break
else:
if sys.hexversion < 0x03000000:
header += char
else:
header += chr(char[0])
return(data.byteswap(swapbytes))
header = header.rstrip()
return header, pos

View file

@ -1,6 +1,13 @@
#pylint: disable-all
"""
Test suite for openjpeg's callback functions.
"""
# R0904: Seems like pylint is fooled in this situation
# pylint: disable=R0904
# 'mock' most certainly is in unittest (Python 3.3)
# pylint: disable=E0611,F0401
import os
import pkg_resources
import re
import sys
import tempfile
@ -24,6 +31,7 @@ import glymur
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
"Missing openjp2 library.")
class TestCallbacks(unittest.TestCase):
"""Test suite for callbacks."""
def setUp(self):
self.jp2file = glymur.data.nemo()
@ -34,7 +42,7 @@ class TestCallbacks(unittest.TestCase):
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_info_callback_on_write(self):
# Verify the messages printed when writing an image in verbose mode.
"""Verify messages printed when writing an image in verbose mode."""
j = glymur.Jp2k(self.jp2file)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
@ -47,12 +55,14 @@ class TestCallbacks(unittest.TestCase):
expected = '[INFO] tile number 1 / 1'
self.assertEqual(actual, expected)
def test_info_warning_callbacks_on_read(self):
def test_info_callbacks_on_read(self):
"""stdio output when info callback handler is enabled"""
# Verify that we get the expected stdio output when our internal info
# callback handler is enabled.
j = glymur.Jp2k(self.j2kfile)
with patch('sys.stdout', new=StringIO()) as fake_out:
d = j.read(rlevel=1, verbose=True, area=(0, 0, 200, 150))
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).',
@ -80,15 +90,18 @@ class TestCallbacks15(unittest.TestCase):
pass
def test_info_callbacks_on_read(self):
# Verify that we get the expected stdio output when our internal info
# callback handler is enabled.
"""Verify stdout when reading.
Verify that we get the expected stdio output when our internal info
callback handler is enabled.
"""
with patch('glymur.lib.openjp2.OPENJP2', new=None):
# Force to use OPENJPEG instead of OPENJP2.
j = glymur.Jp2k(self.j2kfile)
with patch('sys.stdout', new=StringIO()) as fake_out:
d = j.read(rlevel=1, verbose=True)
j.read(rlevel=1, verbose=True)
actual = fake_out.getvalue().strip()
regex = re.compile(r"""\[INFO\]\stile\s1\sof\s1\s+
\[INFO\]\s-\stiers-1\stook\s
[0-9]+\.[0-9]+\ss\s+
@ -97,6 +110,9 @@ class TestCallbacks15(unittest.TestCase):
\[INFO\]\s-\stile\sdecoded\sin\s
[0-9]+\.[0-9]+\ss""",
re.VERBOSE)
# assertRegex in Python 3.3 (python2.7/pylint issue)
# pylint: disable=E1101
if sys.hexversion <= 0x03020000:
self.assertRegexpMatches(actual, regex)
else:

View file

@ -1,4 +1,16 @@
#pylint: disable-all
"""
Test suite for codestream parsing.
"""
# unittest doesn't work well with R0904.
# pylint: disable=R0904
# tempfile.TemporaryDirectory, unittest.assertWarns introduced in 3.2
# pylint: disable=E1101
# unittest2 is python2.6 only (pylint/python-2.7)
# pylint: disable=F0401
import os
import struct
import sys
@ -9,23 +21,21 @@ if sys.hexversion < 0x02070000:
else:
import unittest
import numpy as np
import pkg_resources
from glymur import Jp2k
import glymur
try:
data_root = os.environ['OPJ_DATA_ROOT']
DATA_ROOT = os.environ['OPJ_DATA_ROOT']
except KeyError:
data_root = None
DATA_ROOT = None
except:
raise
@unittest.skipIf(data_root is None,
@unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
class TestCodestream(unittest.TestCase):
"""Test suite for unusual codestream cases."""
def setUp(self):
self.jp2file = glymur.data.nemo()
@ -35,75 +45,76 @@ class TestCodestream(unittest.TestCase):
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_reserved_marker_segment(self):
"""Reserved marker segments are ok."""
# Some marker segments were reserved in FCD15444-1. Since that
# standard is old, some of them may have come into use.
#
# Let's inject a reserved marker segment into a file that
# we know something about to make sure we can still parse it.
filename = os.path.join(data_root, 'input/conformance/p0_01.j2k')
filename = os.path.join(DATA_ROOT, 'input/conformance/p0_01.j2k')
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
with open(filename, 'rb') as ifile:
# Everything up until the first QCD marker.
buffer = ifile.read(45)
tfile.write(buffer)
read_buffer = ifile.read(45)
tfile.write(read_buffer)
# Write the new marker segment, 0xff6f = 65391
buffer = struct.pack('>HHB', int(65391), int(3), int(0))
tfile.write(buffer)
read_buffer = struct.pack('>HHB', int(65391), int(3), int(0))
tfile.write(read_buffer)
# Get the rest of the input file.
buffer = ifile.read()
tfile.write(buffer)
read_buffer = ifile.read()
tfile.write(read_buffer)
tfile.flush()
j = Jp2k(tfile.name)
c = j.get_codestream()
codestream = Jp2k(tfile.name).get_codestream()
self.assertEqual(c.segment[2].marker_id, '0xff6f')
self.assertEqual(c.segment[2].length, 3)
self.assertEqual(c.segment[2]._data, b'\x00')
self.assertEqual(codestream.segment[2].marker_id, '0xff6f')
self.assertEqual(codestream.segment[2].length, 3)
self.assertEqual(codestream.segment[2].data, b'\x00')
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
@unittest.skipIf(sys.hexversion < 0x03020000,
"Uses features introduced in 3.2.")
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_unknown_marker_segment(self):
"""Should warn for an unknown marker."""
# Let's inject a marker segment whose marker does not appear to
# be valid. We still parse the file, but warn about the offending
# marker.
filename = os.path.join(data_root, 'input/conformance/p0_01.j2k')
filename = os.path.join(DATA_ROOT, 'input/conformance/p0_01.j2k')
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
with open(filename, 'rb') as ifile:
# Everything up until the first QCD marker.
buffer = ifile.read(45)
tfile.write(buffer)
read_buffer = ifile.read(45)
tfile.write(read_buffer)
# Write the new marker segment, 0xff79 = 65401
buffer = struct.pack('>HHB', int(65401), int(3), int(0))
tfile.write(buffer)
read_buffer = struct.pack('>HHB', int(65401), int(3), int(0))
tfile.write(read_buffer)
# Get the rest of the input file.
buffer = ifile.read()
tfile.write(buffer)
read_buffer = ifile.read()
tfile.write(read_buffer)
tfile.flush()
with self.assertWarns(UserWarning) as cw:
j = Jp2k(tfile.name)
c = j.get_codestream()
with self.assertWarns(UserWarning):
codestream = Jp2k(tfile.name).get_codestream()
self.assertEqual(c.segment[2].marker_id, '0xff79')
self.assertEqual(c.segment[2].length, 3)
self.assertEqual(c.segment[2]._data, b'\x00')
self.assertEqual(codestream.segment[2].marker_id, '0xff79')
self.assertEqual(codestream.segment[2].length, 3)
self.assertEqual(codestream.segment[2].data, b'\x00')
def test_psot_is_zero(self):
# Psot=0 in SOT is perfectly legal. Issue #78.
filename = os.path.join(data_root,
"""Psot=0 in SOT is perfectly legal. Issue #78."""
filename = os.path.join(DATA_ROOT,
'input/nonregression/123.j2c')
j = Jp2k(filename)
c = j.get_codestream(header_only=False)
codestream = j.get_codestream(header_only=False)
# The codestream is valid, so we should be able to get the entire
# codestream, so the last one is EOC.
self.assertEqual(c.segment[-1].marker_id, 'EOC')
self.assertEqual(codestream.segment[-1].marker_id, 'EOC')
if __name__ == "__main__":
unittest.main()

View file

@ -1,7 +1,15 @@
"""These tests are for edge cases where OPENJPEG does not exist, but
OPENJP2 may be present in some form or other.
"""
#pylint: disable-all
# unittest doesn't work well with R0904.
# pylint: disable=R0904
# tempfile.TemporaryDirectory, unittest.assertWarns introduced in 3.2
# pylint: disable=E1101
# unittest.mock only in Python 3.3 (python2.7/pylint import issue)
# pylint: disable=E0611,F0401
import imp
import os
@ -17,13 +25,9 @@ if sys.hexversion <= 0x03030000:
from mock import patch
else:
from unittest.mock import patch
import warnings
import pkg_resources
import glymur
from glymur import Jp2k
from glymur.lib import openjp2 as opj2
@unittest.skipIf(sys.hexversion < 0x03020000,
@ -31,6 +35,7 @@ from glymur.lib import openjp2 as opj2
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
"Needs openjp2 library first before these tests make sense.")
class TestSuite(unittest.TestCase):
"""Test suite for configuration file operation."""
@classmethod
def setUpClass(cls):
@ -56,30 +61,79 @@ class TestSuite(unittest.TestCase):
filename = os.path.join(configdir, 'glymurrc')
with open(filename, 'wt') as tfile:
tfile.write('[library]\n')
# Need to reliably recover the location of the openjp2 library,
# so using '_name' appears to be the only way to do it.
# pylint: disable=W0212
libloc = glymur.lib.openjp2.OPENJP2._name
line = 'openjp2: {0}\n'.format(libloc)
tfile.write(line)
tfile.flush()
with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
imp.reload(glymur.lib.openjp2)
j = Jp2k(self.jp2file)
Jp2k(self.jp2file)
def test_config_file_via_environ_is_wrong(self):
# A non-existant library location should be rejected.
def test_xdg_env_config_file_is_bad(self):
"""A non-existant library location should be rejected."""
with tempfile.TemporaryDirectory() as tdir:
configdir = os.path.join(tdir, 'glymur')
os.mkdir(configdir)
fname = os.path.join(configdir, 'glymurrc')
with open(fname, 'w') as fp:
with open(fname, 'w') as fptr:
with tempfile.NamedTemporaryFile(suffix='.dylib') as tfile:
fp.write('[library]\n')
fp.write('openjp2: {0}.not.there\n'.format(tfile.name))
fp.flush()
fptr.write('[library]\n')
fptr.write('openjp2: {0}.not.there\n'.format(tfile.name))
fptr.flush()
with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
# Misconfigured new configuration file should
# be rejected.
with self.assertWarns(UserWarning) as cw:
with self.assertWarns(UserWarning):
imp.reload(glymur.lib.openjp2)
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None and
glymur.lib.openjpeg.OPENJPEG is None,
"Missing openjp2 library.")
class TestConfig(unittest.TestCase):
"""Test suite for reading without proper library in place."""
def setUp(self):
self.jp2file = glymur.data.nemo()
self.j2kfile = glymur.data.goodstuff()
def tearDown(self):
pass
def test_read_without_library(self):
"""Don't have either openjp2 or openjpeg libraries? Must error out.
"""
with patch('glymur.lib.openjp2.OPENJP2', new=None):
with patch('glymur.lib.openjpeg.OPENJPEG', new=None):
with self.assertRaises(glymur.jp2k.LibraryNotFoundError):
glymur.Jp2k(self.jp2file).read()
def test_read_bands_without_library(self):
"""Don't have openjp2 library? Must error out.
"""
with patch('glymur.lib.openjp2.OPENJP2', new=None):
with patch('glymur.lib.openjpeg.OPENJPEG', new=None):
with patch('glymur.version.openjpeg_version_tuple',
new=(0, 0, 0)):
with self.assertRaises(glymur.jp2k.LibraryNotFoundError):
glymur.Jp2k(self.jp2file).read_bands()
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_write_without_library(self):
"""Don't have openjpeg libraries? Must error out.
"""
data = glymur.Jp2k(self.j2kfile).read()
with patch('glymur.lib.openjp2.OPENJP2', new=None):
with patch('glymur.lib.openjpeg.OPENJPEG', new=None):
with self.assertRaises(glymur.jp2k.LibraryNotFoundError):
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
ofile = Jp2k(tfile.name, 'wb')
ofile.write(data)
if __name__ == "__main__":
unittest.main()

View file

@ -1,9 +1,18 @@
"""
These tests deal with JPX/JP2/J2K images in the format-corpus repository.
"""
#pylint: disable-all
# R0904: Not too many methods in unittest.
# pylint: disable=R0904
# E1101: assertWarns introduced in python 3.2
# pylint: disable=E1101
# unittest2 is python2.6 only (pylint/python-2.7)
# pylint: disable=F0401
import os
from os.path import join
import re
import sys
if sys.hexversion < 0x02070000:
@ -11,106 +20,105 @@ if sys.hexversion < 0x02070000:
else:
import unittest
import warnings
from glymur import Jp2k
import glymur
from glymur import Jp2k
try:
format_corpus_data_root = os.environ['FORMAT_CORPUS_DATA_ROOT']
FORMAT_CORPUS_DATA_ROOT = os.environ['FORMAT_CORPUS_DATA_ROOT']
except KeyError:
format_corpus_data_root = None
FORMAT_CORPUS_DATA_ROOT = None
try:
opj_data_root = os.environ['OPJ_DATA_ROOT']
OPJ_DATA_ROOT = os.environ['OPJ_DATA_ROOT']
except KeyError:
opj_data_root = None
OPJ_DATA_ROOT = None
@unittest.skipIf(format_corpus_data_root is None,
@unittest.skipIf(FORMAT_CORPUS_DATA_ROOT is None,
"FORMAT_CORPUS_DATA_ROOT environment variable not set")
@unittest.skipIf(sys.hexversion < 0x03020000,
"Requires features introduced in 3.2 (assertWarns)")
class TestSuiteFormatCorpus(unittest.TestCase):
"""Test suite for files in format corpus repository."""
def setUp(self):
pass
def tearDown(self):
pass
@unittest.skipIf(re.match(r"""1\.[0123]""",
glymur.version.openjpeg_version) is not None,
"Needs 1.3+ to catch this.")
def test_balloon_trunc1(self):
# Has one byte shaved off of EOC marker.
jfile = os.path.join(format_corpus_data_root,
"""Has one byte shaved off of EOC marker."""
jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT,
'jp2k-test/byteCorruption/balloon_trunc1.jp2')
j2k = Jp2k(jfile)
with self.assertWarns(UserWarning):
c = j2k.get_codestream(header_only=False)
codestream = j2k.get_codestream(header_only=False)
# The last segment is truncated, so there should not be an EOC marker.
self.assertNotEqual(c.segment[-1].marker_id, 'EOC')
self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC')
# The codestream is not as long as claimed.
with self.assertRaises(OSError):
j2k.read(rlevel=-1)
@unittest.skipIf(re.match(r"""1\.[01234]""",
glymur.version.openjpeg_version) is not None,
"Needs 1.4+ to catch this.")
def test_balloon_trunc2(self):
# Shortened by 5000 bytes.
jfile = os.path.join(format_corpus_data_root,
"""Shortened by 5000 bytes."""
jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT,
'jp2k-test/byteCorruption/balloon_trunc2.jp2')
j2k = Jp2k(jfile)
with self.assertWarns(UserWarning):
c = j2k.get_codestream(header_only=False)
codestream = j2k.get_codestream(header_only=False)
# The last segment is truncated, so there should not be an EOC marker.
self.assertNotEqual(c.segment[-1].marker_id, 'EOC')
self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC')
# The codestream is not as long as claimed.
with self.assertRaises(OSError):
j2k.read(rlevel=-1)
def test_balloon_trunc3(self):
# Most of last tile is missing.
jfile = os.path.join(format_corpus_data_root,
"""Most of last tile is missing."""
jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT,
'jp2k-test/byteCorruption/balloon_trunc3.jp2')
j2k = Jp2k(jfile)
with self.assertWarns(UserWarning):
c = j2k.get_codestream(header_only=False)
codestream = j2k.get_codestream(header_only=False)
# The last segment is truncated, so there should not be an EOC marker.
self.assertNotEqual(c.segment[-1].marker_id, 'EOC')
self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC')
# Should error out, it does not.
#with self.assertRaises(OSError):
# j2k.read(rlevel=-1)
def test_jp2_brand_vs_any_icc_profile(self):
# If 'jp2 ', then the method cannot be any icc profile.
jfile = os.path.join(format_corpus_data_root,
def test_jp2_brand_any_icc_profile(self):
"""If 'jp2 ', then the method cannot be any icc profile."""
jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT,
'jp2k-test', 'icc',
'balloon_eciRGBv2_ps_adobeplugin.jpf')
with self.assertWarns(UserWarning):
j2k = Jp2k(jfile)
Jp2k(jfile)
def test_jp2_brand_vs_any_icc_profile_multiple_colr(self):
# Has colr box, one that conforms, one that does not.
def test_jp2_brand_iccpr_mult_colr(self):
"""Has colr box, one that conforms, one that does not."""
# Wrong 'brand' field; contains two versions of ICC profile: one
# embedded using "Any ICC" method; other embedded using "Restricted
# ICC" method, with description ("Modified eciRGB v2") and profileClass
# ("Input Device") changed relative to original profile.
lst = [format_corpus_data_root, 'jp2k-test', 'icc',
'balloon_eciRGBv2_ps_adobeplugin_jp2compatible.jpf']
jfile = os.path.join(*lst)
jfile = join(FORMAT_CORPUS_DATA_ROOT, 'jp2k-test', 'icc',
'balloon_eciRGBv2_ps_adobeplugin_jp2compatible.jpf')
with self.assertWarns(UserWarning):
j2k = Jp2k(jfile)
Jp2k(jfile)
@unittest.skipIf(opj_data_root is None,
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
@unittest.skipIf(sys.hexversion < 0x03020000,
"Requires features introduced in 3.2 (assertWarns)")
class TestSuiteOpj(unittest.TestCase):
"""Test suite for files in openjpeg repository."""
def setUp(self):
pass
@ -118,12 +126,12 @@ class TestSuiteOpj(unittest.TestCase):
def tearDown(self):
pass
def test_jp2_brand_vs_any_icc_profile(self):
# If 'jp2 ', then the method cannot be any icc profile.
filename = os.path.join(opj_data_root,
def test_jp2_brand_any_icc_profile(self):
"""If 'jp2 ', then the method cannot be any icc profile."""
filename = os.path.join(OPJ_DATA_ROOT,
'input/nonregression/text_GBR.jp2')
with self.assertWarns(UserWarning):
j2k = Jp2k(filename)
Jp2k(filename)
if __name__ == "__main__":
unittest.main()

View file

@ -1,35 +1,32 @@
#pylint: disable-all
"""
ICC profile tests.
"""
# unittest doesn't work well with R0904.
# pylint: disable=R0904
# unittest2 is python2.6 only (pylint/python-2.7)
# pylint: disable=F0401
import datetime
import os
import struct
import sys
import tempfile
if sys.hexversion < 0x02070000:
import unittest2 as unittest
else:
import unittest
import warnings
from xml.etree import cElementTree as ET
import numpy as np
import pkg_resources
from glymur import Jp2k
import glymur
try:
data_root = os.environ['OPJ_DATA_ROOT']
except KeyError:
data_root = None
except:
raise
from .fixtures import OPJ_DATA_ROOT, opj_data_file
@unittest.skipIf(data_root is None,
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
class TestICC(unittest.TestCase):
"""ICC profile tests"""
def setUp(self):
pass
@ -38,7 +35,8 @@ class TestICC(unittest.TestCase):
pass
def test_file5(self):
filename = os.path.join(data_root, 'input/conformance/file5.jp2')
"""basic ICC profile"""
filename = opj_data_file('input/conformance/file5.jp2')
j = Jp2k(filename)
profile = j.box[3].box[1].icc_profile
self.assertEqual(profile['Size'], 546)
@ -70,10 +68,13 @@ class TestICC(unittest.TestCase):
@unittest.skipIf(sys.hexversion < 0x03020000,
"Uses features introduced in 3.2.")
def test_invalid_profile_header(self):
jfile = os.path.join(data_root,
'input/nonregression/orb-blue10-lin-jp2.jp2')
with self.assertWarns(UserWarning) as cw:
j = Jp2k(jfile)
"""invalid ICC header data should cause UserWarning"""
jfile = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2')
# assertWarns in Python 3.3 (python2.7/pylint issue)
# pylint: disable=E1101
with self.assertWarns(UserWarning):
Jp2k(jfile)
if __name__ == "__main__":
unittest.main()

View file

@ -1,8 +1,28 @@
#pylint: disable-all
"""
Test suite specifically targeting JP2 box layout.
"""
# E1103: return value from read may be list or np array
# pylint: disable=E1103
# F0401: unittest2 is needed on python-2.6 (pylint on 2.7)
# pylint: disable=F0401
# R0902: More than 7 instance attributes are just fine for testing.
# pylint: disable=R0902
# R0904: Seems like pylint is fooled in this situation
# pylint: disable=R0904
# W0613: load_tests doesn't need to use ignore or loader arguments.
# pylint: disable=W0613
import doctest
import os
import shutil
import struct
import sys
import tempfile
import uuid
import xml.etree.cElementTree as ET
if sys.hexversion < 0x02070000:
@ -11,37 +31,37 @@ else:
import unittest
import numpy as np
import pkg_resources
import glymur
from glymur import Jp2k
from glymur.jp2box import *
from glymur.jp2box import ColourSpecificationBox, ContiguousCodestreamBox
from glymur.jp2box import FileTypeBox, ImageHeaderBox, JP2HeaderBox
from glymur.jp2box import JPEG2000SignatureBox
from glymur.core import COLOR, OPACITY
from glymur.core import RED, GREEN, BLUE, GREY, WHOLE_IMAGE
from .fixtures import OPENJP2_IS_V2_OFFICIAL
try:
format_corpus_data_root = os.environ['FORMAT_CORPUS_DATA_ROOT']
FORMAT_CORPUS_DATA_ROOT = os.environ['FORMAT_CORPUS_DATA_ROOT']
except KeyError:
format_corpus_data_root = None
FORMAT_CORPUS_DATA_ROOT = None
# Doc tests should be run as well.
def load_tests(loader, tests, ignore):
"""Run doc tests as well."""
if os.name == "nt":
# Can't do it on windows, temporary file issue.
return tests
tests.addTests(doctest.DocTestSuite('glymur.jp2box'))
return tests
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL,
"Requires v2.0.0+ in order to run.")
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2 or
OPENJP2_IS_V2_OFFICIAL,
"Not supported until 2.0+.")
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
"Missing openjp2 library.")
class TestChannelDefinition(unittest.TestCase):
"""Test suite for channel definition boxes."""
@classmethod
def setUpClass(cls):
@ -78,12 +98,12 @@ class TestChannelDefinition(unittest.TestCase):
self.j2kfile = glymur.data.goodstuff()
j2k = Jp2k(self.j2kfile)
c = j2k.get_codestream()
height = c.segment[1].ysiz
width = c.segment[1].xsiz
num_components = len(c.segment[1].xrsiz)
codestream = j2k.get_codestream()
height = codestream.segment[1].ysiz
width = codestream.segment[1].xsiz
num_components = len(codestream.segment[1].xrsiz)
self.jP = JPEG2000SignatureBox()
self.jp2b = JPEG2000SignatureBox()
self.ftyp = FileTypeBox()
self.jp2h = JP2HeaderBox()
self.jp2c = ContiguousCodestreamBox()
@ -110,7 +130,7 @@ class TestChannelDefinition(unittest.TestCase):
association=association)
boxes = [self.ihdr, self.colr_rgb, cdef]
self.jp2h.box = boxes
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
@ -133,7 +153,7 @@ class TestChannelDefinition(unittest.TestCase):
association=association)
boxes = [self.ihdr, self.colr_rgb, cdef]
self.jp2h.box = boxes
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
@ -156,7 +176,7 @@ class TestChannelDefinition(unittest.TestCase):
association=association)
boxes = [self.ihdr, self.colr_rgb, cdef]
self.jp2h.box = boxes
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
@ -177,9 +197,9 @@ class TestChannelDefinition(unittest.TestCase):
association=association)
boxes = [self.ihdr, self.colr_rgb, cdef]
self.jp2h.box = boxes
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError) as ce:
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
def test_grey(self):
@ -191,7 +211,7 @@ class TestChannelDefinition(unittest.TestCase):
association=association)
boxes = [self.ihdr, self.colr_gr, cdef]
self.jp2h.box = boxes
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
@ -212,7 +232,7 @@ class TestChannelDefinition(unittest.TestCase):
association=association)
boxes = [self.ihdr, self.colr_gr, cdef]
self.jp2h.box = boxes
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
@ -236,12 +256,12 @@ class TestChannelDefinition(unittest.TestCase):
association=association)
boxes = [self.ihdr, self.colr_gr, cdef]
self.jp2h.box = boxes
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises((OSError, IOError)) as ce:
with self.assertRaises((OSError, IOError)):
j2k.wrap(tfile.name, boxes=boxes)
def test_only_one_cdef_in_jp2_header(self):
def test_only_one_cdef_in_jp2h(self):
"""There can only be one channel definition box in the jp2 header."""
j2k = Jp2k(self.j2kfile)
@ -253,13 +273,14 @@ class TestChannelDefinition(unittest.TestCase):
boxes = [self.ihdr, cdef, self.colr_rgb, cdef]
self.jp2h.box = boxes
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
def test_not_in_jp2_header(self):
def test_not_in_jp2h(self):
"""need cdef in jp2h"""
j2k = Jp2k(self.j2kfile)
boxes = [self.ihdr, self.colr_rgb]
self.jp2h.box = boxes
@ -269,151 +290,47 @@ class TestChannelDefinition(unittest.TestCase):
cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
association=association)
boxes = [self.jP, self.ftyp, self.jp2h, cdef, self.jp2c]
boxes = [self.jp2b, self.ftyp, self.jp2h, cdef, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
def test_bad_type(self):
# Channel types are limited to 0, 1, 2, 65535
# Should reject if not all of index, channel_type, association the
# same length.
"""Channel types are limited to 0, 1, 2, 65535
Should reject if not all of index, channel_type, association the
same length.
"""
channel_type = (COLOR, COLOR, 3)
association = (RED, GREEN, BLUE)
with self.assertRaises(IOError):
box = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
association=association)
glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
association=association)
def test_wrong_lengths(self):
# Should reject if not all of index, channel_type, association the
# same length.
"""Should reject if not all of index, channel_type, association the
same length.
"""
channel_type = (COLOR, COLOR)
association = (RED, GREEN, BLUE)
with self.assertRaises(IOError):
box = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
association=association)
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
class TestXML(unittest.TestCase):
def setUp(self):
self.jp2file = glymur.data.nemo()
self.j2kfile = glymur.data.goodstuff()
raw_xml = b"""<?xml version="1.0"?>
<data>
<country name="Liechtenstein">
<rank>1</rank>
<year>2008</year>
<gdppc>141100</gdppc>
<neighbor name="Austria" direction="E"/>
<neighbor name="Switzerland" direction="W"/>
</country>
<country name="Singapore">
<rank>4</rank>
<year>2011</year>
<gdppc>59900</gdppc>
<neighbor name="Malaysia" direction="N"/>
</country>
<country name="Panama">
<rank>68</rank>
<year>2011</year>
<gdppc>13600</gdppc>
<neighbor name="Costa Rica" direction="W"/>
<neighbor name="Colombia" direction="E"/>
</country>
</data>"""
with tempfile.NamedTemporaryFile(suffix=".xml", delete=False) as tfile:
tfile.write(raw_xml)
tfile.flush()
self.xmlfile = tfile.name
j2k = Jp2k(self.j2kfile)
c = j2k.get_codestream()
height = c.segment[1].ysiz
width = c.segment[1].xsiz
num_components = len(c.segment[1].xrsiz)
self.jP = JPEG2000SignatureBox()
self.ftyp = FileTypeBox()
self.jp2h = JP2HeaderBox()
self.jp2c = ContiguousCodestreamBox()
self.ihdr = ImageHeaderBox(height=height, width=width,
num_components=num_components)
self.colr = ColourSpecificationBox(colorspace=glymur.core.SRGB)
def tearDown(self):
os.unlink(self.xmlfile)
pass
def test_negative_both_file_and_xml_provided(self):
"""The XML should come from only one source."""
j2k = Jp2k(self.j2kfile)
xml_object = ET.parse(self.xmlfile)
with self.assertRaises((IOError, OSError)) as ce:
xmlb = 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 an XMLBox.
j2k = Jp2k(self.j2kfile)
self.jp2h.box = [self.ihdr, self.colr]
the_xml = ET.fromstring('<?xml version="1.0"?><data>0</data>')
xmlb = glymur.jp2box.XMLBox(xml=the_xml)
self.assertEqual(ET.tostring(xmlb.xml),
b'<data>0</data>')
boxes = [self.jP, self.ftyp, self.jp2h, xmlb, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
jp2 = Jp2k(tfile.name)
self.assertEqual(jp2.box[3].box_id, 'xml ')
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):
j2k = Jp2k(self.j2kfile)
self.jp2h.box = [self.ihdr, self.colr]
xmlb = glymur.jp2box.XMLBox(filename=self.xmlfile)
boxes = [self.jP, self.ftyp, self.jp2h, xmlb, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
jp2 = Jp2k(tfile.name)
output_boxes = [box.box_id for box in jp2.box]
self.assertEqual(output_boxes, ['jP ', 'ftyp', 'jp2h', 'xml ',
'jp2c'])
elts = jp2.box[3].xml.findall('country')
self.assertEqual(len(elts), 3)
neighbor = elts[1].find('neighbor')
self.assertEqual(neighbor.attrib['name'], 'Malaysia')
self.assertEqual(neighbor.attrib['direction'], 'N')
glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
association=association)
class TestColourSpecificationBox(unittest.TestCase):
"""Test suite for colr box instantiation."""
def setUp(self):
self.j2kfile = glymur.data.goodstuff()
j2k = Jp2k(self.j2kfile)
c = j2k.get_codestream()
height = c.segment[1].ysiz
width = c.segment[1].xsiz
num_components = len(c.segment[1].xrsiz)
codestream = j2k.get_codestream()
height = codestream.segment[1].ysiz
width = codestream.segment[1].xsiz
num_components = len(codestream.segment[1].xrsiz)
self.jP = JPEG2000SignatureBox()
self.jp2b = JPEG2000SignatureBox()
self.ftyp = FileTypeBox()
self.jp2h = JP2HeaderBox()
self.jp2c = ContiguousCodestreamBox()
@ -425,10 +342,11 @@ class TestColourSpecificationBox(unittest.TestCase):
@unittest.skipIf(os.name == "nt",
"Problems using NamedTemporaryFile on windows.")
def test_color_specification_box_with_out_enumerated_colorspace(self):
def test_colr_with_out_enum_cspace(self):
"""must supply an enumerated colorspace when writing"""
j2k = Jp2k(self.j2kfile)
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
boxes[2].box = [self.ihdr, ColourSpecificationBox(colorspace=None)]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(NotImplementedError):
@ -436,47 +354,142 @@ class TestColourSpecificationBox(unittest.TestCase):
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_missing_colr_box(self):
"""jp2h must have a colr box"""
j2k = Jp2k(self.j2kfile)
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
boxes[2].box = [self.ihdr]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
def test_default_ColourSpecificationBox(self):
b = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.SRGB)
self.assertEqual(b.method, glymur.core.ENUMERATED_COLORSPACE)
self.assertEqual(b.precedence, 0)
self.assertEqual(b.approximation, 0)
self.assertEqual(b.colorspace, glymur.core.SRGB)
self.assertIsNone(b.icc_profile)
def test_default_colr(self):
"""basic colr instantiation"""
colr = ColourSpecificationBox(colorspace=glymur.core.SRGB)
self.assertEqual(colr.method, glymur.core.ENUMERATED_COLORSPACE)
self.assertEqual(colr.precedence, 0)
self.assertEqual(colr.approximation, 0)
self.assertEqual(colr.colorspace, glymur.core.SRGB)
self.assertIsNone(colr.icc_profile)
def test_ColourSpecificationBox_with_colorspace_and_icc(self):
# Colour specification boxes can't have both.
def test_colr_with_cspace_and_icc(self):
"""Colour specification boxes can't have both."""
with self.assertRaises((OSError, IOError)):
colorspace = glymur.core.SRGB
icc_profile = b'\x01\x02\x03\x04'
b = glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
icc_profile=icc_profile)
rawb = b'\x01\x02\x03\x04'
glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
icc_profile=rawb)
def test_ColourSpecificationBox_with_bad_method(self):
def test_colr_with_bad_method(self):
"""colr must have a valid method field"""
colorspace = glymur.core.SRGB
method = -1
with self.assertRaises(IOError):
b = glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
method=method)
glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
method=method)
def test_ColourSpecificationBox_with_bad_approximation(self):
def test_colr_with_bad_approx(self):
"""colr must have a valid approximation field"""
colorspace = glymur.core.SRGB
approx = -1
with self.assertRaises(IOError):
b = glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
approximation=approx)
glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
approximation=approx)
class TestAppend(unittest.TestCase):
"""Tests for append method."""
def setUp(self):
self.j2kfile = glymur.data.goodstuff()
self.jp2file = glymur.data.nemo()
def tearDown(self):
pass
def test_append_xml(self):
"""Should be able to append an XML box."""
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
shutil.copyfile(self.jp2file, tfile.name)
jp2 = Jp2k(tfile.name)
the_xml = ET.fromstring('<?xml version="1.0"?><data>0</data>')
xmlbox = glymur.jp2box.XMLBox(xml=the_xml)
jp2.append(xmlbox)
# The sequence of box IDs should be the same as before, but with an
# xml box at the end.
box_ids = [box.box_id for box in jp2.box]
expected = ['jP ', 'ftyp', 'jp2h', 'uuid', 'uuid', 'jp2c', 'xml ']
self.assertEqual(box_ids, expected)
self.assertEqual(ET.tostring(jp2.box[-1].xml.getroot()),
b'<data>0</data>')
def test_only_jp2_allowed_to_append(self):
"""Only JP2 files are allowed to be appended."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
shutil.copyfile(self.j2kfile, tfile.name)
jp2 = Jp2k(tfile.name)
# Make a UUID box.
uuid_instance = uuid.UUID('00000000-0000-0000-0000-000000000000')
data = b'0123456789'
uuidbox = glymur.jp2box.UUIDBox(uuid_instance, data)
with self.assertRaises(IOError):
jp2.append(uuidbox)
def test_length_field_is_zero(self):
"""L=0 (length field in box header) is handled.
L=0 implies that the containing box is the last box. If this is not
handled properly, the appended box is never seen.
"""
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.
offset = baseline_jp2.box[-1].offset
tfile.write(ifile.read(offset))
# Write the L, T fields of the jp2c box such that L == 0
write_buffer = struct.pack('>I4s', int(0), b'jp2c')
tfile.write(write_buffer)
# Write out the rest of the codestream.
ifile.seek(offset+8)
tfile.write(ifile.read())
tfile.flush()
jp2 = Jp2k(tfile.name)
the_xml = ET.fromstring('<?xml version="1.0"?><data>0</data>')
xmlbox = glymur.jp2box.XMLBox(xml=the_xml)
jp2.append(xmlbox)
# The sequence of box IDs should be the same as before, but with an
# xml box at the end.
box_ids = [box.box_id for box in jp2.box]
expected = ['jP ', 'ftyp', 'jp2h', 'uuid', 'uuid', 'jp2c', 'xml ']
self.assertEqual(box_ids, expected)
self.assertEqual(ET.tostring(jp2.box[-1].xml.getroot()),
b'<data>0</data>')
def test_only_xml_allowed_to_append(self):
"""Only XML boxes are allowed to be appended."""
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
shutil.copyfile(self.jp2file, tfile.name)
jp2 = Jp2k(tfile.name)
# Make a UUID box.
uuid_instance = uuid.UUID('00000000-0000-0000-0000-000000000000')
data = b'0123456789'
uuidbox = glymur.jp2box.UUIDBox(uuid_instance, data)
with self.assertRaises(IOError):
jp2.append(uuidbox)
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
"Missing openjp2 library.")
class TestWrap(unittest.TestCase):
"""Tests for wrap method."""
def setUp(self):
self.j2kfile = glymur.data.goodstuff()
@ -486,7 +499,7 @@ class TestWrap(unittest.TestCase):
pass
def verify_wrapped_raw(self, jp2file):
# Shared method by at least two tests.
"""Shared fixture"""
jp2 = Jp2k(jp2file)
self.assertEqual(len(jp2.box), 4)
@ -536,6 +549,7 @@ class TestWrap(unittest.TestCase):
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_wrap(self):
"""basic test for rewrapping a j2c file, no specified boxes"""
j2k = Jp2k(self.j2kfile)
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name)
@ -543,6 +557,7 @@ class TestWrap(unittest.TestCase):
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_wrap_jp2(self):
"""basic test for rewrapping a jp2 file, no specified boxes"""
j2k = Jp2k(self.jp2file)
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
jp2 = j2k.wrap(tfile.name)
@ -550,16 +565,17 @@ class TestWrap(unittest.TestCase):
self.assertEqual(boxes, ['jP ', 'ftyp', 'jp2h', 'jp2c'])
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_default_layout_but_with_specified_boxes(self):
def test_default_layout_with_boxes(self):
"""basic test for rewrapping a jp2 file, boxes specified"""
j2k = Jp2k(self.j2kfile)
boxes = [JPEG2000SignatureBox(),
FileTypeBox(),
JP2HeaderBox(),
ContiguousCodestreamBox()]
c = j2k.get_codestream()
height = c.segment[1].ysiz
width = c.segment[1].xsiz
num_components = len(c.segment[1].xrsiz)
codestream = j2k.get_codestream()
height = codestream.segment[1].ysiz
width = codestream.segment[1].xsiz
num_components = len(codestream.segment[1].xrsiz)
boxes[2].box = [ImageHeaderBox(height=height,
width=width,
num_components=num_components),
@ -569,17 +585,17 @@ class TestWrap(unittest.TestCase):
self.verify_wrapped_raw(tfile.name)
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_image_header_box_not_first_in_jp2_header(self):
# The specification says that ihdr must be the first box in jp2h.
def test_ihdr_not_first_in_jp2h(self):
"""The specification says that ihdr must be the first box in jp2h."""
j2k = Jp2k(self.j2kfile)
boxes = [JPEG2000SignatureBox(),
FileTypeBox(),
JP2HeaderBox(),
ContiguousCodestreamBox()]
c = j2k.get_codestream()
height = c.segment[1].ysiz
width = c.segment[1].xsiz
num_components = len(c.segment[1].xrsiz)
codestream = j2k.get_codestream()
height = codestream.segment[1].ysiz
width = codestream.segment[1].xsiz
num_components = len(codestream.segment[1].xrsiz)
boxes[2].box = [ColourSpecificationBox(colorspace=glymur.core.SRGB),
ImageHeaderBox(height=height,
width=width,
@ -589,14 +605,15 @@ class TestWrap(unittest.TestCase):
j2k.wrap(tfile.name, boxes=boxes)
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_first_2_boxes_not_jP_and_ftyp(self):
def test_first_boxes_jp_and_ftyp(self):
"""first two boxes must be jP followed by ftyp"""
j2k = Jp2k(self.j2kfile)
c = j2k.get_codestream()
height = c.segment[1].ysiz
width = c.segment[1].xsiz
num_components = len(c.segment[1].xrsiz)
codestream = j2k.get_codestream()
height = codestream.segment[1].ysiz
width = codestream.segment[1].xsiz
num_components = len(codestream.segment[1].xrsiz)
jP = JPEG2000SignatureBox()
jp2b = JPEG2000SignatureBox()
ftyp = FileTypeBox()
jp2h = JP2HeaderBox()
jp2c = ContiguousCodestreamBox()
@ -604,20 +621,21 @@ class TestWrap(unittest.TestCase):
ihdr = ImageHeaderBox(height=height, width=width,
num_components=num_components)
jp2h.box = [ihdr, colr]
boxes = [ftyp, jP, jp2h, jp2c]
boxes = [ftyp, jp2b, jp2h, jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_jp2h_not_preceeding_jp2c(self):
"""jp2h must precede jp2c"""
j2k = Jp2k(self.j2kfile)
c = j2k.get_codestream()
height = c.segment[1].ysiz
width = c.segment[1].xsiz
num_components = len(c.segment[1].xrsiz)
codestream = j2k.get_codestream()
height = codestream.segment[1].ysiz
width = codestream.segment[1].xsiz
num_components = len(codestream.segment[1].xrsiz)
jP = JPEG2000SignatureBox()
jp2b = JPEG2000SignatureBox()
ftyp = FileTypeBox()
jp2h = JP2HeaderBox()
jp2c = ContiguousCodestreamBox()
@ -625,68 +643,74 @@ class TestWrap(unittest.TestCase):
ihdr = ImageHeaderBox(height=height, width=width,
num_components=num_components)
jp2h.box = [ihdr, colr]
boxes = [jP, ftyp, jp2c, jp2h]
boxes = [jp2b, ftyp, jp2c, jp2h]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_missing_codestream(self):
"""Need a codestream box in order to call wrap method."""
j2k = Jp2k(self.j2kfile)
c = j2k.get_codestream()
height = c.segment[1].ysiz
width = c.segment[1].xsiz
num_components = len(c.segment[1].xrsiz)
codestream = j2k.get_codestream()
height = codestream.segment[1].ysiz
width = codestream.segment[1].xsiz
num_components = len(codestream.segment[1].xrsiz)
jP = JPEG2000SignatureBox()
jp2k = JPEG2000SignatureBox()
ftyp = FileTypeBox()
jp2h = JP2HeaderBox()
ihdr = ImageHeaderBox(height=height, width=width,
num_components=num_components)
jp2h.box = [ihdr]
boxes = [jP, ftyp, jp2h]
boxes = [jp2k, ftyp, jp2h]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
class TestJp2Boxes(unittest.TestCase):
"""Tests for canonical JP2 boxes."""
def test_default_JPEG2000SignatureBox(self):
# Should be able to instantiate a JPEG2000SignatureBox
b = glymur.jp2box.JPEG2000SignatureBox()
self.assertEqual(b.signature, (13, 10, 135, 10))
def test_default_jp2k(self):
"""Should be able to instantiate a JPEG2000SignatureBox"""
jp2k = glymur.jp2box.JPEG2000SignatureBox()
self.assertEqual(jp2k.signature, (13, 10, 135, 10))
def test_default_FileTypeBox(self):
# Should be able to instantiate a FileTypeBox
b = glymur.jp2box.FileTypeBox()
self.assertEqual(b.brand, 'jp2 ')
self.assertEqual(b.minor_version, 0)
self.assertEqual(b.compatibility_list, ['jp2 '])
def test_default_ftyp(self):
"""Should be able to instantiate a FileTypeBox"""
ftyp = glymur.jp2box.FileTypeBox()
self.assertEqual(ftyp.brand, 'jp2 ')
self.assertEqual(ftyp.minor_version, 0)
self.assertEqual(ftyp.compatibility_list, ['jp2 '])
def test_default_ImageHeaderBox(self):
# Should be able to instantiate an image header box.
b = glymur.jp2box.ImageHeaderBox(height=512, width=256,
num_components=3)
self.assertEqual(b.height, 512)
self.assertEqual(b.width, 256)
self.assertEqual(b.num_components, 3)
self.assertEqual(b.bits_per_component, 8)
self.assertFalse(b.signed)
self.assertFalse(b.colorspace_unknown)
def test_default_ihdr(self):
"""Should be able to instantiate an image header box."""
ihdr = glymur.jp2box.ImageHeaderBox(height=512, width=256,
num_components=3)
self.assertEqual(ihdr.height, 512)
self.assertEqual(ihdr.width, 256)
self.assertEqual(ihdr.num_components, 3)
self.assertEqual(ihdr.bits_per_component, 8)
self.assertFalse(ihdr.signed)
self.assertFalse(ihdr.colorspace_unknown)
def test_default_JP2HeaderBox(self):
b1 = JP2HeaderBox()
b1.box = [ImageHeaderBox(height=512, width=256),
ColourSpecificationBox(colorspace=glymur.core.GREYSCALE)]
def test_default_jp2headerbox(self):
"""Should be able to set jp2h boxes."""
box = JP2HeaderBox()
box.box = [ImageHeaderBox(height=512, width=256),
ColourSpecificationBox(colorspace=glymur.core.GREYSCALE)]
self.assertTrue(True)
def test_default_ContiguousCodestreamBox(self):
b = ContiguousCodestreamBox()
self.assertEqual(b.box_id, 'jp2c')
self.assertIsNone(b.main_header)
def test_default_ccodestreambox(self):
"""Raw instantiation should not produce a main_header."""
box = ContiguousCodestreamBox()
self.assertEqual(box.box_id, 'jp2c')
self.assertIsNone(box.main_header)
class TestJpxBoxes(unittest.TestCase):
"""Tests for JPX boxes."""
def setUp(self):
pass
@ -694,11 +718,11 @@ class TestJpxBoxes(unittest.TestCase):
def tearDown(self):
pass
@unittest.skipIf(format_corpus_data_root is None,
@unittest.skipIf(FORMAT_CORPUS_DATA_ROOT is None,
"FORMAT_CORPUS_DATA_ROOT environment variable not set")
def test_codestream_header(self):
# Should recognize codestream header box.
jfile = os.path.join(format_corpus_data_root,
"""Should recognize codestream header box."""
jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT,
'jp2k-formats/balloon.jpf')
jpx = Jp2k(jfile)
@ -706,11 +730,11 @@ class TestJpxBoxes(unittest.TestCase):
self.assertEqual(jpx.box[4].box_id, 'jpch')
self.assertEqual(len(jpx.box[4].box), 0)
@unittest.skipIf(format_corpus_data_root is None,
@unittest.skipIf(FORMAT_CORPUS_DATA_ROOT is None,
"FORMAT_CORPUS_DATA_ROOT environment variable not set")
def test_compositing_layer_header(self):
# Should recognize compositing layer header box.
jfile = os.path.join(format_corpus_data_root,
"""Should recognize compositing layer header box."""
jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT,
'jp2k-formats/balloon.jpf')
jpx = Jp2k(jfile)

View file

@ -0,0 +1,294 @@
# -*- coding: utf-8 -*-
"""
Test suite specifically targeting JP2 box layout.
"""
# E1103: return value from read may be list or np array
# pylint: disable=E1103
# F0401: unittest2 is needed on python-2.6 (pylint on 2.7)
# pylint: disable=F0401
# R0902: More than 7 instance attributes are just fine for testing.
# pylint: disable=R0902
# R0904: Seems like pylint is fooled in this situation
# pylint: disable=R0904
# W0613: load_tests doesn't need to use ignore or loader arguments.
# pylint: disable=W0613
import os
import struct
import sys
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:
import unittest
import glymur
from glymur import Jp2k
from glymur.jp2box import ColourSpecificationBox, ContiguousCodestreamBox
from glymur.jp2box import FileTypeBox, ImageHeaderBox, JP2HeaderBox
from glymur.jp2box import JPEG2000SignatureBox
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
class TestXML(unittest.TestCase):
"""Test suite for XML boxes."""
def setUp(self):
self.jp2file = glymur.data.nemo()
self.j2kfile = glymur.data.goodstuff()
raw_xml = b"""<?xml version="1.0"?>
<data>
<country name="Liechtenstein">
<rank>1</rank>
<year>2008</year>
<gdppc>141100</gdppc>
<neighbor name="Austria" direction="E"/>
<neighbor name="Switzerland" direction="W"/>
</country>
<country name="Singapore">
<rank>4</rank>
<year>2011</year>
<gdppc>59900</gdppc>
<neighbor name="Malaysia" direction="N"/>
</country>
<country name="Panama">
<rank>68</rank>
<year>2011</year>
<gdppc>13600</gdppc>
<neighbor name="Costa Rica" direction="W"/>
<neighbor name="Colombia" direction="E"/>
</country>
</data>"""
with tempfile.NamedTemporaryFile(suffix=".xml", delete=False) as tfile:
tfile.write(raw_xml)
tfile.flush()
self.xmlfile = tfile.name
j2k = Jp2k(self.j2kfile)
codestream = j2k.get_codestream()
height = codestream.segment[1].ysiz
width = codestream.segment[1].xsiz
num_components = len(codestream.segment[1].xrsiz)
self.jp2b = JPEG2000SignatureBox()
self.ftyp = FileTypeBox()
self.jp2h = JP2HeaderBox()
self.jp2c = ContiguousCodestreamBox()
self.ihdr = ImageHeaderBox(height=height, width=width,
num_components=num_components)
self.colr = ColourSpecificationBox(colorspace=glymur.core.SRGB)
def tearDown(self):
os.unlink(self.xmlfile)
def test_negative_file_and_xml(self):
"""The XML should come from only one source."""
xml_object = ET.parse(self.xmlfile)
with self.assertRaises((IOError, OSError)):
glymur.jp2box.XMLBox(filename=self.xmlfile, xml=xml_object)
def test_basic_xml(self):
"""Should be able to write a basic XMLBox"""
j2k = Jp2k(self.j2kfile)
self.jp2h.box = [self.ihdr, self.colr]
the_xml = ET.fromstring('<?xml version="1.0"?><data>0</data>')
xmlb = glymur.jp2box.XMLBox(xml=the_xml)
self.assertEqual(ET.tostring(xmlb.xml),
b'<data>0</data>')
boxes = [self.jp2b, self.ftyp, self.jp2h, xmlb, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
jp2 = Jp2k(tfile.name)
self.assertEqual(jp2.box[3].box_id, 'xml ')
self.assertEqual(ET.tostring(jp2.box[3].xml.getroot()),
b'<data>0</data>')
def test_xml_from_file(self):
"""Must be able to create an XML box from an XML file."""
j2k = Jp2k(self.j2kfile)
self.jp2h.box = [self.ihdr, self.colr]
xmlb = glymur.jp2box.XMLBox(filename=self.xmlfile)
boxes = [self.jp2b, self.ftyp, self.jp2h, xmlb, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
jp2 = Jp2k(tfile.name)
output_boxes = [box.box_id for box in jp2.box]
self.assertEqual(output_boxes, ['jP ', 'ftyp', 'jp2h', 'xml ',
'jp2c'])
elts = jp2.box[3].xml.findall('country')
self.assertEqual(len(elts), 3)
neighbor = elts[1].find('neighbor')
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):
"""Test suite for bad XML box situations"""
@classmethod
def setUpClass(cls):
"""Setup a JP2 file with a bad XML box. We only need to do this once
per class rather than once per test.
"""
jp2file = glymur.data.nemo()
with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile:
cls._bad_xml_file = tfile.name
with open(jp2file, 'rb') as ifile:
# Everything up until the UUID box.
write_buffer = ifile.read(77)
tfile.write(write_buffer)
# Write the xml box with bad xml
# Length = 28, id is 'xml '.
write_buffer = struct.pack('>I4s', int(28), b'xml ')
tfile.write(write_buffer)
write_buffer = '<test>this is a test'
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()
@classmethod
def tearDownClass(cls):
os.unlink(cls._bad_xml_file)
def setUp(self):
self.jp2file = glymur.data.nemo()
def tearDown(self):
pass
@unittest.skipIf(sys.hexversion < 0x03020000,
"Uses features introduced in 3.2.")
def test_invalid_xml_box_warning(self):
"""Should warn in case of bad XML"""
with self.assertWarns(UserWarning):
Jp2k(self._bad_xml_file)
def test_invalid_xml_box(self):
"""Should be able to recover info from xml box with bad xml."""
with warnings.catch_warnings():
warnings.simplefilter("ignore")
jp2k = Jp2k(self._bad_xml_file)
self.assertEqual(jp2k.box[3].box_id, 'xml ')
self.assertEqual(jp2k.box[3].offset, 77)
self.assertEqual(jp2k.box[3].length, 28)
self.assertIsNone(jp2k.box[3].xml)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
class TestBadButRecoverableXmlFile(unittest.TestCase):
"""Test suite for XML box that is bad, but we can still recover the XML."""
@classmethod
def setUpClass(cls):
"""Setup a JP2 file with bad bytes preceding the XML. We only need
to do this once per class rather than once per test.
"""
jp2file = glymur.data.nemo()
with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile:
cls._bad_xml_file = tfile.name
with open(jp2file, 'rb') as ifile:
# Everything up until the UUID box.
write_buffer = ifile.read(77)
tfile.write(write_buffer)
# Write the xml box with bad xml
# Length = 64, id is 'xml '.
write_buffer = struct.pack('>I4s', int(64), b'xml ')
tfile.write(write_buffer)
# Write out 8 bad bytes.
write_buffer = b'\x00\x00\x07\x90xml '
tfile.write(write_buffer)
# Write out 48 good bytes constituting the XML payload.
write_buffer = b'<?xml version="1.0"?>'
tfile.write(write_buffer)
write_buffer = b'<test>this is a test</test>'
tfile.write(write_buffer)
# Get the rest of the input file.
write_buffer = ifile.read()
tfile.write(write_buffer)
tfile.flush()
@classmethod
def tearDownClass(cls):
os.unlink(cls._bad_xml_file)
@unittest.skipIf(sys.hexversion < 0x03020000,
"Uses features introduced in 3.2.")
def test_bad_xml_box_warning(self):
"""Should warn in case of bad XML"""
with self.assertWarns(UserWarning):
Jp2k(self._bad_xml_file)
def test_recover_from_bad_xml(self):
"""Should be able to recover info from xml box with bad xml."""
with warnings.catch_warnings():
warnings.simplefilter("ignore")
jp2 = Jp2k(self._bad_xml_file)
self.assertEqual(jp2.box[3].box_id, 'xml ')
self.assertEqual(jp2.box[3].offset, 77)
self.assertEqual(jp2.box[3].length, 64)
self.assertEqual(ET.tostring(jp2.box[3].xml.getroot()),
b'<test>this is a test</test>')

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2,8 +2,17 @@
The tests here do not correspond directly to the OpenJPEG test suite, but
seem like logical negative tests to add.
"""
#pylint: disable-all
# E1101: assertWarns introduced in python 3.2
# pylint: disable=E1101
# R0904: Not too many methods in unittest.
# pylint: disable=R0904
# unittest2 is python2.6 only (pylint/python-2.7)
# pylint: disable=F0401
import os
import re
import sys
import tempfile
@ -12,48 +21,21 @@ if sys.hexversion < 0x02070000:
else:
import unittest
import warnings
import numpy as np
import pkg_resources
from glymur.lib import openjp2 as opj2
msg = "Matplotlib with the PIL backend must be available in order to run the "
msg += "tests in this suite."
no_read_backend_msg = msg
try:
from PIL import Image
from matplotlib.pyplot import imread
no_read_backend = False
except:
no_read_backend = True
from .fixtures import OPJ_DATA_ROOT, opj_data_file, read_image
from .fixtures import NO_READ_BACKEND, NO_READ_BACKEND_MSG
from glymur import Jp2k
import glymur
try:
data_root = os.environ['OPJ_DATA_ROOT']
except KeyError:
data_root = None
except:
raise
def read_image(infile):
# PIL issues warnings which we do not care about, so suppress them.
with warnings.catch_warnings():
warnings.simplefilter("ignore")
data = imread(infile)
return data
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
"Missing openjp2 library.")
@unittest.skipIf(no_read_backend, no_read_backend_msg)
@unittest.skipIf(data_root is None,
"OPJ_DATA_ROOT environment variable not set")
@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version),
"Functionality not implemented for 1.3, 1.4")
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_OPJ_DATA_ROOT environment variable not set")
class TestSuiteNegative(unittest.TestCase):
"""Test suite for certain negative tests from openjpeg suite."""
def setUp(self):
self.jp2file = glymur.data.nemo()
@ -62,46 +44,52 @@ class TestSuiteNegative(unittest.TestCase):
def tearDown(self):
pass
@unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG)
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_negative_psnr_with_cratios(self):
# Using psnr with cratios options is not allowed.
def test_psnr_with_cratios(self):
"""Using psnr with cratios options is not allowed."""
# Not an OpenJPEG test, but close.
infile = os.path.join(data_root, 'input/nonregression/Bretagne1.ppm')
infile = opj_data_file('input/nonregression/Bretagne1.ppm')
data = read_image(infile)
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
with self.assertRaises(IOError):
j.write(data, psnr=[30, 35, 40], cratios=[2, 3, 4])
def test_NR_MarkerIsNotCompliant_j2k_dump(self):
def test_nr_marker_not_compliant(self):
"""non-compliant marker, should still be able to read"""
relpath = 'input/nonregression/MarkerIsNotCompliant.j2k'
jfile = os.path.join(data_root, relpath)
jfile = opj_data_file(relpath)
jp2k = Jp2k(jfile)
c = jp2k.get_codestream(header_only=False)
jp2k.get_codestream(header_only=False)
self.assertTrue(True)
@unittest.skipIf(sys.hexversion < 0x03020000,
"Uses features introduced in 3.2.")
def test_NR_illegalcolortransform_dump(self):
# EOC marker is bad
def test_nr_illegalclrtransform(self):
"""EOC marker is bad"""
relpath = 'input/nonregression/illegalcolortransform.j2k'
jfile = os.path.join(data_root, relpath)
jfile = opj_data_file(relpath)
jp2k = Jp2k(jfile)
with self.assertWarns(UserWarning) as cw:
c = jp2k.get_codestream(header_only=False)
with self.assertWarns(UserWarning):
codestream = jp2k.get_codestream(header_only=False)
# Verify that the last segment returned in the codestream is SOD,
# not EOC. Codestream parsing should stop when we try to jump to
# the end of SOT.
self.assertEqual(c.segment[-1].marker_id, 'SOD')
self.assertEqual(codestream.segment[-1].marker_id, 'SOD')
def test_NR_Cannotreaddatawithnosizeknown_j2k(self):
def test_nr_cannotreadwnosizeknown(self):
"""not sure exactly what is wrong with this file"""
relpath = 'input/nonregression/Cannotreaddatawithnosizeknown.j2k'
jfile = os.path.join(data_root, relpath)
jfile = opj_data_file(relpath)
jp2k = Jp2k(jfile)
c = jp2k.get_codestream(header_only=False)
jp2k.get_codestream(header_only=False)
self.assertTrue(True)
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_code_block_dimensions(self):
"""don't allow extreme codeblock sizes"""
# opj_compress doesn't allow the dimensions of a codeblock
# to be too small or too big, so neither will we.
data = np.zeros((256, 256), dtype=np.uint8)
@ -109,55 +97,55 @@ class TestSuiteNegative(unittest.TestCase):
j = Jp2k(tfile.name, 'wb')
# opj_compress doesn't allow code block area to exceed 4096.
with self.assertRaises(IOError) as cr:
with self.assertRaises(IOError):
j.write(data, cbsize=(256, 256))
# opj_compress doesn't allow either dimension to be less than 4.
with self.assertRaises(IOError) as cr:
with self.assertRaises(IOError):
j.write(data, cbsize=(2048, 2))
with self.assertRaises(IOError) as cr:
with self.assertRaises(IOError):
j.write(data, cbsize=(2, 2048))
@unittest.skipIf(sys.hexversion < 0x03020000,
"Uses features introduced in 3.2.")
def test_exceeded_box(self):
"""should warn if reading past end of a box"""
# Verify that a warning is issued if we read past the end of a box
# This file has a palette (pclr) box whose length is impossibly
# short.
infile = os.path.join(data_root,
infile = os.path.join(OPJ_DATA_ROOT,
'input/nonregression/mem-b2ace68c-1381.jp2')
with self.assertWarns(UserWarning) as cw:
j = Jp2k(infile)
with self.assertWarns(UserWarning):
Jp2k(infile)
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_precinct_size_not_multiple_of_two(self):
# Seems like precinct sizes should be powers of two.
def test_precinct_size_not_p2(self):
"""precinct sizes should be powers of two."""
ifile = Jp2k(self.j2kfile)
data = ifile.read(rlevel=2)
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
ofile = Jp2k(tfile.name, 'wb')
with self.assertRaises(IOError) as ce:
with self.assertRaises(IOError):
ofile.write(data, psizes=[(13, 13)])
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_codeblock_size_not_multiple_of_two(self):
# Seems like code block sizes should be powers of two.
def test_cblk_size_not_power_of_two(self):
"""code block sizes should be powers of two."""
ifile = Jp2k(self.j2kfile)
data = ifile.read(rlevel=2)
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
ofile = Jp2k(tfile.name, 'wb')
with self.assertRaises(IOError) as ce:
with self.assertRaises(IOError):
ofile.write(data, cbsize=(13, 12))
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_codeblock_size_with_precinct_size(self):
# Seems like code block sizes should never exceed half that of
# precinct size.
def test_cblk_size_precinct_size(self):
"""code block sizes should never exceed half that of precinct size."""
ifile = Jp2k(self.j2kfile)
data = ifile.read(rlevel=2)
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
ofile = Jp2k(tfile.name, 'wb')
with self.assertRaises(IOError) as ce:
with self.assertRaises(IOError):
ofile.write(data,
cbsize=(64, 64),
psizes=[(64, 64)])

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,22 @@
#pylint: disable-all
# -*- coding: utf-8 -*-
"""Test suite for printing.
"""
# C0302: don't care too much about having too many lines in a test module
# pylint: disable=C0302
# E061: unittest.mock introduced in 3.3 (python-2.7/pylint issue)
# pylint: disable=E0611,F0401
# R0904: Not too many methods in unittest.
# pylint: disable=R0904
import os
import pkg_resources
import re
import struct
import sys
import tempfile
import warnings
from xml.etree import cElementTree as ET
if sys.hexversion < 0x02070000:
import unittest2 as unittest
@ -23,18 +35,12 @@ else:
import glymur
from glymur import Jp2k
try:
data_root = os.environ['OPJ_DATA_ROOT']
except KeyError:
data_root = None
except:
raise
from .fixtures import OPJ_DATA_ROOT, opj_data_file
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
"Missing openjp2 library.")
@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version),
"Need at least 1.5 in order to write jp2 files.")
class TestPrintingNeedsLib(unittest.TestCase):
"""These tests require the library, mostly in order to just setup the test.
"""
@ -121,12 +127,13 @@ class TestPrintingNeedsLib(unittest.TestCase):
+ '(0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), '
+ '(0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), '
+ '(0, 10)]']
self.expectedPlain = '\n'.join(lines)
self.expected_plain = '\n'.join(lines)
def tearDown(self):
pass
def test_asoc_label_box(self):
"""verify printing of asoc, label boxes"""
# Construct a fake file with an asoc and a label box, as
# OpenJPEG doesn't have such a file.
data = glymur.Jp2k(self.jp2file).read(rlevel=1)
@ -137,30 +144,30 @@ class TestPrintingNeedsLib(unittest.TestCase):
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2:
# Offset of the codestream is where we start.
buffer = tfile.read(77)
tfile2.write(buffer)
wbuffer = tfile.read(77)
tfile2.write(wbuffer)
# read the rest of the file, it's the codestream.
codestream = tfile.read()
# Write the asoc superbox.
# Length = 36, id is 'asoc'.
buffer = struct.pack('>I4s', int(56), b'asoc')
tfile2.write(buffer)
wbuffer = struct.pack('>I4s', int(56), b'asoc')
tfile2.write(wbuffer)
# Write the contained label box
buffer = struct.pack('>I4s', int(13), b'lbl ')
tfile2.write(buffer)
wbuffer = struct.pack('>I4s', int(13), b'lbl ')
tfile2.write(wbuffer)
tfile2.write('label'.encode())
# Write the xml box
# Length = 36, id is 'xml '.
buffer = struct.pack('>I4s', int(35), b'xml ')
tfile2.write(buffer)
wbuffer = struct.pack('>I4s', int(35), b'xml ')
tfile2.write(wbuffer)
buffer = '<test>this is a test</test>'
buffer = buffer.encode()
tfile2.write(buffer)
wbuffer = '<test>this is a test</test>'
wbuffer = wbuffer.encode()
tfile2.write(wbuffer)
# Now append the codestream.
tfile2.write(codestream)
@ -179,6 +186,7 @@ class TestPrintingNeedsLib(unittest.TestCase):
self.assertEqual(actual, expected)
def test_jp2dump(self):
"""basic jp2dump test"""
with patch('sys.stdout', new=StringIO()) as fake_out:
glymur.jp2dump(self._plain_nemo_file)
actual = fake_out.getvalue().strip()
@ -187,9 +195,10 @@ class TestPrintingNeedsLib(unittest.TestCase):
lst = actual.split('\n')
lst = lst[1:]
actual = '\n'.join(lst)
self.assertEqual(actual, self.expectedPlain)
self.assertEqual(actual, self.expected_plain)
def test_entire_file(self):
"""verify output from printing entire file"""
j = glymur.Jp2k(self._plain_nemo_file)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j)
@ -200,10 +209,11 @@ class TestPrintingNeedsLib(unittest.TestCase):
lst = lst[1:]
actual = '\n'.join(lst)
self.assertEqual(actual, self.expectedPlain)
self.assertEqual(actual, self.expected_plain)
class TestPrinting(unittest.TestCase):
"""Test suite for printing where the libraries are not needed"""
def setUp(self):
# Save sys.stdout.
@ -212,7 +222,8 @@ class TestPrinting(unittest.TestCase):
def tearDown(self):
pass
def test_COC_segment(self):
def test_coc_segment(self):
"""verify printing of COC segment"""
j = glymur.Jp2k(self.jp2file)
codestream = j.get_codestream(header_only=False)
with patch('sys.stdout', new=StringIO()) as fake_out:
@ -239,7 +250,8 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
def test_COD_segment(self):
def test_cod_segment(self):
"""verify printing of COD segment"""
j = glymur.Jp2k(self.jp2file)
codestream = j.get_codestream()
with patch('sys.stdout', new=StringIO()) as fake_out:
@ -270,14 +282,13 @@ class TestPrinting(unittest.TestCase):
' Segmentation symbols: False']
expected = '\n'.join(lines)
self.actual = actual
self.expected = expected
self.assertEqual(actual, expected)
@unittest.skipIf(data_root is None,
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_icc_profile(self):
filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2')
"""verify printing of colr box with ICC profile"""
filename = opj_data_file('input/nonregression/text_GBR.jp2')
with warnings.catch_warnings():
# brand is 'jp2 ', but has any icc profile.
warnings.simplefilter("ignore")
@ -342,24 +353,26 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(data_root is None,
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_CRG(self):
filename = os.path.join(data_root, 'input/conformance/p0_03.j2k')
def test_crg(self):
"""verify printing of CRG segment"""
filename = opj_data_file('input/conformance/p0_03.j2k')
j = glymur.Jp2k(filename)
codestream = j.get_codestream()
with patch('sys.stdout', new=StringIO()) as fake_out:
print(codestream.segment[-5])
actual = fake_out.getvalue().strip()
lines = ['CRG marker segment at (87, 6)',
lines = ['CRG marker segment @ (87, 6)',
' Vertical, Horizontal offset: (0.50, 1.00)']
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(data_root is None,
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_RGN(self):
filename = os.path.join(data_root, 'input/conformance/p0_03.j2k')
def test_rgn(self):
"""verify printing of RGN segment"""
filename = opj_data_file('input/conformance/p0_03.j2k')
j = glymur.Jp2k(filename)
codestream = j.get_codestream(header_only=False)
with patch('sys.stdout', new=StringIO()) as fake_out:
@ -372,10 +385,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(data_root is None,
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_SOP(self):
filename = os.path.join(data_root, 'input/conformance/p0_03.j2k')
def test_sop(self):
"""verify printing of SOP segment"""
filename = opj_data_file('input/conformance/p0_03.j2k')
j = glymur.Jp2k(filename)
codestream = j.get_codestream(header_only=False)
with patch('sys.stdout', new=StringIO()) as fake_out:
@ -386,11 +400,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(data_root is None,
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_CME(self):
# Test printing a CME or comment marker segment.
filename = os.path.join(data_root, 'input/conformance/p0_02.j2k')
def test_cme(self):
"""Test printing a CME or comment marker segment."""
filename = opj_data_file('input/conformance/p0_02.j2k')
j = glymur.Jp2k(filename)
codestream = j.get_codestream()
# 2nd to last segment in the main header
@ -402,7 +416,8 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
def test_EOC_segment(self):
def test_eoc_segment(self):
"""verify printing of eoc segment"""
j = glymur.Jp2k(self.jp2file)
codestream = j.get_codestream(header_only=False)
with patch('sys.stdout', new=StringIO()) as fake_out:
@ -413,10 +428,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(data_root is None,
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_PLT_segment(self):
filename = os.path.join(data_root, 'input/conformance/p0_07.j2k')
def test_plt_segment(self):
"""verify printing of PLT segment"""
filename = opj_data_file('input/conformance/p0_07.j2k')
j = glymur.Jp2k(filename)
codestream = j.get_codestream(header_only=False)
with patch('sys.stdout', new=StringIO()) as fake_out:
@ -431,10 +447,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(data_root is None,
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_POD_segment(self):
filename = os.path.join(data_root, 'input/conformance/p0_13.j2k')
def test_pod_segment(self):
"""verify printing of POD segment"""
filename = opj_data_file('input/conformance/p0_13.j2k')
j = glymur.Jp2k(filename)
codestream = j.get_codestream()
with patch('sys.stdout', new=StringIO()) as fake_out:
@ -460,10 +477,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(data_root is None,
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_PPM_segment(self):
filename = os.path.join(data_root, 'input/conformance/p1_03.j2k')
def test_ppm_segment(self):
"""verify printing of PPM segment"""
filename = opj_data_file('input/conformance/p1_03.j2k')
j = glymur.Jp2k(filename)
codestream = j.get_codestream()
with patch('sys.stdout', new=StringIO()) as fake_out:
@ -477,10 +495,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(data_root is None,
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_PPT_segment(self):
filename = os.path.join(data_root, 'input/conformance/p1_06.j2k')
def test_ppt_segment(self):
"""verify printing of ppt segment"""
filename = opj_data_file('input/conformance/p1_06.j2k')
j = glymur.Jp2k(filename)
codestream = j.get_codestream(header_only=False)
with patch('sys.stdout', new=StringIO()) as fake_out:
@ -494,7 +513,8 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
def test_QCC_segment(self):
def test_qcc_segment(self):
"""verify printing of qcc segment"""
j = glymur.Jp2k(self.jp2file)
codestream = j.get_codestream(header_only=False)
with patch('sys.stdout', new=StringIO()) as fake_out:
@ -509,7 +529,8 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
def test_QCD_segment_5x3_transform(self):
def test_qcd_segment_5x3_transform(self):
"""verify printing of qcd segment"""
j = glymur.Jp2k(self.jp2file)
codestream = j.get_codestream()
with patch('sys.stdout', new=StringIO()) as fake_out:
@ -523,7 +544,8 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
def test_SIZ_segment(self):
def test_siz_segment(self):
"""verify printing of SIZ segment"""
j = glymur.Jp2k(self.jp2file)
codestream = j.get_codestream()
with patch('sys.stdout', new=StringIO()) as fake_out:
@ -544,7 +566,8 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
def test_SOC_segment(self):
def test_soc_segment(self):
"""verify printing of SOC segment"""
j = glymur.Jp2k(self.jp2file)
codestream = j.get_codestream()
with patch('sys.stdout', new=StringIO()) as fake_out:
@ -555,7 +578,8 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
def test_SOD_segment(self):
def test_sod_segment(self):
"""verify printing of SOD segment"""
j = glymur.Jp2k(self.jp2file)
codestream = j.get_codestream(header_only=False)
with patch('sys.stdout', new=StringIO()) as fake_out:
@ -566,7 +590,8 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
def test_SOT_segment(self):
def test_sot_segment(self):
"""verify printing of SOT segment"""
j = glymur.Jp2k(self.jp2file)
codestream = j.get_codestream(header_only=False)
with patch('sys.stdout', new=StringIO()) as fake_out:
@ -580,13 +605,13 @@ class TestPrinting(unittest.TestCase):
' Number of tile parts: 1']
expected = '\n'.join(lines)
self.maxDiff = None
self.assertEqual(actual, expected)
@unittest.skipIf(data_root is None,
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_TLM_segment(self):
filename = os.path.join(data_root, 'input/conformance/p0_15.j2k')
def test_tlm_segment(self):
"""verify printing of TLM segment"""
filename = opj_data_file('input/conformance/p0_15.j2k')
j = glymur.Jp2k(filename)
codestream = j.get_codestream()
with patch('sys.stdout', new=StringIO()) as fake_out:
@ -604,7 +629,7 @@ class TestPrinting(unittest.TestCase):
@unittest.skipIf(sys.hexversion < 0x02070000,
"Differences in XML printing between 2.6 and 2.7")
def test_xmp(self):
# Verify the printing of a UUID/XMP box.
"""Verify the printing of a UUID/XMP box."""
j = glymur.Jp2k(self.jp2file)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.box[4])
@ -626,6 +651,7 @@ class TestPrinting(unittest.TestCase):
self.assertEqual(actual, expected)
def test_codestream(self):
"""verify printing of entire codestream"""
j = glymur.Jp2k(self.jp2file)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.get_codestream())
@ -671,15 +697,15 @@ class TestPrinting(unittest.TestCase):
' CME marker segment @ (3209, 37)',
' "Created by OpenJPEG version 2.0.0"']
expected = '\n'.join(lst)
self.maxDiff = None
self.assertEqual(actual, expected)
@unittest.skipIf(sys.hexversion < 0x02070000,
"Differences in XML printing between 2.6 and 2.7")
@unittest.skipIf(data_root is None,
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_xml(self):
filename = os.path.join(data_root, 'input/conformance/file1.jp2')
"""verify printing of XML box"""
filename = opj_data_file('input/conformance/file1.jp2')
j = glymur.Jp2k(filename)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.box[2])
@ -706,10 +732,70 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(data_root is None,
@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):
filename = os.path.join(data_root, 'input/conformance/file2.jp2')
"""verify printing of cdef box"""
filename = opj_data_file('input/conformance/file2.jp2')
j = glymur.Jp2k(filename)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.box[2].box[2])
@ -721,10 +807,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(data_root is None,
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_component_mapping(self):
filename = os.path.join(data_root, 'input/conformance/file9.jp2')
"""verify printing of cmap box"""
filename = opj_data_file('input/conformance/file9.jp2')
j = glymur.Jp2k(filename)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.box[2].box[2])
@ -736,10 +823,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(data_root is None,
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_palette(self):
filename = os.path.join(data_root, 'input/conformance/file9.jp2')
def test_palette7(self):
"""verify printing of pclr box"""
filename = opj_data_file('input/conformance/file9.jp2')
j = glymur.Jp2k(filename)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.box[2].box[1])
@ -749,10 +837,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(data_root is None,
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_palette(self):
filename = os.path.join(data_root, 'input/conformance/file7.jp2')
def test_rreq(self):
"""verify printing of reader requirements box"""
filename = opj_data_file('input/conformance/file7.jp2')
j = glymur.Jp2k(filename)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.box[2])
@ -772,25 +861,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(data_root is None,
"OPJ_DATA_ROOT environment variable not set")
def test_CRG(self):
filename = os.path.join(data_root, 'input/conformance/p0_03.j2k')
j = glymur.Jp2k(filename)
codestream = j.get_codestream()
with patch('sys.stdout', new=StringIO()) as fake_out:
print(codestream.segment[6])
actual = fake_out.getvalue().strip()
lines = ['CRG marker segment @ (87, 6)',
' Vertical, Horizontal offset: (0.50, 1.00)']
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(data_root is None,
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_differing_subsamples(self):
# Issue 86.
filename = os.path.join(data_root, 'input/conformance/p0_05.j2k')
"""verify printing of SIZ with different subsampling... Issue 86."""
filename = opj_data_file('input/conformance/p0_05.j2k')
j = glymur.Jp2k(filename)
codestream = j.get_codestream()
with patch('sys.stdout', new=StringIO()) as fake_out:
@ -809,11 +884,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(data_root is None,
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_palette_box(self):
# Verify that palette (pclr) boxes are printed without error.
filename = os.path.join(data_root, 'input/conformance/file9.jp2')
"""Verify that palette (pclr) boxes are printed without error."""
filename = opj_data_file('input/conformance/file9.jp2')
j = glymur.Jp2k(filename)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.box[2].box[1])
@ -825,54 +900,56 @@ class TestPrinting(unittest.TestCase):
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_less_common_boxes(self):
"""verify uinf, ulst, url, res, resd, resc box printing"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
with open(self.jp2file, 'rb') as ifile:
# Everything up until the jp2c box.
buffer = ifile.read(77)
tfile.write(buffer)
wbuffer = ifile.read(77)
tfile.write(wbuffer)
# Write the UINF superbox
# Length = 50, id is uinf.
buffer = struct.pack('>I4s', int(50), b'uinf')
tfile.write(buffer)
wbuffer = struct.pack('>I4s', int(50), b'uinf')
tfile.write(wbuffer)
# Write the ULST box.
# Length is 26, 1 UUID, hard code that UUID as zeros.
buffer = struct.pack('>I4sHIIII', int(26), b'ulst', int(1),
int(0), int(0), int(0), int(0))
tfile.write(buffer)
wbuffer = struct.pack('>I4sHIIII', int(26), b'ulst', int(1),
int(0), int(0), int(0), int(0))
tfile.write(wbuffer)
# Write the URL box.
# Length is 16, version is one byte, flag is 3 bytes, url
# is the rest.
buffer = struct.pack('>I4sBBBB',
int(16), b'url ',
int(0), int(0), int(0), int(0))
tfile.write(buffer)
buffer = struct.pack('>ssss', b'a', b'b', b'c', b'd')
tfile.write(buffer)
wbuffer = struct.pack('>I4sBBBB',
int(16), b'url ',
int(0), int(0), int(0), int(0))
tfile.write(wbuffer)
wbuffer = struct.pack('>ssss', b'a', b'b', b'c', b'd')
tfile.write(wbuffer)
# Start the resolution superbox.
buffer = struct.pack('>I4s', int(44), b'res ')
tfile.write(buffer)
wbuffer = struct.pack('>I4s', int(44), b'res ')
tfile.write(wbuffer)
# Write the capture resolution box.
buffer = struct.pack('>I4sHHHHBB',
int(18), b'resc',
int(1), int(1), int(1), int(1),
int(0), int(1))
tfile.write(buffer)
wbuffer = struct.pack('>I4sHHHHBB',
int(18), b'resc',
int(1), int(1), int(1), int(1),
int(0), int(1))
tfile.write(wbuffer)
# Write the display resolution box.
buffer = struct.pack('>I4sHHHHBB',
int(18), b'resd',
int(1), int(1), int(1), int(1),
int(1), int(0))
tfile.write(buffer)
wbuffer = struct.pack('>I4sHHHHBB',
int(18), b'resd',
int(1), int(1), int(1), int(1),
int(1), int(0))
tfile.write(wbuffer)
# Get the rest of the input file.
buffer = ifile.read()
tfile.write(buffer)
wbuffer = ifile.read()
tfile.write(wbuffer)
tfile.flush()
jp2k = glymur.Jp2k(tfile.name)
@ -900,12 +977,13 @@ class TestPrinting(unittest.TestCase):
@unittest.skipIf(sys.hexversion < 0x03000000,
"Ordered dicts not printing well in 2.7")
@unittest.skipIf(data_root is None,
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_jpx_approximation_with_icc_profile(self):
def test_jpx_approx_icc_profile(self):
"""verify jpx with approx field equal to zero"""
# ICC profiles may be used in JP2, but the approximation field should
# be zero unless we have jpx. This file does both.
filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2')
filename = opj_data_file('input/nonregression/text_GBR.jp2')
with warnings.catch_warnings():
# brand is 'jp2 ', but has any icc profile.
warnings.simplefilter("ignore")
@ -944,11 +1022,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(data_root is None,
@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_uuid(self):
# UUID box
filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2')
"""verify printing of UUID box"""
filename = opj_data_file('input/nonregression/text_GBR.jp2')
with warnings.catch_warnings():
# brand is 'jp2 ', but has any icc profile.
warnings.simplefilter("ignore")
@ -967,6 +1045,7 @@ class TestPrinting(unittest.TestCase):
@unittest.skipIf(sys.hexversion < 0x03000000,
"Ordered dicts not printing well in 2.7")
def test_exif_uuid(self):
"""Verify printing of exif information"""
j = glymur.Jp2k(self.jp2file)
with patch('sys.stdout', new=StringIO()) as fake_out:
@ -1026,5 +1105,6 @@ class TestPrinting(unittest.TestCase):
self.assertEqual(actual, expected)
if __name__ == "__main__":
unittest.main()

55
glymur/version.py Normal file
View file

@ -0,0 +1,55 @@
# This file is part of glymur, a Python interface for accessing JPEG 2000.
#
# http://glymur.readthedocs.org
#
# Copyright 2013 John Evans
#
# License: MIT
import sys
import numpy as np
from distutils.version import LooseVersion
from .lib import openjpeg as opj
from .lib import openjp2 as opj2
# Do not change the format of this next line! Doing so risks breaking
# setup.py
version = "0.5.6"
_sv = LooseVersion(version)
version_tuple = _sv.version
if opj.OPENJPEG is None and opj2.OPENJP2 is None:
openjpeg_version = '0.0.0'
elif opj2.OPENJP2 is None:
openjpeg_version = opj.version()
else:
openjpeg_version = opj2.version()
_sv = LooseVersion(openjpeg_version)
openjpeg_version_tuple = _sv.version
__doc__ = """\
This is glymur **{glymur_version}**
* OPENJPEG version: **{openjpeg}**
""".format(glymur_version=version,
openjpeg=openjpeg_version)
info = """\
Summary of glymur configuration
-------------------------------
glymur {glymur}
OPENJPEG {openjpeg}
Python {python}
sys.platform {platform}
sys.maxsize {maxsize}
numpy {numpy}
""".format(glymur=version,
openjpeg=openjpeg_version,
python=sys.version,
platform=sys.platform,
maxsize=sys.maxsize,
numpy=np.__version__)

View file

@ -1,59 +0,0 @@
| OS | Python | Python | Python | Notes |
| | 2.6 | 2.7 | 3.3 | |
+-----------+--------+--------+--------+--------------------------------------+
| Windows | | X | | WinPython with OpenJPEG 1.5.1 and |
| | | | | 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. |
+-----------+--------+--------+--------+--------------------------------------+
| Mac | X | | | MacPorts with both OpenJPEG 1.5.1 |
| 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. |
+-----------+--------+--------+--------+--------------------------------------+
| Mac | | X | | MacPorts with both OpenJPEG 1.5.1 |
| 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. |
+-----------+--------+--------+--------+--------------------------------------+
| Mac | | | X | MacPorts with both OpenJPEG 1.5.1 |
| 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. |
+-----------+--------+--------+-----------------------------------------------+
| Fedora 19 | | | X | Ships with 1.5.1, openjp2 svn built, |
| | | | | too. 407 of 461 tests should pass. |
+-----------+--------+--------+--------+--------------------------------------+
| Fedora 18 | | X | | Ships with 1.5.1. 173 of 456 tests |
| | | | | should pass. |
+-----------+--------+--------+--------+--------------------------------------+
| Fedora 17 | | X | | Ships with 1.4.0. 171 of 456 tests |
| | | | | should pass. |
+-----------+--------+--------+--------+--------------------------------------+
| CentOS | X | | | Ships with 1.3.0. 169 of 456 tests |
| 6.3 | | | | should pass. |
+-----------+--------+--------+--------+--------------------------------------+
| Raspberry | | X | | Ships with 1.3.0. 171 of 456 tests |
| Pi | | | | should pass. |
| Debian 7 | | | | |
+-----------+--------+--------+--------+--------------------------------------+
Make release branch, qualify on all platforms.
Pylint on entire package should exceed 0.95.
pep8 should be pass cleanly.
Coverage should exceed 95%.
Make release candidate, push to Pypi, qualify in virtual environment.
Merge release branch to master, tag, push to master, push to Pypi.

View file

@ -1,8 +1,9 @@
from setuptools import setup, find_packages
import os
import re
import sys
kwargs = {'name': 'Glymur',
'version': '0.3.0',
'description': 'Tools for accessing JPEG2000 files',
'long_description': open('README.md').read(),
'author': 'John Evans',
@ -12,9 +13,8 @@ kwargs = {'name': 'Glymur',
'glymur.lib.test'],
'package_data': {'glymur': ['data/*.jp2', 'data/*.j2k']},
'scripts': ['bin/jp2dump'],
'license': 'LICENSE.txt',
'test_suite': 'glymur.test',
'platforms': ['darwin']}
'license': 'MIT',
'test_suite': 'glymur.test'}
instllrqrs = ['numpy>=1.4.1']
if sys.hexversion < 0x03030000:
@ -39,4 +39,12 @@ clssfrs = ["Programming Language :: Python",
"Intended Audience :: Information Technology",
"Topic :: Software Development :: Libraries :: Python Modules"]
kwargs['classifiers'] = clssfrs
# 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)