Merge branch 'devel'
This commit is contained in:
commit
22450ddd55
23 changed files with 1475 additions and 1083 deletions
|
|
@ -1,3 +1,10 @@
|
||||||
|
Jun 16, 2013 - v0.1.9 Reading ICC profile headers as ordered dicts. Exif
|
||||||
|
dictionaries changed to ordered dicts. Honoring XDG_CONFIG_HOME
|
||||||
|
environment variable.
|
||||||
|
|
||||||
|
Jun 14, 2013 - v0.1.8 Added reduce=-1 option to get lowest resolution
|
||||||
|
thumbnail.
|
||||||
|
|
||||||
Jun 07, 2013 - v0.1.7 Changed Exif dictionary names from ['Exif', 'Photo',
|
Jun 07, 2013 - v0.1.7 Changed Exif dictionary names from ['Exif', 'Photo',
|
||||||
'Iop', 'GPSInfo'] to ['Image', 'Photo', 'Iop', 'GPSInfo'].
|
'Iop', 'GPSInfo'] to ['Image', 'Photo', 'Iop', 'GPSInfo'].
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
include *.txt *.md
|
include *.txt *.md
|
||||||
recursive-include *.txt *.py
|
|
||||||
prune build
|
prune build
|
||||||
exclude readthedocs-pip-requirements.txt
|
exclude readthedocs-pip-requirements.txt
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ copyright = u'2013, John Evans'
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '0.1'
|
version = '0.1'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '0.1.7'
|
release = '0.1.9'
|
||||||
|
|
||||||
# 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.
|
||||||
|
|
|
||||||
110
docs/source/detailed_installation.rst
Normal file
110
docs/source/detailed_installation.rst
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
-----------------------------------------------------
|
||||||
|
Detailed Instructions for Package Management, Testing
|
||||||
|
-----------------------------------------------------
|
||||||
|
|
||||||
|
You only need to read this page if you want detailed
|
||||||
|
platform-specific instructions on running as many tests as possible or wish to
|
||||||
|
use your system's package manager to install as many required
|
||||||
|
packages/RPMs/ports/whatever without going through pip. Otherwise go on to
|
||||||
|
the next page.
|
||||||
|
|
||||||
|
''''''''
|
||||||
|
Platform
|
||||||
|
''''''''
|
||||||
|
|
||||||
|
Mac OS X
|
||||||
|
--------
|
||||||
|
All the necessary packages are available to use glymur with Python 3.3 via
|
||||||
|
MacPorts. A minimal set of ports includes
|
||||||
|
|
||||||
|
* python33
|
||||||
|
* py33-numpy
|
||||||
|
* py33-distribute
|
||||||
|
|
||||||
|
To run all the testing, one of the following combinations of ports must
|
||||||
|
additionally be installed:
|
||||||
|
|
||||||
|
* py33-scikit-image and either py33-Pillow or freeimage
|
||||||
|
* py33-matplotlib and py33-Pillow
|
||||||
|
|
||||||
|
Linux
|
||||||
|
-----
|
||||||
|
|
||||||
|
Fedora 18
|
||||||
|
'''''''''
|
||||||
|
Fedora 18 ships with Python 3.3, so all the necessary RPMs are available to
|
||||||
|
meet the minimal set of requirements.
|
||||||
|
|
||||||
|
* python3
|
||||||
|
* python3-numpy
|
||||||
|
* python3-setuptools
|
||||||
|
* python3-matplotlib (for running tests)
|
||||||
|
* python3-matplotlib-tk (or whichever matplotlib backend you prefer)
|
||||||
|
|
||||||
|
A few tests still will not run, however, unless one of the following
|
||||||
|
combinations of RPMs / Python packages is installed.
|
||||||
|
|
||||||
|
* scikit-image and either Pillow or freeimage
|
||||||
|
* matplotlib and Pillow
|
||||||
|
|
||||||
|
The 2nd route is probably the easiest, so go ahead and install Pillow
|
||||||
|
via pip since Pillow is not yet available in Fedora 18 default
|
||||||
|
repositories::
|
||||||
|
|
||||||
|
$ yum install python3-devel # pip needs this in order to compile Pillow
|
||||||
|
$ yum install python3-pip
|
||||||
|
$ pip-python3 install Pillow --user
|
||||||
|
$ export PYTHONPATH=$HOME/.local/lib/python3.3/site-packages:$PYTHONPATH
|
||||||
|
|
||||||
|
Fedora 17
|
||||||
|
'''''''''
|
||||||
|
Fedora 17 ships with Python 3.2 and 2.7, so these steps detail working with
|
||||||
|
2.7.
|
||||||
|
|
||||||
|
Required RPMs include::
|
||||||
|
|
||||||
|
* python
|
||||||
|
* python-mock
|
||||||
|
* python-pip
|
||||||
|
* python-setuptools
|
||||||
|
* numpy
|
||||||
|
|
||||||
|
In addition, you must install contextlib2 via pip.
|
||||||
|
|
||||||
|
A few tests still will not run, however, unless one of the following
|
||||||
|
combinations of RPMs / Python packages is installed.
|
||||||
|
|
||||||
|
* scikit-image and either Pillow or freeimage
|
||||||
|
* matplotlib and Pillow
|
||||||
|
|
||||||
|
scikit-image is not available in the Fedora 17 default repositories, but
|
||||||
|
it may be installed via pip::
|
||||||
|
|
||||||
|
$ yum install Cython # pip needs this in order to compile scikit-image
|
||||||
|
$ yum install python-devel # pip needs this in order to compile scikit-image
|
||||||
|
$ yum install freeimage # scikit-image uses this as a backend
|
||||||
|
$ yum install scipy # needed by scikit-image
|
||||||
|
$ pip-python install scikit-image --user
|
||||||
|
$ pip-python install contextlib2 --user
|
||||||
|
$ export PYTHONPATH=$HOME/.local/lib/python2.7/site-packages:$PYTHONPATH
|
||||||
|
|
||||||
|
Windows
|
||||||
|
-------
|
||||||
|
Not currently supported.
|
||||||
|
|
||||||
|
'''''''
|
||||||
|
Testing
|
||||||
|
'''''''
|
||||||
|
|
||||||
|
If you wish to run the tests (strongly suggested :-), try the following
|
||||||
|
|
||||||
|
$ cd /to/where/you/unpacked/glymur
|
||||||
|
$ python -m unittest discover
|
||||||
|
|
||||||
|
Quite a few tests are currently skipped. These include tests whose
|
||||||
|
OpenJPEG counterparts are already failing, and others which do pass but
|
||||||
|
still produce heaps of output on stderr. Rather than let this swamp
|
||||||
|
the signal (that most of the tests are actually passing), they've been
|
||||||
|
filtered out for now. There are also more skipped tests on Python 2.7
|
||||||
|
than on Python 3.3. The important part is whether or not any test
|
||||||
|
errors are reported at the end.
|
||||||
|
|
@ -3,6 +3,17 @@ How do I...?
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|
||||||
|
Read the lowest resolution thumbnail?
|
||||||
|
=====================================
|
||||||
|
Printing the Jp2k object should reveal the number of resolutions (look in the
|
||||||
|
COD segment section), but you can take a shortcut by supplying -1 as the reduce
|
||||||
|
level. ::
|
||||||
|
|
||||||
|
>>> import glymur
|
||||||
|
>>> file = glymur.data.nemo()
|
||||||
|
>>> j = glymur.Jp2k(file)
|
||||||
|
>>> thumbnail = j.read(reduce=-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
|
||||||
|
|
@ -12,9 +23,8 @@ available. ::
|
||||||
|
|
||||||
From within Python, it is as simple as printing the Jp2k object, i.e. ::
|
From within Python, it is as simple as printing the Jp2k object, i.e. ::
|
||||||
|
|
||||||
>>> import pkg_resources
|
|
||||||
>>> from glymur import Jp2k
|
>>> from glymur import Jp2k
|
||||||
>>> file = pkg_resources.resource_filename(glymur.__name__, "data/nemo.jp2")
|
>>> file = glymur.data.nemo()
|
||||||
>>> j = Jp2k(file)
|
>>> j = Jp2k(file)
|
||||||
>>> print(j)
|
>>> print(j)
|
||||||
|
|
||||||
|
|
@ -26,11 +36,10 @@ codestream box, only the main header is printed. It is possible to print
|
||||||
|
|
||||||
Work with XMP UUIDs?
|
Work with XMP UUIDs?
|
||||||
====================
|
====================
|
||||||
The example JP2 file shipped with glymur has an XMP UUID::
|
The example JP2 file shipped with glymur has an XMP UUID. ::
|
||||||
|
|
||||||
>>> import pkg_resources
|
|
||||||
>>> from glymur import Jp2k
|
>>> from glymur import Jp2k
|
||||||
>>> file = pkg_resources.resource_filename(glymur.__name__, "data/nemo.jp2")
|
>>> file = glymur.data.nemo()
|
||||||
>>> j = Jp2k(file)
|
>>> j = Jp2k(file)
|
||||||
>>> print(j.box[4])
|
>>> print(j.box[4])
|
||||||
UUID Box (uuid) @ (715, 2412)
|
UUID Box (uuid) @ (715, 2412)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ Contents:
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
introduction
|
introduction
|
||||||
|
detailed_installation
|
||||||
how_do_i
|
how_do_i
|
||||||
roadmap
|
roadmap
|
||||||
api
|
api
|
||||||
|
|
|
||||||
|
|
@ -19,189 +19,54 @@ some very limited support for reading JPX metadata. For instance,
|
||||||
**asoc** and **labl** boxes are recognized, so GMLJP2 metadata can
|
**asoc** and **labl** boxes are recognized, so GMLJP2 metadata can
|
||||||
be retrieved from such JPX files.
|
be retrieved from such JPX files.
|
||||||
|
|
||||||
''''''''''''
|
Glymur works on Python 2.7 and 3.3. Python 3.3 is strongly recommended.
|
||||||
Requirements
|
|
||||||
''''''''''''
|
|
||||||
glymur works on Python 2.7 and 3.3. Python 3.3 is strongly recommended.
|
|
||||||
|
|
||||||
OpenJPEG
|
OpenJPEG Installation
|
||||||
========
|
=====================
|
||||||
OpenJPEG must be built as a shared library. In addition, you
|
OpenJPEG must be built as a shared library. In addition, you
|
||||||
currently must compile OpenJPEG from the developmental source that
|
currently must compile OpenJPEG from the developmental source that
|
||||||
you can retrieve via subversion. As of this time of writing, svn
|
you can retrieve via subversion. As of this time of writing, svn
|
||||||
revision 2345 works. In addition, you should also retrieve their test data, as
|
revision 2345 works. You should download the test data for the purpose
|
||||||
you will need it when running glymur's test suite.
|
of configuring and running OpenJPEG's test suite, check their instructions for
|
||||||
|
all this. You should set the **OPJ_DATA_ROOT** environment variable for the
|
||||||
|
purpose of running Glymur's test suite. ::
|
||||||
|
|
||||||
|
$ svn co http://openjpeg.googlecode.com/svn/data
|
||||||
|
$ export OPJ_DATA_ROOT=`pwd`/data
|
||||||
|
|
||||||
Earlier versions of OpenJPEG through the 2.0 official release will **NOT**
|
Earlier versions of OpenJPEG through the 2.0 official release will **NOT**
|
||||||
work and are not supported.
|
work and are not supported.
|
||||||
|
|
||||||
Be sure to have the following ports/RPMs/debs installed.
|
Glymur uses ctypes (for the moment) to access the openjp2 library, and
|
||||||
|
|
||||||
* gcc
|
|
||||||
* gcc-c++
|
|
||||||
* cmake
|
|
||||||
|
|
||||||
You should build OpenJPEG with testing turned on. Consult the OpenJPEG
|
|
||||||
documentation on how to do this. If you use linux, make sure that you
|
|
||||||
have the following development packages installed
|
|
||||||
|
|
||||||
* zlib-devel
|
|
||||||
* png-devel
|
|
||||||
* libtiff-devel
|
|
||||||
* lcms2-devel
|
|
||||||
|
|
||||||
OS
|
|
||||||
==
|
|
||||||
|
|
||||||
Mac OS X
|
|
||||||
--------
|
|
||||||
All the necessary packages are available to use glymur with Python 3.3 via
|
|
||||||
MacPorts. A minimal set of ports includes
|
|
||||||
|
|
||||||
* python33
|
|
||||||
* py33-numpy
|
|
||||||
* py33-distribute
|
|
||||||
|
|
||||||
To run all the testing, one of the following combinations of ports must
|
|
||||||
additionally be installed:
|
|
||||||
|
|
||||||
* py33-scikit-image and either py33-Pillow or freeimage
|
|
||||||
* py33-matplotlib and py33-Pillow
|
|
||||||
|
|
||||||
Linux
|
|
||||||
-----
|
|
||||||
|
|
||||||
Fedora 18
|
|
||||||
'''''''''
|
|
||||||
Fedora 18 ships with Python 3.3, so all the necessary RPMs are available to
|
|
||||||
meet the minimal set of requirements.
|
|
||||||
|
|
||||||
* python3
|
|
||||||
* python3-numpy
|
|
||||||
* python3-setuptools
|
|
||||||
* python3-matplotlib (for running tests)
|
|
||||||
* python3-matplotlib-tk (or whichever matplotlib backend you prefer)
|
|
||||||
|
|
||||||
A few tests still will not run, however, unless one of the following
|
|
||||||
combinations of RPMs / Python packages is installed.
|
|
||||||
|
|
||||||
* scikit-image and either Pillow or freeimage
|
|
||||||
* matplotlib and Pillow
|
|
||||||
|
|
||||||
The 2nd route is probably the easiest, so go ahead and install Pillow
|
|
||||||
via pip since Pillow is not yet available in Fedora 18 default
|
|
||||||
repositories::
|
|
||||||
|
|
||||||
$ yum install python3-devel # pip needs this in order to compile Pillow
|
|
||||||
$ yum install python3-pip
|
|
||||||
$ pip-python3 install Pillow --user
|
|
||||||
$ export PYTHONPATH=$HOME/.local/lib/python3.3/site-packages:$PYTHONPATH
|
|
||||||
|
|
||||||
Fedora 17
|
|
||||||
'''''''''
|
|
||||||
Fedora 17 ships with Python 3.2 and 2.7, so these steps detail working with
|
|
||||||
2.7.
|
|
||||||
|
|
||||||
Required RPMs include::
|
|
||||||
|
|
||||||
* python
|
|
||||||
* python-mock
|
|
||||||
* python-pip
|
|
||||||
* python-setuptools
|
|
||||||
* numpy
|
|
||||||
|
|
||||||
In addition, you must install contextlib2 via pip.
|
|
||||||
|
|
||||||
A few tests still will not run, however, unless one of the following
|
|
||||||
combinations of RPMs / Python packages is installed.
|
|
||||||
|
|
||||||
* scikit-image and either Pillow or freeimage
|
|
||||||
* matplotlib and Pillow
|
|
||||||
|
|
||||||
scikit-image is not available in the Fedora 17 default repositories, but
|
|
||||||
it may be installed via pip::
|
|
||||||
|
|
||||||
$ yum install Cython # pip needs this in order to compile scikit-image
|
|
||||||
$ yum install python-devel # pip needs this in order to compile scikit-image
|
|
||||||
$ yum install freeimage # scikit-image uses this as a backend
|
|
||||||
$ yum install scipy # needed by scikit-image
|
|
||||||
$ pip-python install scikit-image --user
|
|
||||||
$ pip-python install contextlib2 --user
|
|
||||||
$ export PYTHONPATH=$HOME/.local/lib/python2.7/site-packages:$PYTHONPATH
|
|
||||||
|
|
||||||
Windows
|
|
||||||
-------
|
|
||||||
Not currently supported.
|
|
||||||
|
|
||||||
''''''''''''''''''''''''''''''''''''
|
|
||||||
Installation, Testing, Configuration
|
|
||||||
''''''''''''''''''''''''''''''''''''
|
|
||||||
|
|
||||||
From this point forward, python3 will be referred to as just "python".
|
|
||||||
|
|
||||||
Installation
|
|
||||||
============
|
|
||||||
|
|
||||||
You can install glymur via pip from the command line::
|
|
||||||
|
|
||||||
$ pip install glymur
|
|
||||||
|
|
||||||
or manually retrieve the code from either of GitHub or PyPI
|
|
||||||
|
|
||||||
* https://pypi.python.org/pypi/Glymur/ (stable releases)
|
|
||||||
* http://github.com/quintusdias/glymur (bleeding edge)
|
|
||||||
|
|
||||||
and then unpack and install with::
|
|
||||||
|
|
||||||
$ python setup.py install --prefix=/install/path
|
|
||||||
|
|
||||||
In addition to merely installing glymur, you should adjust your **$PATH**
|
|
||||||
environment variable in order to be able to use the *jp2dump* script from
|
|
||||||
the unix command line.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
$ export PYTHONPATH=/install/path/lib/python3.3/site-packages
|
|
||||||
$ export PATH=/install/path/bin:$PATH
|
|
||||||
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
=============
|
|
||||||
glymur uses ctypes (for the moment) to access the openjp2 library, and
|
|
||||||
because ctypes access libraries in a platform-dependent manner, it is
|
because ctypes access libraries in a platform-dependent manner, it is
|
||||||
recommended that you create a configuration file to help glymur properly find
|
recommended that you create a configuration file to help Glymur properly find
|
||||||
the openjp2 library. You may create the configuration file as follows::
|
the openjp2 library. You may create the configuration file as follows::
|
||||||
|
|
||||||
$ mkdir ~/.glymur
|
$ mkdir -p ~/.config/glymur
|
||||||
$ cd ~/.glymur
|
$ cd ~/.config/glymur
|
||||||
$ cat > glymurrc << EOF
|
$ cat > glymurrc << EOF
|
||||||
> [library]
|
> [library]
|
||||||
> openjp2: /opt/openjp2-svn/lib/libopenjp2.so
|
> openjp2: /opt/openjp2-svn/lib/libopenjp2.so
|
||||||
> EOF
|
> EOF
|
||||||
|
|
||||||
That assumes, of course, that you've installed OpenJPEG into /opt/openjp2-svn.
|
That assumes, of course, that you've installed OpenJPEG into /opt/openjp2-svn.
|
||||||
|
You may also replace **$HOME/.config** with **$XDG_CONFIG_HOME**.
|
||||||
|
|
||||||
|
Glymur Installation
|
||||||
|
===================
|
||||||
|
You can retrieve the source for Glymur from either of
|
||||||
|
|
||||||
Testing
|
* https://pypi.python.org/pypi/Glymur/ (stable releases)
|
||||||
=======
|
* http://github.com/quintusdias/glymur (bleeding edge)
|
||||||
In order to run all of the test suite, you will first need the OpenJPEG test
|
|
||||||
data that you previously retrieved.
|
|
||||||
Then you should set the **OPJ_DATA_ROOT** environment variable to
|
|
||||||
point to this directory, e.g. ::
|
|
||||||
|
|
||||||
$ cd /somewhere/outside/the/glymur/unpacking/directory
|
but you should now be able to get a functional installation of Glymur via
|
||||||
$ svn co http://openjpeg.googlecode.com/svn/data
|
pip ::
|
||||||
$ export OPJ_DATA_ROOT=`pwd`/data
|
|
||||||
|
|
||||||
The test suite may then be run with::
|
$ pip install glymur
|
||||||
|
|
||||||
$ cd /back/to/glymur/unpacking/directory
|
This will install the **jp2dump** script, so you should adjust your **$PATH**
|
||||||
$ python -m unittest discover
|
to take advantage of it. For example, if you install with pip's
|
||||||
|
`--user` option on linux ::
|
||||||
|
|
||||||
Quite a few tests are currently skipped. These include tests whose
|
$ export PYTHONPATH=$HOME/.local/lib/python3.3/site-packages
|
||||||
OpenJPEG counterparts are already failing, and others which do pass but
|
$ export PATH=$HOME/.local/bin:$PATH
|
||||||
still produce heaps of output on stderr. Rather than let this swamp
|
|
||||||
the signal (that most of the tests are actually passing), they've been
|
|
||||||
filtered out for now. There are also more skipped tests on Python 2.7
|
|
||||||
than on Python 3.3. The important part is whether or not any test
|
|
||||||
errors are reported at the end.
|
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ def _glymurrc_fname():
|
||||||
|
|
||||||
Search order:
|
Search order:
|
||||||
1) current working directory
|
1) current working directory
|
||||||
2) environ var GLYMURCONFIGDIR
|
2) environ var XDG_CONFIG_HOME
|
||||||
3) HOME/.glymur/glymurrc
|
3) $HOME/.config/glymur/glymurrc
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Current directory.
|
# Current directory.
|
||||||
|
|
@ -62,15 +62,15 @@ def _config():
|
||||||
def _get_configdir():
|
def _get_configdir():
|
||||||
"""Return string representing the configuration directory.
|
"""Return string representing the configuration directory.
|
||||||
|
|
||||||
Default is HOME/.glymur. You can override this with the GLYMURCONFIGDIR
|
Default is $HOME/.config/glymur. You can override this with the
|
||||||
environment variable.
|
XDG_CONFIG_HOME environment variable.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if 'GLYMURCONFIGDIR' in os.environ:
|
if 'XDG_CONFIG_HOME' in os.environ:
|
||||||
return os.environ['GLYMURCONFIGDIR']
|
return os.path.join(os.environ['XDG_CONFIG_HOME'], 'glymur')
|
||||||
|
|
||||||
if 'HOME' in os.environ:
|
if 'HOME' in os.environ:
|
||||||
return os.path.join(os.environ['HOME'], '.glymur')
|
return os.path.join(os.environ['HOME'], '.config', 'glymur')
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -88,4 +88,5 @@ _OPENJP2 = _config()
|
||||||
from .jp2k import Jp2k
|
from .jp2k import Jp2k
|
||||||
from .jp2dump import jp2dump
|
from .jp2dump import jp2dump
|
||||||
|
|
||||||
|
from . import data
|
||||||
from . import test
|
from . import test
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ for _marker in range(0xff90, 0xff94):
|
||||||
_valid_markers.append(_marker)
|
_valid_markers.append(_marker)
|
||||||
|
|
||||||
|
|
||||||
class Codestream:
|
class Codestream(object):
|
||||||
"""Container for codestream information.
|
"""Container for codestream information.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
|
|
@ -226,7 +226,7 @@ class Codestream:
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = 'Codestream:\n'
|
msg = 'Codestream:\n'
|
||||||
for segment in self.segment:
|
for segment in self.segment:
|
||||||
strs = segment.__str__()
|
strs = str(segment)
|
||||||
|
|
||||||
# Add indentation
|
# Add indentation
|
||||||
strs = [(' ' + x + '\n') for x in strs.split('\n')]
|
strs = [(' ' + x + '\n') for x in strs.split('\n')]
|
||||||
|
|
@ -869,7 +869,7 @@ class Codestream:
|
||||||
return TLMsegment(**kwargs)
|
return TLMsegment(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Segment:
|
class Segment(object):
|
||||||
"""Segment information.
|
"""Segment information.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
|
|
|
||||||
19
glymur/data/__init__.py
Normal file
19
glymur/data/__init__.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
"""Shipping JPEG 2000 files.
|
||||||
|
|
||||||
|
There is only one JP2 file at the moment, "nemo.jp2", converted from the
|
||||||
|
original JPEG photo of the aftermath of NEMO, the nor'easter that shutdown
|
||||||
|
Boston in February of 2013.
|
||||||
|
"""
|
||||||
|
import pkg_resources
|
||||||
|
|
||||||
|
|
||||||
|
def nemo():
|
||||||
|
"""Shortcut for specifying path to nemo.jp2.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
file : str
|
||||||
|
Platform-independent path to nemo.jp2.
|
||||||
|
"""
|
||||||
|
file = pkg_resources.resource_filename(__name__, "nemo.jp2")
|
||||||
|
return file
|
||||||
178
glymur/jp2box.py
178
glymur/jp2box.py
|
|
@ -4,14 +4,16 @@ References
|
||||||
----------
|
----------
|
||||||
.. [JP2K15444-1i] International Organization for Standardication. ISO/IEC
|
.. [JP2K15444-1i] International Organization for Standardication. ISO/IEC
|
||||||
15444-1:2004 - Information technology -- JPEG 2000 image coding system:
|
15444-1:2004 - Information technology -- JPEG 2000 image coding system:
|
||||||
Core coding system"
|
Core coding system
|
||||||
|
|
||||||
.. [JP2K15444-2m] International Organization for Standardication. ISO/IEC
|
.. [JP2K15444-2m] International Organization for Standardication. ISO/IEC
|
||||||
15444-2:2004 - Information technology -- JPEG 2000 image coding system:
|
15444-2:2004 - Information technology -- JPEG 2000 image coding system:
|
||||||
Extensions
|
Extensions
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import collections
|
||||||
import copy
|
import copy
|
||||||
|
import datetime
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import pprint
|
import pprint
|
||||||
|
|
@ -31,7 +33,7 @@ from .core import _method_display
|
||||||
from .core import _reader_requirements_display
|
from .core import _reader_requirements_display
|
||||||
|
|
||||||
|
|
||||||
class Jp2kBox:
|
class Jp2kBox(object):
|
||||||
"""Superclass for JPEG 2000 boxes.
|
"""Superclass for JPEG 2000 boxes.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
|
|
@ -153,9 +155,9 @@ class ColourSpecificationBox(Jp2kBox):
|
||||||
colorspace : int or None
|
colorspace : int or None
|
||||||
Enumerated colorspace, corresponds to one of 'sRGB', 'greyscale', or
|
Enumerated colorspace, corresponds to one of 'sRGB', 'greyscale', or
|
||||||
'YCC'. If not None, then icc_profile must be None.
|
'YCC'. If not None, then icc_profile must be None.
|
||||||
icc_profile : byte array or None
|
icc_profile : dict
|
||||||
ICC profile according to ICC profile specification. If not None, then
|
ICC profile header according to ICC profile specification. If
|
||||||
color_space must be None.
|
colorspace is not None, then icc_profile must be empty.
|
||||||
"""
|
"""
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
Jp2kBox.__init__(self, id='', longname='Colour Specification')
|
Jp2kBox.__init__(self, id='', longname='Colour Specification')
|
||||||
|
|
@ -174,8 +176,15 @@ class ColourSpecificationBox(Jp2kBox):
|
||||||
x = _colorspace_map_display[self.colorspace]
|
x = _colorspace_map_display[self.colorspace]
|
||||||
msg += '\n Colorspace: {0}'.format(x)
|
msg += '\n Colorspace: {0}'.format(x)
|
||||||
else:
|
else:
|
||||||
x = len(self.icc_profile)
|
# 2.7 has trouble pretty-printing ordered dicts so we just have
|
||||||
msg += '\n ICC Profile: {0} bytes'.format(x)
|
# to print as a regular dict in this case.
|
||||||
|
if sys.hexversion < 0x03000000:
|
||||||
|
icc_profile = dict(self.icc_profile)
|
||||||
|
else:
|
||||||
|
icc_profile = self.icc_profile
|
||||||
|
x = pprint.pformat(icc_profile)
|
||||||
|
lines = [' ' * 8 + y for y in x.split('\n')]
|
||||||
|
msg += '\n ICC Profile:\n{0}'.format('\n'.join(lines))
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
@ -221,12 +230,130 @@ class ColourSpecificationBox(Jp2kBox):
|
||||||
# ICC profile
|
# ICC profile
|
||||||
kwargs['colorspace'] = None
|
kwargs['colorspace'] = None
|
||||||
n = offset + length - f.tell()
|
n = offset + length - f.tell()
|
||||||
kwargs['icc_profile'] = f.read(n)
|
if n < 128:
|
||||||
|
msg = "ICC profile header is corrupt, length is "
|
||||||
|
msg += "only {0} instead of 128."
|
||||||
|
warnings.warn(msg.format(n), UserWarning)
|
||||||
|
kwargs['icc_profile'] = None
|
||||||
|
else:
|
||||||
|
icc_profile = _ICCProfile(f.read(n))
|
||||||
|
kwargs['icc_profile'] = icc_profile.header
|
||||||
|
|
||||||
box = ColourSpecificationBox(**kwargs)
|
box = ColourSpecificationBox(**kwargs)
|
||||||
return box
|
return box
|
||||||
|
|
||||||
|
|
||||||
|
class _ICCProfile(object):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
profile_class = {b'scnr': 'input device profile',
|
||||||
|
b'mntr': 'display device profile',
|
||||||
|
b'prtr': 'output device profile',
|
||||||
|
b'link': 'devicelink profile',
|
||||||
|
b'spac': 'colorspace conversion profile',
|
||||||
|
b'abst': 'abstract profile',
|
||||||
|
b'nmcl': 'name colour profile'}
|
||||||
|
|
||||||
|
colour_space_dict = {b'XYZ ': 'XYZ',
|
||||||
|
b'Lab ': 'Lab',
|
||||||
|
b'Luv ': 'Luv',
|
||||||
|
b'YCbr': 'YCbCr',
|
||||||
|
b'Yxy ': 'Yxy',
|
||||||
|
b'RGB ': 'RGB',
|
||||||
|
b'GRAY': 'gray',
|
||||||
|
b'HSV ': 'hsv',
|
||||||
|
b'HLS ': 'hls',
|
||||||
|
b'CMYK': 'CMYK',
|
||||||
|
b'CMY ': 'cmy',
|
||||||
|
b'2CLR': '2colour',
|
||||||
|
b'3CLR': '3colour',
|
||||||
|
b'4CLR': '4colour',
|
||||||
|
b'5CLR': '5colour',
|
||||||
|
b'6CLR': '6colour',
|
||||||
|
b'7CLR': '7colour',
|
||||||
|
b'8CLR': '8colour',
|
||||||
|
b'9CLR': '9colour',
|
||||||
|
b'ACLR': '10colour',
|
||||||
|
b'BCLR': '11colour',
|
||||||
|
b'CCLR': '12colour',
|
||||||
|
b'DCLR': '13colour',
|
||||||
|
b'ECLR': '14colour',
|
||||||
|
b'FCLR': '15colour'}
|
||||||
|
|
||||||
|
rendering_intent_dict = {0: 'perceptual',
|
||||||
|
1: 'media-relative colorimetric',
|
||||||
|
2: 'saturation',
|
||||||
|
3: 'ICC-absolute colorimetric'}
|
||||||
|
|
||||||
|
def __init__(self, buffer):
|
||||||
|
self._raw_buffer = buffer
|
||||||
|
header = collections.OrderedDict()
|
||||||
|
|
||||||
|
data = struct.unpack('>IIBB', self._raw_buffer[0:10])
|
||||||
|
header['Size'] = data[0]
|
||||||
|
header['Preferred CMM Type'] = data[1]
|
||||||
|
major = data[2]
|
||||||
|
minor = (data[3] & 0xf0) >> 4
|
||||||
|
bugfix = (data[3] & 0x0f)
|
||||||
|
header['Version'] = '{0}.{1}.{2}'.format(major, minor, bugfix)
|
||||||
|
|
||||||
|
header['Device Class'] = self.profile_class[self._raw_buffer[12:16]]
|
||||||
|
header['Color Space'] = self.colour_space_dict[self._raw_buffer[16:20]]
|
||||||
|
data = self.colour_space_dict[self._raw_buffer[20:24]]
|
||||||
|
header['Connection Space'] = data
|
||||||
|
|
||||||
|
data = struct.unpack('>HHHHHH', self._raw_buffer[24:36])
|
||||||
|
header['Datetime'] = datetime.datetime(*data)
|
||||||
|
header['File Signature'] = buffer[36:40].decode('utf-8')
|
||||||
|
if buffer[40:44] == b'\x00\x00\x00\x00':
|
||||||
|
header['Platform'] = 'unrecognized'
|
||||||
|
else:
|
||||||
|
header['Platform'] = buffer[40:44].decode('utf-8')
|
||||||
|
|
||||||
|
x, = struct.unpack('>I', buffer[44:48])
|
||||||
|
y = 'embedded, ' if x & 0x01 else 'not embedded, '
|
||||||
|
y += 'cannot ' if x & 0x02 else 'can '
|
||||||
|
y += 'be used independently'
|
||||||
|
header['Flags'] = y
|
||||||
|
|
||||||
|
header['Device Manufacturer'] = buffer[48:52].decode('utf-8')
|
||||||
|
if buffer[52:56] == b'\x00\x00\x00\x00':
|
||||||
|
device_model = ''
|
||||||
|
else:
|
||||||
|
device_model = buffer[52:56].decode('utf-8')
|
||||||
|
header['Device Model'] = device_model
|
||||||
|
|
||||||
|
x, = struct.unpack('>Q', buffer[56:64])
|
||||||
|
y = 'transparency, ' if x & 0x01 else 'reflective, '
|
||||||
|
y += 'matte, ' if x & 0x02 else 'glossy, '
|
||||||
|
y += 'negative ' if x & 0x04 else 'positive '
|
||||||
|
y += 'media polarity, '
|
||||||
|
y += 'black and white media' if x & 0x08 else 'color media'
|
||||||
|
header['Device Attributes'] = y
|
||||||
|
|
||||||
|
x, = struct.unpack('>I', buffer[64:68])
|
||||||
|
try:
|
||||||
|
header['Rendering Intent'] = self.rendering_intent_dict[x]
|
||||||
|
except KeyError:
|
||||||
|
header['Rendering Intent'] = 'unknown'
|
||||||
|
|
||||||
|
data = struct.unpack('>iii', buffer[68:80])
|
||||||
|
header['Illuminant'] = np.array(data, dtype=np.float64) / 65536
|
||||||
|
|
||||||
|
if buffer[80:84] == b'\x00\x00\x00\x00':
|
||||||
|
creator = 'unrecognized'
|
||||||
|
else:
|
||||||
|
creator = buffer[80:84].decode('utf-8')
|
||||||
|
header['Creator'] = creator
|
||||||
|
|
||||||
|
if header['Version'][0] == '4':
|
||||||
|
header['Profile Id'] = buffer[84:100]
|
||||||
|
|
||||||
|
# Final 27 bytes are reserved.
|
||||||
|
|
||||||
|
self.header = header
|
||||||
|
|
||||||
|
|
||||||
class ComponentDefinitionBox(Jp2kBox):
|
class ComponentDefinitionBox(Jp2kBox):
|
||||||
"""Container for component definition box information.
|
"""Container for component definition box information.
|
||||||
|
|
||||||
|
|
@ -405,7 +532,7 @@ class ContiguousCodestreamBox(Jp2kBox):
|
||||||
msg = Jp2kBox.__str__(self)
|
msg = Jp2kBox.__str__(self)
|
||||||
msg += '\n Main header:'
|
msg += '\n Main header:'
|
||||||
for segment in self.main_header.segment:
|
for segment in self.main_header.segment:
|
||||||
segstr = segment.__str__()
|
segstr = str(segment)
|
||||||
|
|
||||||
# Add indentation.
|
# Add indentation.
|
||||||
strs = [('\n ' + x) for x in segstr.split('\n')]
|
strs = [('\n ' + x) for x in segstr.split('\n')]
|
||||||
|
|
@ -633,7 +760,7 @@ class AssociationBox(Jp2kBox):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = Jp2kBox.__str__(self)
|
msg = Jp2kBox.__str__(self)
|
||||||
for box in self.box:
|
for box in self.box:
|
||||||
boxstr = box.__str__()
|
boxstr = str(box)
|
||||||
|
|
||||||
# Add indentation.
|
# Add indentation.
|
||||||
strs = [('\n ' + x) for x in boxstr.split('\n')]
|
strs = [('\n ' + x) for x in boxstr.split('\n')]
|
||||||
|
|
@ -697,7 +824,7 @@ class JP2HeaderBox(Jp2kBox):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = Jp2kBox.__str__(self)
|
msg = Jp2kBox.__str__(self)
|
||||||
for box in self.box:
|
for box in self.box:
|
||||||
boxstr = box.__str__()
|
boxstr = str(box)
|
||||||
|
|
||||||
# Add indentation.
|
# Add indentation.
|
||||||
strs = [('\n ' + x) for x in boxstr.split('\n')]
|
strs = [('\n ' + x) for x in boxstr.split('\n')]
|
||||||
|
|
@ -1050,7 +1177,7 @@ class ResolutionBox(Jp2kBox):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = Jp2kBox.__str__(self)
|
msg = Jp2kBox.__str__(self)
|
||||||
for box in self.box:
|
for box in self.box:
|
||||||
boxstr = box.__str__()
|
boxstr = str(box)
|
||||||
|
|
||||||
# Add indentation.
|
# Add indentation.
|
||||||
strs = [('\n ' + x) for x in boxstr.split('\n')]
|
strs = [('\n ' + x) for x in boxstr.split('\n')]
|
||||||
|
|
@ -1427,7 +1554,7 @@ class UUIDInfoBox(Jp2kBox):
|
||||||
msg = Jp2kBox.__str__(self)
|
msg = Jp2kBox.__str__(self)
|
||||||
|
|
||||||
for box in self.box:
|
for box in self.box:
|
||||||
box_str = box.__str__()
|
box_str = str(box)
|
||||||
|
|
||||||
# Add indentation.
|
# Add indentation.
|
||||||
lst = [('\n ' + x) for x in box_str.split('\n')]
|
lst = [('\n ' + x) for x in box_str.split('\n')]
|
||||||
|
|
@ -1556,9 +1683,15 @@ class UUIDBox(Jp2kBox):
|
||||||
more verbose description of the box.
|
more verbose description of the box.
|
||||||
uuid : uuid.UUID
|
uuid : uuid.UUID
|
||||||
16-byte UUID
|
16-byte UUID
|
||||||
data : bytes or dictionary or ElementTree.Element
|
data : bytes or dict or ElementTree.Element
|
||||||
Vendor-specific UUID data. Exif UUIDs are interpreted as dictionaries.
|
Vendor-specific data. Exif UUIDs are interpreted as dictionaries.
|
||||||
XMP UUIDs are interpreted as standard XML.
|
XMP UUIDs are interpreted as standard XML.
|
||||||
|
|
||||||
|
References
|
||||||
|
----------
|
||||||
|
.. [XMP] International Organization for Standardication. ISO/IEC
|
||||||
|
16684-1:2012 - Graphic technology -- Extensible metadata platform (XMP)
|
||||||
|
specification -- Part 1: Data model, serialization and core properties
|
||||||
"""
|
"""
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
Jp2kBox.__init__(self, id='', longname='UUID')
|
Jp2kBox.__init__(self, id='', longname='UUID')
|
||||||
|
|
@ -1574,7 +1707,10 @@ class UUIDBox(Jp2kBox):
|
||||||
uuid_data = _pretty_print_xml(self.data)
|
uuid_data = _pretty_print_xml(self.data)
|
||||||
elif self.uuid.bytes == b'JpgTiffExif->JP2':
|
elif self.uuid.bytes == b'JpgTiffExif->JP2':
|
||||||
uuid_type = ' (Exif)'
|
uuid_type = ' (Exif)'
|
||||||
uuid_data = '\n' + pprint.pformat(self.data)
|
# 2.7 has trouble pretty-printing ordered dicts, so print them
|
||||||
|
# as regular dicts. Not ideal, but at least it's good on 3.3+.
|
||||||
|
x = self.data if sys.hexversion >= 0x03000000 else dict(self.data)
|
||||||
|
uuid_data = '\n' + pprint.pformat(x)
|
||||||
else:
|
else:
|
||||||
uuid_type = ''
|
uuid_type = ''
|
||||||
uuid_data = '{0} bytes'.format(len(self.data))
|
uuid_data = '{0} bytes'.format(len(self.data))
|
||||||
|
|
@ -1625,7 +1761,7 @@ class UUIDBox(Jp2kBox):
|
||||||
kwargs['data'] = ET.fromstring(text)
|
kwargs['data'] = ET.fromstring(text)
|
||||||
elif kwargs['uuid'].bytes == b'JpgTiffExif->JP2':
|
elif kwargs['uuid'].bytes == b'JpgTiffExif->JP2':
|
||||||
e = Exif(buffer)
|
e = Exif(buffer)
|
||||||
d = {}
|
d = collections.OrderedDict()
|
||||||
d['Image'] = e.exif_image
|
d['Image'] = e.exif_image
|
||||||
d['Photo'] = e.exif_photo
|
d['Photo'] = e.exif_photo
|
||||||
d['GPSInfo'] = e.exif_gpsinfo
|
d['GPSInfo'] = e.exif_gpsinfo
|
||||||
|
|
@ -1637,7 +1773,7 @@ class UUIDBox(Jp2kBox):
|
||||||
return box
|
return box
|
||||||
|
|
||||||
|
|
||||||
class Exif:
|
class Exif(object):
|
||||||
"""
|
"""
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
|
|
@ -1690,7 +1826,7 @@ class Exif:
|
||||||
self.exif_gpsinfo = gps.processed_ifd
|
self.exif_gpsinfo = gps.processed_ifd
|
||||||
|
|
||||||
|
|
||||||
class _Ifd:
|
class _Ifd(object):
|
||||||
"""
|
"""
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
|
|
@ -1720,7 +1856,7 @@ class _Ifd:
|
||||||
def __init__(self, endian, buffer, offset):
|
def __init__(self, endian, buffer, offset):
|
||||||
self.endian = endian
|
self.endian = endian
|
||||||
self.buffer = buffer
|
self.buffer = buffer
|
||||||
self.processed_ifd = {}
|
self.processed_ifd = collections.OrderedDict()
|
||||||
|
|
||||||
self.num_tags, = struct.unpack(endian + 'H',
|
self.num_tags, = struct.unpack(endian + 'H',
|
||||||
buffer[offset:offset + 2])
|
buffer[offset:offset + 2])
|
||||||
|
|
@ -1728,7 +1864,7 @@ class _Ifd:
|
||||||
fmt = self.endian + 'HHII' * self.num_tags
|
fmt = self.endian + 'HHII' * self.num_tags
|
||||||
ifd_buffer = buffer[offset + 2:offset + 2 + self.num_tags * 12]
|
ifd_buffer = buffer[offset + 2:offset + 2 + self.num_tags * 12]
|
||||||
data = struct.unpack(fmt, ifd_buffer)
|
data = struct.unpack(fmt, ifd_buffer)
|
||||||
self.raw_ifd = {}
|
self.raw_ifd = collections.OrderedDict()
|
||||||
for j, tag in enumerate(data[0::4]):
|
for j, tag in enumerate(data[0::4]):
|
||||||
# The offset to the tag offset/payload is the offset to the IFD
|
# The offset to the tag offset/payload is the offset to the IFD
|
||||||
# plus 2 bytes for the number of tags plus 12 bytes for each
|
# plus 2 bytes for the number of tags plus 12 bytes for each
|
||||||
|
|
|
||||||
|
|
@ -87,10 +87,10 @@ class Jp2k(Jp2kBox):
|
||||||
metadata = ['File: ' + os.path.basename(self.filename)]
|
metadata = ['File: ' + os.path.basename(self.filename)]
|
||||||
if len(self.box) > 0:
|
if len(self.box) > 0:
|
||||||
for box in self.box:
|
for box in self.box:
|
||||||
metadata.append(box.__str__())
|
metadata.append(str(box))
|
||||||
else:
|
else:
|
||||||
c = self.get_codestream()
|
c = self.get_codestream()
|
||||||
metadata.append(c.__str__())
|
metadata.append(str(c))
|
||||||
return '\n'.join(metadata)
|
return '\n'.join(metadata)
|
||||||
|
|
||||||
def _parse(self):
|
def _parse(self):
|
||||||
|
|
@ -138,7 +138,7 @@ class Jp2k(Jp2kBox):
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
self.box = self._parse_superbox(f)
|
self.box = self._parse_superbox(f)
|
||||||
|
|
||||||
def write(self, data, cratios=None, eph=False, psnr=None, numres=None,
|
def write(self, img_array, cratios=None, eph=False, psnr=None, numres=None,
|
||||||
cbsize=None, psizes=None, grid_offset=None, sop=False,
|
cbsize=None, psizes=None, grid_offset=None, sop=False,
|
||||||
subsam=None, tilesize=None, prog=None, modesw=None,
|
subsam=None, tilesize=None, prog=None, modesw=None,
|
||||||
colorspace=None, verbose=False):
|
colorspace=None, verbose=False):
|
||||||
|
|
@ -150,7 +150,7 @@ class Jp2k(Jp2kBox):
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
data : array
|
img_array : ndarray
|
||||||
Image data to be written to file.
|
Image data to be written to file.
|
||||||
callbacks : bool, optional
|
callbacks : bool, optional
|
||||||
If true, enable default info handler such that INFO messages
|
If true, enable default info handler such that INFO messages
|
||||||
|
|
@ -197,8 +197,7 @@ class Jp2k(Jp2kBox):
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
>>> import glymur
|
>>> import glymur
|
||||||
>>> import pkg_resources as pkg
|
>>> jfile = glymur.data.nemo()
|
||||||
>>> jfile = pkg.resource_filename(glymur.__name__, "data/nemo.jp2")
|
|
||||||
>>> jp2 = glymur.Jp2k(jfile)
|
>>> jp2 = glymur.Jp2k(jfile)
|
||||||
>>> data = jp2.read(reduce=3)
|
>>> data = jp2.read(reduce=3)
|
||||||
>>> from tempfile import NamedTemporaryFile
|
>>> from tempfile import NamedTemporaryFile
|
||||||
|
|
@ -307,19 +306,19 @@ class Jp2k(Jp2kBox):
|
||||||
msg = "Cannot specify cratios and psnr together."
|
msg = "Cannot specify cratios and psnr together."
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
if data.ndim == 2:
|
if img_array.ndim == 2:
|
||||||
numrows, numcols = data.shape
|
numrows, numcols = img_array.shape
|
||||||
data = data.reshape(numrows, numcols, 1)
|
img_array = img_array.reshape(numrows, numcols, 1)
|
||||||
elif data.ndim == 3:
|
elif img_array.ndim == 3:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
msg = "{0}D imagery is not allowed.".format(data.ndim)
|
msg = "{0}D imagery is not allowed.".format(img_array.ndim)
|
||||||
raise IOError(msg)
|
raise IOError(msg)
|
||||||
|
|
||||||
numrows, numcols, num_comps = data.shape
|
numrows, numcols, num_comps = img_array.shape
|
||||||
|
|
||||||
if colorspace is None:
|
if colorspace is None:
|
||||||
if data.shape[2] == 1 or data.shape[2] == 2:
|
if img_array.shape[2] == 1 or img_array.shape[2] == 2:
|
||||||
colorspace = opj2._CLRSPC_GRAY
|
colorspace = opj2._CLRSPC_GRAY
|
||||||
else:
|
else:
|
||||||
# No YCC unless specifically told to do so.
|
# No YCC unless specifically told to do so.
|
||||||
|
|
@ -331,15 +330,15 @@ class Jp2k(Jp2kBox):
|
||||||
if colorspace not in ('rgb', 'grey', 'gray'):
|
if colorspace not in ('rgb', 'grey', 'gray'):
|
||||||
msg = 'Invalid colorspace "{0}"'.format(colorspace)
|
msg = 'Invalid colorspace "{0}"'.format(colorspace)
|
||||||
raise IOError(msg)
|
raise IOError(msg)
|
||||||
elif colorspace == 'rgb' and data.shape[2] < 3:
|
elif colorspace == 'rgb' and img_array.shape[2] < 3:
|
||||||
msg = 'RGB colorspace requires at least 3 components.'
|
msg = 'RGB colorspace requires at least 3 components.'
|
||||||
raise IOError(msg)
|
raise IOError(msg)
|
||||||
else:
|
else:
|
||||||
colorspace = _cspace_map[colorspace]
|
colorspace = _cspace_map[colorspace]
|
||||||
|
|
||||||
if data.dtype == np.uint8:
|
if img_array.dtype == np.uint8:
|
||||||
comp_prec = 8
|
comp_prec = 8
|
||||||
elif data.dtype == np.uint16:
|
elif img_array.dtype == np.uint16:
|
||||||
comp_prec = 16
|
comp_prec = 16
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("unhandled datatype")
|
raise RuntimeError("unhandled datatype")
|
||||||
|
|
@ -368,7 +367,7 @@ class Jp2k(Jp2kBox):
|
||||||
|
|
||||||
# Stage the image data to the openjpeg data structure.
|
# Stage the image data to the openjpeg data structure.
|
||||||
for k in range(0, num_comps):
|
for k in range(0, num_comps):
|
||||||
layer = np.ascontiguousarray(data[:, :, k], dtype=np.int32)
|
layer = np.ascontiguousarray(img_array[:, :, k], dtype=np.int32)
|
||||||
dest = image.contents.comps[k].data
|
dest = image.contents.comps[k].data
|
||||||
src = layer.ctypes.data
|
src = layer.ctypes.data
|
||||||
ctypes.memmove(dest, src, layer.nbytes)
|
ctypes.memmove(dest, src, layer.nbytes)
|
||||||
|
|
@ -407,7 +406,8 @@ class Jp2k(Jp2kBox):
|
||||||
layer : int, optional
|
layer : int, optional
|
||||||
Number of quality layer to decode.
|
Number of quality layer to decode.
|
||||||
reduce : int, optional
|
reduce : int, optional
|
||||||
Factor by which to reduce output resolution.
|
Factor by which to reduce output resolution. Use -1 to get the
|
||||||
|
lowest resolution thumbnail.
|
||||||
area : tuple, optional
|
area : tuple, optional
|
||||||
Specifies decoding image area,
|
Specifies decoding image area,
|
||||||
(first_row, first_col, last_row, last_col)
|
(first_row, first_col, last_row, last_col)
|
||||||
|
|
@ -418,7 +418,7 @@ class Jp2k(Jp2kBox):
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
result : array
|
img_array : ndarray
|
||||||
The image data.
|
The image data.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
|
|
@ -429,10 +429,17 @@ class Jp2k(Jp2kBox):
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
>>> import glymur
|
>>> import glymur
|
||||||
>>> import pkg_resources as pkg
|
>>> jfile = glymur.data.nemo()
|
||||||
>>> jfile = pkg.resource_filename(glymur.__name__, "data/nemo.jp2")
|
|
||||||
>>> jp = glymur.Jp2k(jfile)
|
>>> jp = glymur.Jp2k(jfile)
|
||||||
>>> data = jp.read(reduce=1)
|
>>> image = jp.read()
|
||||||
|
>>> image.shape
|
||||||
|
(1456, 2592, 3)
|
||||||
|
|
||||||
|
Read the lowest resolution thumbnail.
|
||||||
|
|
||||||
|
>>> thumbnail = jp.read(reduce=-1)
|
||||||
|
>>> thumbnail.shape
|
||||||
|
(46, 81, 3)
|
||||||
"""
|
"""
|
||||||
# Check for differing subsample factors.
|
# Check for differing subsample factors.
|
||||||
codestream = self.get_codestream(header_only=True)
|
codestream = self.get_codestream(header_only=True)
|
||||||
|
|
@ -442,18 +449,18 @@ class Jp2k(Jp2kBox):
|
||||||
msg = "Components must all have the same subsampling factors."
|
msg = "Components must all have the same subsampling factors."
|
||||||
raise IOError(msg)
|
raise IOError(msg)
|
||||||
|
|
||||||
data = self._read_common(reduce=reduce,
|
img_array = self._read_common(reduce=reduce,
|
||||||
layer=layer,
|
layer=layer,
|
||||||
area=area,
|
area=area,
|
||||||
tile=tile,
|
tile=tile,
|
||||||
verbose=verbose,
|
verbose=verbose,
|
||||||
as_bands=False)
|
as_bands=False)
|
||||||
|
|
||||||
if data.shape[2] == 1:
|
if img_array.shape[2] == 1:
|
||||||
data = data.view()
|
img_array = img_array.view()
|
||||||
data.shape = data.shape[0:2]
|
img_array.shape = img_array.shape[0:2]
|
||||||
|
|
||||||
return data
|
return img_array
|
||||||
|
|
||||||
def _read_common(self, reduce=0, layer=0, area=None, tile=None,
|
def _read_common(self, reduce=0, layer=0, area=None, tile=None,
|
||||||
verbose=False, as_bands=False):
|
verbose=False, as_bands=False):
|
||||||
|
|
@ -477,7 +484,7 @@ class Jp2k(Jp2kBox):
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
data : list or array
|
img_array : ndarray
|
||||||
The individual image components or a single array.
|
The individual image components or a single array.
|
||||||
"""
|
"""
|
||||||
dparam = opj2._set_default_decoder_parameters()
|
dparam = opj2._set_default_decoder_parameters()
|
||||||
|
|
@ -490,6 +497,12 @@ class Jp2k(Jp2kBox):
|
||||||
dparam.decod_format = self._codec_format
|
dparam.decod_format = self._codec_format
|
||||||
|
|
||||||
dparam.cp_layer = layer
|
dparam.cp_layer = layer
|
||||||
|
|
||||||
|
if reduce == -1:
|
||||||
|
# Get the lowest resolution thumbnail.
|
||||||
|
codestream = self.get_codestream()
|
||||||
|
reduce = codestream.segment[2].SPcod[4]
|
||||||
|
|
||||||
dparam.cp_reduce = reduce
|
dparam.cp_reduce = reduce
|
||||||
if area is not None:
|
if area is not None:
|
||||||
if area[0] < 0 or area[1] < 0:
|
if area[0] < 0 or area[1] < 0:
|
||||||
|
|
@ -618,8 +631,7 @@ class Jp2k(Jp2kBox):
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
>>> import glymur
|
>>> import glymur
|
||||||
>>> import pkg_resources as pkg
|
>>> jfile = glymur.data.nemo()
|
||||||
>>> jfile = pkg.resource_filename(glymur.__name__, "data/nemo.jp2")
|
|
||||||
>>> jp = glymur.Jp2k(jfile)
|
>>> jp = glymur.Jp2k(jfile)
|
||||||
>>> components_lst = jp.read_bands(reduce=1)
|
>>> components_lst = jp.read_bands(reduce=1)
|
||||||
"""
|
"""
|
||||||
|
|
@ -648,8 +660,7 @@ class Jp2k(Jp2kBox):
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
>>> import glymur
|
>>> import glymur
|
||||||
>>> import pkg_resources as pkg
|
>>> jfile = glymur.data.nemo()
|
||||||
>>> jfile = pkg.resource_filename(glymur.__name__, "data/nemo.jp2")
|
|
||||||
>>> jp = glymur.Jp2k(jfile)
|
>>> jp = glymur.Jp2k(jfile)
|
||||||
>>> codestream = jp.get_codestream()
|
>>> codestream = jp.get_codestream()
|
||||||
>>> print(codestream.segment[1])
|
>>> print(codestream.segment[1])
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
from .test_openjp2 import TestOpenJP2 as openjp2
|
#from .test_openjp2 import TestOpenJP2 as openjp2
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,14 @@ import numpy as np
|
||||||
import glymur
|
import glymur
|
||||||
|
|
||||||
|
|
||||||
# Doc tests should be run as well.
|
|
||||||
def load_tests(loader, tests, ignore):
|
def load_tests(loader, tests, ignore):
|
||||||
tests.addTests(doctest.DocTestSuite('glymur.lib.openjp2'))
|
if glymur.lib.openjp2._OPENJP2 is not None:
|
||||||
|
tests.addTests(doctest.DocTestSuite('glymur.lib.openjp2'))
|
||||||
return tests
|
return tests
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(glymur.lib.openjp2._OPENJP2 is None,
|
||||||
|
"Missing openjp2 library.")
|
||||||
class TestOpenJP2(unittest.TestCase):
|
class TestOpenJP2(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
from .test_callbacks import TestCallbacks as callbacks
|
from .test_callbacks import TestCallbacks as callbacks
|
||||||
from .test_codestream import TestCodestream as codestream
|
from .test_codestream import TestCodestream as codestream
|
||||||
from .test_jp2k import TestJp2k as jp2k
|
from .test_jp2k import TestJp2k as jp2k
|
||||||
|
from .test_icc import TestICC as icc
|
||||||
from .test_printing import TestPrinting as printing
|
from .test_printing import TestPrinting as printing
|
||||||
from .test_opj_suite import TestSuite as suite
|
from .test_opj_suite import TestSuite as suite
|
||||||
from .test_opj_suite_write import TestSuiteWrite as suitew
|
from .test_opj_suite_write import TestSuiteWrite as suitew
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,9 @@ else:
|
||||||
|
|
||||||
import glymur
|
import glymur
|
||||||
|
|
||||||
try:
|
|
||||||
data_root = os.environ['OPJ_DATA_ROOT']
|
|
||||||
except KeyError:
|
|
||||||
data_root = None
|
|
||||||
except:
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(glymur.lib.openjp2._OPENJP2 is None,
|
||||||
|
"Missing openjp2 library.")
|
||||||
class TestCallbacks(unittest.TestCase):
|
class TestCallbacks(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
||||||
73
glymur/test/test_icc.py
Normal file
73
glymur/test/test_icc.py
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
import warnings
|
||||||
|
from xml.etree import cElementTree as ET
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pkg_resources
|
||||||
|
|
||||||
|
from glymur import Jp2k
|
||||||
|
import glymur
|
||||||
|
|
||||||
|
try:
|
||||||
|
data_root = os.environ['OPJ_DATA_ROOT']
|
||||||
|
except KeyError:
|
||||||
|
data_root = None
|
||||||
|
except:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(data_root is None,
|
||||||
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
|
class TestICC(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_file5(self):
|
||||||
|
filename = os.path.join(data_root, 'input/conformance/file5.jp2')
|
||||||
|
j = Jp2k(filename)
|
||||||
|
profile = j.box[3].box[1].icc_profile
|
||||||
|
self.assertEqual(profile['Size'], 546)
|
||||||
|
self.assertEqual(profile['Preferred CMM Type'], 0)
|
||||||
|
self.assertEqual(profile['Version'], '2.2.0')
|
||||||
|
self.assertEqual(profile['Device Class'], 'input device profile')
|
||||||
|
self.assertEqual(profile['Color Space'], 'RGB')
|
||||||
|
self.assertEqual(profile['Datetime'],
|
||||||
|
datetime.datetime(2001, 8, 30, 13, 32, 37))
|
||||||
|
self.assertEqual(profile['File Signature'], 'acsp')
|
||||||
|
self.assertEqual(profile['Platform'], 'unrecognized')
|
||||||
|
self.assertEqual(profile['Flags'],
|
||||||
|
'embedded, can be used independently')
|
||||||
|
|
||||||
|
self.assertEqual(profile['Device Manufacturer'], 'KODA')
|
||||||
|
self.assertEqual(profile['Device Model'], 'ROMM')
|
||||||
|
|
||||||
|
self.assertEqual(profile['Device Attributes'],
|
||||||
|
'reflective, glossy, positive media polarity, '
|
||||||
|
+ 'color media')
|
||||||
|
self.assertEqual(profile['Rendering Intent'], 'perceptual')
|
||||||
|
|
||||||
|
np.testing.assert_almost_equal(profile['Illuminant'],
|
||||||
|
(0.964203, 1.000000, 0.824905),
|
||||||
|
decimal=6)
|
||||||
|
|
||||||
|
self.assertEqual(profile['Creator'], 'JPEG')
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.hexversion < 0x03020000,
|
||||||
|
"Uses features introduced in 3.2.")
|
||||||
|
def test_invalid_profile_header(self):
|
||||||
|
jfile = os.path.join(data_root,
|
||||||
|
'input/nonregression/orb-blue10-lin-jp2.jp2')
|
||||||
|
with self.assertWarns(UserWarning) as cw:
|
||||||
|
j = Jp2k(jfile)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
|
|
@ -34,26 +34,19 @@ except:
|
||||||
|
|
||||||
# Doc tests should be run as well.
|
# Doc tests should be run as well.
|
||||||
def load_tests(loader, tests, ignore):
|
def load_tests(loader, tests, ignore):
|
||||||
tests.addTests(doctest.DocTestSuite('glymur.jp2k'))
|
if glymur.lib.openjp2._OPENJP2 is not None:
|
||||||
|
tests.addTests(doctest.DocTestSuite('glymur.jp2k'))
|
||||||
return tests
|
return tests
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@unittest.skipIf(glymur.lib.openjp2._OPENJP2 is None,
|
||||||
def chdir(dirname=None):
|
"Missing openjp2 library.")
|
||||||
curdir = os.getcwd()
|
|
||||||
try:
|
|
||||||
if dirname is not None:
|
|
||||||
os.chdir(dirname)
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
os.chdir(curdir)
|
|
||||||
|
|
||||||
|
|
||||||
class TestJp2k(unittest.TestCase):
|
class TestJp2k(unittest.TestCase):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
# Setup a JP2 file with a bad XML box.
|
# Setup a JP2 file with a bad XML box. We only need to do this once
|
||||||
|
# per class rather than once per test.
|
||||||
jp2file = pkg_resources.resource_filename(glymur.__name__,
|
jp2file = pkg_resources.resource_filename(glymur.__name__,
|
||||||
"data/nemo.jp2")
|
"data/nemo.jp2")
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile:
|
with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile:
|
||||||
|
|
@ -92,10 +85,18 @@ class TestJp2k(unittest.TestCase):
|
||||||
"Uses features introduced in 3.2.")
|
"Uses features introduced in 3.2.")
|
||||||
def test_invalid_xml_box_warning(self):
|
def test_invalid_xml_box_warning(self):
|
||||||
# Should be able to recover from xml box with bad xml.
|
# Should be able to recover from xml box with bad xml.
|
||||||
# Just verify that a warning is issued on 3.2+
|
# Just verify that a warning is issued on 3.3+
|
||||||
with self.assertWarns(UserWarning) as cw:
|
with self.assertWarns(UserWarning) as cw:
|
||||||
jp2k = Jp2k(self._bad_xml_file)
|
jp2k = Jp2k(self._bad_xml_file)
|
||||||
|
|
||||||
|
def test_reduce_max(self):
|
||||||
|
# Verify that reduce=-1 gets us the lowest resolution image
|
||||||
|
j = Jp2k(self.jp2file)
|
||||||
|
thumbnail1 = j.read(reduce=-1)
|
||||||
|
thumbnail2 = j.read(reduce=5)
|
||||||
|
np.testing.assert_array_equal(thumbnail1, thumbnail2)
|
||||||
|
self.assertEqual(thumbnail1.shape, (46, 81, 3))
|
||||||
|
|
||||||
def test_invalid_xml_box(self):
|
def test_invalid_xml_box(self):
|
||||||
# Should be able to recover from xml box with bad xml.
|
# Should be able to recover from xml box with bad xml.
|
||||||
with warnings.catch_warnings():
|
with warnings.catch_warnings():
|
||||||
|
|
@ -598,13 +599,15 @@ class TestJp2k(unittest.TestCase):
|
||||||
def test_config_file_via_environ(self):
|
def test_config_file_via_environ(self):
|
||||||
"""Verify that we can read a configuration file set via environ var."""
|
"""Verify that we can read a configuration file set via environ var."""
|
||||||
with tempfile.TemporaryDirectory() as tdir:
|
with tempfile.TemporaryDirectory() as tdir:
|
||||||
filename = os.path.join(tdir, 'glymurrc')
|
configdir = os.path.join(tdir, 'glymur')
|
||||||
|
os.mkdir(configdir)
|
||||||
|
filename = os.path.join(configdir, 'glymurrc')
|
||||||
with open(filename, 'wb') as tfile:
|
with open(filename, 'wb') as tfile:
|
||||||
tfile.write('[library]\n'.encode())
|
tfile.write('[library]\n'.encode())
|
||||||
line = 'openjp2: {0}\n'.format(glymur._OPENJP2._name)
|
line = 'openjp2: {0}\n'.format(glymur._OPENJP2._name)
|
||||||
tfile.write(line.encode())
|
tfile.write(line.encode())
|
||||||
tfile.flush()
|
tfile.flush()
|
||||||
with patch.dict('os.environ', {'GLYMURCONFIGDIR': tdir}):
|
with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
|
||||||
imp.reload(glymur)
|
imp.reload(glymur)
|
||||||
j = Jp2k(self.jp2file)
|
j = Jp2k(self.jp2file)
|
||||||
|
|
||||||
|
|
@ -613,13 +616,15 @@ class TestJp2k(unittest.TestCase):
|
||||||
def test_config_file_via_environ_is_wrong(self):
|
def test_config_file_via_environ_is_wrong(self):
|
||||||
# A non-existant library location should be rejected.
|
# A non-existant library location should be rejected.
|
||||||
with tempfile.TemporaryDirectory() as tdir:
|
with tempfile.TemporaryDirectory() as tdir:
|
||||||
fname = os.path.join(tdir, 'glymurrc')
|
configdir = os.path.join(tdir, 'glymur')
|
||||||
|
os.mkdir(configdir)
|
||||||
|
fname = os.path.join(configdir, 'glymurrc')
|
||||||
with open(fname, 'w') as fp:
|
with open(fname, 'w') as fp:
|
||||||
with tempfile.NamedTemporaryFile(suffix='.dylib') as tfile:
|
with tempfile.NamedTemporaryFile(suffix='.dylib') as tfile:
|
||||||
fp.write('[library]\n')
|
fp.write('[library]\n')
|
||||||
fp.write('openjp2: {0}.not.there\n'.format(tfile.name))
|
fp.write('openjp2: {0}.not.there\n'.format(tfile.name))
|
||||||
fp.flush()
|
fp.flush()
|
||||||
with patch.dict('os.environ', {'GLYMURCONFIGDIR': tdir}):
|
with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
|
||||||
# Misconfigured new configuration file should
|
# Misconfigured new configuration file should
|
||||||
# be rejected.
|
# be rejected.
|
||||||
with self.assertWarns(UserWarning) as cw:
|
with self.assertWarns(UserWarning) as cw:
|
||||||
|
|
@ -631,7 +636,7 @@ class TestJp2k(unittest.TestCase):
|
||||||
# Verify that we error out properly if the configuration file
|
# Verify that we error out properly if the configuration file
|
||||||
# specified via environment variable is not found.
|
# specified via environment variable is not found.
|
||||||
with tempfile.TemporaryDirectory() as tdir:
|
with tempfile.TemporaryDirectory() as tdir:
|
||||||
with patch.dict('os.environ', {'GLYMURCONFIGDIR': tdir}):
|
with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
|
||||||
# Misconfigured new configuration file should
|
# Misconfigured new configuration file should
|
||||||
# be rejected.
|
# be rejected.
|
||||||
with self.assertWarns(UserWarning) as cw:
|
with self.assertWarns(UserWarning) as cw:
|
||||||
|
|
@ -640,7 +645,7 @@ class TestJp2k(unittest.TestCase):
|
||||||
@unittest.skipIf(sys.hexversion < 0x03020000,
|
@unittest.skipIf(sys.hexversion < 0x03020000,
|
||||||
"Uses features introduced in 3.2.")
|
"Uses features introduced in 3.2.")
|
||||||
def test_home_dir_missing_config_dir(self):
|
def test_home_dir_missing_config_dir(self):
|
||||||
# Verify no exception is raised if $HOME is missing .glymur directory.
|
# Verify no exception is raised if $HOME is missing .config directory.
|
||||||
with tempfile.TemporaryDirectory() as tdir:
|
with tempfile.TemporaryDirectory() as tdir:
|
||||||
with patch.dict('os.environ', {'HOME': tdir}):
|
with patch.dict('os.environ', {'HOME': tdir}):
|
||||||
# Misconfigured new configuration file should
|
# Misconfigured new configuration file should
|
||||||
|
|
@ -648,6 +653,24 @@ class TestJp2k(unittest.TestCase):
|
||||||
with self.assertWarns(UserWarning) as cw:
|
with self.assertWarns(UserWarning) as cw:
|
||||||
imp.reload(glymur)
|
imp.reload(glymur)
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.hexversion < 0x03020000,
|
||||||
|
"Uses features introduced in 3.2.")
|
||||||
|
def test_home_dir_missing_glymur_rc_dir(self):
|
||||||
|
# Should warn but not error if $HOME/.config but no glymurrc dir.
|
||||||
|
with tempfile.TemporaryDirectory() as tdir:
|
||||||
|
# We need the subdirectory to be specifically named as ".config"
|
||||||
|
# in order for this test to work. A specifically-named temporary
|
||||||
|
# directory does not seem to be possible, so try to symlink it.
|
||||||
|
# Supposedly the symlink gets cleaned up with tdir gets cleaned up.
|
||||||
|
with tempfile.TemporaryDirectory(suffix=".config", dir=tdir) \
|
||||||
|
as tdir_config:
|
||||||
|
os.symlink(tdir_config, os.path.join(tdir, '.config'))
|
||||||
|
with patch.dict('os.environ', {'HOME': tdir}):
|
||||||
|
# Misconfigured new configuration file should
|
||||||
|
# be rejected.
|
||||||
|
with self.assertWarns(UserWarning) as cw:
|
||||||
|
imp.reload(glymur)
|
||||||
|
|
||||||
def test_xmp_attribute(self):
|
def test_xmp_attribute(self):
|
||||||
# Verify that we can read the XMP packet in our shipping example file.
|
# Verify that we can read the XMP packet in our shipping example file.
|
||||||
j = Jp2k(self.jp2file)
|
j = Jp2k(self.jp2file)
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -57,6 +57,8 @@ def read_image(infile):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(glymur.lib.openjp2._OPENJP2 is None,
|
||||||
|
"Missing openjp2 library.")
|
||||||
@unittest.skipIf(no_read_backend, no_read_backend_msg)
|
@unittest.skipIf(no_read_backend, no_read_backend_msg)
|
||||||
@unittest.skipIf(data_root is None,
|
@unittest.skipIf(data_root is None,
|
||||||
"OPJ_DATA_ROOT environment variable not set")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,8 @@ def read_image(infile):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(glymur.lib.openjp2._OPENJP2 is None,
|
||||||
|
"Missing openjp2 library.")
|
||||||
@unittest.skipIf(no_read_backend, no_read_backend_msg)
|
@unittest.skipIf(no_read_backend, no_read_backend_msg)
|
||||||
@unittest.skipIf(data_root is None,
|
@unittest.skipIf(data_root is None,
|
||||||
"OPJ_DATA_ROOT environment variable not set")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,11 @@ except:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
class TestPrinting(unittest.TestCase):
|
@unittest.skipIf(glymur.lib.openjp2._OPENJP2 is None,
|
||||||
|
"Missing openjp2 library.")
|
||||||
|
class TestPrintingNeedsLib(unittest.TestCase):
|
||||||
|
"""These tests require the library, mostly in order to just setup the test.
|
||||||
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
|
|
@ -40,11 +44,11 @@ class TestPrinting(unittest.TestCase):
|
||||||
os.unlink(cls._plain_nemo_file)
|
os.unlink(cls._plain_nemo_file)
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
self.jp2file = pkg_resources.resource_filename(glymur.__name__,
|
||||||
|
"data/nemo.jp2")
|
||||||
# Save sys.stdout.
|
# Save sys.stdout.
|
||||||
self.stdout = sys.stdout
|
self.stdout = sys.stdout
|
||||||
sys.stdout = StringIO()
|
sys.stdout = StringIO()
|
||||||
self.jp2file = pkg_resources.resource_filename(glymur.__name__,
|
|
||||||
"data/nemo.jp2")
|
|
||||||
|
|
||||||
# Save the output of dumping nemo.jp2 for more than one test.
|
# Save the output of dumping nemo.jp2 for more than one test.
|
||||||
lines = ['JPEG 2000 Signature Box (jP ) @ (0, 12)',
|
lines = ['JPEG 2000 Signature Box (jP ) @ (0, 12)',
|
||||||
|
|
@ -115,6 +119,57 @@ class TestPrinting(unittest.TestCase):
|
||||||
# Restore stdout.
|
# Restore stdout.
|
||||||
sys.stdout = self.stdout
|
sys.stdout = self.stdout
|
||||||
|
|
||||||
|
def test_asoc_label_box(self):
|
||||||
|
# Construct a fake file with an asoc and a label box, as
|
||||||
|
# OpenJPEG doesn't have such a file.
|
||||||
|
data = glymur.Jp2k(self.jp2file).read(reduce=3)
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
j = glymur.Jp2k(tfile.name, 'wb')
|
||||||
|
j.write(data)
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2:
|
||||||
|
|
||||||
|
# Offset of the codestream is where we start.
|
||||||
|
buffer = tfile.read(77)
|
||||||
|
tfile2.write(buffer)
|
||||||
|
|
||||||
|
# read the rest of the file, it's the codestream.
|
||||||
|
codestream = tfile.read()
|
||||||
|
|
||||||
|
# Write the asoc superbox.
|
||||||
|
# Length = 36, id is 'asoc'.
|
||||||
|
buffer = struct.pack('>I4s', int(56), b'asoc')
|
||||||
|
tfile2.write(buffer)
|
||||||
|
|
||||||
|
# Write the contained label box
|
||||||
|
buffer = struct.pack('>I4s', int(13), b'lbl ')
|
||||||
|
tfile2.write(buffer)
|
||||||
|
tfile2.write('label'.encode())
|
||||||
|
|
||||||
|
# Write the xml box
|
||||||
|
# Length = 36, id is 'xml '.
|
||||||
|
buffer = struct.pack('>I4s', int(35), b'xml ')
|
||||||
|
tfile2.write(buffer)
|
||||||
|
|
||||||
|
buffer = '<test>this is a test</test>'
|
||||||
|
buffer = buffer.encode()
|
||||||
|
tfile2.write(buffer)
|
||||||
|
|
||||||
|
# Now append the codestream.
|
||||||
|
tfile2.write(codestream)
|
||||||
|
tfile2.flush()
|
||||||
|
|
||||||
|
jasoc = glymur.Jp2k(tfile2.name)
|
||||||
|
print(jasoc.box[3])
|
||||||
|
actual = sys.stdout.getvalue().strip()
|
||||||
|
lines = ['Association Box (asoc) @ (77, 56)',
|
||||||
|
' Label Box (lbl ) @ (85, 13)',
|
||||||
|
' Label: label',
|
||||||
|
' XML Box (xml ) @ (98, 35)',
|
||||||
|
' <test>this is a test</test>']
|
||||||
|
expected = '\n'.join(lines)
|
||||||
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
def test_jp2dump(self):
|
def test_jp2dump(self):
|
||||||
glymur.jp2dump(self._plain_nemo_file)
|
glymur.jp2dump(self._plain_nemo_file)
|
||||||
actual = sys.stdout.getvalue().strip()
|
actual = sys.stdout.getvalue().strip()
|
||||||
|
|
@ -126,6 +181,32 @@ class TestPrinting(unittest.TestCase):
|
||||||
|
|
||||||
self.assertEqual(actual, self.expectedPlain)
|
self.assertEqual(actual, self.expectedPlain)
|
||||||
|
|
||||||
|
def test_entire_file(self):
|
||||||
|
j = glymur.Jp2k(self._plain_nemo_file)
|
||||||
|
print(j)
|
||||||
|
actual = sys.stdout.getvalue().strip()
|
||||||
|
|
||||||
|
# Get rid of the filename line, as it is not set in stone.
|
||||||
|
lst = actual.split('\n')
|
||||||
|
lst = lst[1:]
|
||||||
|
actual = '\n'.join(lst)
|
||||||
|
|
||||||
|
self.assertEqual(actual, self.expectedPlain)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPrinting(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
# Save sys.stdout.
|
||||||
|
self.stdout = sys.stdout
|
||||||
|
sys.stdout = StringIO()
|
||||||
|
self.jp2file = pkg_resources.resource_filename(glymur.__name__,
|
||||||
|
"data/nemo.jp2")
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
# Restore stdout.
|
||||||
|
sys.stdout = self.stdout
|
||||||
|
|
||||||
def test_COC_segment(self):
|
def test_COC_segment(self):
|
||||||
j = glymur.Jp2k(self.jp2file)
|
j = glymur.Jp2k(self.jp2file)
|
||||||
codestream = j.get_codestream(header_only=False)
|
codestream = j.get_codestream(header_only=False)
|
||||||
|
|
@ -186,6 +267,70 @@ class TestPrinting(unittest.TestCase):
|
||||||
self.expected = expected
|
self.expected = expected
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
@unittest.skipIf(data_root is None,
|
||||||
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
|
def test_icc_profile(self):
|
||||||
|
filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2')
|
||||||
|
j = glymur.Jp2k(filename)
|
||||||
|
print(j.box[3].box[1])
|
||||||
|
actual = sys.stdout.getvalue().strip()
|
||||||
|
lin27 = ["Colour Specification Box (colr) @ (179, 1339)",
|
||||||
|
" Method: any ICC profile",
|
||||||
|
" Precedence: 2",
|
||||||
|
" Approximation: accurately represents correct "
|
||||||
|
+ "colorspace definition",
|
||||||
|
" ICC Profile:",
|
||||||
|
" {'Color Space': 'RGB',",
|
||||||
|
" 'Connection Space': 'XYZ',",
|
||||||
|
" 'Creator': u'appl',",
|
||||||
|
" 'Datetime': "
|
||||||
|
+ "datetime.datetime(2009, 2, 25, 11, 26, 11),",
|
||||||
|
" 'Device Attributes': 'reflective, glossy, "
|
||||||
|
+ "positive media polarity, color media',",
|
||||||
|
" 'Device Class': 'display device profile',",
|
||||||
|
" 'Device Manufacturer': u'appl',",
|
||||||
|
" 'Device Model': '',",
|
||||||
|
" 'File Signature': u'acsp',",
|
||||||
|
" 'Flags': "
|
||||||
|
+ "'not embedded, can be used independently',",
|
||||||
|
" 'Illuminant': "
|
||||||
|
+ "array([ 0.96420288, 1. , 0.8249054 ]),",
|
||||||
|
" 'Platform': u'APPL',",
|
||||||
|
" 'Preferred CMM Type': 1634758764,",
|
||||||
|
" 'Rendering Intent': 'perceptual',",
|
||||||
|
" 'Size': 1328,",
|
||||||
|
" 'Version': '2.2.0'}"]
|
||||||
|
lin33 = ["Colour Specification Box (colr) @ (179, 1339)",
|
||||||
|
" Method: any ICC profile",
|
||||||
|
" Precedence: 2",
|
||||||
|
" Approximation: accurately represents correct "
|
||||||
|
+ "colorspace definition",
|
||||||
|
" ICC Profile:",
|
||||||
|
" {'Size': 1328,",
|
||||||
|
" 'Preferred CMM Type': 1634758764,",
|
||||||
|
" 'Version': '2.2.0',",
|
||||||
|
" 'Device Class': 'display device profile',",
|
||||||
|
" 'Color Space': 'RGB',",
|
||||||
|
" 'Connection Space': 'XYZ',",
|
||||||
|
" 'Datetime': "
|
||||||
|
+ "datetime.datetime(2009, 2, 25, 11, 26, 11),",
|
||||||
|
" 'File Signature': 'acsp',",
|
||||||
|
" 'Platform': 'APPL',",
|
||||||
|
" 'Flags': 'not embedded, can be used "
|
||||||
|
+ "independently',",
|
||||||
|
" 'Device Manufacturer': 'appl',",
|
||||||
|
" 'Device Model': '',",
|
||||||
|
" 'Device Attributes': 'reflective, glossy, "
|
||||||
|
+ "positive media polarity, color media',",
|
||||||
|
" 'Rendering Intent': 'perceptual',",
|
||||||
|
" 'Illuminant': "
|
||||||
|
+ "array([ 0.96420288, 1. , 0.8249054 ]),",
|
||||||
|
" 'Creator': 'appl'}"]
|
||||||
|
|
||||||
|
lines = lin27 if sys.hexversion < 0x03000000 else lin33
|
||||||
|
expected = '\n'.join(lines)
|
||||||
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
@unittest.skipIf(data_root is None,
|
@unittest.skipIf(data_root is None,
|
||||||
"OPJ_DATA_ROOT environment variable not set")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_CRG(self):
|
def test_CRG(self):
|
||||||
|
|
@ -452,18 +597,6 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lst)
|
expected = '\n'.join(lst)
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
def test_entire_file(self):
|
|
||||||
j = glymur.Jp2k(self._plain_nemo_file)
|
|
||||||
print(j)
|
|
||||||
actual = sys.stdout.getvalue().strip()
|
|
||||||
|
|
||||||
# Get rid of the filename line, as it is not set in stone.
|
|
||||||
lst = actual.split('\n')
|
|
||||||
lst = lst[1:]
|
|
||||||
actual = '\n'.join(lst)
|
|
||||||
|
|
||||||
self.assertEqual(actual, self.expectedPlain)
|
|
||||||
|
|
||||||
def test_codestream(self):
|
def test_codestream(self):
|
||||||
j = glymur.Jp2k(self.jp2file)
|
j = glymur.Jp2k(self.jp2file)
|
||||||
print(j.get_codestream())
|
print(j.get_codestream())
|
||||||
|
|
@ -651,57 +784,6 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
def test_asoc_label_box(self):
|
|
||||||
# Construct a fake file with an asoc and a label box, as
|
|
||||||
# OpenJPEG doesn't have such a file.
|
|
||||||
data = glymur.Jp2k(self.jp2file).read(reduce=3)
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
j = glymur.Jp2k(tfile.name, 'wb')
|
|
||||||
j.write(data)
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2:
|
|
||||||
|
|
||||||
# Offset of the codestream is where we start.
|
|
||||||
buffer = tfile.read(77)
|
|
||||||
tfile2.write(buffer)
|
|
||||||
|
|
||||||
# read the rest of the file, it's the codestream.
|
|
||||||
codestream = tfile.read()
|
|
||||||
|
|
||||||
# Write the asoc superbox.
|
|
||||||
# Length = 36, id is 'asoc'.
|
|
||||||
buffer = struct.pack('>I4s', int(56), b'asoc')
|
|
||||||
tfile2.write(buffer)
|
|
||||||
|
|
||||||
# Write the contained label box
|
|
||||||
buffer = struct.pack('>I4s', int(13), b'lbl ')
|
|
||||||
tfile2.write(buffer)
|
|
||||||
tfile2.write('label'.encode())
|
|
||||||
|
|
||||||
# Write the xml box
|
|
||||||
# Length = 36, id is 'xml '.
|
|
||||||
buffer = struct.pack('>I4s', int(35), b'xml ')
|
|
||||||
tfile2.write(buffer)
|
|
||||||
|
|
||||||
buffer = '<test>this is a test</test>'
|
|
||||||
buffer = buffer.encode()
|
|
||||||
tfile2.write(buffer)
|
|
||||||
|
|
||||||
# Now append the codestream.
|
|
||||||
tfile2.write(codestream)
|
|
||||||
tfile2.flush()
|
|
||||||
|
|
||||||
jasoc = glymur.Jp2k(tfile2.name)
|
|
||||||
print(jasoc.box[3])
|
|
||||||
actual = sys.stdout.getvalue().strip()
|
|
||||||
lines = ['Association Box (asoc) @ (77, 56)',
|
|
||||||
' Label Box (lbl ) @ (85, 13)',
|
|
||||||
' Label: label',
|
|
||||||
' XML Box (xml ) @ (98, 35)',
|
|
||||||
' <test>this is a test</test>']
|
|
||||||
expected = '\n'.join(lines)
|
|
||||||
self.assertEqual(actual, expected)
|
|
||||||
|
|
||||||
def test_less_common_boxes(self):
|
def test_less_common_boxes(self):
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
with open(self.jp2file, 'rb') as ifile:
|
with open(self.jp2file, 'rb') as ifile:
|
||||||
|
|
@ -775,6 +857,8 @@ 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,
|
||||||
|
"Ordered dicts not printing well in 2.7")
|
||||||
@unittest.skipIf(data_root is None,
|
@unittest.skipIf(data_root is None,
|
||||||
"OPJ_DATA_ROOT environment variable not set")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_jpx_approximation_with_icc_profile(self):
|
def test_jpx_approximation_with_icc_profile(self):
|
||||||
|
|
@ -785,12 +869,32 @@ class TestPrinting(unittest.TestCase):
|
||||||
|
|
||||||
print(j.box[3].box[1])
|
print(j.box[3].box[1])
|
||||||
actual = sys.stdout.getvalue().strip()
|
actual = sys.stdout.getvalue().strip()
|
||||||
lines = ['Colour Specification Box (colr) @ (179, 1339)',
|
lines = ["Colour Specification Box (colr) @ (179, 1339)",
|
||||||
' Method: any ICC profile',
|
" Method: any ICC profile",
|
||||||
' Precedence: 2',
|
" Precedence: 2",
|
||||||
' Approximation: accurately represents '
|
" Approximation: accurately represents "
|
||||||
+ 'correct colorspace definition',
|
+ "correct colorspace definition",
|
||||||
' ICC Profile: 1328 bytes']
|
" ICC Profile:",
|
||||||
|
" {'Size': 1328,",
|
||||||
|
" 'Preferred CMM Type': 1634758764,",
|
||||||
|
" 'Version': '2.2.0',",
|
||||||
|
" 'Device Class': 'display device profile',",
|
||||||
|
" 'Color Space': 'RGB',",
|
||||||
|
" 'Connection Space': 'XYZ',",
|
||||||
|
" 'Datetime': "
|
||||||
|
+ "datetime.datetime(2009, 2, 25, 11, 26, 11),",
|
||||||
|
" 'File Signature': 'acsp',",
|
||||||
|
" 'Platform': 'APPL',",
|
||||||
|
" 'Flags': 'not embedded, "
|
||||||
|
+ "can be used independently',",
|
||||||
|
" 'Device Manufacturer': 'appl',",
|
||||||
|
" 'Device Model': '',",
|
||||||
|
" 'Device Attributes': 'reflective, glossy, "
|
||||||
|
+ "positive media polarity, color media',",
|
||||||
|
" 'Rendering Intent': 'perceptual',",
|
||||||
|
" 'Illuminant': array([ 0.96420288, 1. ,"
|
||||||
|
+ " 0.8249054 ]),",
|
||||||
|
" 'Creator': 'appl'}"]
|
||||||
|
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
@ -811,6 +915,8 @@ 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,
|
||||||
|
"Ordered dicts not printing well in 2.7")
|
||||||
def test_exif_uuid(self):
|
def test_exif_uuid(self):
|
||||||
j = glymur.Jp2k(self.jp2file)
|
j = glymur.Jp2k(self.jp2file)
|
||||||
|
|
||||||
|
|
@ -820,13 +926,33 @@ class TestPrinting(unittest.TestCase):
|
||||||
lines = ["UUID Box (uuid) @ (77, 638)",
|
lines = ["UUID Box (uuid) @ (77, 638)",
|
||||||
" UUID: 4a706754-6966-6645-7869-662d3e4a5032 (Exif)",
|
" UUID: 4a706754-6966-6645-7869-662d3e4a5032 (Exif)",
|
||||||
" UUID Data: ",
|
" UUID Data: ",
|
||||||
"{'GPSInfo': {'GPSAltitude': 0.0,",
|
"{'Image': {'Make': 'HTC',",
|
||||||
" 'GPSAltitudeRef': 0,",
|
" 'Model': 'HTC Glacier',",
|
||||||
" 'GPSDateStamp': '2013:02:09',",
|
" 'XResolution': 72.0,",
|
||||||
" 'GPSLatitude': [42.0, 20.0, 33.61],",
|
" 'YResolution': 72.0,",
|
||||||
|
" 'ResolutionUnit': 2,",
|
||||||
|
" 'YCbCrPositioning': 1,",
|
||||||
|
" 'ExifTag': 138,",
|
||||||
|
" 'GPSTag': 354},",
|
||||||
|
" 'Photo': {'ISOSpeedRatings': 76,",
|
||||||
|
" 'ExifVersion': (48, 50, 50, 48),",
|
||||||
|
" 'DateTimeOriginal': '2013:02:09 14:47:53',",
|
||||||
|
" 'DateTimeDigitized': '2013:02:09 14:47:53',",
|
||||||
|
" 'ComponentsConfiguration': (1, 2, 3, 0),",
|
||||||
|
" 'FocalLength': 3.53,",
|
||||||
|
" 'FlashpixVersion': (48, 49, 48, 48),",
|
||||||
|
" 'ColorSpace': 1,",
|
||||||
|
" 'PixelXDimension': 2528,",
|
||||||
|
" 'PixelYDimension': 1424,",
|
||||||
|
" 'InteroperabilityTag': 324},",
|
||||||
|
" 'GPSInfo': {'GPSVersionID': (2, 2, 0),",
|
||||||
" 'GPSLatitudeRef': 'N',",
|
" 'GPSLatitudeRef': 'N',",
|
||||||
" 'GPSLongitude': [71.0, 5.0, 17.32],",
|
" 'GPSLatitude': [42.0, 20.0, 33.61],",
|
||||||
" 'GPSLongitudeRef': 'W',",
|
" 'GPSLongitudeRef': 'W',",
|
||||||
|
" 'GPSLongitude': [71.0, 5.0, 17.32],",
|
||||||
|
" 'GPSAltitudeRef': 0,",
|
||||||
|
" 'GPSAltitude': 0.0,",
|
||||||
|
" 'GPSTimeStamp': [19.0, 47.0, 53.0],",
|
||||||
" 'GPSMapDatum': 'WGS-84',",
|
" 'GPSMapDatum': 'WGS-84',",
|
||||||
" 'GPSProcessingMethod': (65,",
|
" 'GPSProcessingMethod': (65,",
|
||||||
" 83,",
|
" 83,",
|
||||||
|
|
@ -843,28 +969,8 @@ class TestPrinting(unittest.TestCase):
|
||||||
" 79,",
|
" 79,",
|
||||||
" 82,",
|
" 82,",
|
||||||
" 75),",
|
" 75),",
|
||||||
" 'GPSTimeStamp': [19.0, 47.0, 53.0],",
|
" 'GPSDateStamp': '2013:02:09'},",
|
||||||
" 'GPSVersionID': (2, 2, 0)},",
|
" 'Iop': None}"]
|
||||||
" 'Image': {'ExifTag': 138,",
|
|
||||||
" 'GPSTag': 354,",
|
|
||||||
" 'Make': 'HTC',",
|
|
||||||
" 'Model': 'HTC Glacier',",
|
|
||||||
" 'ResolutionUnit': 2,",
|
|
||||||
" 'XResolution': 72.0,",
|
|
||||||
" 'YCbCrPositioning': 1,",
|
|
||||||
" 'YResolution': 72.0},",
|
|
||||||
" 'Iop': None,",
|
|
||||||
" 'Photo': {'ColorSpace': 1,",
|
|
||||||
" 'ComponentsConfiguration': (1, 2, 3, 0),",
|
|
||||||
" 'DateTimeDigitized': '2013:02:09 14:47:53',",
|
|
||||||
" 'DateTimeOriginal': '2013:02:09 14:47:53',",
|
|
||||||
" 'ExifVersion': (48, 50, 50, 48),",
|
|
||||||
" 'FlashpixVersion': (48, 49, 48, 48),",
|
|
||||||
" 'FocalLength': 3.53,",
|
|
||||||
" 'ISOSpeedRatings': 76,",
|
|
||||||
" 'InteroperabilityTag': 324,",
|
|
||||||
" 'PixelXDimension': 2528,",
|
|
||||||
" 'PixelYDimension': 1424}}"]
|
|
||||||
|
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
|
|
||||||
|
|
|
||||||
14
setup.py
14
setup.py
|
|
@ -1,18 +1,26 @@
|
||||||
from distutils.core import setup
|
from setuptools import setup, find_packages
|
||||||
|
import sys
|
||||||
|
|
||||||
kwargs = {'name': 'Glymur',
|
kwargs = {'name': 'Glymur',
|
||||||
'version': '0.1.7',
|
'version': '0.1.9',
|
||||||
'description': 'Tools for accessing JPEG2000 files',
|
'description': 'Tools for accessing JPEG2000 files',
|
||||||
'long_description': open('README.md').read(),
|
'long_description': open('README.md').read(),
|
||||||
'author': 'John Evans',
|
'author': 'John Evans',
|
||||||
'author_email': 'johnevans938 at gmail dot com',
|
'author_email': 'johnevans938 at gmail dot com',
|
||||||
'url': 'https://github.com/quintusdias/glymur',
|
'url': 'https://github.com/quintusdias/glymur',
|
||||||
'packages': ['glymur', 'glymur.test', 'glymur.lib',
|
'packages': ['glymur', 'glymur.data', 'glymur.test', 'glymur.lib',
|
||||||
'glymur.lib.test'],
|
'glymur.lib.test'],
|
||||||
'package_data': {'glymur': ['data/*.jp2']},
|
'package_data': {'glymur': ['data/*.jp2']},
|
||||||
'scripts': ['bin/jp2dump'],
|
'scripts': ['bin/jp2dump'],
|
||||||
'license': 'LICENSE.txt',
|
'license': 'LICENSE.txt',
|
||||||
'platforms': ['darwin']}
|
'platforms': ['darwin']}
|
||||||
|
|
||||||
|
instllrqrs = ['numpy>1.6.2']
|
||||||
|
if sys.hexversion < 0x03030000:
|
||||||
|
instllrqrs.append('contextlib2>=0.4')
|
||||||
|
instllrqrs.append('mock>=1.0.1')
|
||||||
|
kwargs['install_requires'] = instllrqrs
|
||||||
|
|
||||||
clssfrs = ["Programming Language :: Python",
|
clssfrs = ["Programming Language :: Python",
|
||||||
"Programming Language :: Python :: 2.7",
|
"Programming Language :: Python :: 2.7",
|
||||||
"Programming Language :: Python :: 3.3",
|
"Programming Language :: Python :: 3.3",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue