Merge branch 'devel'

This commit is contained in:
jevans 2013-06-20 19:26:48 -04:00
commit 22450ddd55
23 changed files with 1475 additions and 1083 deletions

View file

@ -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',
'Iop', 'GPSInfo'] to ['Image', 'Photo', 'Iop', 'GPSInfo'].

View file

@ -1,4 +1,3 @@
include *.txt *.md
recursive-include *.txt *.py
prune build
exclude readthedocs-pip-requirements.txt

View file

@ -78,7 +78,7 @@ copyright = u'2013, John Evans'
# The short X.Y version.
version = '0.1'
# 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
# for a list of supported languages.

View 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.

View file

@ -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?
=================
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. ::
>>> import pkg_resources
>>> from glymur import Jp2k
>>> file = pkg_resources.resource_filename(glymur.__name__, "data/nemo.jp2")
>>> file = glymur.data.nemo()
>>> j = Jp2k(file)
>>> print(j)
@ -26,11 +36,10 @@ codestream box, only the main header is printed. It is possible to print
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
>>> file = pkg_resources.resource_filename(glymur.__name__, "data/nemo.jp2")
>>> file = glymur.data.nemo()
>>> j = Jp2k(file)
>>> print(j.box[4])
UUID Box (uuid) @ (715, 2412)

View file

@ -13,6 +13,7 @@ Contents:
:maxdepth: 2
introduction
detailed_installation
how_do_i
roadmap
api

View file

@ -19,189 +19,54 @@ some very limited support for reading JPX metadata. For instance,
**asoc** and **labl** boxes are recognized, so GMLJP2 metadata can
be retrieved from such JPX files.
''''''''''''
Requirements
''''''''''''
glymur works on Python 2.7 and 3.3. Python 3.3 is strongly recommended.
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
currently must compile OpenJPEG from the developmental source that
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
you will need it when running glymur's test suite.
revision 2345 works. You should download the test data for the purpose
of configuring and running OpenJPEG's test suite, check their instructions for
all this. You should set the **OPJ_DATA_ROOT** environment variable for the
purpose of running Glymur's test suite. ::
$ 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**
work and are not supported.
Be sure to have the following ports/RPMs/debs installed.
* 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
Glymur uses ctypes (for the moment) to access the openjp2 library, and
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::
$ mkdir ~/.glymur
$ cd ~/.glymur
$ mkdir -p ~/.config/glymur
$ cd ~/.config/glymur
$ cat > glymurrc << EOF
> [library]
> openjp2: /opt/openjp2-svn/lib/libopenjp2.so
> EOF
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
=======
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. ::
* https://pypi.python.org/pypi/Glymur/ (stable releases)
* http://github.com/quintusdias/glymur (bleeding edge)
$ cd /somewhere/outside/the/glymur/unpacking/directory
$ svn co http://openjpeg.googlecode.com/svn/data
$ export OPJ_DATA_ROOT=`pwd`/data
but you should now be able to get a functional installation of Glymur via
pip ::
The test suite may then be run with::
$ pip install glymur
$ cd /back/to/glymur/unpacking/directory
$ python -m unittest discover
This will install the **jp2dump** script, so you should adjust your **$PATH**
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
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.
$ export PYTHONPATH=$HOME/.local/lib/python3.3/site-packages
$ export PATH=$HOME/.local/bin:$PATH

View file

@ -7,8 +7,8 @@ def _glymurrc_fname():
Search order:
1) current working directory
2) environ var GLYMURCONFIGDIR
3) HOME/.glymur/glymurrc
2) environ var XDG_CONFIG_HOME
3) $HOME/.config/glymur/glymurrc
"""
# Current directory.
@ -62,15 +62,15 @@ def _config():
def _get_configdir():
"""Return string representing the configuration directory.
Default is HOME/.glymur. You can override this with the GLYMURCONFIGDIR
environment variable.
Default is $HOME/.config/glymur. You can override this with the
XDG_CONFIG_HOME environment variable.
"""
if 'GLYMURCONFIGDIR' in os.environ:
return os.environ['GLYMURCONFIGDIR']
if 'XDG_CONFIG_HOME' in os.environ:
return os.path.join(os.environ['XDG_CONFIG_HOME'], 'glymur')
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 sys
@ -88,4 +88,5 @@ _OPENJP2 = _config()
from .jp2k import Jp2k
from .jp2dump import jp2dump
from . import data
from . import test

View file

@ -29,7 +29,7 @@ for _marker in range(0xff90, 0xff94):
_valid_markers.append(_marker)
class Codestream:
class Codestream(object):
"""Container for codestream information.
Attributes
@ -226,7 +226,7 @@ class Codestream:
def __str__(self):
msg = 'Codestream:\n'
for segment in self.segment:
strs = segment.__str__()
strs = str(segment)
# Add indentation
strs = [(' ' + x + '\n') for x in strs.split('\n')]
@ -869,7 +869,7 @@ class Codestream:
return TLMsegment(**kwargs)
class Segment:
class Segment(object):
"""Segment information.
Attributes

19
glymur/data/__init__.py Normal file
View 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

View file

@ -4,14 +4,16 @@ References
----------
.. [JP2K15444-1i] International Organization for Standardication. ISO/IEC
15444-1:2004 - Information technology -- JPEG 2000 image coding system:
Core coding system"
Core coding system
.. [JP2K15444-2m] International Organization for Standardication. ISO/IEC
15444-2:2004 - Information technology -- JPEG 2000 image coding system:
Extensions
"""
import collections
import copy
import datetime
import math
import os
import pprint
@ -31,7 +33,7 @@ from .core import _method_display
from .core import _reader_requirements_display
class Jp2kBox:
class Jp2kBox(object):
"""Superclass for JPEG 2000 boxes.
Attributes
@ -153,9 +155,9 @@ class ColourSpecificationBox(Jp2kBox):
colorspace : int or None
Enumerated colorspace, corresponds to one of 'sRGB', 'greyscale', or
'YCC'. If not None, then icc_profile must be None.
icc_profile : byte array or None
ICC profile according to ICC profile specification. If not None, then
color_space must be None.
icc_profile : dict
ICC profile header according to ICC profile specification. If
colorspace is not None, then icc_profile must be empty.
"""
def __init__(self, **kwargs):
Jp2kBox.__init__(self, id='', longname='Colour Specification')
@ -174,8 +176,15 @@ class ColourSpecificationBox(Jp2kBox):
x = _colorspace_map_display[self.colorspace]
msg += '\n Colorspace: {0}'.format(x)
else:
x = len(self.icc_profile)
msg += '\n ICC Profile: {0} bytes'.format(x)
# 2.7 has trouble pretty-printing ordered dicts so we just have
# 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
@ -221,12 +230,130 @@ class ColourSpecificationBox(Jp2kBox):
# ICC profile
kwargs['colorspace'] = None
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)
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):
"""Container for component definition box information.
@ -405,7 +532,7 @@ class ContiguousCodestreamBox(Jp2kBox):
msg = Jp2kBox.__str__(self)
msg += '\n Main header:'
for segment in self.main_header.segment:
segstr = segment.__str__()
segstr = str(segment)
# Add indentation.
strs = [('\n ' + x) for x in segstr.split('\n')]
@ -633,7 +760,7 @@ class AssociationBox(Jp2kBox):
def __str__(self):
msg = Jp2kBox.__str__(self)
for box in self.box:
boxstr = box.__str__()
boxstr = str(box)
# Add indentation.
strs = [('\n ' + x) for x in boxstr.split('\n')]
@ -697,7 +824,7 @@ class JP2HeaderBox(Jp2kBox):
def __str__(self):
msg = Jp2kBox.__str__(self)
for box in self.box:
boxstr = box.__str__()
boxstr = str(box)
# Add indentation.
strs = [('\n ' + x) for x in boxstr.split('\n')]
@ -1050,7 +1177,7 @@ class ResolutionBox(Jp2kBox):
def __str__(self):
msg = Jp2kBox.__str__(self)
for box in self.box:
boxstr = box.__str__()
boxstr = str(box)
# Add indentation.
strs = [('\n ' + x) for x in boxstr.split('\n')]
@ -1427,7 +1554,7 @@ class UUIDInfoBox(Jp2kBox):
msg = Jp2kBox.__str__(self)
for box in self.box:
box_str = box.__str__()
box_str = str(box)
# Add indentation.
lst = [('\n ' + x) for x in box_str.split('\n')]
@ -1556,9 +1683,15 @@ class UUIDBox(Jp2kBox):
more verbose description of the box.
uuid : uuid.UUID
16-byte UUID
data : bytes or dictionary or ElementTree.Element
Vendor-specific UUID data. Exif UUIDs are interpreted as dictionaries.
data : bytes or dict or ElementTree.Element
Vendor-specific data. Exif UUIDs are interpreted as dictionaries.
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):
Jp2kBox.__init__(self, id='', longname='UUID')
@ -1574,7 +1707,10 @@ class UUIDBox(Jp2kBox):
uuid_data = _pretty_print_xml(self.data)
elif self.uuid.bytes == b'JpgTiffExif->JP2':
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:
uuid_type = ''
uuid_data = '{0} bytes'.format(len(self.data))
@ -1625,7 +1761,7 @@ class UUIDBox(Jp2kBox):
kwargs['data'] = ET.fromstring(text)
elif kwargs['uuid'].bytes == b'JpgTiffExif->JP2':
e = Exif(buffer)
d = {}
d = collections.OrderedDict()
d['Image'] = e.exif_image
d['Photo'] = e.exif_photo
d['GPSInfo'] = e.exif_gpsinfo
@ -1637,7 +1773,7 @@ class UUIDBox(Jp2kBox):
return box
class Exif:
class Exif(object):
"""
Attributes
----------
@ -1690,7 +1826,7 @@ class Exif:
self.exif_gpsinfo = gps.processed_ifd
class _Ifd:
class _Ifd(object):
"""
Attributes
----------
@ -1720,7 +1856,7 @@ class _Ifd:
def __init__(self, endian, buffer, offset):
self.endian = endian
self.buffer = buffer
self.processed_ifd = {}
self.processed_ifd = collections.OrderedDict()
self.num_tags, = struct.unpack(endian + 'H',
buffer[offset:offset + 2])
@ -1728,7 +1864,7 @@ class _Ifd:
fmt = self.endian + 'HHII' * self.num_tags
ifd_buffer = buffer[offset + 2:offset + 2 + self.num_tags * 12]
data = struct.unpack(fmt, ifd_buffer)
self.raw_ifd = {}
self.raw_ifd = collections.OrderedDict()
for j, tag in enumerate(data[0::4]):
# 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

View file

@ -87,10 +87,10 @@ class Jp2k(Jp2kBox):
metadata = ['File: ' + os.path.basename(self.filename)]
if len(self.box) > 0:
for box in self.box:
metadata.append(box.__str__())
metadata.append(str(box))
else:
c = self.get_codestream()
metadata.append(c.__str__())
metadata.append(str(c))
return '\n'.join(metadata)
def _parse(self):
@ -138,7 +138,7 @@ class Jp2k(Jp2kBox):
f.seek(0)
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,
subsam=None, tilesize=None, prog=None, modesw=None,
colorspace=None, verbose=False):
@ -150,7 +150,7 @@ class Jp2k(Jp2kBox):
Parameters
----------
data : array
img_array : ndarray
Image data to be written to file.
callbacks : bool, optional
If true, enable default info handler such that INFO messages
@ -197,8 +197,7 @@ class Jp2k(Jp2kBox):
Examples
--------
>>> import glymur
>>> import pkg_resources as pkg
>>> jfile = pkg.resource_filename(glymur.__name__, "data/nemo.jp2")
>>> jfile = glymur.data.nemo()
>>> jp2 = glymur.Jp2k(jfile)
>>> data = jp2.read(reduce=3)
>>> from tempfile import NamedTemporaryFile
@ -307,19 +306,19 @@ class Jp2k(Jp2kBox):
msg = "Cannot specify cratios and psnr together."
raise RuntimeError(msg)
if data.ndim == 2:
numrows, numcols = data.shape
data = data.reshape(numrows, numcols, 1)
elif data.ndim == 3:
if img_array.ndim == 2:
numrows, numcols = img_array.shape
img_array = img_array.reshape(numrows, numcols, 1)
elif img_array.ndim == 3:
pass
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)
numrows, numcols, num_comps = data.shape
numrows, numcols, num_comps = img_array.shape
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
else:
# No YCC unless specifically told to do so.
@ -331,15 +330,15 @@ class Jp2k(Jp2kBox):
if colorspace not in ('rgb', 'grey', 'gray'):
msg = 'Invalid colorspace "{0}"'.format(colorspace)
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.'
raise IOError(msg)
else:
colorspace = _cspace_map[colorspace]
if data.dtype == np.uint8:
if img_array.dtype == np.uint8:
comp_prec = 8
elif data.dtype == np.uint16:
elif img_array.dtype == np.uint16:
comp_prec = 16
else:
raise RuntimeError("unhandled datatype")
@ -368,7 +367,7 @@ class Jp2k(Jp2kBox):
# Stage the image data to the openjpeg data structure.
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
src = layer.ctypes.data
ctypes.memmove(dest, src, layer.nbytes)
@ -407,7 +406,8 @@ class Jp2k(Jp2kBox):
layer : int, optional
Number of quality layer to decode.
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
Specifies decoding image area,
(first_row, first_col, last_row, last_col)
@ -418,7 +418,7 @@ class Jp2k(Jp2kBox):
Returns
-------
result : array
img_array : ndarray
The image data.
Raises
@ -429,10 +429,17 @@ class Jp2k(Jp2kBox):
Examples
--------
>>> import glymur
>>> import pkg_resources as pkg
>>> jfile = pkg.resource_filename(glymur.__name__, "data/nemo.jp2")
>>> jfile = glymur.data.nemo()
>>> 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.
codestream = self.get_codestream(header_only=True)
@ -442,18 +449,18 @@ class Jp2k(Jp2kBox):
msg = "Components must all have the same subsampling factors."
raise IOError(msg)
data = self._read_common(reduce=reduce,
layer=layer,
area=area,
tile=tile,
verbose=verbose,
as_bands=False)
img_array = self._read_common(reduce=reduce,
layer=layer,
area=area,
tile=tile,
verbose=verbose,
as_bands=False)
if data.shape[2] == 1:
data = data.view()
data.shape = data.shape[0:2]
if img_array.shape[2] == 1:
img_array = img_array.view()
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,
verbose=False, as_bands=False):
@ -477,7 +484,7 @@ class Jp2k(Jp2kBox):
Returns
-------
data : list or array
img_array : ndarray
The individual image components or a single array.
"""
dparam = opj2._set_default_decoder_parameters()
@ -490,6 +497,12 @@ class Jp2k(Jp2kBox):
dparam.decod_format = self._codec_format
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
if area is not None:
if area[0] < 0 or area[1] < 0:
@ -618,8 +631,7 @@ class Jp2k(Jp2kBox):
Examples
--------
>>> import glymur
>>> import pkg_resources as pkg
>>> jfile = pkg.resource_filename(glymur.__name__, "data/nemo.jp2")
>>> jfile = glymur.data.nemo()
>>> jp = glymur.Jp2k(jfile)
>>> components_lst = jp.read_bands(reduce=1)
"""
@ -648,8 +660,7 @@ class Jp2k(Jp2kBox):
Examples
--------
>>> import glymur
>>> import pkg_resources as pkg
>>> jfile = pkg.resource_filename(glymur.__name__, "data/nemo.jp2")
>>> jfile = glymur.data.nemo()
>>> jp = glymur.Jp2k(jfile)
>>> codestream = jp.get_codestream()
>>> print(codestream.segment[1])

View file

@ -1 +1 @@
from .test_openjp2 import TestOpenJP2 as openjp2
#from .test_openjp2 import TestOpenJP2 as openjp2

View file

@ -12,12 +12,14 @@ import numpy as np
import glymur
# Doc tests should be run as well.
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
@unittest.skipIf(glymur.lib.openjp2._OPENJP2 is None,
"Missing openjp2 library.")
class TestOpenJP2(unittest.TestCase):
def setUp(self):

View file

@ -1,6 +1,7 @@
from .test_callbacks import TestCallbacks as callbacks
from .test_codestream import TestCodestream as codestream
from .test_jp2k import TestJp2k as jp2k
from .test_icc import TestICC as icc
from .test_printing import TestPrinting as printing
from .test_opj_suite import TestSuite as suite
from .test_opj_suite_write import TestSuiteWrite as suitew

View file

@ -12,14 +12,9 @@ else:
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):
def setUp(self):

73
glymur/test/test_icc.py Normal file
View 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()

View file

@ -34,26 +34,19 @@ except:
# Doc tests should be run as well.
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
@contextlib.contextmanager
def chdir(dirname=None):
curdir = os.getcwd()
try:
if dirname is not None:
os.chdir(dirname)
yield
finally:
os.chdir(curdir)
@unittest.skipIf(glymur.lib.openjp2._OPENJP2 is None,
"Missing openjp2 library.")
class TestJp2k(unittest.TestCase):
@classmethod
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__,
"data/nemo.jp2")
with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile:
@ -92,10 +85,18 @@ class TestJp2k(unittest.TestCase):
"Uses features introduced in 3.2.")
def test_invalid_xml_box_warning(self):
# 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:
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):
# Should be able to recover from xml box with bad xml.
with warnings.catch_warnings():
@ -598,13 +599,15 @@ class TestJp2k(unittest.TestCase):
def test_config_file_via_environ(self):
"""Verify that we can read a configuration file set via environ var."""
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:
tfile.write('[library]\n'.encode())
line = 'openjp2: {0}\n'.format(glymur._OPENJP2._name)
tfile.write(line.encode())
tfile.flush()
with patch.dict('os.environ', {'GLYMURCONFIGDIR': tdir}):
with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
imp.reload(glymur)
j = Jp2k(self.jp2file)
@ -613,13 +616,15 @@ class TestJp2k(unittest.TestCase):
def test_config_file_via_environ_is_wrong(self):
# A non-existant library location should be rejected.
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 tempfile.NamedTemporaryFile(suffix='.dylib') as tfile:
fp.write('[library]\n')
fp.write('openjp2: {0}.not.there\n'.format(tfile.name))
fp.flush()
with patch.dict('os.environ', {'GLYMURCONFIGDIR': tdir}):
with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
# Misconfigured new configuration file should
# be rejected.
with self.assertWarns(UserWarning) as cw:
@ -631,7 +636,7 @@ class TestJp2k(unittest.TestCase):
# Verify that we error out properly if the configuration file
# specified via environment variable is not found.
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
# be rejected.
with self.assertWarns(UserWarning) as cw:
@ -640,7 +645,7 @@ class TestJp2k(unittest.TestCase):
@unittest.skipIf(sys.hexversion < 0x03020000,
"Uses features introduced in 3.2.")
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 patch.dict('os.environ', {'HOME': tdir}):
# Misconfigured new configuration file should
@ -648,6 +653,24 @@ class TestJp2k(unittest.TestCase):
with self.assertWarns(UserWarning) as cw:
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):
# Verify that we can read the XMP packet in our shipping example file.
j = Jp2k(self.jp2file)

File diff suppressed because it is too large Load diff

View file

@ -57,6 +57,8 @@ def read_image(infile):
return data
@unittest.skipIf(glymur.lib.openjp2._OPENJP2 is None,
"Missing openjp2 library.")
@unittest.skipIf(no_read_backend, no_read_backend_msg)
@unittest.skipIf(data_root is None,
"OPJ_DATA_ROOT environment variable not set")

View file

@ -57,6 +57,8 @@ def read_image(infile):
return data
@unittest.skipIf(glymur.lib.openjp2._OPENJP2 is None,
"Missing openjp2 library.")
@unittest.skipIf(no_read_backend, no_read_backend_msg)
@unittest.skipIf(data_root is None,
"OPJ_DATA_ROOT environment variable not set")

View file

@ -21,7 +21,11 @@ except:
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
def setUpClass(cls):
@ -40,11 +44,11 @@ class TestPrinting(unittest.TestCase):
os.unlink(cls._plain_nemo_file)
def setUp(self):
self.jp2file = pkg_resources.resource_filename(glymur.__name__,
"data/nemo.jp2")
# Save sys.stdout.
self.stdout = sys.stdout
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.
lines = ['JPEG 2000 Signature Box (jP ) @ (0, 12)',
@ -115,6 +119,57 @@ class TestPrinting(unittest.TestCase):
# Restore 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):
glymur.jp2dump(self._plain_nemo_file)
actual = sys.stdout.getvalue().strip()
@ -126,6 +181,32 @@ class TestPrinting(unittest.TestCase):
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):
j = glymur.Jp2k(self.jp2file)
codestream = j.get_codestream(header_only=False)
@ -186,6 +267,70 @@ class TestPrinting(unittest.TestCase):
self.expected = 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,
"OPJ_DATA_ROOT environment variable not set")
def test_CRG(self):
@ -452,18 +597,6 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lst)
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):
j = glymur.Jp2k(self.jp2file)
print(j.get_codestream())
@ -651,57 +784,6 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
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):
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
with open(self.jp2file, 'rb') as ifile:
@ -775,6 +857,8 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(sys.hexversion < 0x03000000,
"Ordered dicts not printing well in 2.7")
@unittest.skipIf(data_root is None,
"OPJ_DATA_ROOT environment variable not set")
def test_jpx_approximation_with_icc_profile(self):
@ -785,12 +869,32 @@ class TestPrinting(unittest.TestCase):
print(j.box[3].box[1])
actual = sys.stdout.getvalue().strip()
lines = ['Colour Specification Box (colr) @ (179, 1339)',
' Method: any ICC profile',
' Precedence: 2',
' Approximation: accurately represents '
+ 'correct colorspace definition',
' ICC Profile: 1328 bytes']
lines = ["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'}"]
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@ -811,6 +915,8 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(sys.hexversion < 0x03000000,
"Ordered dicts not printing well in 2.7")
def test_exif_uuid(self):
j = glymur.Jp2k(self.jp2file)
@ -820,13 +926,33 @@ class TestPrinting(unittest.TestCase):
lines = ["UUID Box (uuid) @ (77, 638)",
" UUID: 4a706754-6966-6645-7869-662d3e4a5032 (Exif)",
" UUID Data: ",
"{'GPSInfo': {'GPSAltitude': 0.0,",
" 'GPSAltitudeRef': 0,",
" 'GPSDateStamp': '2013:02:09',",
" 'GPSLatitude': [42.0, 20.0, 33.61],",
"{'Image': {'Make': 'HTC',",
" 'Model': 'HTC Glacier',",
" 'XResolution': 72.0,",
" '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',",
" 'GPSLongitude': [71.0, 5.0, 17.32],",
" 'GPSLatitude': [42.0, 20.0, 33.61],",
" 'GPSLongitudeRef': 'W',",
" 'GPSLongitude': [71.0, 5.0, 17.32],",
" 'GPSAltitudeRef': 0,",
" 'GPSAltitude': 0.0,",
" 'GPSTimeStamp': [19.0, 47.0, 53.0],",
" 'GPSMapDatum': 'WGS-84',",
" 'GPSProcessingMethod': (65,",
" 83,",
@ -843,28 +969,8 @@ class TestPrinting(unittest.TestCase):
" 79,",
" 82,",
" 75),",
" 'GPSTimeStamp': [19.0, 47.0, 53.0],",
" 'GPSVersionID': (2, 2, 0)},",
" '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}}"]
" 'GPSDateStamp': '2013:02:09'},",
" 'Iop': None}"]
expected = '\n'.join(lines)

View file

@ -1,18 +1,26 @@
from distutils.core import setup
from setuptools import setup, find_packages
import sys
kwargs = {'name': 'Glymur',
'version': '0.1.7',
'version': '0.1.9',
'description': 'Tools for accessing JPEG2000 files',
'long_description': open('README.md').read(),
'author': 'John Evans',
'author_email': 'johnevans938 at gmail dot com',
'url': 'https://github.com/quintusdias/glymur',
'packages': ['glymur', 'glymur.test', 'glymur.lib',
'packages': ['glymur', 'glymur.data', 'glymur.test', 'glymur.lib',
'glymur.lib.test'],
'package_data': {'glymur': ['data/*.jp2']},
'scripts': ['bin/jp2dump'],
'license': 'LICENSE.txt',
'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",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.3",