Merge branch 'issue138'
This commit is contained in:
commit
170f99ce3f
17 changed files with 738 additions and 864 deletions
15
.travis.yml
15
.travis.yml
|
|
@ -1,19 +1,26 @@
|
||||||
language: python
|
language: python
|
||||||
python:
|
python:
|
||||||
|
- "2.6"
|
||||||
- "2.7"
|
- "2.7"
|
||||||
- "3.3"
|
- "3.3"
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- sudo apt-get update -qq
|
- sudo apt-get update -qq
|
||||||
- sudo apt-get install -qq libopenjpeg2
|
|
||||||
- sudo apt-get install -qq python-numpy
|
- sudo apt-get install -qq python-numpy
|
||||||
- sudo apt-get install -qq python3-numpy
|
- wget http://openjpeg.googlecode.com/files/openjpeg-1.5.0-Linux-x86_64.tar.gz
|
||||||
|
- sudo tar -xvf openjpeg-1.5.0-Linux-x86_64.tar.gz --strip-components=1 -C /
|
||||||
|
|
||||||
# command to install dependencies
|
# command to install dependencies
|
||||||
install: "pip install -r travis-requirements.txt --use-mirrors"
|
install:
|
||||||
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install --use-mirrors contextlib2 mock ordereddict unittest2; fi
|
||||||
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors contextlib2 mock; fi
|
||||||
|
- if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install --use-mirrors numpy; fi
|
||||||
|
|
||||||
# command to run tests
|
# command to run tests
|
||||||
script: "python -m unittest discover"
|
script:
|
||||||
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then unit2 discover; fi
|
||||||
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then python -m unittest discover; fi
|
||||||
|
- if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then python -m unittest discover; fi
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
email: "john.g.evans.ne@gmail.com"
|
email: "john.g.evans.ne@gmail.com"
|
||||||
|
|
|
||||||
10
CHANGES.txt
10
CHANGES.txt
|
|
@ -1,9 +1,17 @@
|
||||||
|
Oct 28, 2013 - v0.5.7 Fixed bad import error message when libopenjpeg library
|
||||||
|
not installed on mac.
|
||||||
|
|
||||||
|
Oct 13, 2013 - v0.5.6 Fixed handling of non-ascii chars in XML boxes. Fixed
|
||||||
|
some docstring errors in jp2box module.
|
||||||
|
|
||||||
|
Oct 03, 2013 - v0.5.5 Fixed pip install error introduced in 0.5.0.
|
||||||
|
|
||||||
Sep 24, 2013 - v0.5.4 Fixed test error restricted to v2.0.
|
Sep 24, 2013 - v0.5.4 Fixed test error restricted to v2.0.
|
||||||
|
|
||||||
Sep 24, 2013 - v0.5.3 Removed a duplicated channel definition test in
|
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.
|
test_jp2box that could cause a segfault in 1.3 if not properly skipped.
|
||||||
|
|
||||||
Sep 23, 2013 - v0.5.2 Fixed some teests that have been failing since 0.5.
|
Sep 23, 2013 - v0.5.2 Fixed some tests that have been failing since 0.5.
|
||||||
under various edge cases.
|
under various edge cases.
|
||||||
|
|
||||||
Sep 19, 2013 - v0.5.1 Added more resiliency to XML box parsing. Fixed tests
|
Sep 19, 2013 - v0.5.1 Added more resiliency to XML box parsing. Fixed tests
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ copyright = u'2013, John Evans'
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '0.5'
|
version = '0.5'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '0.5.4'
|
release = '0.5.7'
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,13 @@ 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. If you have version 1.5 you can
|
being properly installed on your system. If you have version 1.5 you can
|
||||||
both read and write JPEG 2000 files, but you may wish to install version 2.0
|
both read and write JPEG 2000 files, but you may wish to install version 2.0
|
||||||
or the 2.0+ version from OpenJPEG's development trunk for better performance.
|
or the 2.0+ version from OpenJPEG's development trunk for better performance.
|
||||||
If you do that, you should compile it as a shared library (named *openjp2*
|
If you do that, you should compile it as a shared library (named *openjp2*
|
||||||
instead of *openjpeg*) from the developmental source that you can retrieve
|
instead of *openjpeg*) from the developmental source that you can retrieve
|
||||||
via subversion. As of this time of writing, svn revision 2345 works.
|
via subversion. As of this time of writing, svn revision 2347 works.
|
||||||
You should also download the test data for the purpose of configuring
|
You should also download the test data for the purpose of configuring
|
||||||
and running OpenJPEG's test suite, check their instructions for all this.
|
and running OpenJPEG's test suite, check their instructions for all this.
|
||||||
You should set the **OPJ_DATA_ROOT** environment variable for the purpose
|
You should set the **OPJ_DATA_ROOT** environment variable for the purpose
|
||||||
|
|
@ -25,16 +25,17 @@ of running Glymur's test suite. ::
|
||||||
Glymur uses ctypes to access the openjp2/openjpeg libraries,
|
Glymur uses ctypes to access the openjp2/openjpeg libraries,
|
||||||
and because ctypes accesses 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 openjpeg or openjp2 libraries (linux users don't need to bother if you are
|
the openjpeg or openjp2 libraries (linux users don't need to bother with this
|
||||||
using OpenJPEG as provided by your package manager). The configuration
|
if you are using OpenJPEG as provided by your package manager). The
|
||||||
format is the same as used by Python's configparser module, i.e. ::
|
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
|
||||||
|
|
@ -70,7 +71,8 @@ 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 2.6, 2.7,
|
All the necessary packages are available to use glymur with Python 2.6, 2.7,
|
||||||
and 3.3 via MacPorts. You should install the following set of ports:
|
and 3.3 via MacPorts. For python 3.3, you should install the following set of
|
||||||
|
ports:
|
||||||
|
|
||||||
* python33
|
* python33
|
||||||
* py33-numpy
|
* py33-numpy
|
||||||
|
|
|
||||||
|
|
@ -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,7 +34,7 @@ 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).
|
You can append any number of XML boxes to a JP2 file (not to a raw codestream).
|
||||||
Consider the following XML file `data.xml` : ::
|
Consider the following XML file `data.xml` : ::
|
||||||
|
|
@ -66,7 +66,7 @@ The **append** method can add an XML box as shown below::
|
||||||
>>> jp2.append(xmlbox)
|
>>> jp2.append(xmlbox)
|
||||||
>>> print(jp2)
|
>>> print(jp2)
|
||||||
|
|
||||||
add metadata in a more general fashion?
|
... add metadata in a more general fashion?
|
||||||
=======================================
|
=======================================
|
||||||
An existing raw codestream (or JP2 file) can be wrapped (re-wrapped) in a
|
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
|
||||||
|
|
@ -157,18 +157,18 @@ to add metadata, you should keep in mind that **wrap** produces a new JP2 file,
|
||||||
while **append** modifies an existing file and is currently limited to XML
|
while **append** modifies an existing file and is currently limited to XML
|
||||||
boxes.
|
boxes.
|
||||||
|
|
||||||
create an image with an alpha layer?
|
... create an image with an alpha layer?
|
||||||
====================================
|
====================================
|
||||||
|
|
||||||
OpenJPEG can create JP2 files with more than 3 components (requires
|
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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -24,11 +23,10 @@ only supported with the 1.5 or better, however, and the trunk/development
|
||||||
version is strongly recommended. For more information about OpenJPEG,
|
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,13 +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, but the The important thing is whether or
|
|
||||||
not any tests fail.
|
|
||||||
|
|
|
||||||
|
|
@ -568,7 +568,7 @@ class CodestreamHeaderBox(Jp2kBox):
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
AssociationBox instance
|
CodestreamHeaderBox instance
|
||||||
"""
|
"""
|
||||||
box = CodestreamHeaderBox(length=length, offset=offset)
|
box = CodestreamHeaderBox(length=length, offset=offset)
|
||||||
|
|
||||||
|
|
@ -627,7 +627,7 @@ class CompositingLayerHeaderBox(Jp2kBox):
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
AssociationBox instance
|
CompositingLayerHeaderBox instance
|
||||||
"""
|
"""
|
||||||
box = CompositingLayerHeaderBox(length=length, offset=offset)
|
box = CompositingLayerHeaderBox(length=length, offset=offset)
|
||||||
|
|
||||||
|
|
@ -638,7 +638,7 @@ class CompositingLayerHeaderBox(Jp2kBox):
|
||||||
|
|
||||||
|
|
||||||
class ComponentMappingBox(Jp2kBox):
|
class ComponentMappingBox(Jp2kBox):
|
||||||
"""Container for channel identification information.
|
"""Container for component mapping information.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
|
|
@ -1637,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
|
||||||
----------
|
----------
|
||||||
|
|
@ -1694,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
|
||||||
----------
|
----------
|
||||||
|
|
@ -1870,10 +1870,8 @@ class XMLBox(Jp2kBox):
|
||||||
# Strip out any trailing nulls, as they can foul up XML parsing.
|
# Strip out any trailing nulls, as they can foul up XML parsing.
|
||||||
text = text.rstrip(chr(0))
|
text = text.rstrip(chr(0))
|
||||||
|
|
||||||
# Scan for the start of the xml declaration.
|
|
||||||
|
|
||||||
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:'
|
msg = 'A problem was encountered while parsing an XML box:'
|
||||||
|
|
@ -2047,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
|
||||||
----------
|
----------
|
||||||
|
|
@ -2749,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
|
||||||
|
|
|
||||||
|
|
@ -474,41 +474,37 @@ class Jp2k(Jp2kBox):
|
||||||
|
|
||||||
comptparms = _populate_comptparms(img_array, cparams)
|
comptparms = _populate_comptparms(img_array, cparams)
|
||||||
|
|
||||||
image = opj2.image_create(comptparms, colorspace)
|
with ExitStack() as stack:
|
||||||
_populate_image_struct(cparams, image, img_array)
|
image = opj2.image_create(comptparms, colorspace)
|
||||||
|
stack.callback(opj2.image_destroy, image)
|
||||||
codec = opj2.create_compress(cparams.codec_fmt)
|
|
||||||
|
|
||||||
info_handler = _INFO_CALLBACK if verbose else None
|
|
||||||
opj2.set_info_handler(codec, info_handler)
|
|
||||||
opj2.set_warning_handler(codec, _WARNING_CALLBACK)
|
|
||||||
opj2.set_error_handler(codec, _ERROR_CALLBACK)
|
|
||||||
|
|
||||||
opj2.setup_encoder(codec, cparams, image)
|
|
||||||
|
|
||||||
if _OPENJP2_IS_OFFICIAL_V2:
|
|
||||||
fptr = libc.fopen(self.filename, 'wb')
|
|
||||||
strm = opj2.stream_create_default_file_stream(fptr, False)
|
|
||||||
else:
|
|
||||||
# This routine introduced in 2.0 devel series.
|
|
||||||
strm = opj2.stream_create_default_file_stream_v3(self.filename,
|
|
||||||
False)
|
|
||||||
|
|
||||||
# Start to clean up after ourselves.
|
|
||||||
opj2.start_compress(codec, image, strm)
|
|
||||||
opj2.encode(codec, strm)
|
|
||||||
opj2.end_compress(codec, strm)
|
|
||||||
|
|
||||||
if _OPENJP2_IS_OFFICIAL_V2:
|
|
||||||
opj2.stream_destroy(strm)
|
|
||||||
libc.fclose(fptr)
|
|
||||||
else:
|
|
||||||
# This routine introduced in 2.0 devel series.
|
|
||||||
opj2.stream_destroy_v3(strm)
|
|
||||||
|
|
||||||
opj2.destroy_codec(codec)
|
|
||||||
opj2.image_destroy(image)
|
|
||||||
|
|
||||||
|
_populate_image_struct(cparams, image, img_array)
|
||||||
|
|
||||||
|
codec = opj2.create_compress(cparams.codec_fmt)
|
||||||
|
stack.callback(opj2.destroy_codec, codec)
|
||||||
|
|
||||||
|
info_handler = _INFO_CALLBACK if verbose else None
|
||||||
|
opj2.set_info_handler(codec, info_handler)
|
||||||
|
opj2.set_warning_handler(codec, _WARNING_CALLBACK)
|
||||||
|
opj2.set_error_handler(codec, _ERROR_CALLBACK)
|
||||||
|
|
||||||
|
opj2.setup_encoder(codec, cparams, image)
|
||||||
|
|
||||||
|
if _OPENJP2_IS_OFFICIAL_V2:
|
||||||
|
fptr = libc.fopen(self.filename, 'wb')
|
||||||
|
strm = opj2.stream_create_default_file_stream(fptr, False)
|
||||||
|
stack.callback(opj2.stream_destroy, strm)
|
||||||
|
stack.callback(libc.fclose, fptr)
|
||||||
|
else:
|
||||||
|
# This routine introduced in 2.0 devel series.
|
||||||
|
strm = opj2.stream_create_default_file_stream_v3(self.filename,
|
||||||
|
False)
|
||||||
|
stack.callback(opj2.stream_destroy_v3, strm)
|
||||||
|
|
||||||
|
opj2.start_compress(codec, image, strm)
|
||||||
|
opj2.encode(codec, strm)
|
||||||
|
opj2.end_compress(codec, strm)
|
||||||
|
|
||||||
# Refresh the metadata.
|
# Refresh the metadata.
|
||||||
self.parse()
|
self.parse()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,37 +43,62 @@ def glymurrc_fname():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def load_openjpeg(libopenjpeg_path):
|
def load_openjpeg(path):
|
||||||
"""Load the openjpeg library, falling back on defaults if necessary.
|
"""Load the openjpeg library, falling back on defaults if necessary.
|
||||||
"""
|
"""
|
||||||
if libopenjpeg_path is None:
|
if path is None:
|
||||||
# Let ctypes try to find it.
|
# Let ctypes try to find it.
|
||||||
libopenjpeg_path = find_library('openjpeg')
|
path = find_library('openjpeg')
|
||||||
|
|
||||||
# If we could not find it, then look in some likely locations.
|
# If we could not find it, then look in some likely locations on mac
|
||||||
if libopenjpeg_path is None:
|
# and win.
|
||||||
|
if path is None:
|
||||||
if platform.system() == 'Darwin':
|
if platform.system() == 'Darwin':
|
||||||
|
# MacPorts
|
||||||
path = '/opt/local/lib/libopenjpeg.dylib'
|
path = '/opt/local/lib/libopenjpeg.dylib'
|
||||||
if os.path.exists(path):
|
|
||||||
libopenjpeg_path = path
|
|
||||||
elif os.name == 'nt':
|
elif os.name == 'nt':
|
||||||
path = os.path.join('C:\\', 'Program files', 'OpenJPEG 1.5',
|
path = os.path.join('C:\\', 'Program files', 'OpenJPEG 1.5',
|
||||||
'bin', 'openjpeg.dll')
|
'bin', 'openjpeg.dll')
|
||||||
if os.path.exists(path):
|
|
||||||
libopenjpeg_path = path
|
return load_library_handle(path)
|
||||||
else:
|
|
||||||
# No sense trying further on Linux
|
|
||||||
return None
|
def load_openjp2(path):
|
||||||
|
"""Load the openjp2 library, falling back on defaults if necessary.
|
||||||
|
"""
|
||||||
|
if path is None:
|
||||||
|
# No help from the config file, try to find it ourselves.
|
||||||
|
path = find_library('openjp2')
|
||||||
|
|
||||||
|
if path is None:
|
||||||
|
if platform.system() == 'Darwin':
|
||||||
|
# MacPorts
|
||||||
|
path = '/opt/local/lib/libopenjp2.dylib'
|
||||||
|
elif os.name == 'nt':
|
||||||
|
path = os.path.join('C:\\', 'Program files', 'OpenJPEG 2.0',
|
||||||
|
'bin', 'openjp2.dll')
|
||||||
|
|
||||||
|
return load_library_handle(path)
|
||||||
|
|
||||||
|
|
||||||
|
def load_library_handle(path):
|
||||||
|
"""Load the library, return the ctypes handle."""
|
||||||
|
|
||||||
|
if path is None:
|
||||||
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
openjpeg_lib = ctypes.windll.LoadLibrary(libopenjpeg_path)
|
opj_lib = ctypes.windll.LoadLibrary(path)
|
||||||
else:
|
else:
|
||||||
openjpeg_lib = ctypes.CDLL(libopenjpeg_path)
|
opj_lib = ctypes.CDLL(path)
|
||||||
except OSError:
|
except (TypeError, OSError):
|
||||||
openjpeg_lib = None
|
msg = '"Library {0}" could not be loaded. Operating in degraded mode.'
|
||||||
|
msg = msg.format(path)
|
||||||
|
warnings.warn(msg, UserWarning)
|
||||||
|
opj_lib = None
|
||||||
|
|
||||||
return openjpeg_lib
|
return opj_lib
|
||||||
|
|
||||||
|
|
||||||
def read_config_file():
|
def read_config_file():
|
||||||
|
|
@ -98,41 +123,6 @@ def read_config_file():
|
||||||
return lib
|
return lib
|
||||||
|
|
||||||
|
|
||||||
def load_openjp2(libopenjp2_path):
|
|
||||||
"""Load the openjp2 library, falling back on defaults if necessary.
|
|
||||||
"""
|
|
||||||
if libopenjp2_path is None:
|
|
||||||
# No help from the config file, try to find it ourselves.
|
|
||||||
libopenjp2_path = find_library('openjp2')
|
|
||||||
|
|
||||||
if libopenjp2_path is None:
|
|
||||||
if platform.system() == 'Darwin':
|
|
||||||
path = '/opt/local/lib/libopenjp2.dylib'
|
|
||||||
if os.path.exists(path):
|
|
||||||
libopenjp2_path = path
|
|
||||||
elif os.name == 'nt':
|
|
||||||
path = os.path.join('C:\\', 'Program files', 'OpenJPEG 2.0',
|
|
||||||
'bin', 'openjp2.dll')
|
|
||||||
if os.path.exists(path):
|
|
||||||
libopenjp2_path = path
|
|
||||||
|
|
||||||
if libopenjp2_path is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
if os.name == "nt":
|
|
||||||
openjp2_lib = ctypes.windll.LoadLibrary(libopenjp2_path)
|
|
||||||
else:
|
|
||||||
openjp2_lib = ctypes.CDLL(libopenjp2_path)
|
|
||||||
except (TypeError, OSError):
|
|
||||||
msg = '"Library {0}" could not be loaded. Operating in degraded mode.'
|
|
||||||
msg = msg.format(libopenjp2_path)
|
|
||||||
warnings.warn(msg, UserWarning)
|
|
||||||
openjp2_lib = None
|
|
||||||
|
|
||||||
return openjp2_lib
|
|
||||||
|
|
||||||
|
|
||||||
def glymur_config():
|
def glymur_config():
|
||||||
"""Try to ascertain locations of openjp2, openjpeg libraries.
|
"""Try to ascertain locations of openjp2, openjpeg libraries.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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)),
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ class TestConfig(unittest.TestCase):
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
def test_write_without_library(self):
|
def test_write_without_library(self):
|
||||||
"""Don't have openjp2 library? Must error out.
|
"""Don't have openjpeg libraries? Must error out.
|
||||||
"""
|
"""
|
||||||
data = glymur.Jp2k(self.j2kfile).read()
|
data = glymur.Jp2k(self.j2kfile).read()
|
||||||
with patch('glymur.lib.openjp2.OPENJP2', new=None):
|
with patch('glymur.lib.openjp2.OPENJP2', new=None):
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
Test suite specifically targeting JP2 box layout.
|
Test suite specifically targeting JP2 box layout.
|
||||||
"""
|
"""
|
||||||
|
|
@ -23,6 +24,16 @@ import tempfile
|
||||||
import warnings
|
import warnings
|
||||||
import xml.etree.cElementTree as ET
|
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:
|
if sys.hexversion < 0x02070000:
|
||||||
import unittest2 as unittest
|
import unittest2 as unittest
|
||||||
else:
|
else:
|
||||||
|
|
@ -94,8 +105,6 @@ class TestXML(unittest.TestCase):
|
||||||
with self.assertRaises((IOError, OSError)):
|
with self.assertRaises((IOError, OSError)):
|
||||||
glymur.jp2box.XMLBox(filename=self.xmlfile, xml=xml_object)
|
glymur.jp2box.XMLBox(filename=self.xmlfile, xml=xml_object)
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt",
|
|
||||||
"Problems using NamedTemporaryFile on windows.")
|
|
||||||
def test_basic_xml(self):
|
def test_basic_xml(self):
|
||||||
"""Should be able to write a basic XMLBox"""
|
"""Should be able to write a basic XMLBox"""
|
||||||
j2k = Jp2k(self.j2kfile)
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
|
@ -116,8 +125,6 @@ class TestXML(unittest.TestCase):
|
||||||
self.assertEqual(ET.tostring(jp2.box[3].xml.getroot()),
|
self.assertEqual(ET.tostring(jp2.box[3].xml.getroot()),
|
||||||
b'<data>0</data>')
|
b'<data>0</data>')
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt",
|
|
||||||
"Problems using NamedTemporaryFile on windows.")
|
|
||||||
def test_xml_from_file(self):
|
def test_xml_from_file(self):
|
||||||
"""Must be able to create an XML box from an XML file."""
|
"""Must be able to create an XML box from an XML file."""
|
||||||
j2k = Jp2k(self.j2kfile)
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
|
@ -141,6 +148,28 @@ class TestXML(unittest.TestCase):
|
||||||
self.assertEqual(neighbor.attrib['name'], 'Malaysia')
|
self.assertEqual(neighbor.attrib['name'], 'Malaysia')
|
||||||
self.assertEqual(neighbor.attrib['direction'], 'N')
|
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")
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
class TestJp2kBadXmlFile(unittest.TestCase):
|
class TestJp2kBadXmlFile(unittest.TestCase):
|
||||||
|
|
@ -179,7 +208,6 @@ class TestJp2kBadXmlFile(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.jp2file = glymur.data.nemo()
|
self.jp2file = glymur.data.nemo()
|
||||||
self.j2kfile = glymur.data.goodstuff()
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -55,598 +55,11 @@ def load_tests(loader, tests, ignore):
|
||||||
return tests
|
return tests
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2 or
|
|
||||||
OPENJP2_IS_V2_OFFICIAL,
|
|
||||||
"Missing openjp2 library version 2.0+.")
|
|
||||||
class TestJp2k_2_1(unittest.TestCase):
|
|
||||||
"""Test suite for version 2.0+ of openjpeg software"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.jp2file = glymur.data.nemo()
|
|
||||||
self.j2kfile = glymur.data.goodstuff()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_grey_with_extra_component(self):
|
|
||||||
"""version 2.0 cannot write gray + extra"""
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
j = Jp2k(tfile.name, 'wb')
|
|
||||||
data = np.zeros((128, 128, 2), dtype=np.uint8)
|
|
||||||
j.write(data)
|
|
||||||
self.assertEqual(j.box[2].box[0].height, 128)
|
|
||||||
self.assertEqual(j.box[2].box[0].width, 128)
|
|
||||||
self.assertEqual(j.box[2].box[0].num_components, 2)
|
|
||||||
self.assertEqual(j.box[2].box[1].colorspace,
|
|
||||||
glymur.core.GREYSCALE)
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_rgb_with_extra_component(self):
|
|
||||||
"""v2.0+ should be able to write extra components"""
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
j = Jp2k(tfile.name, 'wb')
|
|
||||||
data = np.zeros((128, 128, 4), dtype=np.uint8)
|
|
||||||
j.write(data)
|
|
||||||
self.assertEqual(j.box[2].box[0].height, 128)
|
|
||||||
self.assertEqual(j.box[2].box[0].width, 128)
|
|
||||||
self.assertEqual(j.box[2].box[0].num_components, 4)
|
|
||||||
self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB)
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_openjpeg_library_message(self):
|
|
||||||
"""Verify the error message produced by the openjpeg library"""
|
|
||||||
# This will confirm that the error callback mechanism is working.
|
|
||||||
with open(self.jp2file, 'rb') as fptr:
|
|
||||||
data = fptr.read()
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
# Codestream starts at byte 3127. SIZ marker at 3137.
|
|
||||||
# COD marker at 3186. Subsampling at 3180.
|
|
||||||
tfile.write(data[0:3179])
|
|
||||||
|
|
||||||
# Make the DY bytes of the SIZ segment zero. That means that
|
|
||||||
# a subsampling factor is zero, which is illegal.
|
|
||||||
tfile.write(b'\x00')
|
|
||||||
tfile.write(data[3180:3182])
|
|
||||||
tfile.write(b'\x00')
|
|
||||||
tfile.write(data[3184:3186])
|
|
||||||
tfile.write(b'\x00')
|
|
||||||
|
|
||||||
tfile.write(data[3186:])
|
|
||||||
tfile.flush()
|
|
||||||
with warnings.catch_warnings():
|
|
||||||
warnings.simplefilter("ignore")
|
|
||||||
j = Jp2k(tfile.name)
|
|
||||||
regexp = re.compile(r'''OpenJPEG\slibrary\serror:\s+
|
|
||||||
Invalid\svalues\sfor\scomp\s=\s0\s+
|
|
||||||
:\sdx=1\sdy=0''', re.VERBOSE)
|
|
||||||
if sys.hexversion < 0x03020000:
|
|
||||||
with self.assertRaisesRegexp((IOError, OSError), regexp):
|
|
||||||
j.read(rlevel=1)
|
|
||||||
else:
|
|
||||||
with self.assertRaisesRegex((IOError, OSError), regexp):
|
|
||||||
j.read(rlevel=1)
|
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2,
|
|
||||||
"Not tested for 1.x")
|
|
||||||
class TestJp2k_2_0(unittest.TestCase):
|
|
||||||
"""Test suite requiring at least version 2.0"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.jp2file = glymur.data.nemo()
|
|
||||||
self.j2kfile = glymur.data.goodstuff()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@unittest.skipIf(not OPENJP2_IS_V2_OFFICIAL,
|
|
||||||
"Behavior is specific to 2.0 official.")
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_extra_components_on_v2(self):
|
|
||||||
"""must error out in 1.x with extra components."""
|
|
||||||
# Extra components seems to require 2.0+. Verify that we error out.
|
|
||||||
with self.assertRaises((IOError, OSError)):
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
j = Jp2k(tfile.name, 'wb')
|
|
||||||
data = np.zeros((128, 128, 4), dtype=np.uint8)
|
|
||||||
j.write(data)
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_unrecognized_jp2_clrspace(self):
|
|
||||||
"""We only allow RGB and GRAYSCALE. Should error out with others"""
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
j = Jp2k(tfile.name, 'wb')
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
data = np.zeros((128, 128, 3), dtype=np.uint8)
|
|
||||||
j.write(data, colorspace='cmyk')
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_asoc_label_box(self):
|
|
||||||
"""Test asoc and label box"""
|
|
||||||
# Construct a fake file with an asoc and a label box, as
|
|
||||||
# OpenJPEG doesn't have such a file.
|
|
||||||
data = Jp2k(self.jp2file).read(rlevel=1)
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
j = Jp2k(tfile.name, 'wb')
|
|
||||||
j.write(data)
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2:
|
|
||||||
|
|
||||||
# Offset of the codestream is where we start.
|
|
||||||
read_buffer = tfile.read(77)
|
|
||||||
tfile2.write(read_buffer)
|
|
||||||
|
|
||||||
# read the rest of the file, it's the codestream.
|
|
||||||
codestream = tfile.read()
|
|
||||||
|
|
||||||
# Write the asoc superbox.
|
|
||||||
# Length = 36, id is 'asoc'.
|
|
||||||
write_buffer = struct.pack('>I4s', int(56), b'asoc')
|
|
||||||
tfile2.write(write_buffer)
|
|
||||||
|
|
||||||
# Write the contained label box
|
|
||||||
write_buffer = struct.pack('>I4s', int(13), b'lbl ')
|
|
||||||
tfile2.write(write_buffer)
|
|
||||||
tfile2.write('label'.encode())
|
|
||||||
|
|
||||||
# Write the xml box
|
|
||||||
# Length = 36, id is 'xml '.
|
|
||||||
write_buffer = struct.pack('>I4s', int(35), b'xml ')
|
|
||||||
tfile2.write(write_buffer)
|
|
||||||
|
|
||||||
write_buffer = '<test>this is a test</test>'
|
|
||||||
write_buffer = write_buffer.encode()
|
|
||||||
tfile2.write(write_buffer)
|
|
||||||
|
|
||||||
# Now append the codestream.
|
|
||||||
tfile2.write(codestream)
|
|
||||||
tfile2.flush()
|
|
||||||
|
|
||||||
jasoc = Jp2k(tfile2.name)
|
|
||||||
self.assertEqual(jasoc.box[3].box_id, 'asoc')
|
|
||||||
self.assertEqual(jasoc.box[3].box[0].box_id, 'lbl ')
|
|
||||||
self.assertEqual(jasoc.box[3].box[0].label, 'label')
|
|
||||||
self.assertEqual(jasoc.box[3].box[1].box_id, 'xml ')
|
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
|
|
||||||
"Missing openjp2 library.")
|
|
||||||
class TestJp2k_1_x(unittest.TestCase):
|
|
||||||
"""Test suite for versions up to 1.5.1 of openjpeg but no further"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.jp2file = glymur.data.nemo()
|
|
||||||
self.j2kfile = glymur.data.goodstuff()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_rlevel_max(self):
|
|
||||||
"""Verify that rlevel=-1 gets us the lowest resolution image"""
|
|
||||||
j = Jp2k(self.j2kfile)
|
|
||||||
thumbnail1 = j.read(rlevel=-1)
|
|
||||||
thumbnail2 = j.read(rlevel=5)
|
|
||||||
np.testing.assert_array_equal(thumbnail1, thumbnail2)
|
|
||||||
self.assertEqual(thumbnail1.shape, (25, 15, 3))
|
|
||||||
|
|
||||||
def test_bad_area_parameter(self):
|
|
||||||
"""Should error out appropriately if given a bad area parameter."""
|
|
||||||
j = Jp2k(self.jp2file)
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
# Start corner must be >= 0
|
|
||||||
j.read(area=(-1, -1, 1, 1))
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
# End corner must be > 0
|
|
||||||
j.read(area=(10, 10, 0, 0))
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
# End corner must be >= start corner
|
|
||||||
j.read(area=(10, 10, 8, 8))
|
|
||||||
|
|
||||||
def test_rlevel_too_high(self):
|
|
||||||
"""Should error out appropriately if reduce level too high"""
|
|
||||||
j = Jp2k(self.jp2file)
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
j.read(rlevel=6)
|
|
||||||
|
|
||||||
def test_jp2_boxes(self):
|
|
||||||
"""Verify the boxes of a JP2 file. Basic jp2 test."""
|
|
||||||
jp2k = Jp2k(self.jp2file)
|
|
||||||
|
|
||||||
# top-level boxes
|
|
||||||
self.assertEqual(len(jp2k.box), 6)
|
|
||||||
|
|
||||||
self.assertEqual(jp2k.box[0].box_id, 'jP ')
|
|
||||||
self.assertEqual(jp2k.box[0].offset, 0)
|
|
||||||
self.assertEqual(jp2k.box[0].length, 12)
|
|
||||||
self.assertEqual(jp2k.box[0].longname, 'JPEG 2000 Signature')
|
|
||||||
|
|
||||||
self.assertEqual(jp2k.box[1].box_id, 'ftyp')
|
|
||||||
self.assertEqual(jp2k.box[1].offset, 12)
|
|
||||||
self.assertEqual(jp2k.box[1].length, 20)
|
|
||||||
self.assertEqual(jp2k.box[1].longname, 'File Type')
|
|
||||||
|
|
||||||
self.assertEqual(jp2k.box[2].box_id, 'jp2h')
|
|
||||||
self.assertEqual(jp2k.box[2].offset, 32)
|
|
||||||
self.assertEqual(jp2k.box[2].length, 45)
|
|
||||||
self.assertEqual(jp2k.box[2].longname, 'JP2 Header')
|
|
||||||
|
|
||||||
self.assertEqual(jp2k.box[3].box_id, 'uuid')
|
|
||||||
self.assertEqual(jp2k.box[3].offset, 77)
|
|
||||||
self.assertEqual(jp2k.box[3].length, 638)
|
|
||||||
|
|
||||||
self.assertEqual(jp2k.box[4].box_id, 'uuid')
|
|
||||||
self.assertEqual(jp2k.box[4].offset, 715)
|
|
||||||
self.assertEqual(jp2k.box[4].length, 2412)
|
|
||||||
|
|
||||||
self.assertEqual(jp2k.box[5].box_id, 'jp2c')
|
|
||||||
self.assertEqual(jp2k.box[5].offset, 3127)
|
|
||||||
self.assertEqual(jp2k.box[5].length, 1132296)
|
|
||||||
|
|
||||||
# jp2h super box
|
|
||||||
self.assertEqual(len(jp2k.box[2].box), 2)
|
|
||||||
|
|
||||||
self.assertEqual(jp2k.box[2].box[0].box_id, 'ihdr')
|
|
||||||
self.assertEqual(jp2k.box[2].box[0].offset, 40)
|
|
||||||
self.assertEqual(jp2k.box[2].box[0].length, 22)
|
|
||||||
self.assertEqual(jp2k.box[2].box[0].longname, 'Image Header')
|
|
||||||
self.assertEqual(jp2k.box[2].box[0].height, 1456)
|
|
||||||
self.assertEqual(jp2k.box[2].box[0].width, 2592)
|
|
||||||
self.assertEqual(jp2k.box[2].box[0].num_components, 3)
|
|
||||||
self.assertEqual(jp2k.box[2].box[0].bits_per_component, 8)
|
|
||||||
self.assertEqual(jp2k.box[2].box[0].signed, False)
|
|
||||||
self.assertEqual(jp2k.box[2].box[0].compression, 7)
|
|
||||||
self.assertEqual(jp2k.box[2].box[0].colorspace_unknown, False)
|
|
||||||
self.assertEqual(jp2k.box[2].box[0].ip_provided, False)
|
|
||||||
|
|
||||||
self.assertEqual(jp2k.box[2].box[1].box_id, 'colr')
|
|
||||||
self.assertEqual(jp2k.box[2].box[1].offset, 62)
|
|
||||||
self.assertEqual(jp2k.box[2].box[1].length, 15)
|
|
||||||
self.assertEqual(jp2k.box[2].box[1].longname, 'Colour Specification')
|
|
||||||
self.assertEqual(jp2k.box[2].box[1].precedence, 0)
|
|
||||||
self.assertEqual(jp2k.box[2].box[1].approximation, 0)
|
|
||||||
self.assertEqual(jp2k.box[2].box[1].colorspace, glymur.core.SRGB)
|
|
||||||
self.assertIsNone(jp2k.box[2].box[1].icc_profile)
|
|
||||||
|
|
||||||
def test_j2k_box(self):
|
|
||||||
"""A J2K/J2C file must not have any boxes."""
|
|
||||||
# Verify that a J2K file has no boxes.
|
|
||||||
jp2k = Jp2k(self.j2kfile)
|
|
||||||
self.assertEqual(len(jp2k.box), 0)
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_64bit_xl_field(self):
|
|
||||||
"""XL field should be supported"""
|
|
||||||
# Verify that boxes with the XL field are properly read.
|
|
||||||
# Don't have such a file on hand, so we create one. Copy our example
|
|
||||||
# file, but making the codestream have a 64-bit XL field.
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
with open(self.jp2file, 'rb') as ifile:
|
|
||||||
# Everything up until the jp2c box.
|
|
||||||
write_buffer = ifile.read(3127)
|
|
||||||
tfile.write(write_buffer)
|
|
||||||
|
|
||||||
# The L field must be 1 in order to signal the presence of the
|
|
||||||
# XL field. The actual length of the jp2c box increased by 8
|
|
||||||
# (8 bytes for the XL field).
|
|
||||||
length = 1
|
|
||||||
typ = b'jp2c'
|
|
||||||
xlen = 1133427 + 8
|
|
||||||
write_buffer = struct.pack('>I4sQ', int(length), typ, xlen)
|
|
||||||
tfile.write(write_buffer)
|
|
||||||
|
|
||||||
# Get the rest of the input file (minus the 8 bytes for L and
|
|
||||||
# T.
|
|
||||||
ifile.seek(8, 1)
|
|
||||||
write_buffer = ifile.read()
|
|
||||||
tfile.write(write_buffer)
|
|
||||||
tfile.flush()
|
|
||||||
|
|
||||||
jp2k = Jp2k(tfile.name)
|
|
||||||
|
|
||||||
self.assertEqual(jp2k.box[5].box_id, 'jp2c')
|
|
||||||
self.assertEqual(jp2k.box[5].offset, 3127)
|
|
||||||
self.assertEqual(jp2k.box[5].length, 1133427 + 8)
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_length_field_is_zero(self):
|
|
||||||
"""L=0 (length field in box header) is allowed"""
|
|
||||||
# Verify that boxes with the L field as zero are correctly read.
|
|
||||||
# This should only happen in the last box of a JPEG 2000 file.
|
|
||||||
# Our example image has its last box at byte 588458.
|
|
||||||
baseline_jp2 = Jp2k(self.jp2file)
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
with open(self.jp2file, 'rb') as ifile:
|
|
||||||
# Everything up until the jp2c box.
|
|
||||||
write_buffer = ifile.read(588458)
|
|
||||||
tfile.write(write_buffer)
|
|
||||||
|
|
||||||
length = 0
|
|
||||||
typ = b'uuid'
|
|
||||||
write_buffer = struct.pack('>I4s', int(length), typ)
|
|
||||||
tfile.write(write_buffer)
|
|
||||||
|
|
||||||
# Get the rest of the input file (minus the 8 bytes for L and
|
|
||||||
# T.
|
|
||||||
ifile.seek(8, 1)
|
|
||||||
write_buffer = ifile.read()
|
|
||||||
tfile.write(write_buffer)
|
|
||||||
tfile.flush()
|
|
||||||
|
|
||||||
new_jp2 = Jp2k(tfile.name)
|
|
||||||
|
|
||||||
# The top level boxes in each file should match.
|
|
||||||
for j in range(len(baseline_jp2.box)):
|
|
||||||
self.assertEqual(new_jp2.box[j].box_id,
|
|
||||||
baseline_jp2.box[j].box_id)
|
|
||||||
self.assertEqual(new_jp2.box[j].offset,
|
|
||||||
baseline_jp2.box[j].offset)
|
|
||||||
self.assertEqual(new_jp2.box[j].length,
|
|
||||||
baseline_jp2.box[j].length)
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_cblkh_different_than_width(self):
|
|
||||||
"""Verify that we can set a code block size where height does not equal
|
|
||||||
width.
|
|
||||||
"""
|
|
||||||
data = np.zeros((128, 128), dtype=np.uint8)
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
|
|
||||||
j = Jp2k(tfile.name, 'wb')
|
|
||||||
|
|
||||||
# The code block dimensions are given as rows x columns.
|
|
||||||
j.write(data, cbsize=(16, 32))
|
|
||||||
|
|
||||||
codestream = j.get_codestream()
|
|
||||||
|
|
||||||
# Code block size is reported as XY in the codestream.
|
|
||||||
self.assertEqual(tuple(codestream.segment[2].spcod[5:7]), (3, 2))
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_too_many_dimensions(self):
|
|
||||||
"""OpenJP2 only allows 2D or 3D images."""
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
|
|
||||||
j = Jp2k(tfile.name, 'wb')
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
data = np.zeros((128, 128, 2, 2), dtype=np.uint8)
|
|
||||||
j.write(data)
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_2d_rgb(self):
|
|
||||||
"""RGB must have at least 3 components."""
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
j = Jp2k(tfile.name, 'wb')
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
data = np.zeros((128, 128, 2), dtype=np.uint8)
|
|
||||||
j.write(data, colorspace='rgb')
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_colorspace_with_j2k(self):
|
|
||||||
"""Specifying a colorspace with J2K does not make sense"""
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
|
|
||||||
j = Jp2k(tfile.name, 'wb')
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
data = np.zeros((128, 128, 3), dtype=np.uint8)
|
|
||||||
j.write(data, colorspace='rgb')
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_specify_rgb(self):
|
|
||||||
"""specify RGB explicitly"""
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
j = Jp2k(tfile.name, 'wb')
|
|
||||||
data = np.zeros((128, 128, 3), dtype=np.uint8)
|
|
||||||
j.write(data, colorspace='rgb')
|
|
||||||
self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB)
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_specify_gray(self):
|
|
||||||
"""test gray explicitly specified (that's GRAY, not GREY)"""
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
j = Jp2k(tfile.name, 'wb')
|
|
||||||
data = np.zeros((128, 128), dtype=np.uint8)
|
|
||||||
j.write(data, colorspace='gray')
|
|
||||||
self.assertEqual(j.box[2].box[1].colorspace,
|
|
||||||
glymur.core.GREYSCALE)
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_specify_grey(self):
|
|
||||||
"""test grey explicitly specified"""
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
j = Jp2k(tfile.name, 'wb')
|
|
||||||
data = np.zeros((128, 128), dtype=np.uint8)
|
|
||||||
j.write(data, colorspace='grey')
|
|
||||||
self.assertEqual(j.box[2].box[1].colorspace,
|
|
||||||
glymur.core.GREYSCALE)
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_grey_with_two_extra_comps(self):
|
|
||||||
"""should be able to write gray + two extra components"""
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
j = Jp2k(tfile.name, 'wb')
|
|
||||||
data = np.zeros((128, 128, 3), dtype=np.uint8)
|
|
||||||
j.write(data, colorspace='gray')
|
|
||||||
self.assertEqual(j.box[2].box[0].height, 128)
|
|
||||||
self.assertEqual(j.box[2].box[0].width, 128)
|
|
||||||
self.assertEqual(j.box[2].box[0].num_components, 3)
|
|
||||||
self.assertEqual(j.box[2].box[1].colorspace,
|
|
||||||
glymur.core.GREYSCALE)
|
|
||||||
|
|
||||||
def test_specify_ycc(self):
|
|
||||||
"""Should reject YCC"""
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
j = Jp2k(tfile.name, 'wb')
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
data = np.zeros((128, 128, 3), dtype=np.uint8)
|
|
||||||
j.write(data, colorspace='ycc')
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_write_with_jp2_in_caps(self):
|
|
||||||
"""should be able to write with JP2 suffix."""
|
|
||||||
j2k = Jp2k(self.j2kfile)
|
|
||||||
expdata = j2k.read()
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.JP2') as tfile:
|
|
||||||
ofile = Jp2k(tfile.name, 'wb')
|
|
||||||
ofile.write(expdata)
|
|
||||||
actdata = ofile.read()
|
|
||||||
np.testing.assert_array_equal(actdata, expdata)
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_write_srgb_without_mct(self):
|
|
||||||
"""should be able to write RGB without specifying mct"""
|
|
||||||
j2k = Jp2k(self.j2kfile)
|
|
||||||
expdata = j2k.read()
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
ofile = Jp2k(tfile.name, 'wb')
|
|
||||||
ofile.write(expdata, mct=False)
|
|
||||||
actdata = ofile.read()
|
|
||||||
np.testing.assert_array_equal(actdata, expdata)
|
|
||||||
|
|
||||||
codestream = ofile.get_codestream()
|
|
||||||
self.assertEqual(codestream.segment[2].spcod[3], 0) # no mct
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_write_grayscale_with_mct(self):
|
|
||||||
"""MCT usage makes no sense for grayscale images."""
|
|
||||||
j2k = Jp2k(self.j2kfile)
|
|
||||||
expdata = j2k.read()
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
ofile = Jp2k(tfile.name, 'wb')
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
ofile.write(expdata[:, :, 0], mct=True)
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_write_cprl(self):
|
|
||||||
"""Must be able to write a CPRL progression order file"""
|
|
||||||
# Issue 17
|
|
||||||
j = Jp2k(self.jp2file)
|
|
||||||
expdata = j.read(rlevel=1)
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
ofile = Jp2k(tfile.name, 'wb')
|
|
||||||
ofile.write(expdata, prog='CPRL')
|
|
||||||
actdata = ofile.read()
|
|
||||||
np.testing.assert_array_equal(actdata, expdata)
|
|
||||||
|
|
||||||
codestream = ofile.get_codestream()
|
|
||||||
self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL)
|
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is not None,
|
|
||||||
"Don't bother if openjp2 is present.")
|
|
||||||
@unittest.skipIf(glymur.lib.openjpeg.OPENJPEG is None,
|
|
||||||
"Missing openjpeg library.")
|
|
||||||
class TestJp2k15(unittest.TestCase):
|
|
||||||
"""Test suite for openjpeg 1.x, not appropriate for 2.x"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.jp2file = glymur.data.nemo()
|
|
||||||
self.j2kfile = glymur.data.goodstuff()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_area(self):
|
|
||||||
"""Area option not allowed for 1.5.1.
|
|
||||||
"""
|
|
||||||
j2k = Jp2k(self.j2kfile)
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
j2k.read(area=(0, 0, 100, 100))
|
|
||||||
|
|
||||||
def test_tile(self):
|
|
||||||
"""tile option not allowed for 1.5.1.
|
|
||||||
"""
|
|
||||||
j2k = Jp2k(self.j2kfile)
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
j2k.read(tile=0)
|
|
||||||
|
|
||||||
def test_layer(self):
|
|
||||||
"""layer option not allowed for 1.5.1.
|
|
||||||
"""
|
|
||||||
j2k = Jp2k(self.j2kfile)
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
j2k.read(layer=1)
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
@unittest.skipIf(glymur.version.openjpeg_version_tuple[1] < 5,
|
|
||||||
"Writing only supported with openjpeg version 1.5+.")
|
|
||||||
def test_2d_rgb(self):
|
|
||||||
"""RGB must have at least 3 components."""
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
j = Jp2k(tfile.name, 'wb')
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
data = np.zeros((128, 128, 2), dtype=np.uint8)
|
|
||||||
j.write(data, colorspace='rgb')
|
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_xml_with_trailing_nulls(self):
|
|
||||||
"""ElementTree doesn't like trailing null chars after valid XML text"""
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
with open(self.jp2file, 'rb') as ifile:
|
|
||||||
# Everything up until the jp2c box.
|
|
||||||
write_buffer = ifile.read(77)
|
|
||||||
tfile.write(write_buffer)
|
|
||||||
|
|
||||||
# Write the xml box
|
|
||||||
# Length = 36, id is 'xml '.
|
|
||||||
write_buffer = struct.pack('>I4s', int(36), b'xml ')
|
|
||||||
tfile.write(write_buffer)
|
|
||||||
|
|
||||||
write_buffer = '<test>this is a test</test>' + chr(0)
|
|
||||||
write_buffer = write_buffer.encode()
|
|
||||||
tfile.write(write_buffer)
|
|
||||||
|
|
||||||
# Get the rest of the input file.
|
|
||||||
write_buffer = ifile.read()
|
|
||||||
tfile.write(write_buffer)
|
|
||||||
tfile.flush()
|
|
||||||
|
|
||||||
jp2k = Jp2k(tfile.name)
|
|
||||||
|
|
||||||
self.assertEqual(jp2k.box[3].box_id, 'xml ')
|
|
||||||
self.assertEqual(jp2k.box[3].offset, 77)
|
|
||||||
self.assertEqual(jp2k.box[3].length, 36)
|
|
||||||
self.assertEqual(ET.tostring(jp2k.box[3].xml.getroot()),
|
|
||||||
b'<test>this is a test</test>')
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2,
|
|
||||||
"Segfault on official v1.x series.")
|
|
||||||
def test_openjpeg_library_message(self):
|
|
||||||
"""Verify the error message produced by the openjpeg library"""
|
|
||||||
# This will confirm that the error callback mechanism is working.
|
|
||||||
with open(self.jp2file, 'rb') as fptr:
|
|
||||||
data = fptr.read()
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
# Codestream starts at byte 3127. SIZ marker at 3137.
|
|
||||||
# COD marker at 3186. Subsampling at 3180.
|
|
||||||
tfile.write(data[0:3179])
|
|
||||||
|
|
||||||
# Make the DY bytes of the SIZ segment zero. That means that
|
|
||||||
# a subsampling factor is zero, which is illegal.
|
|
||||||
tfile.write(b'\x00')
|
|
||||||
tfile.write(data[3180:3182])
|
|
||||||
tfile.write(b'\x00')
|
|
||||||
tfile.write(data[3184:3186])
|
|
||||||
tfile.write(b'\x00')
|
|
||||||
|
|
||||||
tfile.write(data[3186:])
|
|
||||||
tfile.flush()
|
|
||||||
with warnings.catch_warnings():
|
|
||||||
warnings.simplefilter("ignore")
|
|
||||||
j = Jp2k(tfile.name)
|
|
||||||
regexp = re.compile(r'''OpenJPEG\slibrary\serror:\s+
|
|
||||||
Invalid\svalues\sfor\scomp\s=\s0\s+
|
|
||||||
:\sdx=1\sdy=0''', re.VERBOSE)
|
|
||||||
if sys.hexversion < 0x03020000:
|
|
||||||
with self.assertRaisesRegexp((IOError, OSError), regexp):
|
|
||||||
j.read(rlevel=1)
|
|
||||||
else:
|
|
||||||
with self.assertRaisesRegex((IOError, OSError), regexp):
|
|
||||||
j.read(rlevel=1)
|
|
||||||
|
|
||||||
|
|
||||||
class TestJp2k(unittest.TestCase):
|
class TestJp2k(unittest.TestCase):
|
||||||
"""Test suite for openjpeg software starting at 1.3"""
|
"""Test suite for openjpeg software starting at 1.3"""
|
||||||
|
|
||||||
|
# These tests should be run by just about all configuration.
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.jp2file = glymur.data.nemo()
|
self.jp2file = glymur.data.nemo()
|
||||||
self.j2kfile = glymur.data.goodstuff()
|
self.j2kfile = glymur.data.goodstuff()
|
||||||
|
|
@ -985,5 +398,368 @@ class TestJp2k(unittest.TestCase):
|
||||||
self.assertFalse('Make' in exif['Image'].keys())
|
self.assertFalse('Make' in exif['Image'].keys())
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version),
|
||||||
|
"Requires at least version 1.5")
|
||||||
|
class TestJp2k_write(unittest.TestCase):
|
||||||
|
"""Write tests, can be run by versions 1.5+"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.jp2file = glymur.data.nemo()
|
||||||
|
self.j2kfile = glymur.data.goodstuff()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
|
def test_cblkh_different_than_width(self):
|
||||||
|
"""Verify that we can set a code block size where height does not equal
|
||||||
|
width.
|
||||||
|
"""
|
||||||
|
data = np.zeros((128, 128), dtype=np.uint8)
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
|
||||||
|
j = Jp2k(tfile.name, 'wb')
|
||||||
|
|
||||||
|
# The code block dimensions are given as rows x columns.
|
||||||
|
j.write(data, cbsize=(16, 32))
|
||||||
|
|
||||||
|
codestream = j.get_codestream()
|
||||||
|
|
||||||
|
# Code block size is reported as XY in the codestream.
|
||||||
|
self.assertEqual(tuple(codestream.segment[2].spcod[5:7]), (3, 2))
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
|
def test_too_many_dimensions(self):
|
||||||
|
"""OpenJP2 only allows 2D or 3D images."""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
|
||||||
|
j = Jp2k(tfile.name, 'wb')
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
data = np.zeros((128, 128, 2, 2), dtype=np.uint8)
|
||||||
|
j.write(data)
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
|
def test_2d_rgb(self):
|
||||||
|
"""RGB must have at least 3 components."""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
j = Jp2k(tfile.name, 'wb')
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
data = np.zeros((128, 128, 2), dtype=np.uint8)
|
||||||
|
j.write(data, colorspace='rgb')
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
|
def test_colorspace_with_j2k(self):
|
||||||
|
"""Specifying a colorspace with J2K does not make sense"""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
|
||||||
|
j = Jp2k(tfile.name, 'wb')
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
data = np.zeros((128, 128, 3), dtype=np.uint8)
|
||||||
|
j.write(data, colorspace='rgb')
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
|
def test_specify_rgb(self):
|
||||||
|
"""specify RGB explicitly"""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
j = Jp2k(tfile.name, 'wb')
|
||||||
|
data = np.zeros((128, 128, 3), dtype=np.uint8)
|
||||||
|
j.write(data, colorspace='rgb')
|
||||||
|
self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB)
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
|
def test_specify_gray(self):
|
||||||
|
"""test gray explicitly specified (that's GRAY, not GREY)"""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
j = Jp2k(tfile.name, 'wb')
|
||||||
|
data = np.zeros((128, 128), dtype=np.uint8)
|
||||||
|
j.write(data, colorspace='gray')
|
||||||
|
self.assertEqual(j.box[2].box[1].colorspace,
|
||||||
|
glymur.core.GREYSCALE)
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
|
def test_specify_grey(self):
|
||||||
|
"""test grey explicitly specified"""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
j = Jp2k(tfile.name, 'wb')
|
||||||
|
data = np.zeros((128, 128), dtype=np.uint8)
|
||||||
|
j.write(data, colorspace='grey')
|
||||||
|
self.assertEqual(j.box[2].box[1].colorspace,
|
||||||
|
glymur.core.GREYSCALE)
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
|
def test_grey_with_two_extra_comps(self):
|
||||||
|
"""should be able to write gray + two extra components"""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
j = Jp2k(tfile.name, 'wb')
|
||||||
|
data = np.zeros((128, 128, 3), dtype=np.uint8)
|
||||||
|
j.write(data, colorspace='gray')
|
||||||
|
self.assertEqual(j.box[2].box[0].height, 128)
|
||||||
|
self.assertEqual(j.box[2].box[0].width, 128)
|
||||||
|
self.assertEqual(j.box[2].box[0].num_components, 3)
|
||||||
|
self.assertEqual(j.box[2].box[1].colorspace,
|
||||||
|
glymur.core.GREYSCALE)
|
||||||
|
|
||||||
|
def test_specify_ycc(self):
|
||||||
|
"""Should reject YCC"""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
j = Jp2k(tfile.name, 'wb')
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
data = np.zeros((128, 128, 3), dtype=np.uint8)
|
||||||
|
j.write(data, colorspace='ycc')
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
|
def test_write_with_jp2_in_caps(self):
|
||||||
|
"""should be able to write with JP2 suffix."""
|
||||||
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
expdata = j2k.read()
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.JP2') as tfile:
|
||||||
|
ofile = Jp2k(tfile.name, 'wb')
|
||||||
|
ofile.write(expdata)
|
||||||
|
actdata = ofile.read()
|
||||||
|
np.testing.assert_array_equal(actdata, expdata)
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
|
def test_write_srgb_without_mct(self):
|
||||||
|
"""should be able to write RGB without specifying mct"""
|
||||||
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
expdata = j2k.read()
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
ofile = Jp2k(tfile.name, 'wb')
|
||||||
|
ofile.write(expdata, mct=False)
|
||||||
|
actdata = ofile.read()
|
||||||
|
np.testing.assert_array_equal(actdata, expdata)
|
||||||
|
|
||||||
|
codestream = ofile.get_codestream()
|
||||||
|
self.assertEqual(codestream.segment[2].spcod[3], 0) # no mct
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
|
def test_write_grayscale_with_mct(self):
|
||||||
|
"""MCT usage makes no sense for grayscale images."""
|
||||||
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
expdata = j2k.read()
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
ofile = Jp2k(tfile.name, 'wb')
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
ofile.write(expdata[:, :, 0], mct=True)
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
|
def test_write_cprl(self):
|
||||||
|
"""Must be able to write a CPRL progression order file"""
|
||||||
|
# Issue 17
|
||||||
|
j = Jp2k(self.jp2file)
|
||||||
|
expdata = j.read(rlevel=1)
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
ofile = Jp2k(tfile.name, 'wb')
|
||||||
|
ofile.write(expdata, prog='CPRL')
|
||||||
|
actdata = ofile.read()
|
||||||
|
np.testing.assert_array_equal(actdata, expdata)
|
||||||
|
|
||||||
|
codestream = ofile.get_codestream()
|
||||||
|
self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL)
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] >= 2,
|
||||||
|
"Negative tests only for version 1.x")
|
||||||
|
class TestJp2k_1_x(unittest.TestCase):
|
||||||
|
"""Test suite for openjpeg 1.x, not appropriate for 2.x"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.jp2file = glymur.data.nemo()
|
||||||
|
self.j2kfile = glymur.data.goodstuff()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_area(self):
|
||||||
|
"""Area option not allowed for 1.x.
|
||||||
|
"""
|
||||||
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
j2k.read(area=(0, 0, 100, 100))
|
||||||
|
|
||||||
|
def test_tile(self):
|
||||||
|
"""tile option not allowed for 1.x.
|
||||||
|
"""
|
||||||
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
j2k.read(tile=0)
|
||||||
|
|
||||||
|
def test_layer(self):
|
||||||
|
"""layer option not allowed for 1.x.
|
||||||
|
"""
|
||||||
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
j2k.read(layer=1)
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(not OPENJP2_IS_V2_OFFICIAL,
|
||||||
|
"Tests only to be run on 2.0 official.")
|
||||||
|
class TestJp2k_2_0_official(unittest.TestCase):
|
||||||
|
"""Test suite to only be run on v2.0 official."""
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
|
def test_extra_components_on_v2(self):
|
||||||
|
"""Can only write 4 components on 2.0+, should error out otherwise."""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
j = Jp2k(tfile.name, 'wb')
|
||||||
|
data = np.zeros((128, 128, 4), dtype=np.uint8)
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
j.write(data)
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2,
|
||||||
|
"Requires as least v2.0")
|
||||||
|
class TestJp2k_2_0(unittest.TestCase):
|
||||||
|
"""Test suite requiring at least version 2.0"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.jp2file = glymur.data.nemo()
|
||||||
|
self.j2kfile = glymur.data.goodstuff()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_bad_area_parameter(self):
|
||||||
|
"""Should error out appropriately if given a bad area parameter."""
|
||||||
|
j = Jp2k(self.jp2file)
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
# Start corner must be >= 0
|
||||||
|
j.read(area=(-1, -1, 1, 1))
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
# End corner must be > 0
|
||||||
|
j.read(area=(10, 10, 0, 0))
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
# End corner must be >= start corner
|
||||||
|
j.read(area=(10, 10, 8, 8))
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
|
def test_unrecognized_jp2_clrspace(self):
|
||||||
|
"""We only allow RGB and GRAYSCALE. Should error out with others"""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
j = Jp2k(tfile.name, 'wb')
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
data = np.zeros((128, 128, 3), dtype=np.uint8)
|
||||||
|
j.write(data, colorspace='cmyk')
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
|
def test_asoc_label_box(self):
|
||||||
|
"""Test asoc and label box"""
|
||||||
|
# Construct a fake file with an asoc and a label box, as
|
||||||
|
# OpenJPEG doesn't have such a file.
|
||||||
|
data = Jp2k(self.jp2file).read(rlevel=1)
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
j = Jp2k(tfile.name, 'wb')
|
||||||
|
j.write(data)
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2:
|
||||||
|
|
||||||
|
# Offset of the codestream is where we start.
|
||||||
|
read_buffer = tfile.read(77)
|
||||||
|
tfile2.write(read_buffer)
|
||||||
|
|
||||||
|
# read the rest of the file, it's the codestream.
|
||||||
|
codestream = tfile.read()
|
||||||
|
|
||||||
|
# Write the asoc superbox.
|
||||||
|
# Length = 36, id is 'asoc'.
|
||||||
|
write_buffer = struct.pack('>I4s', int(56), b'asoc')
|
||||||
|
tfile2.write(write_buffer)
|
||||||
|
|
||||||
|
# Write the contained label box
|
||||||
|
write_buffer = struct.pack('>I4s', int(13), b'lbl ')
|
||||||
|
tfile2.write(write_buffer)
|
||||||
|
tfile2.write('label'.encode())
|
||||||
|
|
||||||
|
# Write the xml box
|
||||||
|
# Length = 36, id is 'xml '.
|
||||||
|
write_buffer = struct.pack('>I4s', int(35), b'xml ')
|
||||||
|
tfile2.write(write_buffer)
|
||||||
|
|
||||||
|
write_buffer = '<test>this is a test</test>'
|
||||||
|
write_buffer = write_buffer.encode()
|
||||||
|
tfile2.write(write_buffer)
|
||||||
|
|
||||||
|
# Now append the codestream.
|
||||||
|
tfile2.write(codestream)
|
||||||
|
tfile2.flush()
|
||||||
|
|
||||||
|
jasoc = Jp2k(tfile2.name)
|
||||||
|
self.assertEqual(jasoc.box[3].box_id, 'asoc')
|
||||||
|
self.assertEqual(jasoc.box[3].box[0].box_id, 'lbl ')
|
||||||
|
self.assertEqual(jasoc.box[3].box[0].label, 'label')
|
||||||
|
self.assertEqual(jasoc.box[3].box[1].box_id, 'xml ')
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2 or
|
||||||
|
OPENJP2_IS_V2_OFFICIAL,
|
||||||
|
"Missing openjp2 library version 2.0+.")
|
||||||
|
class TestJp2k_2_1(unittest.TestCase):
|
||||||
|
"""Only to be run in 2.0+."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.jp2file = glymur.data.nemo()
|
||||||
|
self.j2kfile = glymur.data.goodstuff()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
|
def test_grey_with_extra_component(self):
|
||||||
|
"""version 2.0 cannot write gray + extra"""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
j = Jp2k(tfile.name, 'wb')
|
||||||
|
data = np.zeros((128, 128, 2), dtype=np.uint8)
|
||||||
|
j.write(data)
|
||||||
|
self.assertEqual(j.box[2].box[0].height, 128)
|
||||||
|
self.assertEqual(j.box[2].box[0].width, 128)
|
||||||
|
self.assertEqual(j.box[2].box[0].num_components, 2)
|
||||||
|
self.assertEqual(j.box[2].box[1].colorspace,
|
||||||
|
glymur.core.GREYSCALE)
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
|
def test_rgb_with_extra_component(self):
|
||||||
|
"""v2.0+ should be able to write extra components"""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
j = Jp2k(tfile.name, 'wb')
|
||||||
|
data = np.zeros((128, 128, 4), dtype=np.uint8)
|
||||||
|
j.write(data)
|
||||||
|
self.assertEqual(j.box[2].box[0].height, 128)
|
||||||
|
self.assertEqual(j.box[2].box[0].width, 128)
|
||||||
|
self.assertEqual(j.box[2].box[0].num_components, 4)
|
||||||
|
self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB)
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
|
def test_openjpeg_library_message(self):
|
||||||
|
"""Verify the error message produced by the openjpeg library"""
|
||||||
|
# This will confirm that the error callback mechanism is working.
|
||||||
|
with open(self.jp2file, 'rb') as fptr:
|
||||||
|
data = fptr.read()
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
# Codestream starts at byte 3127. SIZ marker at 3137.
|
||||||
|
# COD marker at 3186. Subsampling at 3180.
|
||||||
|
tfile.write(data[0:3179])
|
||||||
|
|
||||||
|
# Make the DY bytes of the SIZ segment zero. That means that
|
||||||
|
# a subsampling factor is zero, which is illegal.
|
||||||
|
tfile.write(b'\x00')
|
||||||
|
tfile.write(data[3180:3182])
|
||||||
|
tfile.write(b'\x00')
|
||||||
|
tfile.write(data[3184:3186])
|
||||||
|
tfile.write(b'\x00')
|
||||||
|
|
||||||
|
tfile.write(data[3186:])
|
||||||
|
tfile.flush()
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
j = Jp2k(tfile.name)
|
||||||
|
regexp = re.compile(r'''OpenJPEG\slibrary\serror:\s+
|
||||||
|
Invalid\svalues\sfor\scomp\s=\s0\s+
|
||||||
|
:\sdx=1\sdy=0''', re.VERBOSE)
|
||||||
|
if sys.hexversion < 0x03020000:
|
||||||
|
with self.assertRaisesRegexp((IOError, OSError), regexp):
|
||||||
|
j.read(rlevel=1)
|
||||||
|
else:
|
||||||
|
with self.assertRaisesRegex((IOError, OSError), regexp):
|
||||||
|
j.read(rlevel=1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
"""Test suite for printing.
|
"""Test suite for printing.
|
||||||
"""
|
"""
|
||||||
# C0302: don't care too much about having too many lines in a test module
|
# C0302: don't care too much about having too many lines in a test module
|
||||||
|
|
@ -15,6 +16,7 @@ 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
|
||||||
|
|
@ -730,6 +732,65 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.hexversion < 0x03000000,
|
||||||
|
"Only trusting python3 for printing non-ascii chars")
|
||||||
|
def test_xml_latin1(self):
|
||||||
|
"""Should be able to print an XMLBox with utf-8 encoding (latin1)."""
|
||||||
|
# Seems to be inconsistencies between different versions of python2.x
|
||||||
|
# as to what gets printed.
|
||||||
|
#
|
||||||
|
# 2.7.5 (fedora 19) prints xml entities.
|
||||||
|
# 2.7.3 seems to want to print hex escapes.
|
||||||
|
text = u"""<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<flow>Strömung</flow>"""
|
||||||
|
if sys.hexversion < 0x03000000:
|
||||||
|
xml = ET.parse(StringIO(text.encode('utf-8')))
|
||||||
|
else:
|
||||||
|
xml = ET.parse(StringIO(text))
|
||||||
|
|
||||||
|
xmlbox = glymur.jp2box.XMLBox(xml=xml)
|
||||||
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
print(xmlbox)
|
||||||
|
actual = fake_out.getvalue().strip()
|
||||||
|
if sys.hexversion < 0x03000000:
|
||||||
|
lines = ["XML Box (xml ) @ (-1, 0)",
|
||||||
|
" <flow>Str\xc3\xb6mung</flow>"]
|
||||||
|
else:
|
||||||
|
lines = ["XML Box (xml ) @ (-1, 0)",
|
||||||
|
" <flow>Strömung</flow>"]
|
||||||
|
expected = '\n'.join(lines)
|
||||||
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.hexversion < 0x03000000,
|
||||||
|
"Only trusting python3 for printing non-ascii chars")
|
||||||
|
def test_xml_cyrrilic(self):
|
||||||
|
"""Should be able to print an XMLBox with utf-8 encoding (cyrrillic)."""
|
||||||
|
# Seems to be inconsistencies between different versions of python2.x
|
||||||
|
# as to what gets printed.
|
||||||
|
#
|
||||||
|
# 2.7.5 (fedora 19) prints xml entities.
|
||||||
|
# 2.7.3 seems to want to print hex escapes.
|
||||||
|
text = u"""<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<country>Россия</country>"""
|
||||||
|
if sys.hexversion < 0x03000000:
|
||||||
|
xml = ET.parse(StringIO(text.encode('utf-8')))
|
||||||
|
else:
|
||||||
|
xml = ET.parse(StringIO(text))
|
||||||
|
|
||||||
|
xmlbox = glymur.jp2box.XMLBox(xml=xml)
|
||||||
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
print(xmlbox)
|
||||||
|
actual = fake_out.getvalue().strip()
|
||||||
|
if sys.hexversion < 0x03000000:
|
||||||
|
lines = ["XML Box (xml ) @ (-1, 0)",
|
||||||
|
" <country>Россия</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,
|
@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):
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,9 @@ from distutils.version import LooseVersion
|
||||||
from .lib import openjpeg as opj
|
from .lib import openjpeg as opj
|
||||||
from .lib import openjp2 as opj2
|
from .lib import openjp2 as opj2
|
||||||
|
|
||||||
version = "0.5.4"
|
# Do not change the format of this next line! Doing so risks breaking
|
||||||
|
# setup.py
|
||||||
|
version = "0.5.7"
|
||||||
_sv = LooseVersion(version)
|
_sv = LooseVersion(version)
|
||||||
version_tuple = _sv.version
|
version_tuple = _sv.version
|
||||||
|
|
||||||
|
|
|
||||||
10
setup.py
10
setup.py
|
|
@ -1,4 +1,6 @@
|
||||||
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',
|
||||||
|
|
@ -38,7 +40,11 @@ clssfrs = ["Programming Language :: Python",
|
||||||
"Topic :: Software Development :: Libraries :: Python Modules"]
|
"Topic :: Software Development :: Libraries :: Python Modules"]
|
||||||
kwargs['classifiers'] = clssfrs
|
kwargs['classifiers'] = clssfrs
|
||||||
|
|
||||||
import glymur
|
# Get the version string. Cannot do this by importing glymur!
|
||||||
kwargs['version'] = glymur.version.version
|
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)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
numpy
|
|
||||||
contextlib2>=0.4.0
|
|
||||||
mock>=1.0.1
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue