Merge branch 'release-0.5.6'
This commit is contained in:
commit
b7edcd5b43
36 changed files with 6069 additions and 5254 deletions
26
.travis.yml
Normal file
26
.travis.yml
Normal 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"
|
||||||
29
CHANGES.txt
29
CHANGES.txt
|
|
@ -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 31, 2013 - v0.3.0 Added support for official 2.0.0.
|
||||||
|
|
||||||
Jul 27, 2013 - v0.2.8 Fixed inconsistency regarding configuration
|
Jul 27, 2013 - v0.2.8 Fixed inconsistency regarding configuration
|
||||||
|
|
|
||||||
|
|
@ -76,9 +76,9 @@ copyright = u'2013, John Evans'
|
||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '0.2'
|
version = '0.5'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# 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
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,47 @@
|
||||||
----------------------------------
|
----------------------------------
|
||||||
Advanced Installation Instructions
|
Advanced Installation Instructions
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
Most users won't need to read this! You've been warned...
|
||||||
|
|
||||||
''''''''''''''''''''''
|
''''''''''''''''''''''
|
||||||
Glymur Configuration
|
Glymur Configuration
|
||||||
''''''''''''''''''''''
|
''''''''''''''''''''''
|
||||||
|
|
||||||
The default glymur installation process relies upon OpenJPEG version
|
The default glymur installation process relies upon OpenJPEG
|
||||||
1.X being properly installed on your system. This will, however, only
|
being properly installed on your system. If you have version 1.5 you can
|
||||||
give you you basic read capabilities, so if you wish to take advantage
|
both read and write JPEG 2000 files, but you may wish to install version 2.0
|
||||||
of more of glymur's features, you should install version 2.0 or
|
or the 2.0+ version from OpenJPEG's development trunk for better performance.
|
||||||
compile OpenJPEG as a shared library (named *openjp2* instead of
|
If you do that, you should compile it as a shared library (named *openjp2*
|
||||||
*openjpeg*) from the developmental source that you can retrieve via
|
instead of *openjpeg*) from the developmental source that you can retrieve
|
||||||
subversion. As of this time of writing, svn revision 2345 works.
|
via subversion. As of this time of writing, svn revision 2347 works.
|
||||||
You should also download the test data for the purpose of configuring
|
You should also download the test data for the purpose of configuring
|
||||||
and running OpenJPEG's test suite, check their instructions for all
|
and running OpenJPEG's test suite, check their instructions for all this.
|
||||||
this. You should set the **OPJ_DATA_ROOT** environment variable
|
You should set the **OPJ_DATA_ROOT** environment variable for the purpose
|
||||||
for the purpose of running Glymur's test suite. ::
|
of running Glymur's test suite. ::
|
||||||
|
|
||||||
$ svn co http://openjpeg.googlecode.com/svn/data
|
$ svn co http://openjpeg.googlecode.com/svn/data
|
||||||
$ export OPJ_DATA_ROOT=`pwd`/data
|
$ export OPJ_DATA_ROOT=`pwd`/data
|
||||||
|
|
||||||
Glymur uses ctypes (for the moment) to access the openjp2 library, and
|
Glymur uses ctypes to access the openjp2/openjpeg libraries,
|
||||||
because ctypes access libraries in a platform-dependent manner, it is
|
and because ctypes accesses libraries in a platform-dependent manner, it is
|
||||||
recommended that you create a configuration file to help Glymur properly find
|
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
|
the openjpeg or openjp2 libraries (linux users don't need to bother with this
|
||||||
configparser module, i.e. ::
|
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]
|
[library]
|
||||||
openjp2: /opt/openjp2-svn/lib/libopenjp2.so
|
openjp2: /opt/openjp2-svn/lib/libopenjp2.so
|
||||||
|
|
||||||
This assumes, of course, that you've installed OpenJPEG into
|
This assumes, of course, that you've installed OpenJPEG into
|
||||||
/opt/openjp2-svn on a linux system. The location of the configuration file
|
/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 ::
|
to the configuration file would normally be ::
|
||||||
|
|
||||||
$HOME/.config/glymur/glymurrc
|
$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
|
$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. ::
|
installed in a non-standard place, i.e. ::
|
||||||
|
|
||||||
[library]
|
[library]
|
||||||
openjp2: /opt/openjp2-svn/lib/libopenjp2.so
|
|
||||||
openjpeg: /not/the/usual/location/lib/libopenjpeg.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
|
You only need to read this section if you want detailed
|
||||||
platform-specific instructions on running as many tests as possible or wish to
|
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
|
Mac OS X
|
||||||
--------
|
--------
|
||||||
All the necessary packages are available to use glymur with Python 3.3 via
|
All the necessary packages are available to use glymur with Python 2.6, 2.7,
|
||||||
MacPorts. You should install the following set of ports:
|
and 3.3 via MacPorts. For python 3.3, you should install the following set of
|
||||||
|
ports:
|
||||||
|
|
||||||
* python33
|
* python33
|
||||||
* py33-numpy
|
* py33-numpy
|
||||||
|
|
@ -80,71 +84,38 @@ MacPorts supplies both OpenJPEG 1.5.0 and OpenJPEG 2.0.0.
|
||||||
|
|
||||||
Linux
|
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
|
* setuptools
|
||||||
'''''''''
|
* matplotlib
|
||||||
Fedora 18 ships with Python 3.3 and all the necessary RPMs are available to
|
* pillow
|
||||||
run the maximum number of tests.
|
* contextlib2 (python 2.6, 2.7 only)
|
||||||
|
* mock (python 2.6, 2.7 only)
|
||||||
|
* ordereddict (python 2.6 only)
|
||||||
|
|
||||||
* python3
|
Glymur's been tested on the following linux platforms without any unexpected
|
||||||
* python3-numpy
|
difficulties:
|
||||||
* python3-setuptools
|
|
||||||
* python3-matplotlib (for running tests)
|
|
||||||
* python3-matplotlib-tk (or whichever matplotlib backend you prefer)
|
|
||||||
* python3-pillow (for running tests)
|
|
||||||
|
|
||||||
Fedora 18
|
* OpenSUSE 12.3
|
||||||
'''''''''
|
* Fedora 17, 18, 19
|
||||||
Fedora 18 ships with Python 3.3 and the following RPMs are available to
|
* Raspian
|
||||||
meet the minimal set of requirements for running glymur.
|
* Travis CI (currently Ubuntu 12.04?)
|
||||||
|
* CentOS 6.4
|
||||||
* 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
|
|
||||||
|
|
||||||
Windows
|
Windows
|
||||||
-------
|
-------
|
||||||
32-bit WinPython 2.7.5 seems to work with OpenJPEG 1.X, 2.0, and the
|
32-bit WinPython 2.7.5 seemed to work with OpenJPEG 1.X, 2.0, and the
|
||||||
development version, but still requires contextlib2 and mock to be
|
development version, but still required contextlib2 and mock to be
|
||||||
installed via pip. WinPython 3.3.2, however, seems to have trouble
|
installed via pip. WinPython 3.3.2, however, seems to have trouble
|
||||||
with OpenJPEG 2.0, so I would suggest using the development version
|
with OpenJPEG 2.0, so I would suggest using the development version with
|
||||||
there (I'm unwilling to spend ANY more time trying to figure out what
|
that configuration. I no longer have any access to a windows machine,
|
||||||
the problem is there).
|
so I cannot currently offer much guidance here.
|
||||||
|
|
||||||
64-bit windows is completely untested.
|
|
||||||
|
|
||||||
|
|
||||||
'''''''
|
'''''''
|
||||||
|
|
@ -154,8 +125,8 @@ Testing
|
||||||
There are two environment variables you may wish to set before running the
|
There are two environment variables you may wish to set before running the
|
||||||
tests.
|
tests.
|
||||||
|
|
||||||
* **OPJ_DATA_ROOT** - points to directory for OpenJPEG test data
|
* **OPJ_DATA_ROOT** - points to directory for OpenJPEG test data (see above)
|
||||||
* **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)
|
* **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
|
Setting these two environment variables is not required, as any tests using
|
||||||
either of them will be skipped.
|
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
|
Quite a few tests are currently skipped. These include tests whose
|
||||||
OpenJPEG counterparts are already failing, and others which do pass but
|
OpenJPEG counterparts are already failing, and others which do pass but
|
||||||
still produce heaps of output on stderr. Rather than let this swamp
|
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
|
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
|
than on Python 3.3. The important part is whether or not any test
|
||||||
errors are reported at the end.
|
errors are reported at the end.
|
||||||
|
|
|
||||||
|
|
@ -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
|
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
|
COD segment section), but you can take a shortcut by supplying -1 as the
|
||||||
|
|
@ -14,7 +14,7 @@ resolution level. ::
|
||||||
>>> j = glymur.Jp2k(file)
|
>>> j = glymur.Jp2k(file)
|
||||||
>>> thumbnail = j.read(rlevel=-1)
|
>>> thumbnail = j.read(rlevel=-1)
|
||||||
|
|
||||||
Display metadata?
|
... display metadata?
|
||||||
=================
|
=================
|
||||||
There are two ways. From the unix command line, the script *jp2dump* is
|
There are two ways. From the unix command line, the script *jp2dump* is
|
||||||
available. ::
|
available. ::
|
||||||
|
|
@ -34,8 +34,40 @@ codestream box, only the main header is printed. It is possible to print
|
||||||
|
|
||||||
>>> print(j.get_codestream())
|
>>> 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
|
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
|
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),
|
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.
|
JP2 header superbox.
|
||||||
|
|
||||||
XML boxes are not in the minimal set of box requirements for the JP2 format, so
|
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
|
in order to add an XML box into the mix before the codestream box, we'll need to
|
||||||
boxes. If you already have a JP2 jacket in place, you can just reuse it,
|
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` : ::
|
though. Take the following example content in an XML file `favorites.xml` : ::
|
||||||
|
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
|
|
@ -84,10 +116,11 @@ though. Take the following example content in an XML file `favorites.xml` : ::
|
||||||
<category>Light Ale</category>
|
<category>Light Ale</category>
|
||||||
</favorite_things>
|
</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
|
>>> 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)
|
>>> boxes.insert(3, xmlbox)
|
||||||
>>> jp2_xml = jp2.wrap("newfile_with_xml.jp2", boxes=boxes)
|
>>> jp2_xml = jp2.wrap("newfile_with_xml.jp2", boxes=boxes)
|
||||||
>>> print(jp2_xml)
|
>>> print(jp2_xml)
|
||||||
|
|
@ -119,18 +152,23 @@ and add it after the JP2 header box, but before the codestream box ::
|
||||||
. (truncated)
|
. (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
|
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
|
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.
|
an image in a set of boxes that includes a channel definition box.
|
||||||
|
|
||||||
This example is based on SciPy example code found at
|
This example is based on SciPy example code found at
|
||||||
http://scipy-lectures.github.io/advanced/image_processing/#basic-manipulations .
|
http://scipy-lectures.github.io/advanced/image_processing/#basic-manipulations .
|
||||||
Instead of a circular mask, however, we'll make it an ellipse since the source
|
Instead of a circular mask we'll make it an ellipse since the source
|
||||||
image isn't square.
|
image isn't square. ::
|
||||||
|
|
||||||
>>> import numpy as np
|
>>> import numpy as np
|
||||||
>>> import glymur
|
>>> import glymur
|
||||||
|
|
@ -181,7 +219,7 @@ Here's how the Preview application on the mac shows the RGBA image.
|
||||||
.. image:: goodstuff_alpha.png
|
.. image:: goodstuff_alpha.png
|
||||||
|
|
||||||
|
|
||||||
Work with XMP UUIDs?
|
work with XMP UUIDs?
|
||||||
====================
|
====================
|
||||||
The example JP2 file shipped with glymur has an XMP UUID. ::
|
The example JP2 file shipped with glymur has an XMP UUID. ::
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,8 @@ Glymur: a Python interface for JPEG 2000
|
||||||
|
|
||||||
**Glymur** is an interface to the OpenJPEG library
|
**Glymur** is an interface to the OpenJPEG library
|
||||||
which allows one to read and write JPEG 2000 files from within Python.
|
which allows one to read and write JPEG 2000 files from within Python.
|
||||||
Glymur supports both reading and writing of JPEG 2000 images. Writing
|
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,
|
JPEG 2000 images is currently limited to images that can fit in memory
|
||||||
however.
|
|
||||||
|
|
||||||
Of particular focus is retrieval of metadata. Reading Exif UUIDs is supported,
|
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
|
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,
|
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
|
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,
|
version is strongly recommended. For more information about OpenJPEG,
|
||||||
please consult http://www.openjpeg.org.
|
please consult http://www.openjpeg.org.
|
||||||
|
|
||||||
If you use MacPorts on the mac or if you have a sufficiently recent
|
If you use MacPorts or if you have a sufficiently recent version of
|
||||||
version of Linux, your package manager should already provide you
|
Linux, your package manager should already provide you with a version of
|
||||||
with a version of OpenJPEG 1.X with which glymur can already use
|
OpenJPEG 1.X which glymur can already use. If your platform is windows,
|
||||||
for read-only purposes. If your platform is windows, I suggest
|
I suggest using the windows installers provided to you by the OpenJPEG
|
||||||
using the windows installers provided to you by the OpenJPEG
|
|
||||||
folks at https://code.google.com/p/openjpeg/downloads/list .
|
folks at https://code.google.com/p/openjpeg/downloads/list .
|
||||||
|
|
||||||
Glymur Installation
|
Glymur Installation
|
||||||
|
|
@ -47,14 +45,5 @@ line, so you should adjust your **$PATH**
|
||||||
to take advantage of it. For example, if you install with pip's
|
to take advantage of it. For example, if you install with pip's
|
||||||
`--user` option on linux ::
|
`--user` option on linux ::
|
||||||
|
|
||||||
$ export PYTHONPATH=$HOME/.local/lib/python3.3/site-packages
|
|
||||||
$ export PATH=$HOME/.local/bin:$PATH
|
$ 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.
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
Roadmap
|
||||||
-------
|
-------
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,17 @@
|
||||||
"""
|
"""
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from glymur import version
|
||||||
|
__version__ = version.version
|
||||||
|
|
||||||
from .jp2k import Jp2k
|
from .jp2k import Jp2k
|
||||||
from .jp2dump import jp2dump
|
from .jp2dump import jp2dump
|
||||||
|
|
||||||
from . import data
|
from . import data
|
||||||
|
|
||||||
|
|
||||||
|
# unittest2 only in python-2.6 (pylint/python2.7 issue)
|
||||||
|
# pylint: disable=F0401
|
||||||
def runtests():
|
def runtests():
|
||||||
"""Discover and run all tests for the glymur package.
|
"""Discover and run all tests for the glymur package.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
1037
glymur/codestream.py
1037
glymur/codestream.py
File diff suppressed because it is too large
Load diff
268
glymur/jp2box.py
268
glymur/jp2box.py
|
|
@ -24,6 +24,7 @@ import uuid
|
||||||
import warnings
|
import warnings
|
||||||
import xml.etree.cElementTree as ET
|
import xml.etree.cElementTree as ET
|
||||||
if sys.hexversion < 0x02070000:
|
if sys.hexversion < 0x02070000:
|
||||||
|
# pylint: disable=F0401,E0611
|
||||||
from ordereddict import OrderedDict
|
from ordereddict import OrderedDict
|
||||||
from xml.etree.cElementTree import XMLParserError as ParseError
|
from xml.etree.cElementTree import XMLParserError as ParseError
|
||||||
else:
|
else:
|
||||||
|
|
@ -361,7 +362,8 @@ class _ICCProfile(object):
|
||||||
header['Connection Space'] = data
|
header['Connection Space'] = data
|
||||||
|
|
||||||
data = struct.unpack('>HHHHHH', self._raw_buffer[24:36])
|
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')
|
header['File Signature'] = read_buffer[36:40].decode('utf-8')
|
||||||
if read_buffer[40:44] == b'\x00\x00\x00\x00':
|
if read_buffer[40:44] == b'\x00\x00\x00\x00':
|
||||||
header['Platform'] = 'unrecognized'
|
header['Platform'] = 'unrecognized'
|
||||||
|
|
@ -369,10 +371,9 @@ class _ICCProfile(object):
|
||||||
header['Platform'] = read_buffer[40:44].decode('utf-8')
|
header['Platform'] = read_buffer[40:44].decode('utf-8')
|
||||||
|
|
||||||
fval, = struct.unpack('>I', read_buffer[44:48])
|
fval, = struct.unpack('>I', read_buffer[44:48])
|
||||||
flags = 'embedded, ' if fval & 0x01 else 'not embedded, '
|
flags = "{0}embedded, {1} be used independently"
|
||||||
flags += 'cannot ' if fval & 0x02 else 'can '
|
header['Flags'] = flags.format('' if fval & 0x01 else 'not ',
|
||||||
flags += 'be used independently'
|
'cannot' if fval & 0x02 else 'can')
|
||||||
header['Flags'] = flags
|
|
||||||
|
|
||||||
header['Device Manufacturer'] = read_buffer[48:52].decode('utf-8')
|
header['Device Manufacturer'] = read_buffer[48:52].decode('utf-8')
|
||||||
if read_buffer[52:56] == b'\x00\x00\x00\x00':
|
if read_buffer[52:56] == b'\x00\x00\x00\x00':
|
||||||
|
|
@ -382,11 +383,11 @@ class _ICCProfile(object):
|
||||||
header['Device Model'] = device_model
|
header['Device Model'] = device_model
|
||||||
|
|
||||||
val, = struct.unpack('>Q', read_buffer[56:64])
|
val, = struct.unpack('>Q', read_buffer[56:64])
|
||||||
attr = 'transparency, ' if val & 0x01 else 'reflective, '
|
attr = "{0}, {1}, {2} media polarity, {3} media"
|
||||||
attr += 'matte, ' if val & 0x02 else 'glossy, '
|
attr = attr.format('transparency' if val & 0x01 else 'reflective',
|
||||||
attr += 'negative ' if val & 0x04 else 'positive '
|
'matte' if val & 0x02 else 'glossy',
|
||||||
attr += 'media polarity, '
|
'negative' if val & 0x04 else 'positive',
|
||||||
attr += 'black and white media' if val & 0x08 else 'color media'
|
'black and white' if val & 0x08 else 'color')
|
||||||
header['Device Attributes'] = attr
|
header['Device Attributes'] = attr
|
||||||
|
|
||||||
rval, = struct.unpack('>I', read_buffer[64:68])
|
rval, = struct.unpack('>I', read_buffer[64:68])
|
||||||
|
|
@ -567,7 +568,7 @@ class CodestreamHeaderBox(Jp2kBox):
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
AssociationBox instance
|
CodestreamHeaderBox instance
|
||||||
"""
|
"""
|
||||||
box = CodestreamHeaderBox(length=length, offset=offset)
|
box = CodestreamHeaderBox(length=length, offset=offset)
|
||||||
|
|
||||||
|
|
@ -626,7 +627,7 @@ class CompositingLayerHeaderBox(Jp2kBox):
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
AssociationBox instance
|
CompositingLayerHeaderBox instance
|
||||||
"""
|
"""
|
||||||
box = CompositingLayerHeaderBox(length=length, offset=offset)
|
box = CompositingLayerHeaderBox(length=length, offset=offset)
|
||||||
|
|
||||||
|
|
@ -637,7 +638,7 @@ class CompositingLayerHeaderBox(Jp2kBox):
|
||||||
|
|
||||||
|
|
||||||
class ComponentMappingBox(Jp2kBox):
|
class ComponentMappingBox(Jp2kBox):
|
||||||
"""Container for channel identification information.
|
"""Container for component mapping information.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
|
|
@ -1237,43 +1238,68 @@ class PaletteBox(Jp2kBox):
|
||||||
bps = [((x & 0x07f) + 1) for x in data]
|
bps = [((x & 0x07f) + 1) for x in data]
|
||||||
signed = [((x & 0x80) > 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
|
# Form the format string so that we can intelligently unpack the
|
||||||
# colormap. We have to do this because it is possible that the
|
# colormap. We have to do this because it is possible that the
|
||||||
# colormap columns could have different datatypes.
|
# colormap columns could have different datatypes.
|
||||||
#
|
#
|
||||||
# This means that we store the palette as a list of 1D arrays,
|
# This means that we store the palette as a list of 1D arrays,
|
||||||
# which reverses the usual indexing scheme.
|
# 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)
|
read_buffer = fptr.read(num_entries * row_nbytes)
|
||||||
|
palette = _buffer2palette(read_buffer, num_entries, num_columns, bps)
|
||||||
|
|
||||||
for j in range(num_entries):
|
box = PaletteBox(palette, bps, signed, length=length, offset=offset)
|
||||||
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)
|
|
||||||
return box
|
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.
|
# Map rreq codes to display text.
|
||||||
_READER_REQUIREMENTS_DISPLAY = {
|
_READER_REQUIREMENTS_DISPLAY = {
|
||||||
0: 'File not completely understood',
|
0: 'File not completely understood',
|
||||||
|
|
@ -1434,52 +1460,18 @@ class ReaderRequirementsBox(Jp2kBox):
|
||||||
"""
|
"""
|
||||||
read_buffer = fptr.read(1)
|
read_buffer = fptr.read(1)
|
||||||
mask_length, = struct.unpack('>B', read_buffer)
|
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
|
# Fully Understands Aspect Mask
|
||||||
# Decodes Completely Mask
|
# Decodes Completely Mask
|
||||||
read_buffer = fptr.read(2 * mask_length)
|
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)
|
# The mask length tells us the format string to use when unpacking
|
||||||
num_standard_flags, = struct.unpack('>H', read_buffer)
|
# 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
|
standard_flag, standard_mask = _parse_standard_flag(fptr, mask_length)
|
||||||
# be two bytes, but the standard mask flag is as long as specified by
|
vendor_feature, vendor_mask = _parse_vendor_features(fptr, mask_length)
|
||||||
# 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)
|
|
||||||
|
|
||||||
box = ReaderRequirementsBox(fuam, dcm, standard_flag, standard_mask,
|
box = ReaderRequirementsBox(fuam, dcm, standard_flag, standard_mask,
|
||||||
vendor_feature, vendor_mask,
|
vendor_feature, vendor_mask,
|
||||||
|
|
@ -1487,6 +1479,74 @@ class ReaderRequirementsBox(Jp2kBox):
|
||||||
return box
|
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):
|
class ResolutionBox(Jp2kBox):
|
||||||
"""Container for Resolution superbox information.
|
"""Container for Resolution superbox information.
|
||||||
|
|
||||||
|
|
@ -1577,7 +1637,7 @@ class CaptureResolutionBox(Jp2kBox):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse Resolution box.
|
"""Parse CaptureResolutionBox.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
|
|
@ -1634,7 +1694,7 @@ class DisplayResolutionBox(Jp2kBox):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse Resolution box.
|
"""Parse display resolution box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
|
|
@ -1789,16 +1849,33 @@ class XMLBox(Jp2kBox):
|
||||||
"""
|
"""
|
||||||
num_bytes = offset + length - fptr.tell()
|
num_bytes = offset + length - fptr.tell()
|
||||||
read_buffer = fptr.read(num_bytes)
|
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.
|
# Let the user know that the XML box was problematic.
|
||||||
text = text.rstrip('\0')
|
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:
|
try:
|
||||||
elt = ET.fromstring(text)
|
elt = ET.fromstring(text.encode('utf-8'))
|
||||||
xml = ET.ElementTree(elt)
|
xml = ET.ElementTree(elt)
|
||||||
except ParseError as parse_error:
|
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))
|
msg = msg.format(str(parse_error))
|
||||||
warnings.warn(msg, UserWarning)
|
warnings.warn(msg, UserWarning)
|
||||||
xml = None
|
xml = None
|
||||||
|
|
@ -1968,7 +2045,7 @@ class DataEntryURLBox(Jp2kBox):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse Data Entry URL box.
|
"""Parse data entry URL box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
|
|
@ -2670,9 +2747,18 @@ def _pretty_print_xml(xml, level=0):
|
||||||
"""
|
"""
|
||||||
xml = copy.deepcopy(xml)
|
xml = copy.deepcopy(xml)
|
||||||
_indent(xml.getroot(), level=level)
|
_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.
|
# Indent it a bit.
|
||||||
lst = [(' ' + x) for x in xmltext.split('\n')]
|
lst = [(' ' + x) for x in xmltext.split('\n')]
|
||||||
xml = '\n'.join(lst)
|
try:
|
||||||
return '\n{0}'.format(xml)
|
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
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"""
|
"""
|
||||||
Entry point for jp2dump script.
|
Entry point for jp2dump script.
|
||||||
"""
|
"""
|
||||||
|
import warnings
|
||||||
|
|
||||||
from .jp2k import Jp2k
|
from .jp2k import Jp2k
|
||||||
|
|
||||||
|
|
@ -15,8 +16,21 @@ def jp2dump(filename, codestream=False):
|
||||||
codestream : optional, logical scalar
|
codestream : optional, logical scalar
|
||||||
Whether or not to dump codestream contents.
|
Whether or not to dump codestream contents.
|
||||||
"""
|
"""
|
||||||
j = Jp2k(filename)
|
with warnings.catch_warnings(record=True) as wctx:
|
||||||
if codestream:
|
|
||||||
print(j.get_codestream(header_only=False))
|
# JP2 metadata can be extensive, so don't print any warnings until we
|
||||||
else:
|
# are done with the metadata.
|
||||||
print(j)
|
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))
|
||||||
|
|
|
||||||
1463
glymur/jp2k.py
1463
glymur/jp2k.py
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
||||||
"""This package organizes individual libraries employed by glymur."""
|
"""This package organizes individual libraries employed by glymur."""
|
||||||
from . import openjp2 as _openjp2
|
from . import openjp2 as openjp2
|
||||||
from . import openjpeg as _openjpeg
|
from . import openjpeg as openjpeg
|
||||||
from . import c
|
from . import c
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
"""
|
"""
|
||||||
Configure glymur to use installed libraries if possible.
|
Configure glymur to use installed libraries if possible.
|
||||||
"""
|
"""
|
||||||
|
# configparser is new in python3 (pylint/python-2.7)
|
||||||
|
# pylint: disable=F0401
|
||||||
|
|
||||||
import ctypes
|
import ctypes
|
||||||
from ctypes.util import find_library
|
from ctypes.util import find_library
|
||||||
import os
|
import os
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
Wraps individual functions in openjp2 library.
|
Wraps individual functions in openjp2 library.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# pylint: disable=C0302,R0903
|
# pylint: disable=C0302,R0903,W0201
|
||||||
|
|
||||||
import ctypes
|
import ctypes
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -553,119 +553,6 @@ class CodestreamInfoV2(ctypes.Structure):
|
||||||
# information regarding tiles inside of image
|
# information regarding tiles inside of image
|
||||||
("tile_info", ctypes.POINTER(TileInfoV2))]
|
("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):
|
def check_error(status):
|
||||||
"""Set a generic function as the restype attribute of all OpenJPEG
|
"""Set a generic function as the restype attribute of all OpenJPEG
|
||||||
|
|
@ -684,19 +571,6 @@ def check_error(status):
|
||||||
else:
|
else:
|
||||||
raise IOError("OpenJPEG function failure.")
|
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):
|
def create_compress(codec_format):
|
||||||
"""Creates a J2K/JP2 compress structure.
|
"""Creates a J2K/JP2 compress structure.
|
||||||
|
|
@ -712,6 +586,9 @@ def create_compress(codec_format):
|
||||||
-------
|
-------
|
||||||
codec : Reference to CODEC_TYPE instance.
|
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)
|
codec = OPENJP2.opj_create_compress(codec_format)
|
||||||
return codec
|
return codec
|
||||||
|
|
||||||
|
|
@ -735,6 +612,10 @@ def decode(codec, stream, image):
|
||||||
RuntimeError
|
RuntimeError
|
||||||
If the OpenJPEG library routine opj_decode fails.
|
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)
|
OPENJP2.opj_decode(codec, stream, image)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -761,13 +642,19 @@ def decode_tile_data(codec, tidx, data, data_size, stream):
|
||||||
RuntimeError
|
RuntimeError
|
||||||
If the OpenJPEG library routine opj_decode fails.
|
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))
|
datap = data.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8))
|
||||||
OPENJP2.opj_decode_tile_data(codec,
|
OPENJP2.opj_decode_tile_data(codec,
|
||||||
ctypes.c_uint32(tidx),
|
ctypes.c_uint32(tidx),
|
||||||
datap,
|
datap,
|
||||||
ctypes.c_uint32(data_size),
|
ctypes.c_uint32(data_size),
|
||||||
stream)
|
stream)
|
||||||
return codec
|
|
||||||
|
|
||||||
|
|
||||||
def create_decompress(codec_format):
|
def create_decompress(codec_format):
|
||||||
|
|
@ -784,6 +671,9 @@ def create_decompress(codec_format):
|
||||||
-------
|
-------
|
||||||
codec : Reference to CODEC_TYPE instance.
|
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)
|
codec = OPENJP2.opj_create_decompress(codec_format)
|
||||||
return codec
|
return codec
|
||||||
|
|
||||||
|
|
@ -798,6 +688,8 @@ def destroy_codec(codec):
|
||||||
codec : CODEC_TYPE
|
codec : CODEC_TYPE
|
||||||
Decompressor handle to destroy.
|
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)
|
OPENJP2.opj_destroy_codec(codec)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -818,6 +710,9 @@ def encode(codec, stream):
|
||||||
RuntimeError
|
RuntimeError
|
||||||
If the OpenJPEG library routine opj_encode fails.
|
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)
|
OPENJP2.opj_encode(codec, stream)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -836,6 +731,9 @@ def get_cstr_info(codec):
|
||||||
cstr_info_p : CodestreamInfoV2
|
cstr_info_p : CodestreamInfoV2
|
||||||
Reference to codestream information.
|
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)
|
cstr_info_p = OPENJP2.opj_get_cstr_info(codec)
|
||||||
return cstr_info_p
|
return cstr_info_p
|
||||||
|
|
||||||
|
|
@ -861,6 +759,12 @@ def get_decoded_tile(codec, stream, imagep, tile_index):
|
||||||
RuntimeError
|
RuntimeError
|
||||||
If the OpenJPEG library routine opj_get_decoded_tile fails.
|
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)
|
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
|
cstr_info_p : CodestreamInfoV2 pointer
|
||||||
Pointer to codestream info structure.
|
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))
|
OPENJP2.opj_destroy_cstr_info(ctypes.byref(cstr_info_p))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -894,6 +802,8 @@ def end_compress(codec, stream):
|
||||||
RuntimeError
|
RuntimeError
|
||||||
If the OpenJPEG library routine opj_end_compress fails.
|
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)
|
OPENJP2.opj_end_compress(codec, stream)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -914,6 +824,8 @@ def end_decompress(codec, stream):
|
||||||
RuntimeError
|
RuntimeError
|
||||||
If the OpenJPEG library routine opj_end_decompress fails.
|
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)
|
OPENJP2.opj_end_decompress(codec, stream)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -927,6 +839,9 @@ def image_destroy(image):
|
||||||
image : ImageType pointer
|
image : ImageType pointer
|
||||||
Image resource to be disposed.
|
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)
|
OPENJP2.opj_image_destroy(image)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -947,6 +862,11 @@ def image_create(comptparms, clrspc):
|
||||||
image : ImageType
|
image : ImageType
|
||||||
Reference to ImageType instance.
|
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),
|
image = OPENJP2.opj_image_create(len(comptparms),
|
||||||
comptparms,
|
comptparms,
|
||||||
clrspc)
|
clrspc)
|
||||||
|
|
@ -970,6 +890,12 @@ def image_tile_create(comptparms, clrspc):
|
||||||
image : ImageType
|
image : ImageType
|
||||||
Reference to ImageType instance.
|
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),
|
image = OPENJP2.opj_image_tile_create(len(comptparms),
|
||||||
comptparms,
|
comptparms,
|
||||||
clrspc)
|
clrspc)
|
||||||
|
|
@ -998,6 +924,11 @@ def read_header(stream, codec):
|
||||||
RuntimeError
|
RuntimeError
|
||||||
If the OpenJPEG library routine opj_read_header fails.
|
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)()
|
imagep = ctypes.POINTER(ImageType)()
|
||||||
OPENJP2.opj_read_header(stream, codec, ctypes.byref(imagep))
|
OPENJP2.opj_read_header(stream, codec, ctypes.byref(imagep))
|
||||||
return imagep
|
return imagep
|
||||||
|
|
@ -1035,6 +966,19 @@ def read_tile_header(codec, stream):
|
||||||
RuntimeError
|
RuntimeError
|
||||||
If the OpenJPEG library routine opj_read_tile_header fails.
|
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()
|
tile_index = ctypes.c_uint32()
|
||||||
data_size = ctypes.c_uint32()
|
data_size = ctypes.c_uint32()
|
||||||
col0 = ctypes.c_int32()
|
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
|
RuntimeError
|
||||||
If the OpenJPEG library routine opj_set_decode_area fails.
|
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,
|
OPENJP2.opj_set_decode_area(codec, image,
|
||||||
ctypes.c_int32(start_x),
|
ctypes.c_int32(start_x),
|
||||||
ctypes.c_int32(start_y),
|
ctypes.c_int32(start_y),
|
||||||
|
|
@ -1103,6 +1055,10 @@ def set_default_decoder_parameters():
|
||||||
dparam : DecompressionParametersType
|
dparam : DecompressionParametersType
|
||||||
Decompression parameters.
|
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()
|
dparams = DecompressionParametersType()
|
||||||
OPENJP2.opj_set_default_decoder_parameters(ctypes.byref(dparams))
|
OPENJP2.opj_set_default_decoder_parameters(ctypes.byref(dparams))
|
||||||
return dparams
|
return dparams
|
||||||
|
|
@ -1138,6 +1094,10 @@ def set_default_encoder_parameters():
|
||||||
cparameters : CompressionParametersType
|
cparameters : CompressionParametersType
|
||||||
Compression parameters.
|
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()
|
cparams = CompressionParametersType()
|
||||||
OPENJP2.opj_set_default_encoder_parameters(ctypes.byref(cparams))
|
OPENJP2.opj_set_default_encoder_parameters(ctypes.byref(cparams))
|
||||||
return cparams
|
return cparams
|
||||||
|
|
@ -1162,6 +1122,10 @@ def set_error_handler(codec, handler, data=None):
|
||||||
RuntimeError
|
RuntimeError
|
||||||
If the OpenJPEG library routine opj_set_error_handler fails.
|
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)
|
OPENJP2.opj_set_error_handler(codec, handler, data)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1184,6 +1148,10 @@ def set_info_handler(codec, handler, data=None):
|
||||||
RuntimeError
|
RuntimeError
|
||||||
If the OpenJPEG library routine opj_set_info_handler fails.
|
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)
|
OPENJP2.opj_set_info_handler(codec, handler, data)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1206,6 +1174,11 @@ def set_warning_handler(codec, handler, data=None):
|
||||||
RuntimeError
|
RuntimeError
|
||||||
If the OpenJPEG library routine opj_set_warning_handler fails.
|
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)
|
OPENJP2.opj_set_warning_handler(codec, handler, data)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1226,6 +1199,10 @@ def setup_decoder(codec, dparams):
|
||||||
RuntimeError
|
RuntimeError
|
||||||
If the OpenJPEG library routine opj_setup_decoder fails.
|
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))
|
OPENJP2.opj_setup_decoder(codec, ctypes.byref(dparams))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1249,6 +1226,11 @@ def setup_encoder(codec, cparams, image):
|
||||||
RuntimeError
|
RuntimeError
|
||||||
If the OpenJPEG library routine opj_setup_encoder fails.
|
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)
|
OPENJP2.opj_setup_encoder(codec, ctypes.byref(cparams), image)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1271,6 +1253,11 @@ def start_compress(codec, image, stream):
|
||||||
RuntimeError
|
RuntimeError
|
||||||
If the OpenJPEG library routine opj_start_compress fails.
|
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)
|
OPENJP2.opj_start_compress(codec, image, stream)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1292,6 +1279,9 @@ def stream_create_default_file_stream(fptr, isa_read_stream):
|
||||||
stream : stream_t
|
stream : stream_t
|
||||||
An OpenJPEG file stream.
|
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
|
read_stream = 1 if isa_read_stream else 0
|
||||||
stream = OPENJP2.opj_stream_create_default_file_stream(fptr, read_stream)
|
stream = OPENJP2.opj_stream_create_default_file_stream(fptr, read_stream)
|
||||||
return stream
|
return stream
|
||||||
|
|
@ -1315,6 +1305,9 @@ def stream_create_default_file_stream_v3(fname, isa_read_stream):
|
||||||
stream : stream_t
|
stream : stream_t
|
||||||
An OpenJPEG file stream.
|
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
|
read_stream = 1 if isa_read_stream else 0
|
||||||
file_argument = ctypes.c_char_p(fname.encode())
|
file_argument = ctypes.c_char_p(fname.encode())
|
||||||
stream = OPENJP2.opj_stream_create_default_file_stream_v3(file_argument,
|
stream = OPENJP2.opj_stream_create_default_file_stream_v3(file_argument,
|
||||||
|
|
@ -1332,6 +1325,8 @@ def stream_destroy(stream):
|
||||||
stream : STREAM_TYPE_P
|
stream : STREAM_TYPE_P
|
||||||
The file stream.
|
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)
|
OPENJP2.opj_stream_destroy(stream)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1345,6 +1340,8 @@ def stream_destroy_v3(stream):
|
||||||
stream : STREAM_TYPE_P
|
stream : STREAM_TYPE_P
|
||||||
The file stream.
|
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)
|
OPENJP2.opj_stream_destroy_v3(stream)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1371,6 +1368,13 @@ def write_tile(codec, tile_index, data, data_size, stream):
|
||||||
RuntimeError
|
RuntimeError
|
||||||
If the OpenJPEG library routine opj_write_tile fails.
|
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))
|
datap = data.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8))
|
||||||
OPENJP2.opj_write_tile(codec,
|
OPENJP2.opj_write_tile(codec,
|
||||||
ctypes.c_uint32(int(tile_index)),
|
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):
|
def set_error_message(msg):
|
||||||
"""The openjpeg error handler has recorded an error message."""
|
"""The openjpeg error handler has recorded an error message."""
|
||||||
global ERROR_MSG_LST
|
|
||||||
ERROR_MSG_LST.append(msg)
|
ERROR_MSG_LST.append(msg)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,16 @@
|
||||||
import ctypes
|
import ctypes
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
from .config import glymur_config
|
from .config import glymur_config
|
||||||
_, OPENJPEG = 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():
|
def version():
|
||||||
|
|
@ -52,14 +58,8 @@ class CommonStructType(ctypes.Structure):
|
||||||
("mj2_handle", ctypes.c_void_p)]
|
("mj2_handle", ctypes.c_void_p)]
|
||||||
|
|
||||||
|
|
||||||
class DecompressionInfoType(ctypes.Structure):
|
STREAM_READ = 0x0001 # The stream was opened for reading.
|
||||||
"""This is for decompression contexts.
|
STREAM_WRITE = 0x0002 # The stream was opened for writing.
|
||||||
|
|
||||||
Corresponds to dinfo_t type in openjpeg headers.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CioType(ctypes.Structure):
|
class CioType(ctypes.Structure):
|
||||||
"""Byte input-output stream (CIO)
|
"""Byte input-output stream (CIO)
|
||||||
|
|
||||||
|
|
@ -80,6 +80,266 @@ class CioType(ctypes.Structure):
|
||||||
("bp", ctypes.c_char_p)]
|
("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):
|
class DecompressionParametersType(ctypes.Structure):
|
||||||
"""Decompression parameters.
|
"""Decompression parameters.
|
||||||
|
|
||||||
|
|
@ -111,23 +371,51 @@ class DecompressionParametersType(ctypes.Structure):
|
||||||
_fields_.append(("flags", ctypes.c_uint))
|
_fields_.append(("flags", ctypes.c_uint))
|
||||||
|
|
||||||
|
|
||||||
class ImageCompType(ctypes.Structure):
|
class ImageComptParmType(ctypes.Structure):
|
||||||
"""Defines a single image component.
|
"""Component parameters structure used by the opj_image_create function.
|
||||||
|
|
||||||
Corresponds to image_comp_t type in openjpeg.
|
|
||||||
"""
|
"""
|
||||||
_fields_ = [("dx", ctypes.c_int),
|
_fields_ = [
|
||||||
("dy", ctypes.c_int),
|
# XRsiz: horizontal separation of a sample of ith component with
|
||||||
("w", ctypes.c_int),
|
# respect to the reference grid
|
||||||
("h", ctypes.c_int),
|
("dx", ctypes.c_int),
|
||||||
("x0", ctypes.c_int),
|
|
||||||
("y0", ctypes.c_int),
|
# YRsiz: vertical separation of a sample of ith component with
|
||||||
("prec", ctypes.c_int),
|
# respect to the reference grid */
|
||||||
("bpp", ctypes.c_int),
|
("dy", ctypes.c_int),
|
||||||
("sgnd", ctypes.c_int),
|
|
||||||
("resno_decoded", ctypes.c_int),
|
# data width, height
|
||||||
("factor", ctypes.c_int),
|
("w", ctypes.c_int),
|
||||||
("data", ctypes.POINTER(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):
|
class ImageType(ctypes.Structure):
|
||||||
|
|
@ -146,16 +434,22 @@ class ImageType(ctypes.Structure):
|
||||||
("icc_profile_len", ctypes.c_int)]
|
("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."""
|
"""Wrapper for openjpeg library function opj_cio_open."""
|
||||||
argtypes = [ctypes.POINTER(CommonStructType), ctypes.c_char_p,
|
argtypes = [ctypes.POINTER(CommonStructType), ctypes.c_char_p,
|
||||||
ctypes.c_int]
|
ctypes.c_int]
|
||||||
OPENJPEG.opj_cio_open.argtypes = argtypes
|
OPENJPEG.opj_cio_open.argtypes = argtypes
|
||||||
OPENJPEG.opj_cio_open.restype = ctypes.POINTER(CioType)
|
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,
|
cio = OPENJPEG.opj_cio_open(ctypes.cast(cinfo,
|
||||||
ctypes.POINTER(CommonStructType)),
|
ctypes.POINTER(CommonStructType)),
|
||||||
src, len(src))
|
src,
|
||||||
|
length)
|
||||||
return cio
|
return cio
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -166,6 +460,24 @@ def cio_close(cio):
|
||||||
OPENJPEG.opj_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):
|
def create_decompress(fmt):
|
||||||
"""Wraps openjpeg library function opj_create_decompress.
|
"""Wraps openjpeg library function opj_create_decompress.
|
||||||
"""
|
"""
|
||||||
|
|
@ -186,6 +498,38 @@ def decode(dinfo, cio):
|
||||||
return image
|
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):
|
def destroy_decompress(dinfo):
|
||||||
"""Wraps openjpeg library function opj_destroy_decompress."""
|
"""Wraps openjpeg library function opj_destroy_decompress."""
|
||||||
argtypes = [ctypes.POINTER(DecompressionInfoType)]
|
argtypes = [ctypes.POINTER(DecompressionInfoType)]
|
||||||
|
|
@ -193,12 +537,78 @@ def destroy_decompress(dinfo):
|
||||||
OPENJPEG.opj_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):
|
def image_destroy(image):
|
||||||
"""Wraps openjpeg library function opj_image_destroy."""
|
"""Wraps openjpeg library function opj_image_destroy."""
|
||||||
OPENJPEG.opj_image_destroy.argtypes = [ctypes.POINTER(ImageType)]
|
OPENJPEG.opj_image_destroy.argtypes = [ctypes.POINTER(ImageType)]
|
||||||
OPENJPEG.opj_image_destroy(image)
|
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):
|
def set_default_decoder_parameters(dparams_p):
|
||||||
"""Wrapper for opj_set_default_decoder_parameters.
|
"""Wrapper for opj_set_default_decoder_parameters.
|
||||||
"""
|
"""
|
||||||
|
|
@ -219,6 +629,15 @@ def set_event_mgr(dinfo, event_mgr, context=None):
|
||||||
event_mgr, context)
|
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):
|
def setup_decoder(dinfo, dparams):
|
||||||
"""Wrapper for openjpeg library function opj_setup_decoder."""
|
"""Wrapper for openjpeg library function opj_setup_decoder."""
|
||||||
argtypes = [ctypes.POINTER(DecompressionInfoType),
|
argtypes = [ctypes.POINTER(DecompressionInfoType),
|
||||||
|
|
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
#from .test_openjp2 import TestOpenJP2 as openjp2
|
"""
|
||||||
|
Test suite for openjp2, openjpeg low-level functionality.
|
||||||
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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 os
|
||||||
import pkg_resources
|
|
||||||
import shutil
|
|
||||||
import struct
|
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
|
@ -15,28 +20,28 @@ else:
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
import glymur
|
import glymur
|
||||||
|
from glymur.lib import openjp2
|
||||||
|
|
||||||
OPENJP2_IS_V2_OFFICIAL = False
|
OPENJP2_IS_V2_OFFICIAL = False
|
||||||
if glymur.lib.openjp2.OPENJP2 is not None:
|
if openjp2.OPENJP2 is not None:
|
||||||
if not hasattr(glymur.lib.openjp2.OPENJP2,
|
if not hasattr(openjp2.OPENJP2,
|
||||||
'opj_stream_create_default_file_stream_v3'):
|
'opj_stream_create_default_file_stream_v3'):
|
||||||
OPENJP2_IS_V2_OFFICIAL = True
|
OPENJP2_IS_V2_OFFICIAL = True
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
|
@unittest.skipIf(openjp2.OPENJP2 is None,
|
||||||
"Missing openjp2 library.")
|
"Missing openjp2 library.")
|
||||||
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL, "API followed here specific to V2.0+")
|
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL, "API followed here specific to V2.0+")
|
||||||
class TestOpenJP2(unittest.TestCase):
|
class TestOpenJP2(unittest.TestCase):
|
||||||
|
"""Test openjp2 library functionality.
|
||||||
|
|
||||||
def setUp(self):
|
Some tests correspond to those in the openjpeg test suite.
|
||||||
pass
|
"""
|
||||||
|
|
||||||
def tearDown(self):
|
def test_default_encoder_parameters(self):
|
||||||
pass
|
"""Ensure that the encoder structure is clean upon init."""
|
||||||
|
cparams = openjp2.set_default_encoder_parameters()
|
||||||
def test_set_default_encoder_parameters(self):
|
|
||||||
cparams = glymur.lib._openjp2.set_default_encoder_parameters()
|
|
||||||
|
|
||||||
self.assertEqual(cparams.res_spec, 0)
|
self.assertEqual(cparams.res_spec, 0)
|
||||||
self.assertEqual(cparams.cblockw_init, 64)
|
self.assertEqual(cparams.cblockw_init, 64)
|
||||||
|
|
@ -52,8 +57,9 @@ class TestOpenJP2(unittest.TestCase):
|
||||||
|
|
||||||
self.assertEqual(cparams.irreversible, 0)
|
self.assertEqual(cparams.irreversible, 0)
|
||||||
|
|
||||||
def test_set_default_decoder_parameters(self):
|
def test_default_decoder_parameters(self):
|
||||||
dparams = glymur.lib._openjp2.set_default_decoder_parameters()
|
"""Tests that the structure is clean upon initialization"""
|
||||||
|
dparams = openjp2.set_default_decoder_parameters()
|
||||||
|
|
||||||
self.assertEqual(dparams.DA_x0, 0)
|
self.assertEqual(dparams.DA_x0, 0)
|
||||||
self.assertEqual(dparams.DA_y0, 0)
|
self.assertEqual(dparams.DA_y0, 0)
|
||||||
|
|
@ -61,35 +67,34 @@ class TestOpenJP2(unittest.TestCase):
|
||||||
self.assertEqual(dparams.DA_y1, 0)
|
self.assertEqual(dparams.DA_y1, 0)
|
||||||
|
|
||||||
def tile_macro(self, codec, stream, imagep, tidx):
|
def tile_macro(self, codec, stream, imagep, tidx):
|
||||||
# called only by j2k_random_tile_access
|
"""called only by j2k_random_tile_access"""
|
||||||
glymur.lib._openjp2.get_decoded_tile(codec, stream, imagep, tidx)
|
openjp2.get_decoded_tile(codec, stream, imagep, tidx)
|
||||||
for j in range(imagep.contents.numcomps):
|
for j in range(imagep.contents.numcomps):
|
||||||
self.assertIsNotNone(imagep.contents.comps[j].data)
|
self.assertIsNotNone(imagep.contents.comps[j].data)
|
||||||
|
|
||||||
def j2k_random_tile_access(self, filename, codec_format=None):
|
def j2k_random_tile_access(self, filename, codec_format=None):
|
||||||
# called by the test_rtaX methods
|
"""fixture called by the test_rtaX methods"""
|
||||||
dparam = glymur.lib._openjp2.set_default_decoder_parameters()
|
dparam = openjp2.set_default_decoder_parameters()
|
||||||
|
|
||||||
infile = filename.encode()
|
infile = filename.encode()
|
||||||
nelts = glymur.lib._openjp2.PATH_LEN - len(infile)
|
nelts = openjp2.PATH_LEN - len(infile)
|
||||||
infile += b'0' * nelts
|
infile += b'0' * nelts
|
||||||
dparam.infile = infile
|
dparam.infile = infile
|
||||||
|
|
||||||
dparam.decod_format = codec_format
|
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)
|
openjp2.set_info_handler(codec, None)
|
||||||
glymur.lib._openjp2.set_warning_handler(codec, None)
|
openjp2.set_warning_handler(codec, None)
|
||||||
glymur.lib._openjp2.set_error_handler(codec, None)
|
openjp2.set_error_handler(codec, None)
|
||||||
|
|
||||||
x = (filename, True)
|
stream = openjp2.stream_create_default_file_stream_v3(filename, True)
|
||||||
stream = glymur.lib._openjp2.stream_create_default_file_stream_v3(*x)
|
|
||||||
|
|
||||||
glymur.lib._openjp2.setup_decoder(codec, dparam)
|
openjp2.setup_decoder(codec, dparam)
|
||||||
image = glymur.lib._openjp2.read_header(stream, codec)
|
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_ul = 0
|
||||||
tile_ur = cstr_info.contents.tw - 1
|
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_lr)
|
||||||
self.tile_macro(codec, stream, image, tile_ll)
|
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)
|
openjp2.end_decompress(codec, stream)
|
||||||
glymur.lib._openjp2.destroy_codec(codec)
|
openjp2.destroy_codec(codec)
|
||||||
glymur.lib._openjp2.stream_destroy_v3(stream)
|
openjp2.stream_destroy_v3(stream)
|
||||||
glymur.lib._openjp2.image_destroy(image)
|
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)
|
|
||||||
|
|
||||||
def test_tte0(self):
|
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:
|
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
||||||
self.tte0_setup(tfile.name)
|
ttx0_setup(tfile.name)
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
def test_ttd0(self):
|
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:
|
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
||||||
|
|
||||||
# Produce the tte0 output file for ttd0 input.
|
# Produce the tte0 output file for ttd0 input.
|
||||||
self.tte0_setup(tfile.name)
|
ttx0_setup(tfile.name)
|
||||||
|
|
||||||
kwargs = {'x0': 0,
|
kwargs = {'x0': 0,
|
||||||
'y0': 0,
|
'y0': 0,
|
||||||
'x1': 1000,
|
'x1': 1000,
|
||||||
'y1': 1000,
|
'y1': 1000,
|
||||||
'filename': tfile.name,
|
'filename': tfile.name,
|
||||||
'codec_format': glymur.lib._openjp2.CODEC_J2K}
|
'codec_format': openjp2.CODEC_J2K}
|
||||||
self.tile_decoder(**kwargs)
|
tile_decoder(**kwargs)
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
def tte1_setup(self, filename):
|
def xtx1_setup(self, filename):
|
||||||
|
"""Runs tests tte1, rta1."""
|
||||||
kwargs = {'filename': filename,
|
kwargs = {'filename': filename,
|
||||||
'codec': glymur.lib._openjp2.CODEC_J2K,
|
'codec': openjp2.CODEC_J2K,
|
||||||
'comp_prec': 8,
|
'comp_prec': 8,
|
||||||
'irreversible': 1,
|
'irreversible': 1,
|
||||||
'num_comps': 3,
|
'num_comps': 3,
|
||||||
|
|
@ -261,149 +146,298 @@ class TestOpenJP2(unittest.TestCase):
|
||||||
'image_width': 256,
|
'image_width': 256,
|
||||||
'tile_height': 128,
|
'tile_height': 128,
|
||||||
'tile_width': 128}
|
'tile_width': 128}
|
||||||
self.tile_encoder(**kwargs)
|
tile_encoder(**kwargs)
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
def test_tte1(self):
|
def test_tte1(self):
|
||||||
|
"""Runs test designated tte1 in OpenJPEG test suite."""
|
||||||
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
||||||
# Runs test designated tte1 in OpenJPEG test suite.
|
self.xtx1_setup(tfile.name)
|
||||||
self.tte1_setup(tfile.name)
|
|
||||||
|
|
||||||
def test_ttd1(self):
|
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:
|
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
||||||
|
|
||||||
# Produce the tte0 output file for ttd0 input.
|
# Produce the tte0 output file for ttd0 input.
|
||||||
self.tte1_setup(tfile.name)
|
self.xtx1_setup(tfile.name)
|
||||||
|
|
||||||
kwargs = {'x0': 0,
|
kwargs = {'x0': 0,
|
||||||
'y0': 0,
|
'y0': 0,
|
||||||
'x1': 128,
|
'x1': 128,
|
||||||
'y1': 128,
|
'y1': 128,
|
||||||
'filename': tfile.name,
|
'filename': tfile.name,
|
||||||
'codec_format': glymur.lib._openjp2.CODEC_J2K}
|
'codec_format': openjp2.CODEC_J2K}
|
||||||
self.tile_decoder(**kwargs)
|
tile_decoder(**kwargs)
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
def test_rta1(self):
|
def test_rta1(self):
|
||||||
|
"""Runs test designated rta1 in OpenJPEG test suite."""
|
||||||
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
||||||
# Runs test designated rta1 in OpenJPEG test suite.
|
self.xtx1_setup(tfile.name)
|
||||||
self.tte1_setup(tfile.name)
|
|
||||||
|
|
||||||
kwargs = {'codec_format': glymur.lib._openjp2.CODEC_J2K}
|
codec_format = openjp2.CODEC_J2K
|
||||||
self.j2k_random_tile_access(tfile.name, **kwargs)
|
self.j2k_random_tile_access(tfile.name, codec_format)
|
||||||
|
self.assertTrue(True)
|
||||||
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)
|
|
||||||
|
|
||||||
def test_tte2(self):
|
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:
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
self.tte2_setup(tfile.name)
|
xtx2_setup(tfile.name)
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
def test_ttd2(self):
|
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:
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
# Produce the tte0 output file for ttd0 input.
|
# Produce the tte0 output file for ttd0 input.
|
||||||
self.tte2_setup(tfile.name)
|
xtx2_setup(tfile.name)
|
||||||
|
|
||||||
kwargs = {'x0': 0,
|
kwargs = {'x0': 0,
|
||||||
'y0': 0,
|
'y0': 0,
|
||||||
'x1': 128,
|
'x1': 128,
|
||||||
'y1': 128,
|
'y1': 128,
|
||||||
'filename': tfile.name,
|
'filename': tfile.name,
|
||||||
'codec_format': glymur.lib._openjp2.CODEC_JP2}
|
'codec_format': openjp2.CODEC_JP2}
|
||||||
self.tile_decoder(**kwargs)
|
tile_decoder(**kwargs)
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
def test_rta2(self):
|
def test_rta2(self):
|
||||||
|
"""Runs test designated rta2 in OpenJPEG test suite."""
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
# Runs test designated rta2 in OpenJPEG test suite.
|
xtx2_setup(tfile.name)
|
||||||
self.tte2_setup(tfile.name)
|
|
||||||
|
|
||||||
kwargs = {'codec_format': glymur.lib._openjp2.CODEC_JP2}
|
codec_format = openjp2.CODEC_JP2
|
||||||
self.j2k_random_tile_access(tfile.name, **kwargs)
|
self.j2k_random_tile_access(tfile.name, codec_format)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
def test_tte3(self):
|
def test_tte3(self):
|
||||||
|
"""Runs test designated tte3 in OpenJPEG test suite."""
|
||||||
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
||||||
# Runs test designated tte3 in OpenJPEG test suite.
|
xtx3_setup(tfile.name)
|
||||||
self.tte3_setup(tfile.name)
|
self.assertTrue(True)
|
||||||
|
|
||||||
def test_rta3(self):
|
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:
|
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
||||||
self.tte3_setup(tfile.name)
|
xtx3_setup(tfile.name)
|
||||||
|
|
||||||
kwargs = {'codec_format': glymur.lib._openjp2.CODEC_J2K}
|
codec_format = openjp2.CODEC_J2K
|
||||||
self.j2k_random_tile_access(tfile.name, **kwargs)
|
self.j2k_random_tile_access(tfile.name, codec_format)
|
||||||
|
self.assertTrue(True)
|
||||||
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)
|
|
||||||
|
|
||||||
def test_tte4(self):
|
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:
|
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
||||||
self.tte4_setup(tfile.name)
|
xtx4_setup(tfile.name)
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
def test_rta4(self):
|
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:
|
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
||||||
self.tte4_setup(tfile.name)
|
xtx4_setup(tfile.name)
|
||||||
|
|
||||||
kwargs = {'codec_format': glymur.lib._openjp2.CODEC_J2K}
|
codec_format = openjp2.CODEC_J2K
|
||||||
self.j2k_random_tile_access(tfile.name, **kwargs)
|
self.j2k_random_tile_access(tfile.name, codec_format)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
def test_tte5(self):
|
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:
|
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
||||||
self.tte5_setup(tfile.name)
|
xtx5_setup(tfile.name)
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
def test_rta5(self):
|
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:
|
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
||||||
self.tte5_setup(tfile.name)
|
xtx5_setup(tfile.name)
|
||||||
|
|
||||||
kwargs = {'codec_format': glymur.lib._openjp2.CODEC_J2K}
|
codec_format = openjp2.CODEC_J2K
|
||||||
self.j2k_random_tile_access(tfile.name, **kwargs)
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -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 ctypes
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -10,43 +17,38 @@ else:
|
||||||
|
|
||||||
import glymur
|
import glymur
|
||||||
|
|
||||||
|
@unittest.skipIf(glymur.lib.openjpeg.OPENJPEG is None,
|
||||||
@unittest.skipIf(glymur.lib._openjpeg.OPENJPEG is None,
|
|
||||||
"Missing openjpeg library.")
|
"Missing openjpeg library.")
|
||||||
class TestOpenJPEG(unittest.TestCase):
|
class TestOpenJPEG(unittest.TestCase):
|
||||||
|
"""Test suite for openjpeg functions we choose to expose."""
|
||||||
def setUp(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_version(self):
|
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]')
|
regex = re.compile('1.[345].[0-9]')
|
||||||
if sys.hexversion <= 0x03020000:
|
if sys.hexversion <= 0x03020000:
|
||||||
self.assertRegexpMatches(version, regex)
|
self.assertRegexpMatches(version, regex)
|
||||||
else:
|
else:
|
||||||
self.assertRegex(version, regex)
|
self.assertRegex(version, regex)
|
||||||
|
|
||||||
def test_set_default_decoder_parameters(self):
|
def test_default_decoder_parameters(self):
|
||||||
# Verify that we properly set the default decode parameters.
|
"""Verify that we properly set the default decode parameters."""
|
||||||
version = glymur.lib._openjpeg.version()
|
version = glymur.lib.openjpeg.version()
|
||||||
minor = int(version.split('.')[1])
|
minor = int(version.split('.')[1])
|
||||||
|
|
||||||
dp = glymur.lib._openjpeg.DecompressionParametersType()
|
dcp = glymur.lib.openjpeg.DecompressionParametersType()
|
||||||
glymur.lib._openjpeg.set_default_decoder_parameters(ctypes.byref(dp))
|
glymur.lib.openjpeg.set_default_decoder_parameters(ctypes.byref(dcp))
|
||||||
|
|
||||||
self.assertEqual(dp.cp_reduce, 0)
|
self.assertEqual(dcp.cp_reduce, 0)
|
||||||
self.assertEqual(dp.cp_layer, 0)
|
self.assertEqual(dcp.cp_layer, 0)
|
||||||
self.assertEqual(dp.infile, b'')
|
self.assertEqual(dcp.infile, b'')
|
||||||
self.assertEqual(dp.outfile, b'')
|
self.assertEqual(dcp.outfile, b'')
|
||||||
self.assertEqual(dp.decod_format, -1)
|
self.assertEqual(dcp.decod_format, -1)
|
||||||
self.assertEqual(dp.cod_format, -1)
|
self.assertEqual(dcp.cod_format, -1)
|
||||||
self.assertEqual(dp.jpwl_correct, 0)
|
self.assertEqual(dcp.jpwl_correct, 0)
|
||||||
self.assertEqual(dp.jpwl_exp_comps, 0)
|
self.assertEqual(dcp.jpwl_exp_comps, 0)
|
||||||
self.assertEqual(dp.jpwl_max_tiles, 0)
|
self.assertEqual(dcp.jpwl_max_tiles, 0)
|
||||||
self.assertEqual(dp.cp_limit_decoding, 0)
|
self.assertEqual(dcp.cp_limit_decoding, 0)
|
||||||
if minor > 4:
|
if minor > 4:
|
||||||
# Introduced in 1.5.x
|
# Introduced in 1.5.x
|
||||||
self.assertEqual(dp.flags, 0)
|
self.assertEqual(dcp.flags, 0)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
"""
|
||||||
|
Test suite for glymur high-level functionality.
|
||||||
|
"""
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
|
"""
|
||||||
|
Test fixtures common to more than one test point.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
import glymur
|
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
|
# Need to know of the libopenjp2 version is the official 2.0.0 release and NOT
|
||||||
# the 2.0+ development version.
|
# the 2.0+ development version.
|
||||||
|
|
@ -20,6 +20,47 @@ if glymur.lib.openjp2.OPENJP2 is not None:
|
||||||
OPENJP2_IS_V2_OFFICIAL = True
|
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):
|
def mse(amat, bmat):
|
||||||
"""Mean Square Error"""
|
"""Mean Square Error"""
|
||||||
diff = amat.astype(np.double) - bmat.astype(np.double)
|
diff = amat.astype(np.double) - bmat.astype(np.double)
|
||||||
|
|
@ -36,28 +77,10 @@ def peak_tolerance(amat, bmat):
|
||||||
|
|
||||||
def read_pgx(pgx_file):
|
def read_pgx(pgx_file):
|
||||||
"""Helper function for reading the PGX comparison files.
|
"""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 = ''
|
header, pos = read_pgx_header(pgx_file)
|
||||||
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 = header.rstrip()
|
tokens = re.split(r'\s', header)
|
||||||
tokens = re.split('\s', header)
|
|
||||||
|
|
||||||
if (tokens[1][0] == 'M') and (sys.byteorder == 'little'):
|
if (tokens[1][0] == 'M') and (sys.byteorder == 'little'):
|
||||||
swapbytes = True
|
swapbytes = True
|
||||||
|
|
@ -81,6 +104,29 @@ def read_pgx(pgx_file):
|
||||||
nrows = int(tokens[4])
|
nrows = int(tokens[4])
|
||||||
ncols = int(tokens[3])
|
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 signed:
|
||||||
if bitdepth <= 8:
|
if bitdepth <= 8:
|
||||||
dtype = np.int8
|
dtype = np.int8
|
||||||
|
|
@ -96,12 +142,28 @@ def read_pgx(pgx_file):
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("unhandled bitdepth")
|
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:
|
with open(pgx_file, 'rb') as fptr:
|
||||||
fptr.seek(pos)
|
while True:
|
||||||
data = np.fromfile(file=fptr, dtype=dtype).reshape(shape)
|
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
|
||||||
|
|
|
||||||
|
|
@ -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 os
|
||||||
import pkg_resources
|
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
@ -24,6 +31,7 @@ import glymur
|
||||||
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
|
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
|
||||||
"Missing openjp2 library.")
|
"Missing openjp2 library.")
|
||||||
class TestCallbacks(unittest.TestCase):
|
class TestCallbacks(unittest.TestCase):
|
||||||
|
"""Test suite for callbacks."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.jp2file = glymur.data.nemo()
|
self.jp2file = glymur.data.nemo()
|
||||||
|
|
@ -34,7 +42,7 @@ class TestCallbacks(unittest.TestCase):
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
def test_info_callback_on_write(self):
|
def test_info_callback_on_write(self):
|
||||||
# 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)
|
j = glymur.Jp2k(self.jp2file)
|
||||||
with warnings.catch_warnings():
|
with warnings.catch_warnings():
|
||||||
warnings.simplefilter("ignore")
|
warnings.simplefilter("ignore")
|
||||||
|
|
@ -47,12 +55,14 @@ class TestCallbacks(unittest.TestCase):
|
||||||
expected = '[INFO] tile number 1 / 1'
|
expected = '[INFO] tile number 1 / 1'
|
||||||
self.assertEqual(actual, expected)
|
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
|
# Verify that we get the expected stdio output when our internal info
|
||||||
# callback handler is enabled.
|
# callback handler is enabled.
|
||||||
j = glymur.Jp2k(self.j2kfile)
|
j = glymur.Jp2k(self.j2kfile)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
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()
|
actual = fake_out.getvalue().strip()
|
||||||
|
|
||||||
lines = ['[INFO] Start to read j2k main header (0).',
|
lines = ['[INFO] Start to read j2k main header (0).',
|
||||||
|
|
@ -80,13 +90,16 @@ class TestCallbacks15(unittest.TestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_info_callbacks_on_read(self):
|
def test_info_callbacks_on_read(self):
|
||||||
# Verify that we get the expected stdio output when our internal info
|
"""Verify stdout when reading.
|
||||||
# callback handler is enabled.
|
|
||||||
|
Verify that we get the expected stdio output when our internal info
|
||||||
|
callback handler is enabled.
|
||||||
|
"""
|
||||||
with patch('glymur.lib.openjp2.OPENJP2', new=None):
|
with patch('glymur.lib.openjp2.OPENJP2', new=None):
|
||||||
# Force to use OPENJPEG instead of OPENJP2.
|
# Force to use OPENJPEG instead of OPENJP2.
|
||||||
j = glymur.Jp2k(self.j2kfile)
|
j = glymur.Jp2k(self.j2kfile)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
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()
|
actual = fake_out.getvalue().strip()
|
||||||
|
|
||||||
regex = re.compile(r"""\[INFO\]\stile\s1\sof\s1\s+
|
regex = re.compile(r"""\[INFO\]\stile\s1\sof\s1\s+
|
||||||
|
|
@ -97,6 +110,9 @@ class TestCallbacks15(unittest.TestCase):
|
||||||
\[INFO\]\s-\stile\sdecoded\sin\s
|
\[INFO\]\s-\stile\sdecoded\sin\s
|
||||||
[0-9]+\.[0-9]+\ss""",
|
[0-9]+\.[0-9]+\ss""",
|
||||||
re.VERBOSE)
|
re.VERBOSE)
|
||||||
|
|
||||||
|
# assertRegex in Python 3.3 (python2.7/pylint issue)
|
||||||
|
# pylint: disable=E1101
|
||||||
if sys.hexversion <= 0x03020000:
|
if sys.hexversion <= 0x03020000:
|
||||||
self.assertRegexpMatches(actual, regex)
|
self.assertRegexpMatches(actual, regex)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -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 os
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -9,23 +21,21 @@ if sys.hexversion < 0x02070000:
|
||||||
else:
|
else:
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
from glymur import Jp2k
|
from glymur import Jp2k
|
||||||
import glymur
|
import glymur
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data_root = os.environ['OPJ_DATA_ROOT']
|
DATA_ROOT = os.environ['OPJ_DATA_ROOT']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
data_root = None
|
DATA_ROOT = None
|
||||||
except:
|
except:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(data_root is None,
|
@unittest.skipIf(DATA_ROOT is None,
|
||||||
"OPJ_DATA_ROOT environment variable not set")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
class TestCodestream(unittest.TestCase):
|
class TestCodestream(unittest.TestCase):
|
||||||
|
"""Test suite for unusual codestream cases."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.jp2file = glymur.data.nemo()
|
self.jp2file = glymur.data.nemo()
|
||||||
|
|
@ -35,75 +45,76 @@ class TestCodestream(unittest.TestCase):
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
def test_reserved_marker_segment(self):
|
def test_reserved_marker_segment(self):
|
||||||
|
"""Reserved marker segments are ok."""
|
||||||
|
|
||||||
# Some marker segments were reserved in FCD15444-1. Since that
|
# Some marker segments were reserved in FCD15444-1. Since that
|
||||||
# standard is old, some of them may have come into use.
|
# standard is old, some of them may have come into use.
|
||||||
#
|
#
|
||||||
# Let's inject a reserved marker segment into a file that
|
# Let's inject a reserved marker segment into a file that
|
||||||
# we know something about to make sure we can still parse it.
|
# 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 tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
|
||||||
with open(filename, 'rb') as ifile:
|
with open(filename, 'rb') as ifile:
|
||||||
# Everything up until the first QCD marker.
|
# Everything up until the first QCD marker.
|
||||||
buffer = ifile.read(45)
|
read_buffer = ifile.read(45)
|
||||||
tfile.write(buffer)
|
tfile.write(read_buffer)
|
||||||
|
|
||||||
# Write the new marker segment, 0xff6f = 65391
|
# Write the new marker segment, 0xff6f = 65391
|
||||||
buffer = struct.pack('>HHB', int(65391), int(3), int(0))
|
read_buffer = struct.pack('>HHB', int(65391), int(3), int(0))
|
||||||
tfile.write(buffer)
|
tfile.write(read_buffer)
|
||||||
|
|
||||||
# Get the rest of the input file.
|
# Get the rest of the input file.
|
||||||
buffer = ifile.read()
|
read_buffer = ifile.read()
|
||||||
tfile.write(buffer)
|
tfile.write(read_buffer)
|
||||||
tfile.flush()
|
tfile.flush()
|
||||||
|
|
||||||
j = Jp2k(tfile.name)
|
codestream = Jp2k(tfile.name).get_codestream()
|
||||||
c = j.get_codestream()
|
|
||||||
|
|
||||||
self.assertEqual(c.segment[2].marker_id, '0xff6f')
|
self.assertEqual(codestream.segment[2].marker_id, '0xff6f')
|
||||||
self.assertEqual(c.segment[2].length, 3)
|
self.assertEqual(codestream.segment[2].length, 3)
|
||||||
self.assertEqual(c.segment[2]._data, b'\x00')
|
self.assertEqual(codestream.segment[2].data, b'\x00')
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
|
||||||
@unittest.skipIf(sys.hexversion < 0x03020000,
|
@unittest.skipIf(sys.hexversion < 0x03020000,
|
||||||
"Uses features introduced in 3.2.")
|
"Uses features introduced in 3.2.")
|
||||||
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
def test_unknown_marker_segment(self):
|
def test_unknown_marker_segment(self):
|
||||||
|
"""Should warn for an unknown marker."""
|
||||||
# Let's inject a marker segment whose marker does not appear to
|
# Let's inject a marker segment whose marker does not appear to
|
||||||
# be valid. We still parse the file, but warn about the offending
|
# be valid. We still parse the file, but warn about the offending
|
||||||
# marker.
|
# 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 tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
|
||||||
with open(filename, 'rb') as ifile:
|
with open(filename, 'rb') as ifile:
|
||||||
# Everything up until the first QCD marker.
|
# Everything up until the first QCD marker.
|
||||||
buffer = ifile.read(45)
|
read_buffer = ifile.read(45)
|
||||||
tfile.write(buffer)
|
tfile.write(read_buffer)
|
||||||
|
|
||||||
# Write the new marker segment, 0xff79 = 65401
|
# Write the new marker segment, 0xff79 = 65401
|
||||||
buffer = struct.pack('>HHB', int(65401), int(3), int(0))
|
read_buffer = struct.pack('>HHB', int(65401), int(3), int(0))
|
||||||
tfile.write(buffer)
|
tfile.write(read_buffer)
|
||||||
|
|
||||||
# Get the rest of the input file.
|
# Get the rest of the input file.
|
||||||
buffer = ifile.read()
|
read_buffer = ifile.read()
|
||||||
tfile.write(buffer)
|
tfile.write(read_buffer)
|
||||||
tfile.flush()
|
tfile.flush()
|
||||||
|
|
||||||
with self.assertWarns(UserWarning) as cw:
|
with self.assertWarns(UserWarning):
|
||||||
j = Jp2k(tfile.name)
|
codestream = Jp2k(tfile.name).get_codestream()
|
||||||
c = j.get_codestream()
|
|
||||||
|
|
||||||
self.assertEqual(c.segment[2].marker_id, '0xff79')
|
self.assertEqual(codestream.segment[2].marker_id, '0xff79')
|
||||||
self.assertEqual(c.segment[2].length, 3)
|
self.assertEqual(codestream.segment[2].length, 3)
|
||||||
self.assertEqual(c.segment[2]._data, b'\x00')
|
self.assertEqual(codestream.segment[2].data, b'\x00')
|
||||||
|
|
||||||
def test_psot_is_zero(self):
|
def test_psot_is_zero(self):
|
||||||
# Psot=0 in SOT is perfectly legal. Issue #78.
|
"""Psot=0 in SOT is perfectly legal. Issue #78."""
|
||||||
filename = os.path.join(data_root,
|
filename = os.path.join(DATA_ROOT,
|
||||||
'input/nonregression/123.j2c')
|
'input/nonregression/123.j2c')
|
||||||
j = Jp2k(filename)
|
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
|
# The codestream is valid, so we should be able to get the entire
|
||||||
# codestream, so the last one is EOC.
|
# 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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,15 @@
|
||||||
"""These tests are for edge cases where OPENJPEG does not exist, but
|
"""These tests are for edge cases where OPENJPEG does not exist, but
|
||||||
OPENJP2 may be present in some form or other.
|
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 imp
|
||||||
import os
|
import os
|
||||||
|
|
@ -17,13 +25,9 @@ if sys.hexversion <= 0x03030000:
|
||||||
from mock import patch
|
from mock import patch
|
||||||
else:
|
else:
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
import warnings
|
|
||||||
|
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
import glymur
|
import glymur
|
||||||
from glymur import Jp2k
|
from glymur import Jp2k
|
||||||
from glymur.lib import openjp2 as opj2
|
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(sys.hexversion < 0x03020000,
|
@unittest.skipIf(sys.hexversion < 0x03020000,
|
||||||
|
|
@ -31,6 +35,7 @@ from glymur.lib import openjp2 as opj2
|
||||||
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
|
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
|
||||||
"Needs openjp2 library first before these tests make sense.")
|
"Needs openjp2 library first before these tests make sense.")
|
||||||
class TestSuite(unittest.TestCase):
|
class TestSuite(unittest.TestCase):
|
||||||
|
"""Test suite for configuration file operation."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
|
|
@ -56,30 +61,79 @@ class TestSuite(unittest.TestCase):
|
||||||
filename = os.path.join(configdir, 'glymurrc')
|
filename = os.path.join(configdir, 'glymurrc')
|
||||||
with open(filename, 'wt') as tfile:
|
with open(filename, 'wt') as tfile:
|
||||||
tfile.write('[library]\n')
|
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
|
libloc = glymur.lib.openjp2.OPENJP2._name
|
||||||
line = 'openjp2: {0}\n'.format(libloc)
|
line = 'openjp2: {0}\n'.format(libloc)
|
||||||
tfile.write(line)
|
tfile.write(line)
|
||||||
tfile.flush()
|
tfile.flush()
|
||||||
with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
|
with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
|
||||||
imp.reload(glymur.lib.openjp2)
|
imp.reload(glymur.lib.openjp2)
|
||||||
j = Jp2k(self.jp2file)
|
Jp2k(self.jp2file)
|
||||||
|
|
||||||
def test_config_file_via_environ_is_wrong(self):
|
def test_xdg_env_config_file_is_bad(self):
|
||||||
# A non-existant library location should be rejected.
|
"""A non-existant library location should be rejected."""
|
||||||
with tempfile.TemporaryDirectory() as tdir:
|
with tempfile.TemporaryDirectory() as tdir:
|
||||||
configdir = os.path.join(tdir, 'glymur')
|
configdir = os.path.join(tdir, 'glymur')
|
||||||
os.mkdir(configdir)
|
os.mkdir(configdir)
|
||||||
fname = os.path.join(configdir, 'glymurrc')
|
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:
|
with tempfile.NamedTemporaryFile(suffix='.dylib') as tfile:
|
||||||
fp.write('[library]\n')
|
fptr.write('[library]\n')
|
||||||
fp.write('openjp2: {0}.not.there\n'.format(tfile.name))
|
fptr.write('openjp2: {0}.not.there\n'.format(tfile.name))
|
||||||
fp.flush()
|
fptr.flush()
|
||||||
with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
|
with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
|
||||||
# Misconfigured new configuration file should
|
# Misconfigured new configuration file should
|
||||||
# be rejected.
|
# be rejected.
|
||||||
with self.assertWarns(UserWarning) as cw:
|
with self.assertWarns(UserWarning):
|
||||||
imp.reload(glymur.lib.openjp2)
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,18 @@
|
||||||
"""
|
"""
|
||||||
These tests deal with JPX/JP2/J2K images in the format-corpus repository.
|
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
|
import os
|
||||||
|
from os.path import join
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
if sys.hexversion < 0x02070000:
|
if sys.hexversion < 0x02070000:
|
||||||
|
|
@ -11,106 +20,105 @@ if sys.hexversion < 0x02070000:
|
||||||
else:
|
else:
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
from glymur import Jp2k
|
|
||||||
import glymur
|
import glymur
|
||||||
|
from glymur import Jp2k
|
||||||
|
|
||||||
try:
|
try:
|
||||||
format_corpus_data_root = os.environ['FORMAT_CORPUS_DATA_ROOT']
|
FORMAT_CORPUS_DATA_ROOT = os.environ['FORMAT_CORPUS_DATA_ROOT']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
format_corpus_data_root = None
|
FORMAT_CORPUS_DATA_ROOT = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opj_data_root = os.environ['OPJ_DATA_ROOT']
|
OPJ_DATA_ROOT = os.environ['OPJ_DATA_ROOT']
|
||||||
except KeyError:
|
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")
|
"FORMAT_CORPUS_DATA_ROOT environment variable not set")
|
||||||
@unittest.skipIf(sys.hexversion < 0x03020000,
|
@unittest.skipIf(sys.hexversion < 0x03020000,
|
||||||
"Requires features introduced in 3.2 (assertWarns)")
|
"Requires features introduced in 3.2 (assertWarns)")
|
||||||
class TestSuiteFormatCorpus(unittest.TestCase):
|
class TestSuiteFormatCorpus(unittest.TestCase):
|
||||||
|
"""Test suite for files in format corpus repository."""
|
||||||
|
|
||||||
def setUp(self):
|
@unittest.skipIf(re.match(r"""1\.[0123]""",
|
||||||
pass
|
glymur.version.openjpeg_version) is not None,
|
||||||
|
"Needs 1.3+ to catch this.")
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_balloon_trunc1(self):
|
def test_balloon_trunc1(self):
|
||||||
# Has one byte shaved off of EOC marker.
|
"""Has one byte shaved off of EOC marker."""
|
||||||
jfile = os.path.join(format_corpus_data_root,
|
jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT,
|
||||||
'jp2k-test/byteCorruption/balloon_trunc1.jp2')
|
'jp2k-test/byteCorruption/balloon_trunc1.jp2')
|
||||||
j2k = Jp2k(jfile)
|
j2k = Jp2k(jfile)
|
||||||
with self.assertWarns(UserWarning):
|
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.
|
# 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.
|
# The codestream is not as long as claimed.
|
||||||
with self.assertRaises(OSError):
|
with self.assertRaises(OSError):
|
||||||
j2k.read(rlevel=-1)
|
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):
|
def test_balloon_trunc2(self):
|
||||||
# Shortened by 5000 bytes.
|
"""Shortened by 5000 bytes."""
|
||||||
jfile = os.path.join(format_corpus_data_root,
|
jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT,
|
||||||
'jp2k-test/byteCorruption/balloon_trunc2.jp2')
|
'jp2k-test/byteCorruption/balloon_trunc2.jp2')
|
||||||
j2k = Jp2k(jfile)
|
j2k = Jp2k(jfile)
|
||||||
with self.assertWarns(UserWarning):
|
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.
|
# 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.
|
# The codestream is not as long as claimed.
|
||||||
with self.assertRaises(OSError):
|
with self.assertRaises(OSError):
|
||||||
j2k.read(rlevel=-1)
|
j2k.read(rlevel=-1)
|
||||||
|
|
||||||
def test_balloon_trunc3(self):
|
def test_balloon_trunc3(self):
|
||||||
# Most of last tile is missing.
|
"""Most of last tile is missing."""
|
||||||
jfile = os.path.join(format_corpus_data_root,
|
jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT,
|
||||||
'jp2k-test/byteCorruption/balloon_trunc3.jp2')
|
'jp2k-test/byteCorruption/balloon_trunc3.jp2')
|
||||||
j2k = Jp2k(jfile)
|
j2k = Jp2k(jfile)
|
||||||
with self.assertWarns(UserWarning):
|
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.
|
# 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.
|
# Should error out, it does not.
|
||||||
#with self.assertRaises(OSError):
|
#with self.assertRaises(OSError):
|
||||||
# j2k.read(rlevel=-1)
|
# j2k.read(rlevel=-1)
|
||||||
|
|
||||||
def test_jp2_brand_vs_any_icc_profile(self):
|
def test_jp2_brand_any_icc_profile(self):
|
||||||
# If 'jp2 ', then the method cannot be any icc profile.
|
"""If 'jp2 ', then the method cannot be any icc profile."""
|
||||||
jfile = os.path.join(format_corpus_data_root,
|
jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT,
|
||||||
'jp2k-test', 'icc',
|
'jp2k-test', 'icc',
|
||||||
'balloon_eciRGBv2_ps_adobeplugin.jpf')
|
'balloon_eciRGBv2_ps_adobeplugin.jpf')
|
||||||
with self.assertWarns(UserWarning):
|
with self.assertWarns(UserWarning):
|
||||||
j2k = Jp2k(jfile)
|
Jp2k(jfile)
|
||||||
|
|
||||||
def test_jp2_brand_vs_any_icc_profile_multiple_colr(self):
|
def test_jp2_brand_iccpr_mult_colr(self):
|
||||||
# Has colr box, one that conforms, one that does not.
|
"""Has colr box, one that conforms, one that does not."""
|
||||||
|
|
||||||
# Wrong 'brand' field; contains two versions of ICC profile: one
|
# Wrong 'brand' field; contains two versions of ICC profile: one
|
||||||
# embedded using "Any ICC" method; other embedded using "Restricted
|
# embedded using "Any ICC" method; other embedded using "Restricted
|
||||||
# ICC" method, with description ("Modified eciRGB v2") and profileClass
|
# ICC" method, with description ("Modified eciRGB v2") and profileClass
|
||||||
# ("Input Device") changed relative to original profile.
|
# ("Input Device") changed relative to original profile.
|
||||||
lst = [format_corpus_data_root, 'jp2k-test', 'icc',
|
jfile = join(FORMAT_CORPUS_DATA_ROOT, 'jp2k-test', 'icc',
|
||||||
'balloon_eciRGBv2_ps_adobeplugin_jp2compatible.jpf']
|
'balloon_eciRGBv2_ps_adobeplugin_jp2compatible.jpf')
|
||||||
jfile = os.path.join(*lst)
|
|
||||||
with self.assertWarns(UserWarning):
|
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")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
@unittest.skipIf(sys.hexversion < 0x03020000,
|
@unittest.skipIf(sys.hexversion < 0x03020000,
|
||||||
"Requires features introduced in 3.2 (assertWarns)")
|
"Requires features introduced in 3.2 (assertWarns)")
|
||||||
class TestSuiteOpj(unittest.TestCase):
|
class TestSuiteOpj(unittest.TestCase):
|
||||||
|
"""Test suite for files in openjpeg repository."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
pass
|
pass
|
||||||
|
|
@ -118,12 +126,12 @@ class TestSuiteOpj(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_jp2_brand_vs_any_icc_profile(self):
|
def test_jp2_brand_any_icc_profile(self):
|
||||||
# If 'jp2 ', then the method cannot be any icc profile.
|
"""If 'jp2 ', then the method cannot be any icc profile."""
|
||||||
filename = os.path.join(opj_data_root,
|
filename = os.path.join(OPJ_DATA_ROOT,
|
||||||
'input/nonregression/text_GBR.jp2')
|
'input/nonregression/text_GBR.jp2')
|
||||||
with self.assertWarns(UserWarning):
|
with self.assertWarns(UserWarning):
|
||||||
j2k = Jp2k(filename)
|
Jp2k(filename)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -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 datetime
|
||||||
import os
|
import os
|
||||||
import struct
|
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
|
||||||
|
|
||||||
if sys.hexversion < 0x02070000:
|
if sys.hexversion < 0x02070000:
|
||||||
import unittest2 as unittest
|
import unittest2 as unittest
|
||||||
else:
|
else:
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import warnings
|
|
||||||
from xml.etree import cElementTree as ET
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
from glymur import Jp2k
|
from glymur import Jp2k
|
||||||
import glymur
|
from .fixtures import OPJ_DATA_ROOT, opj_data_file
|
||||||
|
|
||||||
try:
|
|
||||||
data_root = os.environ['OPJ_DATA_ROOT']
|
|
||||||
except KeyError:
|
|
||||||
data_root = None
|
|
||||||
except:
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(data_root is None,
|
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
||||||
"OPJ_DATA_ROOT environment variable not set")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
class TestICC(unittest.TestCase):
|
class TestICC(unittest.TestCase):
|
||||||
|
"""ICC profile tests"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
pass
|
pass
|
||||||
|
|
@ -38,7 +35,8 @@ class TestICC(unittest.TestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_file5(self):
|
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)
|
j = Jp2k(filename)
|
||||||
profile = j.box[3].box[1].icc_profile
|
profile = j.box[3].box[1].icc_profile
|
||||||
self.assertEqual(profile['Size'], 546)
|
self.assertEqual(profile['Size'], 546)
|
||||||
|
|
@ -70,10 +68,13 @@ class TestICC(unittest.TestCase):
|
||||||
@unittest.skipIf(sys.hexversion < 0x03020000,
|
@unittest.skipIf(sys.hexversion < 0x03020000,
|
||||||
"Uses features introduced in 3.2.")
|
"Uses features introduced in 3.2.")
|
||||||
def test_invalid_profile_header(self):
|
def test_invalid_profile_header(self):
|
||||||
jfile = os.path.join(data_root,
|
"""invalid ICC header data should cause UserWarning"""
|
||||||
'input/nonregression/orb-blue10-lin-jp2.jp2')
|
jfile = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2')
|
||||||
with self.assertWarns(UserWarning) as cw:
|
|
||||||
j = Jp2k(jfile)
|
# assertWarns in Python 3.3 (python2.7/pylint issue)
|
||||||
|
# pylint: disable=E1101
|
||||||
|
with self.assertWarns(UserWarning):
|
||||||
|
Jp2k(jfile)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -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 doctest
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
|
import struct
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import uuid
|
||||||
import xml.etree.cElementTree as ET
|
import xml.etree.cElementTree as ET
|
||||||
|
|
||||||
if sys.hexversion < 0x02070000:
|
if sys.hexversion < 0x02070000:
|
||||||
|
|
@ -11,37 +31,37 @@ else:
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
import glymur
|
import glymur
|
||||||
from glymur import Jp2k
|
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 COLOR, OPACITY
|
||||||
from glymur.core import RED, GREEN, BLUE, GREY, WHOLE_IMAGE
|
from glymur.core import RED, GREEN, BLUE, GREY, WHOLE_IMAGE
|
||||||
|
|
||||||
from .fixtures import OPENJP2_IS_V2_OFFICIAL
|
from .fixtures import OPENJP2_IS_V2_OFFICIAL
|
||||||
|
|
||||||
try:
|
try:
|
||||||
format_corpus_data_root = os.environ['FORMAT_CORPUS_DATA_ROOT']
|
FORMAT_CORPUS_DATA_ROOT = os.environ['FORMAT_CORPUS_DATA_ROOT']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
format_corpus_data_root = None
|
FORMAT_CORPUS_DATA_ROOT = None
|
||||||
|
|
||||||
|
|
||||||
# Doc tests should be run as well.
|
|
||||||
def load_tests(loader, tests, ignore):
|
def load_tests(loader, tests, ignore):
|
||||||
|
"""Run doc tests as well."""
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
# Can't do it on windows, temporary file issue.
|
# Can't do it on windows, temporary file issue.
|
||||||
return tests
|
return tests
|
||||||
tests.addTests(doctest.DocTestSuite('glymur.jp2box'))
|
tests.addTests(doctest.DocTestSuite('glymur.jp2box'))
|
||||||
return tests
|
return tests
|
||||||
|
|
||||||
|
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2 or
|
||||||
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL,
|
OPENJP2_IS_V2_OFFICIAL,
|
||||||
"Requires v2.0.0+ in order to run.")
|
"Not supported until 2.0+.")
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
|
|
||||||
"Missing openjp2 library.")
|
|
||||||
class TestChannelDefinition(unittest.TestCase):
|
class TestChannelDefinition(unittest.TestCase):
|
||||||
|
"""Test suite for channel definition boxes."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
|
|
@ -78,12 +98,12 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
self.j2kfile = glymur.data.goodstuff()
|
self.j2kfile = glymur.data.goodstuff()
|
||||||
|
|
||||||
j2k = Jp2k(self.j2kfile)
|
j2k = Jp2k(self.j2kfile)
|
||||||
c = j2k.get_codestream()
|
codestream = j2k.get_codestream()
|
||||||
height = c.segment[1].ysiz
|
height = codestream.segment[1].ysiz
|
||||||
width = c.segment[1].xsiz
|
width = codestream.segment[1].xsiz
|
||||||
num_components = len(c.segment[1].xrsiz)
|
num_components = len(codestream.segment[1].xrsiz)
|
||||||
|
|
||||||
self.jP = JPEG2000SignatureBox()
|
self.jp2b = JPEG2000SignatureBox()
|
||||||
self.ftyp = FileTypeBox()
|
self.ftyp = FileTypeBox()
|
||||||
self.jp2h = JP2HeaderBox()
|
self.jp2h = JP2HeaderBox()
|
||||||
self.jp2c = ContiguousCodestreamBox()
|
self.jp2c = ContiguousCodestreamBox()
|
||||||
|
|
@ -110,7 +130,7 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
association=association)
|
association=association)
|
||||||
boxes = [self.ihdr, self.colr_rgb, cdef]
|
boxes = [self.ihdr, self.colr_rgb, cdef]
|
||||||
self.jp2h.box = boxes
|
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 tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
j2k.wrap(tfile.name, boxes=boxes)
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
|
@ -133,7 +153,7 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
association=association)
|
association=association)
|
||||||
boxes = [self.ihdr, self.colr_rgb, cdef]
|
boxes = [self.ihdr, self.colr_rgb, cdef]
|
||||||
self.jp2h.box = boxes
|
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 tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
j2k.wrap(tfile.name, boxes=boxes)
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
|
@ -156,7 +176,7 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
association=association)
|
association=association)
|
||||||
boxes = [self.ihdr, self.colr_rgb, cdef]
|
boxes = [self.ihdr, self.colr_rgb, cdef]
|
||||||
self.jp2h.box = boxes
|
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 tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
j2k.wrap(tfile.name, boxes=boxes)
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
|
@ -177,9 +197,9 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
association=association)
|
association=association)
|
||||||
boxes = [self.ihdr, self.colr_rgb, cdef]
|
boxes = [self.ihdr, self.colr_rgb, cdef]
|
||||||
self.jp2h.box = boxes
|
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 tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
with self.assertRaises(IOError) as ce:
|
with self.assertRaises(IOError):
|
||||||
j2k.wrap(tfile.name, boxes=boxes)
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
def test_grey(self):
|
def test_grey(self):
|
||||||
|
|
@ -191,7 +211,7 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
association=association)
|
association=association)
|
||||||
boxes = [self.ihdr, self.colr_gr, cdef]
|
boxes = [self.ihdr, self.colr_gr, cdef]
|
||||||
self.jp2h.box = boxes
|
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 tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
j2k.wrap(tfile.name, boxes=boxes)
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
|
@ -212,7 +232,7 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
association=association)
|
association=association)
|
||||||
boxes = [self.ihdr, self.colr_gr, cdef]
|
boxes = [self.ihdr, self.colr_gr, cdef]
|
||||||
self.jp2h.box = boxes
|
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 tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
j2k.wrap(tfile.name, boxes=boxes)
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
|
@ -236,12 +256,12 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
association=association)
|
association=association)
|
||||||
boxes = [self.ihdr, self.colr_gr, cdef]
|
boxes = [self.ihdr, self.colr_gr, cdef]
|
||||||
self.jp2h.box = boxes
|
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 tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
with self.assertRaises((OSError, IOError)) as ce:
|
with self.assertRaises((OSError, IOError)):
|
||||||
j2k.wrap(tfile.name, boxes=boxes)
|
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."""
|
"""There can only be one channel definition box in the jp2 header."""
|
||||||
j2k = Jp2k(self.j2kfile)
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
|
||||||
|
|
@ -253,13 +273,14 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
boxes = [self.ihdr, cdef, self.colr_rgb, cdef]
|
boxes = [self.ihdr, cdef, self.colr_rgb, cdef]
|
||||||
self.jp2h.box = boxes
|
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 tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
j2k.wrap(tfile.name, boxes=boxes)
|
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)
|
j2k = Jp2k(self.j2kfile)
|
||||||
boxes = [self.ihdr, self.colr_rgb]
|
boxes = [self.ihdr, self.colr_rgb]
|
||||||
self.jp2h.box = boxes
|
self.jp2h.box = boxes
|
||||||
|
|
@ -269,151 +290,47 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
association=association)
|
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 tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
j2k.wrap(tfile.name, boxes=boxes)
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
def test_bad_type(self):
|
def test_bad_type(self):
|
||||||
# Channel types are limited to 0, 1, 2, 65535
|
"""Channel types are limited to 0, 1, 2, 65535
|
||||||
# Should reject if not all of index, channel_type, association the
|
Should reject if not all of index, channel_type, association the
|
||||||
# same length.
|
same length.
|
||||||
|
"""
|
||||||
channel_type = (COLOR, COLOR, 3)
|
channel_type = (COLOR, COLOR, 3)
|
||||||
association = (RED, GREEN, BLUE)
|
association = (RED, GREEN, BLUE)
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
box = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
association=association)
|
association=association)
|
||||||
|
|
||||||
def test_wrong_lengths(self):
|
def test_wrong_lengths(self):
|
||||||
# Should reject if not all of index, channel_type, association the
|
"""Should reject if not all of index, channel_type, association the
|
||||||
# same length.
|
same length.
|
||||||
|
"""
|
||||||
channel_type = (COLOR, COLOR)
|
channel_type = (COLOR, COLOR)
|
||||||
association = (RED, GREEN, BLUE)
|
association = (RED, GREEN, BLUE)
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
box = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
association=association)
|
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')
|
|
||||||
|
|
||||||
|
|
||||||
class TestColourSpecificationBox(unittest.TestCase):
|
class TestColourSpecificationBox(unittest.TestCase):
|
||||||
|
"""Test suite for colr box instantiation."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.j2kfile = glymur.data.goodstuff()
|
self.j2kfile = glymur.data.goodstuff()
|
||||||
|
|
||||||
j2k = Jp2k(self.j2kfile)
|
j2k = Jp2k(self.j2kfile)
|
||||||
c = j2k.get_codestream()
|
codestream = j2k.get_codestream()
|
||||||
height = c.segment[1].ysiz
|
height = codestream.segment[1].ysiz
|
||||||
width = c.segment[1].xsiz
|
width = codestream.segment[1].xsiz
|
||||||
num_components = len(c.segment[1].xrsiz)
|
num_components = len(codestream.segment[1].xrsiz)
|
||||||
|
|
||||||
self.jP = JPEG2000SignatureBox()
|
self.jp2b = JPEG2000SignatureBox()
|
||||||
self.ftyp = FileTypeBox()
|
self.ftyp = FileTypeBox()
|
||||||
self.jp2h = JP2HeaderBox()
|
self.jp2h = JP2HeaderBox()
|
||||||
self.jp2c = ContiguousCodestreamBox()
|
self.jp2c = ContiguousCodestreamBox()
|
||||||
|
|
@ -425,10 +342,11 @@ class TestColourSpecificationBox(unittest.TestCase):
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt",
|
@unittest.skipIf(os.name == "nt",
|
||||||
"Problems using NamedTemporaryFile on windows.")
|
"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)
|
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)]
|
boxes[2].box = [self.ihdr, ColourSpecificationBox(colorspace=None)]
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
with self.assertRaises(NotImplementedError):
|
with self.assertRaises(NotImplementedError):
|
||||||
|
|
@ -436,47 +354,142 @@ class TestColourSpecificationBox(unittest.TestCase):
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
def test_missing_colr_box(self):
|
def test_missing_colr_box(self):
|
||||||
|
"""jp2h must have a colr box"""
|
||||||
j2k = Jp2k(self.j2kfile)
|
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]
|
boxes[2].box = [self.ihdr]
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
j2k.wrap(tfile.name, boxes=boxes)
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
def test_default_ColourSpecificationBox(self):
|
def test_default_colr(self):
|
||||||
b = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.SRGB)
|
"""basic colr instantiation"""
|
||||||
self.assertEqual(b.method, glymur.core.ENUMERATED_COLORSPACE)
|
colr = ColourSpecificationBox(colorspace=glymur.core.SRGB)
|
||||||
self.assertEqual(b.precedence, 0)
|
self.assertEqual(colr.method, glymur.core.ENUMERATED_COLORSPACE)
|
||||||
self.assertEqual(b.approximation, 0)
|
self.assertEqual(colr.precedence, 0)
|
||||||
self.assertEqual(b.colorspace, glymur.core.SRGB)
|
self.assertEqual(colr.approximation, 0)
|
||||||
self.assertIsNone(b.icc_profile)
|
self.assertEqual(colr.colorspace, glymur.core.SRGB)
|
||||||
|
self.assertIsNone(colr.icc_profile)
|
||||||
|
|
||||||
def test_ColourSpecificationBox_with_colorspace_and_icc(self):
|
def test_colr_with_cspace_and_icc(self):
|
||||||
# Colour specification boxes can't have both.
|
"""Colour specification boxes can't have both."""
|
||||||
with self.assertRaises((OSError, IOError)):
|
with self.assertRaises((OSError, IOError)):
|
||||||
colorspace = glymur.core.SRGB
|
colorspace = glymur.core.SRGB
|
||||||
icc_profile = b'\x01\x02\x03\x04'
|
rawb = b'\x01\x02\x03\x04'
|
||||||
b = glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
|
glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
|
||||||
icc_profile=icc_profile)
|
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
|
colorspace = glymur.core.SRGB
|
||||||
method = -1
|
method = -1
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
b = glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
|
glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
|
||||||
method=method)
|
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
|
colorspace = glymur.core.SRGB
|
||||||
approx = -1
|
approx = -1
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
b = glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
|
glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
|
||||||
approximation=approx)
|
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):
|
class TestWrap(unittest.TestCase):
|
||||||
|
"""Tests for wrap method."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.j2kfile = glymur.data.goodstuff()
|
self.j2kfile = glymur.data.goodstuff()
|
||||||
|
|
@ -486,7 +499,7 @@ class TestWrap(unittest.TestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def verify_wrapped_raw(self, jp2file):
|
def verify_wrapped_raw(self, jp2file):
|
||||||
# Shared method by at least two tests.
|
"""Shared fixture"""
|
||||||
jp2 = Jp2k(jp2file)
|
jp2 = Jp2k(jp2file)
|
||||||
self.assertEqual(len(jp2.box), 4)
|
self.assertEqual(len(jp2.box), 4)
|
||||||
|
|
||||||
|
|
@ -536,6 +549,7 @@ class TestWrap(unittest.TestCase):
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
def test_wrap(self):
|
def test_wrap(self):
|
||||||
|
"""basic test for rewrapping a j2c file, no specified boxes"""
|
||||||
j2k = Jp2k(self.j2kfile)
|
j2k = Jp2k(self.j2kfile)
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
j2k.wrap(tfile.name)
|
j2k.wrap(tfile.name)
|
||||||
|
|
@ -543,6 +557,7 @@ class TestWrap(unittest.TestCase):
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
def test_wrap_jp2(self):
|
def test_wrap_jp2(self):
|
||||||
|
"""basic test for rewrapping a jp2 file, no specified boxes"""
|
||||||
j2k = Jp2k(self.jp2file)
|
j2k = Jp2k(self.jp2file)
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
jp2 = j2k.wrap(tfile.name)
|
jp2 = j2k.wrap(tfile.name)
|
||||||
|
|
@ -550,16 +565,17 @@ class TestWrap(unittest.TestCase):
|
||||||
self.assertEqual(boxes, ['jP ', 'ftyp', 'jp2h', 'jp2c'])
|
self.assertEqual(boxes, ['jP ', 'ftyp', 'jp2h', 'jp2c'])
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@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)
|
j2k = Jp2k(self.j2kfile)
|
||||||
boxes = [JPEG2000SignatureBox(),
|
boxes = [JPEG2000SignatureBox(),
|
||||||
FileTypeBox(),
|
FileTypeBox(),
|
||||||
JP2HeaderBox(),
|
JP2HeaderBox(),
|
||||||
ContiguousCodestreamBox()]
|
ContiguousCodestreamBox()]
|
||||||
c = j2k.get_codestream()
|
codestream = j2k.get_codestream()
|
||||||
height = c.segment[1].ysiz
|
height = codestream.segment[1].ysiz
|
||||||
width = c.segment[1].xsiz
|
width = codestream.segment[1].xsiz
|
||||||
num_components = len(c.segment[1].xrsiz)
|
num_components = len(codestream.segment[1].xrsiz)
|
||||||
boxes[2].box = [ImageHeaderBox(height=height,
|
boxes[2].box = [ImageHeaderBox(height=height,
|
||||||
width=width,
|
width=width,
|
||||||
num_components=num_components),
|
num_components=num_components),
|
||||||
|
|
@ -569,17 +585,17 @@ class TestWrap(unittest.TestCase):
|
||||||
self.verify_wrapped_raw(tfile.name)
|
self.verify_wrapped_raw(tfile.name)
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
def test_image_header_box_not_first_in_jp2_header(self):
|
def test_ihdr_not_first_in_jp2h(self):
|
||||||
# The specification says that ihdr must be the first box in jp2h.
|
"""The specification says that ihdr must be the first box in jp2h."""
|
||||||
j2k = Jp2k(self.j2kfile)
|
j2k = Jp2k(self.j2kfile)
|
||||||
boxes = [JPEG2000SignatureBox(),
|
boxes = [JPEG2000SignatureBox(),
|
||||||
FileTypeBox(),
|
FileTypeBox(),
|
||||||
JP2HeaderBox(),
|
JP2HeaderBox(),
|
||||||
ContiguousCodestreamBox()]
|
ContiguousCodestreamBox()]
|
||||||
c = j2k.get_codestream()
|
codestream = j2k.get_codestream()
|
||||||
height = c.segment[1].ysiz
|
height = codestream.segment[1].ysiz
|
||||||
width = c.segment[1].xsiz
|
width = codestream.segment[1].xsiz
|
||||||
num_components = len(c.segment[1].xrsiz)
|
num_components = len(codestream.segment[1].xrsiz)
|
||||||
boxes[2].box = [ColourSpecificationBox(colorspace=glymur.core.SRGB),
|
boxes[2].box = [ColourSpecificationBox(colorspace=glymur.core.SRGB),
|
||||||
ImageHeaderBox(height=height,
|
ImageHeaderBox(height=height,
|
||||||
width=width,
|
width=width,
|
||||||
|
|
@ -589,14 +605,15 @@ class TestWrap(unittest.TestCase):
|
||||||
j2k.wrap(tfile.name, boxes=boxes)
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@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)
|
j2k = Jp2k(self.j2kfile)
|
||||||
c = j2k.get_codestream()
|
codestream = j2k.get_codestream()
|
||||||
height = c.segment[1].ysiz
|
height = codestream.segment[1].ysiz
|
||||||
width = c.segment[1].xsiz
|
width = codestream.segment[1].xsiz
|
||||||
num_components = len(c.segment[1].xrsiz)
|
num_components = len(codestream.segment[1].xrsiz)
|
||||||
|
|
||||||
jP = JPEG2000SignatureBox()
|
jp2b = JPEG2000SignatureBox()
|
||||||
ftyp = FileTypeBox()
|
ftyp = FileTypeBox()
|
||||||
jp2h = JP2HeaderBox()
|
jp2h = JP2HeaderBox()
|
||||||
jp2c = ContiguousCodestreamBox()
|
jp2c = ContiguousCodestreamBox()
|
||||||
|
|
@ -604,20 +621,21 @@ class TestWrap(unittest.TestCase):
|
||||||
ihdr = ImageHeaderBox(height=height, width=width,
|
ihdr = ImageHeaderBox(height=height, width=width,
|
||||||
num_components=num_components)
|
num_components=num_components)
|
||||||
jp2h.box = [ihdr, colr]
|
jp2h.box = [ihdr, colr]
|
||||||
boxes = [ftyp, jP, jp2h, jp2c]
|
boxes = [ftyp, jp2b, jp2h, jp2c]
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
j2k.wrap(tfile.name, boxes=boxes)
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
def test_jp2h_not_preceeding_jp2c(self):
|
def test_jp2h_not_preceeding_jp2c(self):
|
||||||
|
"""jp2h must precede jp2c"""
|
||||||
j2k = Jp2k(self.j2kfile)
|
j2k = Jp2k(self.j2kfile)
|
||||||
c = j2k.get_codestream()
|
codestream = j2k.get_codestream()
|
||||||
height = c.segment[1].ysiz
|
height = codestream.segment[1].ysiz
|
||||||
width = c.segment[1].xsiz
|
width = codestream.segment[1].xsiz
|
||||||
num_components = len(c.segment[1].xrsiz)
|
num_components = len(codestream.segment[1].xrsiz)
|
||||||
|
|
||||||
jP = JPEG2000SignatureBox()
|
jp2b = JPEG2000SignatureBox()
|
||||||
ftyp = FileTypeBox()
|
ftyp = FileTypeBox()
|
||||||
jp2h = JP2HeaderBox()
|
jp2h = JP2HeaderBox()
|
||||||
jp2c = ContiguousCodestreamBox()
|
jp2c = ContiguousCodestreamBox()
|
||||||
|
|
@ -625,68 +643,74 @@ class TestWrap(unittest.TestCase):
|
||||||
ihdr = ImageHeaderBox(height=height, width=width,
|
ihdr = ImageHeaderBox(height=height, width=width,
|
||||||
num_components=num_components)
|
num_components=num_components)
|
||||||
jp2h.box = [ihdr, colr]
|
jp2h.box = [ihdr, colr]
|
||||||
boxes = [jP, ftyp, jp2c, jp2h]
|
boxes = [jp2b, ftyp, jp2c, jp2h]
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
j2k.wrap(tfile.name, boxes=boxes)
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
def test_missing_codestream(self):
|
def test_missing_codestream(self):
|
||||||
|
"""Need a codestream box in order to call wrap method."""
|
||||||
j2k = Jp2k(self.j2kfile)
|
j2k = Jp2k(self.j2kfile)
|
||||||
c = j2k.get_codestream()
|
codestream = j2k.get_codestream()
|
||||||
height = c.segment[1].ysiz
|
height = codestream.segment[1].ysiz
|
||||||
width = c.segment[1].xsiz
|
width = codestream.segment[1].xsiz
|
||||||
num_components = len(c.segment[1].xrsiz)
|
num_components = len(codestream.segment[1].xrsiz)
|
||||||
|
|
||||||
jP = JPEG2000SignatureBox()
|
jp2k = JPEG2000SignatureBox()
|
||||||
ftyp = FileTypeBox()
|
ftyp = FileTypeBox()
|
||||||
jp2h = JP2HeaderBox()
|
jp2h = JP2HeaderBox()
|
||||||
ihdr = ImageHeaderBox(height=height, width=width,
|
ihdr = ImageHeaderBox(height=height, width=width,
|
||||||
num_components=num_components)
|
num_components=num_components)
|
||||||
jp2h.box = [ihdr]
|
jp2h.box = [ihdr]
|
||||||
boxes = [jP, ftyp, jp2h]
|
boxes = [jp2k, ftyp, jp2h]
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
j2k.wrap(tfile.name, boxes=boxes)
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
|
||||||
class TestJp2Boxes(unittest.TestCase):
|
class TestJp2Boxes(unittest.TestCase):
|
||||||
|
"""Tests for canonical JP2 boxes."""
|
||||||
|
|
||||||
def test_default_JPEG2000SignatureBox(self):
|
def test_default_jp2k(self):
|
||||||
# Should be able to instantiate a JPEG2000SignatureBox
|
"""Should be able to instantiate a JPEG2000SignatureBox"""
|
||||||
b = glymur.jp2box.JPEG2000SignatureBox()
|
jp2k = glymur.jp2box.JPEG2000SignatureBox()
|
||||||
self.assertEqual(b.signature, (13, 10, 135, 10))
|
self.assertEqual(jp2k.signature, (13, 10, 135, 10))
|
||||||
|
|
||||||
def test_default_FileTypeBox(self):
|
def test_default_ftyp(self):
|
||||||
# Should be able to instantiate a FileTypeBox
|
"""Should be able to instantiate a FileTypeBox"""
|
||||||
b = glymur.jp2box.FileTypeBox()
|
ftyp = glymur.jp2box.FileTypeBox()
|
||||||
self.assertEqual(b.brand, 'jp2 ')
|
self.assertEqual(ftyp.brand, 'jp2 ')
|
||||||
self.assertEqual(b.minor_version, 0)
|
self.assertEqual(ftyp.minor_version, 0)
|
||||||
self.assertEqual(b.compatibility_list, ['jp2 '])
|
self.assertEqual(ftyp.compatibility_list, ['jp2 '])
|
||||||
|
|
||||||
def test_default_ImageHeaderBox(self):
|
def test_default_ihdr(self):
|
||||||
# Should be able to instantiate an image header box.
|
"""Should be able to instantiate an image header box."""
|
||||||
b = glymur.jp2box.ImageHeaderBox(height=512, width=256,
|
ihdr = glymur.jp2box.ImageHeaderBox(height=512, width=256,
|
||||||
num_components=3)
|
num_components=3)
|
||||||
self.assertEqual(b.height, 512)
|
self.assertEqual(ihdr.height, 512)
|
||||||
self.assertEqual(b.width, 256)
|
self.assertEqual(ihdr.width, 256)
|
||||||
self.assertEqual(b.num_components, 3)
|
self.assertEqual(ihdr.num_components, 3)
|
||||||
self.assertEqual(b.bits_per_component, 8)
|
self.assertEqual(ihdr.bits_per_component, 8)
|
||||||
self.assertFalse(b.signed)
|
self.assertFalse(ihdr.signed)
|
||||||
self.assertFalse(b.colorspace_unknown)
|
self.assertFalse(ihdr.colorspace_unknown)
|
||||||
|
|
||||||
def test_default_JP2HeaderBox(self):
|
def test_default_jp2headerbox(self):
|
||||||
b1 = JP2HeaderBox()
|
"""Should be able to set jp2h boxes."""
|
||||||
b1.box = [ImageHeaderBox(height=512, width=256),
|
box = JP2HeaderBox()
|
||||||
ColourSpecificationBox(colorspace=glymur.core.GREYSCALE)]
|
box.box = [ImageHeaderBox(height=512, width=256),
|
||||||
|
ColourSpecificationBox(colorspace=glymur.core.GREYSCALE)]
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
def test_default_ContiguousCodestreamBox(self):
|
def test_default_ccodestreambox(self):
|
||||||
b = ContiguousCodestreamBox()
|
"""Raw instantiation should not produce a main_header."""
|
||||||
self.assertEqual(b.box_id, 'jp2c')
|
box = ContiguousCodestreamBox()
|
||||||
self.assertIsNone(b.main_header)
|
self.assertEqual(box.box_id, 'jp2c')
|
||||||
|
self.assertIsNone(box.main_header)
|
||||||
|
|
||||||
|
|
||||||
class TestJpxBoxes(unittest.TestCase):
|
class TestJpxBoxes(unittest.TestCase):
|
||||||
|
"""Tests for JPX boxes."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
pass
|
pass
|
||||||
|
|
@ -694,11 +718,11 @@ class TestJpxBoxes(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
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")
|
"FORMAT_CORPUS_DATA_ROOT environment variable not set")
|
||||||
def test_codestream_header(self):
|
def test_codestream_header(self):
|
||||||
# Should recognize codestream header box.
|
"""Should recognize codestream header box."""
|
||||||
jfile = os.path.join(format_corpus_data_root,
|
jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT,
|
||||||
'jp2k-formats/balloon.jpf')
|
'jp2k-formats/balloon.jpf')
|
||||||
jpx = Jp2k(jfile)
|
jpx = Jp2k(jfile)
|
||||||
|
|
||||||
|
|
@ -706,11 +730,11 @@ class TestJpxBoxes(unittest.TestCase):
|
||||||
self.assertEqual(jpx.box[4].box_id, 'jpch')
|
self.assertEqual(jpx.box[4].box_id, 'jpch')
|
||||||
self.assertEqual(len(jpx.box[4].box), 0)
|
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")
|
"FORMAT_CORPUS_DATA_ROOT environment variable not set")
|
||||||
def test_compositing_layer_header(self):
|
def test_compositing_layer_header(self):
|
||||||
# Should recognize compositing layer header box.
|
"""Should recognize compositing layer header box."""
|
||||||
jfile = os.path.join(format_corpus_data_root,
|
jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT,
|
||||||
'jp2k-formats/balloon.jpf')
|
'jp2k-formats/balloon.jpf')
|
||||||
jpx = Jp2k(jfile)
|
jpx = Jp2k(jfile)
|
||||||
|
|
||||||
|
|
|
||||||
294
glymur/test/test_jp2box_xml.py
Normal file
294
glymur/test/test_jp2box_xml.py
Normal 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
|
|
@ -2,8 +2,17 @@
|
||||||
The tests here do not correspond directly to the OpenJPEG test suite, but
|
The tests here do not correspond directly to the OpenJPEG test suite, but
|
||||||
seem like logical negative tests to add.
|
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 os
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
|
@ -12,48 +21,21 @@ if sys.hexversion < 0x02070000:
|
||||||
else:
|
else:
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
from glymur.lib import openjp2 as opj2
|
from .fixtures import OPJ_DATA_ROOT, opj_data_file, read_image
|
||||||
|
from .fixtures import NO_READ_BACKEND, NO_READ_BACKEND_MSG
|
||||||
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 glymur import Jp2k
|
from glymur import Jp2k
|
||||||
import glymur
|
import glymur
|
||||||
|
|
||||||
try:
|
|
||||||
data_root = os.environ['OPJ_DATA_ROOT']
|
|
||||||
except KeyError:
|
|
||||||
data_root = None
|
|
||||||
except:
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version),
|
||||||
def read_image(infile):
|
"Functionality not implemented for 1.3, 1.4")
|
||||||
# PIL issues warnings which we do not care about, so suppress them.
|
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
||||||
with warnings.catch_warnings():
|
"OPJ_OPJ_DATA_ROOT environment variable not set")
|
||||||
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")
|
|
||||||
class TestSuiteNegative(unittest.TestCase):
|
class TestSuiteNegative(unittest.TestCase):
|
||||||
|
"""Test suite for certain negative tests from openjpeg suite."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.jp2file = glymur.data.nemo()
|
self.jp2file = glymur.data.nemo()
|
||||||
|
|
@ -62,46 +44,52 @@ class TestSuiteNegative(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG)
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
def test_negative_psnr_with_cratios(self):
|
def test_psnr_with_cratios(self):
|
||||||
# Using psnr with cratios options is not allowed.
|
"""Using psnr with cratios options is not allowed."""
|
||||||
# Not an OpenJPEG test, but close.
|
# 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)
|
data = read_image(infile)
|
||||||
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
|
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
|
||||||
j = Jp2k(tfile.name, 'wb')
|
j = Jp2k(tfile.name, 'wb')
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
j.write(data, psnr=[30, 35, 40], cratios=[2, 3, 4])
|
j.write(data, psnr=[30, 35, 40], cratios=[2, 3, 4])
|
||||||
|
|
||||||
def test_NR_MarkerIsNotCompliant_j2k_dump(self):
|
def test_nr_marker_not_compliant(self):
|
||||||
|
"""non-compliant marker, should still be able to read"""
|
||||||
relpath = 'input/nonregression/MarkerIsNotCompliant.j2k'
|
relpath = 'input/nonregression/MarkerIsNotCompliant.j2k'
|
||||||
jfile = os.path.join(data_root, relpath)
|
jfile = opj_data_file(relpath)
|
||||||
jp2k = Jp2k(jfile)
|
jp2k = Jp2k(jfile)
|
||||||
c = jp2k.get_codestream(header_only=False)
|
jp2k.get_codestream(header_only=False)
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
@unittest.skipIf(sys.hexversion < 0x03020000,
|
@unittest.skipIf(sys.hexversion < 0x03020000,
|
||||||
"Uses features introduced in 3.2.")
|
"Uses features introduced in 3.2.")
|
||||||
def test_NR_illegalcolortransform_dump(self):
|
def test_nr_illegalclrtransform(self):
|
||||||
# EOC marker is bad
|
"""EOC marker is bad"""
|
||||||
relpath = 'input/nonregression/illegalcolortransform.j2k'
|
relpath = 'input/nonregression/illegalcolortransform.j2k'
|
||||||
jfile = os.path.join(data_root, relpath)
|
jfile = opj_data_file(relpath)
|
||||||
jp2k = Jp2k(jfile)
|
jp2k = Jp2k(jfile)
|
||||||
with self.assertWarns(UserWarning) as cw:
|
with self.assertWarns(UserWarning):
|
||||||
c = jp2k.get_codestream(header_only=False)
|
codestream = jp2k.get_codestream(header_only=False)
|
||||||
|
|
||||||
# Verify that the last segment returned in the codestream is SOD,
|
# Verify that the last segment returned in the codestream is SOD,
|
||||||
# not EOC. Codestream parsing should stop when we try to jump to
|
# not EOC. Codestream parsing should stop when we try to jump to
|
||||||
# the end of SOT.
|
# 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'
|
relpath = 'input/nonregression/Cannotreaddatawithnosizeknown.j2k'
|
||||||
jfile = os.path.join(data_root, relpath)
|
jfile = opj_data_file(relpath)
|
||||||
jp2k = Jp2k(jfile)
|
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.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
def test_code_block_dimensions(self):
|
def test_code_block_dimensions(self):
|
||||||
|
"""don't allow extreme codeblock sizes"""
|
||||||
# opj_compress doesn't allow the dimensions of a codeblock
|
# opj_compress doesn't allow the dimensions of a codeblock
|
||||||
# to be too small or too big, so neither will we.
|
# to be too small or too big, so neither will we.
|
||||||
data = np.zeros((256, 256), dtype=np.uint8)
|
data = np.zeros((256, 256), dtype=np.uint8)
|
||||||
|
|
@ -109,55 +97,55 @@ class TestSuiteNegative(unittest.TestCase):
|
||||||
j = Jp2k(tfile.name, 'wb')
|
j = Jp2k(tfile.name, 'wb')
|
||||||
|
|
||||||
# opj_compress doesn't allow code block area to exceed 4096.
|
# opj_compress doesn't allow code block area to exceed 4096.
|
||||||
with self.assertRaises(IOError) as cr:
|
with self.assertRaises(IOError):
|
||||||
j.write(data, cbsize=(256, 256))
|
j.write(data, cbsize=(256, 256))
|
||||||
|
|
||||||
# opj_compress doesn't allow either dimension to be less than 4.
|
# opj_compress doesn't allow either dimension to be less than 4.
|
||||||
with self.assertRaises(IOError) as cr:
|
with self.assertRaises(IOError):
|
||||||
j.write(data, cbsize=(2048, 2))
|
j.write(data, cbsize=(2048, 2))
|
||||||
with self.assertRaises(IOError) as cr:
|
with self.assertRaises(IOError):
|
||||||
j.write(data, cbsize=(2, 2048))
|
j.write(data, cbsize=(2, 2048))
|
||||||
|
|
||||||
@unittest.skipIf(sys.hexversion < 0x03020000,
|
@unittest.skipIf(sys.hexversion < 0x03020000,
|
||||||
"Uses features introduced in 3.2.")
|
"Uses features introduced in 3.2.")
|
||||||
def test_exceeded_box(self):
|
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
|
# 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
|
# This file has a palette (pclr) box whose length is impossibly
|
||||||
# short.
|
# short.
|
||||||
infile = os.path.join(data_root,
|
infile = os.path.join(OPJ_DATA_ROOT,
|
||||||
'input/nonregression/mem-b2ace68c-1381.jp2')
|
'input/nonregression/mem-b2ace68c-1381.jp2')
|
||||||
with self.assertWarns(UserWarning) as cw:
|
with self.assertWarns(UserWarning):
|
||||||
j = Jp2k(infile)
|
Jp2k(infile)
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
def test_precinct_size_not_multiple_of_two(self):
|
def test_precinct_size_not_p2(self):
|
||||||
# Seems like precinct sizes should be powers of two.
|
"""precinct sizes should be powers of two."""
|
||||||
ifile = Jp2k(self.j2kfile)
|
ifile = Jp2k(self.j2kfile)
|
||||||
data = ifile.read(rlevel=2)
|
data = ifile.read(rlevel=2)
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
ofile = Jp2k(tfile.name, 'wb')
|
ofile = Jp2k(tfile.name, 'wb')
|
||||||
with self.assertRaises(IOError) as ce:
|
with self.assertRaises(IOError):
|
||||||
ofile.write(data, psizes=[(13, 13)])
|
ofile.write(data, psizes=[(13, 13)])
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
def test_codeblock_size_not_multiple_of_two(self):
|
def test_cblk_size_not_power_of_two(self):
|
||||||
# Seems like code block sizes should be powers of two.
|
"""code block sizes should be powers of two."""
|
||||||
ifile = Jp2k(self.j2kfile)
|
ifile = Jp2k(self.j2kfile)
|
||||||
data = ifile.read(rlevel=2)
|
data = ifile.read(rlevel=2)
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
ofile = Jp2k(tfile.name, 'wb')
|
ofile = Jp2k(tfile.name, 'wb')
|
||||||
with self.assertRaises(IOError) as ce:
|
with self.assertRaises(IOError):
|
||||||
ofile.write(data, cbsize=(13, 12))
|
ofile.write(data, cbsize=(13, 12))
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
def test_codeblock_size_with_precinct_size(self):
|
def test_cblk_size_precinct_size(self):
|
||||||
# Seems like code block sizes should never exceed half that of
|
"""code block sizes should never exceed half that of precinct size."""
|
||||||
# precinct size.
|
|
||||||
ifile = Jp2k(self.j2kfile)
|
ifile = Jp2k(self.j2kfile)
|
||||||
data = ifile.read(rlevel=2)
|
data = ifile.read(rlevel=2)
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
ofile = Jp2k(tfile.name, 'wb')
|
ofile = Jp2k(tfile.name, 'wb')
|
||||||
with self.assertRaises(IOError) as ce:
|
with self.assertRaises(IOError):
|
||||||
ofile.write(data,
|
ofile.write(data,
|
||||||
cbsize=(64, 64),
|
cbsize=(64, 64),
|
||||||
psizes=[(64, 64)])
|
psizes=[(64, 64)])
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -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 os
|
||||||
import pkg_resources
|
import re
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import warnings
|
import warnings
|
||||||
|
from xml.etree import cElementTree as ET
|
||||||
|
|
||||||
if sys.hexversion < 0x02070000:
|
if sys.hexversion < 0x02070000:
|
||||||
import unittest2 as unittest
|
import unittest2 as unittest
|
||||||
|
|
@ -23,18 +35,12 @@ else:
|
||||||
|
|
||||||
import glymur
|
import glymur
|
||||||
from glymur import Jp2k
|
from glymur import Jp2k
|
||||||
|
from .fixtures import OPJ_DATA_ROOT, opj_data_file
|
||||||
try:
|
|
||||||
data_root = os.environ['OPJ_DATA_ROOT']
|
|
||||||
except KeyError:
|
|
||||||
data_root = None
|
|
||||||
except:
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
|
@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version),
|
||||||
"Missing openjp2 library.")
|
"Need at least 1.5 in order to write jp2 files.")
|
||||||
class TestPrintingNeedsLib(unittest.TestCase):
|
class TestPrintingNeedsLib(unittest.TestCase):
|
||||||
"""These tests require the library, mostly in order to just setup the test.
|
"""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), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), '
|
+ '(0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), '
|
||||||
+ '(0, 10)]']
|
+ '(0, 10)]']
|
||||||
self.expectedPlain = '\n'.join(lines)
|
self.expected_plain = '\n'.join(lines)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_asoc_label_box(self):
|
def test_asoc_label_box(self):
|
||||||
|
"""verify printing of asoc, label boxes"""
|
||||||
# Construct a fake file with an asoc and a label box, as
|
# Construct a fake file with an asoc and a label box, as
|
||||||
# OpenJPEG doesn't have such a file.
|
# OpenJPEG doesn't have such a file.
|
||||||
data = glymur.Jp2k(self.jp2file).read(rlevel=1)
|
data = glymur.Jp2k(self.jp2file).read(rlevel=1)
|
||||||
|
|
@ -137,30 +144,30 @@ class TestPrintingNeedsLib(unittest.TestCase):
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2:
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2:
|
||||||
|
|
||||||
# Offset of the codestream is where we start.
|
# Offset of the codestream is where we start.
|
||||||
buffer = tfile.read(77)
|
wbuffer = tfile.read(77)
|
||||||
tfile2.write(buffer)
|
tfile2.write(wbuffer)
|
||||||
|
|
||||||
# read the rest of the file, it's the codestream.
|
# read the rest of the file, it's the codestream.
|
||||||
codestream = tfile.read()
|
codestream = tfile.read()
|
||||||
|
|
||||||
# Write the asoc superbox.
|
# Write the asoc superbox.
|
||||||
# Length = 36, id is 'asoc'.
|
# Length = 36, id is 'asoc'.
|
||||||
buffer = struct.pack('>I4s', int(56), b'asoc')
|
wbuffer = struct.pack('>I4s', int(56), b'asoc')
|
||||||
tfile2.write(buffer)
|
tfile2.write(wbuffer)
|
||||||
|
|
||||||
# Write the contained label box
|
# Write the contained label box
|
||||||
buffer = struct.pack('>I4s', int(13), b'lbl ')
|
wbuffer = struct.pack('>I4s', int(13), b'lbl ')
|
||||||
tfile2.write(buffer)
|
tfile2.write(wbuffer)
|
||||||
tfile2.write('label'.encode())
|
tfile2.write('label'.encode())
|
||||||
|
|
||||||
# Write the xml box
|
# Write the xml box
|
||||||
# Length = 36, id is 'xml '.
|
# Length = 36, id is 'xml '.
|
||||||
buffer = struct.pack('>I4s', int(35), b'xml ')
|
wbuffer = struct.pack('>I4s', int(35), b'xml ')
|
||||||
tfile2.write(buffer)
|
tfile2.write(wbuffer)
|
||||||
|
|
||||||
buffer = '<test>this is a test</test>'
|
wbuffer = '<test>this is a test</test>'
|
||||||
buffer = buffer.encode()
|
wbuffer = wbuffer.encode()
|
||||||
tfile2.write(buffer)
|
tfile2.write(wbuffer)
|
||||||
|
|
||||||
# Now append the codestream.
|
# Now append the codestream.
|
||||||
tfile2.write(codestream)
|
tfile2.write(codestream)
|
||||||
|
|
@ -179,6 +186,7 @@ class TestPrintingNeedsLib(unittest.TestCase):
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
def test_jp2dump(self):
|
def test_jp2dump(self):
|
||||||
|
"""basic jp2dump test"""
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
glymur.jp2dump(self._plain_nemo_file)
|
glymur.jp2dump(self._plain_nemo_file)
|
||||||
actual = fake_out.getvalue().strip()
|
actual = fake_out.getvalue().strip()
|
||||||
|
|
@ -187,9 +195,10 @@ class TestPrintingNeedsLib(unittest.TestCase):
|
||||||
lst = actual.split('\n')
|
lst = actual.split('\n')
|
||||||
lst = lst[1:]
|
lst = lst[1:]
|
||||||
actual = '\n'.join(lst)
|
actual = '\n'.join(lst)
|
||||||
self.assertEqual(actual, self.expectedPlain)
|
self.assertEqual(actual, self.expected_plain)
|
||||||
|
|
||||||
def test_entire_file(self):
|
def test_entire_file(self):
|
||||||
|
"""verify output from printing entire file"""
|
||||||
j = glymur.Jp2k(self._plain_nemo_file)
|
j = glymur.Jp2k(self._plain_nemo_file)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
print(j)
|
print(j)
|
||||||
|
|
@ -200,10 +209,11 @@ class TestPrintingNeedsLib(unittest.TestCase):
|
||||||
lst = lst[1:]
|
lst = lst[1:]
|
||||||
actual = '\n'.join(lst)
|
actual = '\n'.join(lst)
|
||||||
|
|
||||||
self.assertEqual(actual, self.expectedPlain)
|
self.assertEqual(actual, self.expected_plain)
|
||||||
|
|
||||||
|
|
||||||
class TestPrinting(unittest.TestCase):
|
class TestPrinting(unittest.TestCase):
|
||||||
|
"""Test suite for printing where the libraries are not needed"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Save sys.stdout.
|
# Save sys.stdout.
|
||||||
|
|
@ -212,7 +222,8 @@ class TestPrinting(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_COC_segment(self):
|
def test_coc_segment(self):
|
||||||
|
"""verify printing of COC segment"""
|
||||||
j = glymur.Jp2k(self.jp2file)
|
j = glymur.Jp2k(self.jp2file)
|
||||||
codestream = j.get_codestream(header_only=False)
|
codestream = j.get_codestream(header_only=False)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
|
@ -239,7 +250,8 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
def test_COD_segment(self):
|
def test_cod_segment(self):
|
||||||
|
"""verify printing of COD segment"""
|
||||||
j = glymur.Jp2k(self.jp2file)
|
j = glymur.Jp2k(self.jp2file)
|
||||||
codestream = j.get_codestream()
|
codestream = j.get_codestream()
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
|
@ -270,14 +282,13 @@ class TestPrinting(unittest.TestCase):
|
||||||
' Segmentation symbols: False']
|
' Segmentation symbols: False']
|
||||||
|
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.actual = actual
|
|
||||||
self.expected = expected
|
|
||||||
self.assertEqual(actual, 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")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_icc_profile(self):
|
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():
|
with warnings.catch_warnings():
|
||||||
# brand is 'jp2 ', but has any icc profile.
|
# brand is 'jp2 ', but has any icc profile.
|
||||||
warnings.simplefilter("ignore")
|
warnings.simplefilter("ignore")
|
||||||
|
|
@ -342,24 +353,26 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, 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")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_CRG(self):
|
def test_crg(self):
|
||||||
filename = os.path.join(data_root, 'input/conformance/p0_03.j2k')
|
"""verify printing of CRG segment"""
|
||||||
|
filename = opj_data_file('input/conformance/p0_03.j2k')
|
||||||
j = glymur.Jp2k(filename)
|
j = glymur.Jp2k(filename)
|
||||||
codestream = j.get_codestream()
|
codestream = j.get_codestream()
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
print(codestream.segment[-5])
|
print(codestream.segment[-5])
|
||||||
actual = fake_out.getvalue().strip()
|
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)']
|
' Vertical, Horizontal offset: (0.50, 1.00)']
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, 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")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_RGN(self):
|
def test_rgn(self):
|
||||||
filename = os.path.join(data_root, 'input/conformance/p0_03.j2k')
|
"""verify printing of RGN segment"""
|
||||||
|
filename = opj_data_file('input/conformance/p0_03.j2k')
|
||||||
j = glymur.Jp2k(filename)
|
j = glymur.Jp2k(filename)
|
||||||
codestream = j.get_codestream(header_only=False)
|
codestream = j.get_codestream(header_only=False)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
|
@ -372,10 +385,11 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, 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")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_SOP(self):
|
def test_sop(self):
|
||||||
filename = os.path.join(data_root, 'input/conformance/p0_03.j2k')
|
"""verify printing of SOP segment"""
|
||||||
|
filename = opj_data_file('input/conformance/p0_03.j2k')
|
||||||
j = glymur.Jp2k(filename)
|
j = glymur.Jp2k(filename)
|
||||||
codestream = j.get_codestream(header_only=False)
|
codestream = j.get_codestream(header_only=False)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
|
@ -386,11 +400,11 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, 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")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_CME(self):
|
def test_cme(self):
|
||||||
# Test printing a CME or comment marker segment.
|
"""Test printing a CME or comment marker segment."""
|
||||||
filename = os.path.join(data_root, 'input/conformance/p0_02.j2k')
|
filename = opj_data_file('input/conformance/p0_02.j2k')
|
||||||
j = glymur.Jp2k(filename)
|
j = glymur.Jp2k(filename)
|
||||||
codestream = j.get_codestream()
|
codestream = j.get_codestream()
|
||||||
# 2nd to last segment in the main header
|
# 2nd to last segment in the main header
|
||||||
|
|
@ -402,7 +416,8 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
def test_EOC_segment(self):
|
def test_eoc_segment(self):
|
||||||
|
"""verify printing of eoc segment"""
|
||||||
j = glymur.Jp2k(self.jp2file)
|
j = glymur.Jp2k(self.jp2file)
|
||||||
codestream = j.get_codestream(header_only=False)
|
codestream = j.get_codestream(header_only=False)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
|
@ -413,10 +428,11 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, 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")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_PLT_segment(self):
|
def test_plt_segment(self):
|
||||||
filename = os.path.join(data_root, 'input/conformance/p0_07.j2k')
|
"""verify printing of PLT segment"""
|
||||||
|
filename = opj_data_file('input/conformance/p0_07.j2k')
|
||||||
j = glymur.Jp2k(filename)
|
j = glymur.Jp2k(filename)
|
||||||
codestream = j.get_codestream(header_only=False)
|
codestream = j.get_codestream(header_only=False)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
|
@ -431,10 +447,11 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, 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")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_POD_segment(self):
|
def test_pod_segment(self):
|
||||||
filename = os.path.join(data_root, 'input/conformance/p0_13.j2k')
|
"""verify printing of POD segment"""
|
||||||
|
filename = opj_data_file('input/conformance/p0_13.j2k')
|
||||||
j = glymur.Jp2k(filename)
|
j = glymur.Jp2k(filename)
|
||||||
codestream = j.get_codestream()
|
codestream = j.get_codestream()
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
|
@ -460,10 +477,11 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, 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")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_PPM_segment(self):
|
def test_ppm_segment(self):
|
||||||
filename = os.path.join(data_root, 'input/conformance/p1_03.j2k')
|
"""verify printing of PPM segment"""
|
||||||
|
filename = opj_data_file('input/conformance/p1_03.j2k')
|
||||||
j = glymur.Jp2k(filename)
|
j = glymur.Jp2k(filename)
|
||||||
codestream = j.get_codestream()
|
codestream = j.get_codestream()
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
|
@ -477,10 +495,11 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, 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")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_PPT_segment(self):
|
def test_ppt_segment(self):
|
||||||
filename = os.path.join(data_root, 'input/conformance/p1_06.j2k')
|
"""verify printing of ppt segment"""
|
||||||
|
filename = opj_data_file('input/conformance/p1_06.j2k')
|
||||||
j = glymur.Jp2k(filename)
|
j = glymur.Jp2k(filename)
|
||||||
codestream = j.get_codestream(header_only=False)
|
codestream = j.get_codestream(header_only=False)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
|
@ -494,7 +513,8 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
def test_QCC_segment(self):
|
def test_qcc_segment(self):
|
||||||
|
"""verify printing of qcc segment"""
|
||||||
j = glymur.Jp2k(self.jp2file)
|
j = glymur.Jp2k(self.jp2file)
|
||||||
codestream = j.get_codestream(header_only=False)
|
codestream = j.get_codestream(header_only=False)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
|
@ -509,7 +529,8 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, expected)
|
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)
|
j = glymur.Jp2k(self.jp2file)
|
||||||
codestream = j.get_codestream()
|
codestream = j.get_codestream()
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
|
@ -523,7 +544,8 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
def test_SIZ_segment(self):
|
def test_siz_segment(self):
|
||||||
|
"""verify printing of SIZ segment"""
|
||||||
j = glymur.Jp2k(self.jp2file)
|
j = glymur.Jp2k(self.jp2file)
|
||||||
codestream = j.get_codestream()
|
codestream = j.get_codestream()
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
|
@ -544,7 +566,8 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
def test_SOC_segment(self):
|
def test_soc_segment(self):
|
||||||
|
"""verify printing of SOC segment"""
|
||||||
j = glymur.Jp2k(self.jp2file)
|
j = glymur.Jp2k(self.jp2file)
|
||||||
codestream = j.get_codestream()
|
codestream = j.get_codestream()
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
|
@ -555,7 +578,8 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
def test_SOD_segment(self):
|
def test_sod_segment(self):
|
||||||
|
"""verify printing of SOD segment"""
|
||||||
j = glymur.Jp2k(self.jp2file)
|
j = glymur.Jp2k(self.jp2file)
|
||||||
codestream = j.get_codestream(header_only=False)
|
codestream = j.get_codestream(header_only=False)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
|
@ -566,7 +590,8 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
def test_SOT_segment(self):
|
def test_sot_segment(self):
|
||||||
|
"""verify printing of SOT segment"""
|
||||||
j = glymur.Jp2k(self.jp2file)
|
j = glymur.Jp2k(self.jp2file)
|
||||||
codestream = j.get_codestream(header_only=False)
|
codestream = j.get_codestream(header_only=False)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
|
@ -580,13 +605,13 @@ class TestPrinting(unittest.TestCase):
|
||||||
' Number of tile parts: 1']
|
' Number of tile parts: 1']
|
||||||
|
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.maxDiff = None
|
|
||||||
self.assertEqual(actual, 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")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_TLM_segment(self):
|
def test_tlm_segment(self):
|
||||||
filename = os.path.join(data_root, 'input/conformance/p0_15.j2k')
|
"""verify printing of TLM segment"""
|
||||||
|
filename = opj_data_file('input/conformance/p0_15.j2k')
|
||||||
j = glymur.Jp2k(filename)
|
j = glymur.Jp2k(filename)
|
||||||
codestream = j.get_codestream()
|
codestream = j.get_codestream()
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
|
@ -604,7 +629,7 @@ class TestPrinting(unittest.TestCase):
|
||||||
@unittest.skipIf(sys.hexversion < 0x02070000,
|
@unittest.skipIf(sys.hexversion < 0x02070000,
|
||||||
"Differences in XML printing between 2.6 and 2.7")
|
"Differences in XML printing between 2.6 and 2.7")
|
||||||
def test_xmp(self):
|
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)
|
j = glymur.Jp2k(self.jp2file)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
print(j.box[4])
|
print(j.box[4])
|
||||||
|
|
@ -626,6 +651,7 @@ class TestPrinting(unittest.TestCase):
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
def test_codestream(self):
|
def test_codestream(self):
|
||||||
|
"""verify printing of entire codestream"""
|
||||||
j = glymur.Jp2k(self.jp2file)
|
j = glymur.Jp2k(self.jp2file)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
print(j.get_codestream())
|
print(j.get_codestream())
|
||||||
|
|
@ -671,15 +697,15 @@ class TestPrinting(unittest.TestCase):
|
||||||
' CME marker segment @ (3209, 37)',
|
' CME marker segment @ (3209, 37)',
|
||||||
' "Created by OpenJPEG version 2.0.0"']
|
' "Created by OpenJPEG version 2.0.0"']
|
||||||
expected = '\n'.join(lst)
|
expected = '\n'.join(lst)
|
||||||
self.maxDiff = None
|
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
@unittest.skipIf(sys.hexversion < 0x02070000,
|
@unittest.skipIf(sys.hexversion < 0x02070000,
|
||||||
"Differences in XML printing between 2.6 and 2.7")
|
"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")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_xml(self):
|
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)
|
j = glymur.Jp2k(filename)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
print(j.box[2])
|
print(j.box[2])
|
||||||
|
|
@ -706,10 +732,70 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, expected)
|
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>Россия</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")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_channel_definition(self):
|
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)
|
j = glymur.Jp2k(filename)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
print(j.box[2].box[2])
|
print(j.box[2].box[2])
|
||||||
|
|
@ -721,10 +807,11 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, 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")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_component_mapping(self):
|
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)
|
j = glymur.Jp2k(filename)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
print(j.box[2].box[2])
|
print(j.box[2].box[2])
|
||||||
|
|
@ -736,10 +823,11 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, 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")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_palette(self):
|
def test_palette7(self):
|
||||||
filename = os.path.join(data_root, 'input/conformance/file9.jp2')
|
"""verify printing of pclr box"""
|
||||||
|
filename = opj_data_file('input/conformance/file9.jp2')
|
||||||
j = glymur.Jp2k(filename)
|
j = glymur.Jp2k(filename)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
print(j.box[2].box[1])
|
print(j.box[2].box[1])
|
||||||
|
|
@ -749,10 +837,11 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, 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")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_palette(self):
|
def test_rreq(self):
|
||||||
filename = os.path.join(data_root, 'input/conformance/file7.jp2')
|
"""verify printing of reader requirements box"""
|
||||||
|
filename = opj_data_file('input/conformance/file7.jp2')
|
||||||
j = glymur.Jp2k(filename)
|
j = glymur.Jp2k(filename)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
print(j.box[2])
|
print(j.box[2])
|
||||||
|
|
@ -772,25 +861,11 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, 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_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,
|
|
||||||
"OPJ_DATA_ROOT environment variable not set")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_differing_subsamples(self):
|
def test_differing_subsamples(self):
|
||||||
# Issue 86.
|
"""verify printing of SIZ with different subsampling... Issue 86."""
|
||||||
filename = os.path.join(data_root, 'input/conformance/p0_05.j2k')
|
filename = opj_data_file('input/conformance/p0_05.j2k')
|
||||||
j = glymur.Jp2k(filename)
|
j = glymur.Jp2k(filename)
|
||||||
codestream = j.get_codestream()
|
codestream = j.get_codestream()
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
|
@ -809,11 +884,11 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, 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")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_palette_box(self):
|
def test_palette_box(self):
|
||||||
# Verify that palette (pclr) boxes are printed without error.
|
"""Verify that palette (pclr) boxes are printed without error."""
|
||||||
filename = os.path.join(data_root, 'input/conformance/file9.jp2')
|
filename = opj_data_file('input/conformance/file9.jp2')
|
||||||
j = glymur.Jp2k(filename)
|
j = glymur.Jp2k(filename)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
print(j.box[2].box[1])
|
print(j.box[2].box[1])
|
||||||
|
|
@ -825,54 +900,56 @@ class TestPrinting(unittest.TestCase):
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
def test_less_common_boxes(self):
|
def test_less_common_boxes(self):
|
||||||
|
"""verify uinf, ulst, url, res, resd, resc box printing"""
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
with open(self.jp2file, 'rb') as ifile:
|
with open(self.jp2file, 'rb') as ifile:
|
||||||
# Everything up until the jp2c box.
|
# Everything up until the jp2c box.
|
||||||
buffer = ifile.read(77)
|
wbuffer = ifile.read(77)
|
||||||
tfile.write(buffer)
|
tfile.write(wbuffer)
|
||||||
|
|
||||||
# Write the UINF superbox
|
# Write the UINF superbox
|
||||||
# Length = 50, id is uinf.
|
# Length = 50, id is uinf.
|
||||||
buffer = struct.pack('>I4s', int(50), b'uinf')
|
wbuffer = struct.pack('>I4s', int(50), b'uinf')
|
||||||
tfile.write(buffer)
|
tfile.write(wbuffer)
|
||||||
|
|
||||||
# Write the ULST box.
|
# Write the ULST box.
|
||||||
# Length is 26, 1 UUID, hard code that UUID as zeros.
|
# Length is 26, 1 UUID, hard code that UUID as zeros.
|
||||||
buffer = struct.pack('>I4sHIIII', int(26), b'ulst', int(1),
|
wbuffer = struct.pack('>I4sHIIII', int(26), b'ulst', int(1),
|
||||||
int(0), int(0), int(0), int(0))
|
int(0), int(0), int(0), int(0))
|
||||||
tfile.write(buffer)
|
tfile.write(wbuffer)
|
||||||
|
|
||||||
# Write the URL box.
|
# Write the URL box.
|
||||||
# Length is 16, version is one byte, flag is 3 bytes, url
|
# Length is 16, version is one byte, flag is 3 bytes, url
|
||||||
# is the rest.
|
# is the rest.
|
||||||
buffer = struct.pack('>I4sBBBB',
|
wbuffer = struct.pack('>I4sBBBB',
|
||||||
int(16), b'url ',
|
int(16), b'url ',
|
||||||
int(0), int(0), int(0), int(0))
|
int(0), int(0), int(0), int(0))
|
||||||
tfile.write(buffer)
|
tfile.write(wbuffer)
|
||||||
buffer = struct.pack('>ssss', b'a', b'b', b'c', b'd')
|
|
||||||
tfile.write(buffer)
|
wbuffer = struct.pack('>ssss', b'a', b'b', b'c', b'd')
|
||||||
|
tfile.write(wbuffer)
|
||||||
|
|
||||||
# Start the resolution superbox.
|
# Start the resolution superbox.
|
||||||
buffer = struct.pack('>I4s', int(44), b'res ')
|
wbuffer = struct.pack('>I4s', int(44), b'res ')
|
||||||
tfile.write(buffer)
|
tfile.write(wbuffer)
|
||||||
|
|
||||||
# Write the capture resolution box.
|
# Write the capture resolution box.
|
||||||
buffer = struct.pack('>I4sHHHHBB',
|
wbuffer = struct.pack('>I4sHHHHBB',
|
||||||
int(18), b'resc',
|
int(18), b'resc',
|
||||||
int(1), int(1), int(1), int(1),
|
int(1), int(1), int(1), int(1),
|
||||||
int(0), int(1))
|
int(0), int(1))
|
||||||
tfile.write(buffer)
|
tfile.write(wbuffer)
|
||||||
|
|
||||||
# Write the display resolution box.
|
# Write the display resolution box.
|
||||||
buffer = struct.pack('>I4sHHHHBB',
|
wbuffer = struct.pack('>I4sHHHHBB',
|
||||||
int(18), b'resd',
|
int(18), b'resd',
|
||||||
int(1), int(1), int(1), int(1),
|
int(1), int(1), int(1), int(1),
|
||||||
int(1), int(0))
|
int(1), int(0))
|
||||||
tfile.write(buffer)
|
tfile.write(wbuffer)
|
||||||
|
|
||||||
# Get the rest of the input file.
|
# Get the rest of the input file.
|
||||||
buffer = ifile.read()
|
wbuffer = ifile.read()
|
||||||
tfile.write(buffer)
|
tfile.write(wbuffer)
|
||||||
tfile.flush()
|
tfile.flush()
|
||||||
|
|
||||||
jp2k = glymur.Jp2k(tfile.name)
|
jp2k = glymur.Jp2k(tfile.name)
|
||||||
|
|
@ -900,12 +977,13 @@ class TestPrinting(unittest.TestCase):
|
||||||
|
|
||||||
@unittest.skipIf(sys.hexversion < 0x03000000,
|
@unittest.skipIf(sys.hexversion < 0x03000000,
|
||||||
"Ordered dicts not printing well in 2.7")
|
"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")
|
"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
|
# ICC profiles may be used in JP2, but the approximation field should
|
||||||
# be zero unless we have jpx. This file does both.
|
# 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():
|
with warnings.catch_warnings():
|
||||||
# brand is 'jp2 ', but has any icc profile.
|
# brand is 'jp2 ', but has any icc profile.
|
||||||
warnings.simplefilter("ignore")
|
warnings.simplefilter("ignore")
|
||||||
|
|
@ -944,11 +1022,11 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, 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")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_uuid(self):
|
def test_uuid(self):
|
||||||
# UUID box
|
"""verify printing of UUID box"""
|
||||||
filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2')
|
filename = opj_data_file('input/nonregression/text_GBR.jp2')
|
||||||
with warnings.catch_warnings():
|
with warnings.catch_warnings():
|
||||||
# brand is 'jp2 ', but has any icc profile.
|
# brand is 'jp2 ', but has any icc profile.
|
||||||
warnings.simplefilter("ignore")
|
warnings.simplefilter("ignore")
|
||||||
|
|
@ -967,6 +1045,7 @@ class TestPrinting(unittest.TestCase):
|
||||||
@unittest.skipIf(sys.hexversion < 0x03000000,
|
@unittest.skipIf(sys.hexversion < 0x03000000,
|
||||||
"Ordered dicts not printing well in 2.7")
|
"Ordered dicts not printing well in 2.7")
|
||||||
def test_exif_uuid(self):
|
def test_exif_uuid(self):
|
||||||
|
"""Verify printing of exif information"""
|
||||||
j = glymur.Jp2k(self.jp2file)
|
j = glymur.Jp2k(self.jp2file)
|
||||||
|
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
|
@ -1026,5 +1105,6 @@ class TestPrinting(unittest.TestCase):
|
||||||
|
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
55
glymur/version.py
Normal file
55
glymur/version.py
Normal 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__)
|
||||||
59
release.txt
59
release.txt
|
|
@ -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.
|
|
||||||
|
|
||||||
16
setup.py
16
setup.py
|
|
@ -1,8 +1,9 @@
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
import os
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
kwargs = {'name': 'Glymur',
|
kwargs = {'name': 'Glymur',
|
||||||
'version': '0.3.0',
|
|
||||||
'description': 'Tools for accessing JPEG2000 files',
|
'description': 'Tools for accessing JPEG2000 files',
|
||||||
'long_description': open('README.md').read(),
|
'long_description': open('README.md').read(),
|
||||||
'author': 'John Evans',
|
'author': 'John Evans',
|
||||||
|
|
@ -12,9 +13,8 @@ kwargs = {'name': 'Glymur',
|
||||||
'glymur.lib.test'],
|
'glymur.lib.test'],
|
||||||
'package_data': {'glymur': ['data/*.jp2', 'data/*.j2k']},
|
'package_data': {'glymur': ['data/*.jp2', 'data/*.j2k']},
|
||||||
'scripts': ['bin/jp2dump'],
|
'scripts': ['bin/jp2dump'],
|
||||||
'license': 'LICENSE.txt',
|
'license': 'MIT',
|
||||||
'test_suite': 'glymur.test',
|
'test_suite': 'glymur.test'}
|
||||||
'platforms': ['darwin']}
|
|
||||||
|
|
||||||
instllrqrs = ['numpy>=1.4.1']
|
instllrqrs = ['numpy>=1.4.1']
|
||||||
if sys.hexversion < 0x03030000:
|
if sys.hexversion < 0x03030000:
|
||||||
|
|
@ -39,4 +39,12 @@ clssfrs = ["Programming Language :: Python",
|
||||||
"Intended Audience :: Information Technology",
|
"Intended Audience :: Information Technology",
|
||||||
"Topic :: Software Development :: Libraries :: Python Modules"]
|
"Topic :: Software Development :: Libraries :: Python Modules"]
|
||||||
kwargs['classifiers'] = clssfrs
|
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)
|
setup(**kwargs)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue