Merge branch 'release-0.1.10'

This commit is contained in:
jevans 2013-06-27 19:53:05 -04:00
commit 43229a61f6
24 changed files with 1410 additions and 280 deletions

View file

@ -1,3 +1,7 @@
Jun 27, 2013 - v0.1.10 Can wrap codestreams in custom JP2 jackets. Exposing
parameter to specify multi component transform. Added a raw codestream
file.
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.

View file

@ -5,7 +5,7 @@ API
Jp2k
----
.. autoclass:: glymur.Jp2k
:members: read, write, read_bands, get_codestream
:members: read, write, wrap, read_bands, get_codestream
Individual Boxes
----------------
@ -24,9 +24,9 @@ ColourSpecificationBox
.. autoclass:: glymur.jp2box.ColourSpecificationBox
:members:
ComponentDefinitionBox
ChannelDefinitionBox
''''''''''''''''''''''
.. autoclass:: glymur.jp2box.ComponentDefinitionBox
.. autoclass:: glymur.jp2box.ChannelDefinitionBox
:members:
ComponentMappingBox

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.9'
release = '0.1.10'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View file

@ -56,6 +56,27 @@ repositories::
$ pip-python3 install Pillow --user
$ export PYTHONPATH=$HOME/.local/lib/python3.3/site-packages:$PYTHONPATH
Raspbian
''''''''
Yeah, this was the first thing I tried after getting my new Raspberry Pi hooked
up (couldn't help myself :-) Raspbian ships with Python 3.2 and 2.7, so these steps detail working with 2.7.
Additional required OS packages include::
* python-pip
* python-pkg-resources
* python-mock
You must install contextlib2 via pip, and then you can run at least
a minimal number of tests. To attempt to run more of the tests,
install the following debs::
* python-dev
* python-matplotlib
and then install Pillow via pip. The tests take about 30 minutes to run, with
one unexpected failure as of the time of writing.
Fedora 17
'''''''''
Fedora 17 ships with Python 3.2 and 2.7, so these steps detail working with
@ -77,8 +98,8 @@ 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::
scikit-image was not available in the Fedora 17 default repositories, but
it was installable 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
@ -96,7 +117,13 @@ Not currently supported.
Testing
'''''''
If you wish to run the tests (strongly suggested :-), try the following
If you wish to run the tests (strongly suggested :-), you can either run them
from within python as follows ... ::
>>> import glymur
>>> glymur.runtests()
or from the unix command line. ::
$ cd /to/where/you/unpacked/glymur
$ python -m unittest discover

View file

@ -34,6 +34,91 @@ codestream box, only the main header is printed. It is possible to print
>>> print(j.get_codestream())
Add XML Metadata?
=================
An existing raw codestream (or JP2 file) can be wrapped (re-wrapped) in a
user-defined set of JP2 boxes. To get just a minimal JP2 jacket on the
codestream provided by `goodstuff.j2k`, you can use the **wrap** method with
no box argument: ::
>>> import glymur
>>> jfile = glymur.data.goodstuff()
>>> j2k = glymur.Jp2k(jfile)
>>> jp2 = j2k.wrap("newfile.jp2")
>>> print(jp2)
File: newfile.jp2
JPEG 2000 Signature Box (jP ) @ (0, 12)
Signature: 0d0a870a
File Type Box (ftyp) @ (12, 20)
Brand: jp2
Compatibility: ['jp2 ']
JP2 Header Box (jp2h) @ (32, 45)
Image Header Box (ihdr) @ (40, 22)
Size: [800 480 3]
Bitdepth: 8
Signed: False
Compression: wavelet
Colorspace Unknown: False
Colour Specification Box (colr) @ (62, 15)
Method: enumerated colorspace
Precedence: 0
Colorspace: sRGB
Contiguous Codestream Box (jp2c) @ (77, 115228)
Main header:
.
. (truncated)
.
The raw codestream was wrapped in a JP2 jacket with four boxes in the outer
layer (the signature, file type, JP2 header, and contiguous codestream), with
two additional boxes (image header and color specification) contained in the
JP2 header superbox.
XML boxes are not in the minimal set of box requirements for the JP2 format, so
in order to add an XML box into the mix, we'll need to specify all of the
boxes. If you already have a JP2 jacket in place, you can just reuse it,
though. Take the following example content in an XML file `favorites.xml` : ::
<?xml version="1.0"?>
<favorite_things>
<category>Light Ale</category>
</favorite_things>
and add it after the JP2 header box, but before the codestream box ::
>>> boxes = jp2.box # The box attribute is the list of JP2 boxes
>>> xmlbox = glymur.jp2box.XMLBox(file='favorites.xml')
>>> boxes.insert(3, xmlbox)
>>> jp2_xml = jp2.wrap("newfile_with_xml.jp2", boxes=boxes)
>>> print(jp2_xml)
File: newfile_with_xml.jp2
JPEG 2000 Signature Box (jP ) @ (0, 12)
Signature: 0d0a870a
File Type Box (ftyp) @ (12, 20)
Brand: jp2
Compatibility: ['jp2 ']
JP2 Header Box (jp2h) @ (32, 45)
Image Header Box (ihdr) @ (40, 22)
Size: [800 480 3]
Bitdepth: 8
Signed: False
Compression: wavelet
Colorspace Unknown: False
Colour Specification Box (colr) @ (62, 15)
Method: enumerated colorspace
Precedence: 0
Colorspace: sRGB
XML Box (xml ) @ (77, 76)
<favorite_things>
<category>Light Ale</category>
</favorite_things>
Contiguous Codestream Box (jp2c) @ (153, 115236)
Main header:
.
. (truncated)
.
Work with XMP UUIDs?
====================
The example JP2 file shipped with glymur has an XMP UUID. ::

View file

@ -50,7 +50,7 @@ the openjp2 library. You may create the configuration file as follows::
> EOF
That assumes, of course, that you've installed OpenJPEG into /opt/openjp2-svn.
You may also replace **$HOME/.config** with **$XDG_CONFIG_HOME**.
You may also substitute **$XDG_CONFIG_HOME** for **$HOME/.config**.
Glymur Installation
===================
@ -59,14 +59,22 @@ You can retrieve the source for Glymur from either of
* https://pypi.python.org/pypi/Glymur/ (stable releases)
* http://github.com/quintusdias/glymur (bleeding edge)
but you should now be able to get a functional installation of Glymur via
pip ::
but you should also be able to install Glymur via pip ::
$ pip install glymur
This will install the **jp2dump** script, so you should adjust your **$PATH**
This will install the **jp2dump** script that can be used from the unix command
line, so you should adjust your **$PATH**
to take advantage of it. For example, if you install with pip's
`--user` option on linux ::
$ export PYTHONPATH=$HOME/.local/lib/python3.3/site-packages
$ export PATH=$HOME/.local/bin:$PATH
You can run the tests from within python as follows::
>>> import glymur
>>> glymur.runtests()
Many tests are currently skipped; the important thing is whether or not any
tests fail.

View file

@ -6,7 +6,7 @@ Here's an incomplete list of what I'd like to focus on in the near future.
* continue to monitor upstream changes in the openjp2 library
* investigate using CFFI or cython instead of ctypes to wrap openjp2
* investigate swapping out ElementTree for LXML
* eventually expose the openjp2 API
* add metadata support for ICC profiles
* investigate JPIP

View file

@ -1,92 +1,16 @@
"""glymur - read, write, and interrogate JPEG 2000 files
"""
def _glymurrc_fname():
"""Return the path to the configuration file.
Search order:
1) current working directory
2) environ var XDG_CONFIG_HOME
3) $HOME/.config/glymur/glymurrc
"""
# Current directory.
fname = os.path.join(os.getcwd(), 'glymurrc')
if os.path.exists(fname):
return fname
# Either GLYMURCONFIGDIR/glymurrc or $HOME/.glymur/glymurrc
confdir = _get_configdir()
if confdir is not None:
fname = os.path.join(confdir, 'glymurrc')
if os.path.exists(fname):
return fname
else:
msg = "Configuration file '{0}' does not exist.".format(confdir)
warnings.warn(msg, UserWarning)
# didn't find a configuration file.
return None
def _config():
"""Read configuration file.
Based on matplotlib.
"""
filename = _glymurrc_fname()
if filename is not None:
# Read the configuration file for the library location.
parser = ConfigParser()
parser.read(filename)
libopenjp2_path = parser.get('library', 'openjp2')
else:
# No help from the config file, try to find it ourselves.
from ctypes.util import find_library
libopenjp2_path = find_library('openjp2')
if libopenjp2_path is None:
return None
try:
_OPENJP2 = ctypes.CDLL(libopenjp2_path)
except OSError:
msg = '"Library {0}" could not be loaded. Operating in degraded mode.'
msg = msg.format(libopenjp2_path)
warnings.warn(msg, UserWarning)
_OPENJP2 = None
return _OPENJP2
def _get_configdir():
"""Return string representing the configuration directory.
Default is $HOME/.config/glymur. You can override this with the
XDG_CONFIG_HOME environment variable.
"""
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'], '.config', 'glymur')
import warnings
import sys
if sys.hexversion <= 0x03000000:
from ConfigParser import SafeConfigParser as ConfigParser
from ConfigParser import NoOptionError
else:
from configparser import ConfigParser
from configparser import NoOptionError
import ctypes
import os
_OPENJP2 = _config()
from .jp2k import Jp2k
from .jp2dump import jp2dump
from . import data
from . import test
def runtests():
"""Discover and run all tests for the glymur package.
"""
import unittest
suite = unittest.defaultTestLoader.discover(__path__[0])
unittest.TextTestRunner(verbosity=2).run(suite)

View file

@ -175,7 +175,7 @@ class Codestream(object):
segment = self._parseGenericSegment(f, marker_id)
else:
msg = 'Invalid marker id encountered at byte {0:d}'
msg = 'Invalid marker id encountered at byte {0:d} '
msg += 'in codestream: "0x{1:x}"'
msg = msg.format(offset, marker_id)
raise IOError(msg)

View file

@ -74,6 +74,12 @@ _color_type_map_display = {
_PRE_MULTIPLIED_OPACITY: 'pre-multiplied opacity',
_UNSPECIFIED: 'unspecified'}
# color channel definitions.
RED = 1
GREEN = 2
BLUE = 3
GREY = 1
# enumerated color channel associations
_rgb_colorspace = {"R": 1, "G": 2, "B": 3}
_greyscale_colorspace = {"Y": 1}

View file

@ -1,8 +1,10 @@
"""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.
These include:
nemo.jp2: converted from the original JPEG photo of the aftermath of NEMO,
the nor'easter that shutdown Boston in February of 2013.
goodstuff.j2k: my favorite bevorage.
"""
import pkg_resources
@ -17,3 +19,15 @@ def nemo():
"""
file = pkg_resources.resource_filename(__name__, "nemo.jp2")
return file
def goodstuff():
"""Shortcut for specifying path to goodstuff.j2k.
Returns
-------
file : str
Platform-independent path to goodstuff.j2k.
"""
file = pkg_resources.resource_filename(__name__, "goodstuff.j2k")
return file

BIN
glymur/data/goodstuff.j2k Normal file

Binary file not shown.

View file

@ -31,6 +31,7 @@ from .core import _colorspace_map_display
from .core import _color_type_map_display
from .core import _method_display
from .core import _reader_requirements_display
from .core import *
class Jp2kBox(object):
@ -59,6 +60,12 @@ class Jp2kBox(object):
msg += " @ ({0}, {1})".format(self.offset, self.length)
return msg
def _write(self, f):
"""Must be implemented in a subclass.
"""
msg = "Not supported for {0} box.".format(self.longname)
raise NotImplementedError(msg)
def _parse_superbox(self, f):
"""Parse a superbox (box consisting of nothing but other boxes.
@ -159,9 +166,22 @@ class ColourSpecificationBox(Jp2kBox):
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')
def __init__(self, method=ENUMERATED_COLORSPACE, precedence=0,
approximation=0, colorspace=None, icc_profile=None, **kwargs):
Jp2kBox.__init__(self, id='colr', longname='Colour Specification')
if colorspace is not None and icc_profile is not None:
raise IOError("colorspace and icc_profile cannot both be set.")
if method not in (1, 2, 3, 4):
raise IOError("Invalid method.")
if approximation not in (0, 1, 2, 3, 4):
raise IOError("Invalid approximation.")
self.__dict__.update(**kwargs)
self.method = method
self.precedence = precedence
self.approximation = approximation
self.colorspace = colorspace
self.icc_profile = icc_profile
def __str__(self):
msg = Jp2kBox.__str__(self)
@ -188,6 +208,24 @@ class ColourSpecificationBox(Jp2kBox):
return msg
def _write(self, f):
"""Write an Colour Specification box to file.
"""
if self.colorspace is None:
msg = "Writing Colour Specification boxes without enumerated "
msg += "colorspaces is not supported at this time."
raise NotImplementedError(msg)
length = 15 if self.icc_profile is None else 11 + len(self.icc_profile)
f.write(struct.pack('>I', length))
f.write('colr'.encode())
buffer = struct.pack('>BBBI',
self.method,
self.precedence,
self.approximation,
self.colorspace)
f.write(buffer)
@staticmethod
def _parse(f, id, offset, length):
"""Parse JPEG 2000 color specification box.
@ -354,7 +392,7 @@ class _ICCProfile(object):
self.header = header
class ComponentDefinitionBox(Jp2kBox):
class ChannelDefinitionBox(Jp2kBox):
"""Container for component definition box information.
Attributes
@ -367,29 +405,58 @@ class ComponentDefinitionBox(Jp2kBox):
offset of the box from the start of the file.
longname : str
more verbose description of the box.
component_number : int
number of the component
component_type : int
type of the component
index : int
number of the channel
channel_type : int
type of the channel
association : int
number of the associated color
index of the associated color
"""
def __init__(self, **kwargs):
Jp2kBox.__init__(self, id='', longname='Component Definition')
def __init__(self, index, channel_type, association, **kwargs):
Jp2kBox.__init__(self, id='cdef', longname='Channel Definition')
if len(index) != len(channel_type) or len(index) != len(association):
msg = "Length of channel definition box inputs must be the same."
raise IOError(msg)
# channel types must be one of 0, 1, 2, 65535
if any(x not in [0, 1, 2, 65535] for x in channel_type):
msg = "Channel types must be in the set of\n\n"
msg += " 0 - colour image data for associated color\n"
msg += " 1 - opacity\n"
msg += " 2 - premultiplied opacity\n"
msg += " 65535 - unspecified"
raise IOError(msg)
self.index = index
self.channel_type = channel_type
self.association = association
self.__dict__.update(**kwargs)
def __str__(self):
msg = Jp2kBox.__str__(self)
for j in range(len(self.association)):
color_type_string = _color_type_map_display[self.component_type[j]]
color_type_string = _color_type_map_display[self.channel_type[j]]
if self.association[j] == 0:
assn = 'whole image'
else:
assn = str(self.association[j])
msg += '\n Component {0} ({1}) ==> ({2})'
msg = msg.format(self.component_number[j], color_type_string, assn)
msg += '\n Channel {0} ({1}) ==> ({2})'
msg = msg.format(self.index[j], color_type_string, assn)
return msg
def _write(self, f):
"""Write a channel definition box to file.
"""
N = len(self.association)
f.write(struct.pack('>I', 8 + 2 + N * 6))
f.write('cdef'.encode('utf-8'))
f.write(struct.pack('>H', N))
for j in range(N):
f.write(struct.pack('>' + 'H' * 3,
self.index[j],
self.channel_type[j],
self.association[j]))
@staticmethod
def _parse(f, id, offset, length):
"""Parse component definition box.
@ -418,17 +485,17 @@ class ComponentDefinitionBox(Jp2kBox):
buffer = f.read(2)
N, = struct.unpack('>H', buffer)
component_number = []
component_type = []
index = []
chan_type = []
association = []
buffer = f.read(N * 6)
data = struct.unpack('>' + 'HHH' * N, buffer)
kwargs['component_number'] = data[0:N * 6:3]
kwargs['component_type'] = data[1:N * 6:3]
kwargs['association'] = data[2:N * 6:3]
index = data[0:N * 6:3]
channel_type = data[1:N * 6:3]
association = data[2:N * 6:3]
box = ComponentDefinitionBox(**kwargs)
box = ChannelDefinitionBox(index, channel_type, association, **kwargs)
return box
@ -524,9 +591,10 @@ class ContiguousCodestreamBox(Jp2kBox):
List of segments in the codestream header.
"""
def __init__(self, **kwargs):
def __init__(self, main_header=[], **kwargs):
Jp2kBox.__init__(self, id='jp2c', longname='Contiguous Codestream')
self.__dict__.update(**kwargs)
self.main_header = main_header
def __str__(self):
msg = Jp2kBox.__str__(self)
@ -591,8 +659,14 @@ class FileTypeBox(Jp2kBox):
List of file conformance profiles.
"""
def __init__(self, **kwargs):
Jp2kBox.__init__(self, id='', longname='File Type')
Jp2kBox.__init__(self, id='ftyp', longname='File Type')
self.__dict__.update(**kwargs)
if 'brand' not in kwargs.keys():
self.brand = 'jp2 '
if 'minor_version' not in kwargs.keys():
self.minor_version = 0
if 'compatibility_list' not in kwargs.keys():
self.compatibility_list = ['jp2 ']
def __str__(self):
lst = [Jp2kBox.__str__(self),
@ -603,6 +677,18 @@ class FileTypeBox(Jp2kBox):
return msg
def _write(self, f):
"""Write a File Type box to file.
"""
length = 16 + 4*len(self.compatibility_list)
f.write(struct.pack('>I', length))
f.write('ftyp'.encode())
f.write(self.brand.encode())
f.write(struct.pack('>I', self.minor_version))
for item in self.compatibility_list:
f.write(item.encode())
@staticmethod
def _parse(f, id, offset, length):
"""Parse JPEG 2000 file type box.
@ -673,30 +759,68 @@ class ImageHeaderBox(Jp2kBox):
Bits per component.
signed : bool
False if the image components are unsigned.
compression : nt
compression : int
The compression type, should be 7 if JP2.
cspace_unknown : int
0 if the color space is known and correctly specified.
ip_provided : int
0 if the file does not contain intellectual propery rights information.
colorspace_unknown : bool
False if the color space is known and correctly specified.
ip_provided : bool
False if the file does not contain intellectual propery rights
information.
"""
def __init__(self, **kwargs):
Jp2kBox.__init__(self, id='', longname='Image Header')
def __init__(self, height, width, num_components=1, signed=False,
bits_per_component=8, compression=7, colorspace_unknown=False,
ip_provided=False, **kwargs):
"""
Examples
--------
>>> import glymur
>>> box = glymur.jp2box.ImageHeaderBox(height=512, width=256)
"""
Jp2kBox.__init__(self, id='ihdr', longname='Image Header')
self.height = height
self.width = width
self.num_components = num_components
self.signed = signed
self.bits_per_component = bits_per_component
self.compression = compression
self.colorspace_unknown = colorspace_unknown
self.ip_provided = False
self.__dict__.update(**kwargs)
def __str__(self):
lst = [Jp2kBox.__str__(self)]
lst.append('Size: [{0} {1} {2}]'.format(self.height, self.width,
self.num_components))
lst.append('Bitdepth: {0}'.format(self.bits_per_component))
lst.append('Signed: {0}'.format(self.signed))
if self.compression == 7:
lst.append('Compression: wavelet')
if self.cspace_unknown:
lst.append('Colorspace Unknown: True')
else:
lst.append('Colorspace Unknown: False')
return '\n '.join(lst)
msg = Jp2kBox.__str__(self)
msg = "{0}"
msg += '\n Size: [{1} {2} {3}]'
msg += '\n Bitdepth: {4}'
msg += '\n Signed: {5}'
msg += '\n Compression: {6}'
msg += '\n Colorspace Unknown: {7}'
msg = msg.format(Jp2kBox.__str__(self),
self.height, self.width, self.num_components,
self.bits_per_component,
self.signed,
'wavelet' if self.compression == 7 else 'unknown',
self.colorspace_unknown)
return msg
def _write(self, f):
"""Write an Image Header box to file.
"""
f.write(struct.pack('>I', 22))
f.write('ihdr'.encode())
# signedness and bps are stored together in a single byte
bit_depth_signedness = 0x80 if self.signed else 0x00
bit_depth_signedness |= self.bits_per_component - 1
buffer = struct.pack('>IIHBBBB',
self.height,
self.width,
self.num_components,
bit_depth_signedness,
self.compression,
1 if self.colorspace_unknown else 0,
1 if self.ip_provided else 0)
f.write(buffer)
@staticmethod
def _parse(f, id, offset, length):
@ -724,16 +848,16 @@ class ImageHeaderBox(Jp2kBox):
# Read the box information
buffer = f.read(14)
params = struct.unpack('>IIHBBBB', buffer)
kwargs['height'] = params[0]
kwargs['width'] = params[1]
height = params[0]
width = params[1]
kwargs['num_components'] = params[2]
kwargs['bits_per_component'] = (params[3] & 0x7f) + 1
kwargs['signed'] = (params[3] & 0x80) > 1
kwargs['compression'] = params[4]
kwargs['cspace_unknown'] = params[5]
kwargs['ip_provided'] = params[6]
kwargs['colorspace_unknown'] = True if params[5] else False
kwargs['ip_provided'] = True if params[6] else False
box = ImageHeaderBox(**kwargs)
box = ImageHeaderBox(height, width, **kwargs)
return box
@ -818,7 +942,7 @@ class JP2HeaderBox(Jp2kBox):
List of boxes contained in this superbox.
"""
def __init__(self, **kwargs):
Jp2kBox.__init__(self, id='', longname='JP2 Header')
Jp2kBox.__init__(self, id='jp2h', longname='JP2 Header')
self.__dict__.update(**kwargs)
def __str__(self):
@ -831,6 +955,21 @@ class JP2HeaderBox(Jp2kBox):
msg += ''.join(strs)
return msg
def _write(self, f):
"""Write a JP2 Header box to file.
"""
# Write the contained boxes, then come back and write the length.
orig_pos = f.tell()
f.write(struct.pack('>I', 0))
f.write('jp2h'.encode())
for box in self.box:
box._write(f)
end_pos = f.tell()
f.seek(orig_pos)
f.write(struct.pack('>I', end_pos - orig_pos))
f.seek(end_pos)
@staticmethod
def _parse(f, id, offset, length):
"""Parse JPEG 2000 header box.
@ -882,8 +1021,10 @@ class JPEG2000SignatureBox(Jp2kBox):
Four-byte tuple identifying the file as JPEG 2000.
"""
def __init__(self, **kwargs):
Jp2kBox.__init__(self, id='', longname='JPEG 2000 Signature')
Jp2kBox.__init__(self, id='jP ', longname='JPEG 2000 Signature')
self.__dict__.update(**kwargs)
if 'signature' not in kwargs.keys():
self.signature = (13, 10, 135, 10)
def __str__(self):
msg = Jp2kBox.__str__(self)
@ -891,6 +1032,13 @@ class JPEG2000SignatureBox(Jp2kBox):
msg = msg.format(*self.signature)
return msg
def _write(self, f):
"""Write a JPEG 2000 Signature box to file.
"""
f.write(struct.pack('>I', 12))
f.write(self.id.encode())
f.write(struct.pack('>BBBB', *self.signature))
@staticmethod
def _parse(f, id, offset, length):
"""Parse JPEG 2000 signature box.
@ -1412,19 +1560,50 @@ class XMLBox(Jp2kBox):
xml : ElementTree.Element
XML section.
"""
def __init__(self, **kwargs):
Jp2kBox.__init__(self, id='', longname='XML')
def __init__(self, xml=None, filename=None, **kwargs):
"""
Parameters
----------
xml : ElementTree
An ElementTree object already existing in python.
filename : str
File from which to read XML. If filename is not None, then the xml
keyword argument must be None.
"""
Jp2kBox.__init__(self, id='xml ', longname='XML')
if filename is not None and xml is not None:
msg = "Only one of either filename or xml should be provided."
raise IOError(msg)
if filename is not None:
self.xml = ET.parse(filename)
else:
self.xml = xml
self.__dict__.update(**kwargs)
def __str__(self):
msg = Jp2kBox.__str__(self)
xml = self.xml
if self.xml is not None:
msg += _pretty_print_xml(self.xml)
try:
msg += _pretty_print_xml(self.xml)
except TypeError:
msg += _pretty_print_xml(self.xml.getroot())
else:
msg += '\n {0}'.format(xml)
return msg
def _write(self, f):
"""Write an XML box to file.
"""
try:
buffer = ET.tostring(self.xml, encoding='utf-8')
except AttributeError:
buffer = ET.tostring(self.xml.getroot(), encoding='utf-8')
f.write(struct.pack('>I', len(buffer) + 8))
f.write(self.id.encode())
f.write(buffer)
@staticmethod
def _parse(f, id, offset, length):
"""Parse XML box.
@ -2273,7 +2452,7 @@ class _ExifInteroperabilityIfd(_Ifd):
# Map each box ID to the corresponding class.
_box_with_id = {
'asoc': AssociationBox,
'cdef': ComponentDefinitionBox,
'cdef': ChannelDefinitionBox,
'cmap': ComponentMappingBox,
'colr': ColourSpecificationBox,
'jP ': JPEG2000SignatureBox,

View file

@ -16,8 +16,8 @@ import warnings
import numpy as np
from .codestream import Codestream
from .core import progression_order
from .jp2box import Jp2kBox
from .core import *
from .jp2box import *
from .lib import openjp2 as opj2
_cspace_map = {'rgb': opj2._CLRSPC_SRGB,
@ -141,7 +141,7 @@ class Jp2k(Jp2kBox):
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):
colorspace=None, verbose=False, mct=None):
"""Write image data to a JP2/JPX/J2k file. Intended usage of the
various parameters follows that of OpenJPEG's opj_compress utility.
@ -167,6 +167,9 @@ class Jp2k(Jp2kBox):
If true, write SOP marker after each header packet.
grid_offset : tuple, optional
Offset (DY, DX) of the origin of the image in the reference grid.
mct : bool, optional
Specifies usage of the multi component transform. If not
specified, defaults to True if the colorspace is RGB.
modesw : int, optional
Mode switch.
1 = BYPASS(LAZY)
@ -336,6 +339,18 @@ class Jp2k(Jp2kBox):
else:
colorspace = _cspace_map[colorspace]
if mct is None:
if colorspace == opj2._CLRSPC_SRGB:
cparams.tcp_mct = 1
else:
cparams.tcp_mct = 0
else:
if mct and colorspace == opj2._CLRSPC_GRAY:
msg = "Cannot specify usage of the multi component transform "
msg += "if the colorspace is gray."
raise IOError(msg)
cparams.tcp_mct = 1 if mct else 0
if img_array.dtype == np.uint8:
comp_prec = 8
elif img_array.dtype == np.uint16:
@ -372,12 +387,6 @@ class Jp2k(Jp2kBox):
src = layer.ctypes.data
ctypes.memmove(dest, src, layer.nbytes)
# set multi-component transform?
if image.contents.numcomps == 3:
cparams.tcp_mct = 1
else:
cparams.tcp_mct = 0
codec = opj2._create_compress(codec_fmt)
if verbose:
@ -398,6 +407,143 @@ class Jp2k(Jp2kBox):
self._parse()
def wrap(self, filename, boxes=None):
"""Write the codestream back out to file, wrapped in new JP2 jacket.
Parameters
----------
filename : str
JP2 file to be created from a raw codestream.
boxes : list
JP2 box definitions to define the JP2 file format. If not
provided, a default ""jacket" is assumed, consisting of JP2
signature, file type, JP2 header, and contiguous codestream boxes.
Returns
-------
jp2 : Jp2k object
Newly wrapped Jp2k object.
Examples
--------
>>> import glymur, tempfile
>>> jfile = glymur.data.goodstuff()
>>> j2k = glymur.Jp2k(jfile)
>>> tfile = tempfile.NamedTemporaryFile(suffix='jp2')
>>> jp2 = j2k.wrap(tfile.name)
"""
if boxes is None:
# Try to create a reasonable default.
boxes = [JPEG2000SignatureBox(),
FileTypeBox(),
JP2HeaderBox(),
ContiguousCodestreamBox()]
c = self.get_codestream()
height = c.segment[1].Ysiz
width = c.segment[1].Xsiz
num_components = len(c.segment[1].XRsiz)
boxes[2].box = [ImageHeaderBox(height=height,
width=width,
num_components=num_components),
ColourSpecificationBox(colorspace=SRGB)]
# Check for a bad sequence of boxes.
# 1st two boxes must be 'jP ' and 'ftyp'
if boxes[0].id != 'jP ' or boxes[1].id != 'ftyp':
msg = "The first box must be the signature box and the second "
msg += "must be the file type box."
raise IOError(msg)
# jp2c must be preceeded by jp2h
jp2h_lst = [idx for (idx, box) in enumerate(boxes) if box.id == 'jp2h']
jp2h_idx = jp2h_lst[0]
jp2c_lst = [idx for (idx, box) in enumerate(boxes) if box.id == 'jp2c']
if len(jp2c_lst) == 0:
msg = "A codestream box must be defined in the outermost "
msg += "list of boxes."
raise IOError(msg)
jp2c_idx = jp2c_lst[0]
if jp2h_idx >= jp2c_idx:
msg = "The codestream box must be preceeded by a jp2 header box."
raise IOError(msg)
# 1st jp2 header box must be ihdr
jp2h = boxes[jp2h_idx]
if jp2h.box[0].id != 'ihdr':
msg = "The first box in the jp2 header box must be the image "
msg += "header box."
raise IOError(msg)
# colr must be present in jp2 header box.
jp2hb = jp2h.box
colr_lst = [j for (j, box) in enumerate(jp2h.box) if box.id == 'colr']
if len(colr_lst) == 0:
msg = "The jp2 header box must contain a color definition box."
raise IOError(msg)
colr = jp2h.box[colr_lst[0]]
# Any cdef box must be in the jp2 header following the image header.
cdef_lst = [j for (j, box) in enumerate(boxes) if box.id == 'cdef']
if len(cdef_lst) != 0:
msg = "Any channel defintion box must be in the JP2 header "
msg += "following the image header."
raise IOError(msg)
cdef_lst = [j for (j, box) in enumerate(jp2h.box) if box.id == 'cdef']
if len(cdef_lst) > 1:
msg = "Only one channel definition box is allowed in the "
msg += "JP2 header."
raise IOError(msg)
elif len(cdef_lst) == 1:
cdef = jp2h.box[cdef_lst[0]]
assn = cdef.association
typ = cdef.channel_type
index = cdef.index
if colr.colorspace == SRGB:
if any([chan + 1 not in assn or typ[chan] != 0
for chan in [0, 1, 2]]):
msg = "All color channels must be defined in the "
msg += "channel definition box."
raise IOError(msg)
elif colr.colorspace == GREYSCALE:
if 0 not in typ:
msg = "All color channels must be defined in the "
msg += "channel definition box."
raise IOError(msg)
with open(filename, 'wb') as ofile:
for box in boxes:
if box.id != 'jp2c':
box._write(ofile)
else:
# The codestream gets written last.
if len(self.box) == 0:
# Am I a raw codestream? If so, then it is pretty
# easy, just write the codestream box header plus all
# of myself out to file.
ofile.write(struct.pack('>I', self.length + 8))
ofile.write('jp2c'.encode())
with open(self.filename, 'rb') as ifile:
ofile.write(ifile.read())
else:
# OK, I'm a jp2 file. Need to find out where the
# raw codestream actually starts.
jp2c = [box for box in self.box if box.id == 'jp2c']
jp2c = jp2c[0]
ofile.write(struct.pack('>I', jp2c.length + 8))
ofile.write('jp2c'.encode())
with open(self.filename, 'rb') as ifile:
# Seek 8 bytes past the L, T fields to get to the
# raw codestream.
ifile.seek(jp2c.offset + 8)
ofile.write(ifile.read(jp2c.length - 8))
ofile.flush()
jp2 = Jp2k(filename)
return jp2
def read(self, reduce=0, layer=0, area=None, tile=None, verbose=False):
"""Read a JPEG 2000 image.

View file

@ -2,12 +2,99 @@
Wraps individual functions in openjp2 library.
"""
def _glymurrc_fname():
"""Return the path to the configuration file.
Search order:
1) current working directory
2) environ var XDG_CONFIG_HOME
3) $HOME/.config/glymur/glymurrc
"""
# Current directory.
fname = os.path.join(os.getcwd(), 'glymurrc')
if os.path.exists(fname):
return fname
# Either GLYMURCONFIGDIR/glymurrc or $HOME/.glymur/glymurrc
confdir = _get_configdir()
if confdir is not None:
fname = os.path.join(confdir, 'glymurrc')
if os.path.exists(fname):
return fname
else:
msg = "Configuration file '{0}' does not exist.".format(confdir)
warnings.warn(msg, UserWarning)
# didn't find a configuration file.
return None
def _config():
"""Read configuration file.
Based on matplotlib.
"""
filename = _glymurrc_fname()
if filename is not None:
# Read the configuration file for the library location.
parser = ConfigParser()
parser.read(filename)
libopenjp2_path = parser.get('library', 'openjp2')
else:
# No help from the config file, try to find it ourselves.
from ctypes.util import find_library
libopenjp2_path = find_library('openjp2')
if libopenjp2_path is None:
return None
try:
_OPENJP2 = ctypes.CDLL(libopenjp2_path)
except OSError:
msg = '"Library {0}" could not be loaded. Operating in degraded mode.'
msg = msg.format(libopenjp2_path)
warnings.warn(msg, UserWarning)
_OPENJP2 = None
return _OPENJP2
def _get_configdir():
"""Return string representing the configuration directory.
Default is $HOME/.config/glymur. You can override this with the
XDG_CONFIG_HOME environment variable.
"""
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'], '.config', 'glymur')
import ctypes
import os
import warnings
import sys
if sys.hexversion <= 0x03000000:
from ConfigParser import SafeConfigParser as ConfigParser
from ConfigParser import NoOptionError
else:
from configparser import ConfigParser
from configparser import NoOptionError
_OPENJP2 = _config()
import numpy as np
from .. import _OPENJP2
from ..core import LRCP, RLCP, RPCL, PCRL, CPRL
# Progression order
LRCP = 0
RLCP = 1
RPCL = 2
PCRL = 3
CPRL = 4
_ERROR_MSG_LST = []

View file

@ -6,3 +6,4 @@ from .test_printing import TestPrinting as printing
from .test_opj_suite import TestSuite as suite
from .test_opj_suite_write import TestSuiteWrite as suitew
from .test_opj_suite_neg import TestSuiteNegative as suiteneg
from .test_jp2box import TestJp2Boxes as box

101
glymur/test/test_config.py Normal file
View file

@ -0,0 +1,101 @@
import imp
import os
import sys
import tempfile
import unittest
if sys.hexversion <= 0x03030000:
from mock import patch
else:
from unittest.mock import patch
import warnings
import pkg_resources
import glymur
from glymur import Jp2k
from glymur.lib import openjp2 as opj2
@unittest.skipIf(sys.hexversion < 0x03020000,
"Uses features introduced in 3.2.")
class TestJp2k(unittest.TestCase):
def setUp(self):
self.jp2file = pkg_resources.resource_filename(glymur.__name__,
"data/nemo.jp2")
imp.reload(glymur)
imp.reload(glymur.lib.openjp2)
def tearDown(self):
imp.reload(glymur)
imp.reload(glymur.lib.openjp2)
def test_config_file_via_environ(self):
"""Verify that we can read a configuration file set via environ var."""
with tempfile.TemporaryDirectory() as tdir:
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())
libloc = glymur.lib.openjp2._OPENJP2._name
line = 'openjp2: {0}\n'.format(libloc)
tfile.write(line.encode())
tfile.flush()
with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
imp.reload(glymur.lib.openjp2)
j = Jp2k(self.jp2file)
def test_config_file_via_environ_is_wrong(self):
# A non-existant library location should be rejected.
with tempfile.TemporaryDirectory() as tdir:
configdir = os.path.join(tdir, 'glymur')
os.mkdir(configdir)
fname = os.path.join(configdir, 'glymurrc')
with open(fname, 'w') as fp:
with 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', {'XDG_CONFIG_HOME': tdir}):
# Misconfigured new configuration file should
# be rejected.
with self.assertWarns(UserWarning) as cw:
imp.reload(glymur.lib.openjp2)
def test_missing_config_file_via_environ(self):
# 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', {'XDG_CONFIG_HOME': tdir}):
# Misconfigured new configuration file should
# be rejected.
with self.assertWarns(UserWarning) as cw:
imp.reload(glymur.lib.openjp2)
def test_home_dir_missing_config_dir(self):
# 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
# be rejected.
with self.assertWarns(UserWarning) as cw:
imp.reload(glymur.lib.openjp2)
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.lib.openjp2)
if __name__ == "__main__":
unittest.main()

604
glymur/test/test_jp2box.py Normal file
View file

@ -0,0 +1,604 @@
import doctest
import tempfile
import xml.etree.cElementTree as ET
import unittest
import numpy as np
import pkg_resources
import glymur
from glymur import Jp2k
from glymur.jp2box import *
# Doc tests should be run as well.
def load_tests(loader, tests, ignore):
tests.addTests(doctest.DocTestSuite('glymur.jp2box'))
return tests
class TestChannelDefinition(unittest.TestCase):
@classmethod
def setUpClass(cls):
"""Need a one_plane plane image for greyscale testing."""
j2k = Jp2k(glymur.data.goodstuff())
data = j2k.read()
# Write the first component back out to file.
with tempfile.NamedTemporaryFile(suffix=".j2k", delete=False) as tfile:
grey_j2k = Jp2k(tfile.name, 'wb')
grey_j2k.write(data[:, :, 0])
cls.one_plane = tfile.name
# Write the first two components back out to file.
with tempfile.NamedTemporaryFile(suffix=".j2k", delete=False) as tfile:
grey_j2k = Jp2k(tfile.name, 'wb')
grey_j2k.write(data[:, :, 0:1])
cls.two_planes = tfile.name
# Write four components back out to file.
with tempfile.NamedTemporaryFile(suffix=".j2k", delete=False) as tfile:
rgba_jp2 = Jp2k(tfile.name, 'wb')
shape = (data.shape[0], data.shape[1], 1)
alpha = np.zeros((shape), dtype=data.dtype)
data4 = np.concatenate((data, alpha), axis=2)
rgba_jp2.write(data4)
cls.four_planes = tfile.name
@classmethod
def tearDownClass(cls):
os.unlink(cls.one_plane)
os.unlink(cls.two_planes)
os.unlink(cls.four_planes)
def setUp(self):
self.jp2file = glymur.data.nemo()
self.j2kfile = glymur.data.goodstuff()
j2k = Jp2k(self.j2kfile)
c = j2k.get_codestream()
height = c.segment[1].Ysiz
width = c.segment[1].Xsiz
num_components = len(c.segment[1].XRsiz)
self.jP = JPEG2000SignatureBox()
self.ftyp = FileTypeBox()
self.jp2h = JP2HeaderBox()
self.jp2c = ContiguousCodestreamBox()
self.ihdr = ImageHeaderBox(height=height, width=width,
num_components=num_components)
self.colr_rgb = ColourSpecificationBox(colorspace=glymur.core.SRGB)
self.colr_gr = ColourSpecificationBox(colorspace=glymur.core.GREYSCALE)
def tearDown(self):
pass
def test_rgb(self):
"""Just regular RGB."""
j2k = Jp2k(self.j2kfile)
cdef = glymur.jp2box.ChannelDefinitionBox(index=[0, 1, 2],
channel_type=[0, 0, 0],
association=[1, 2, 3])
boxes = [self.ihdr, self.colr_rgb, cdef]
self.jp2h.box = boxes
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
jp2 = Jp2k(tfile.name)
jp2h = jp2.box[2]
boxes = [box.id for box in jp2h.box]
self.assertEqual(boxes, ['ihdr', 'colr', 'cdef'])
self.assertEqual(jp2h.box[2].index, (0, 1, 2))
self.assertEqual(jp2h.box[2].channel_type, (0, 0, 0))
self.assertEqual(jp2h.box[2].association, (1, 2, 3))
def test_rgba(self):
"""Just regular RGBA."""
j2k = Jp2k(self.four_planes)
cdef = glymur.jp2box.ChannelDefinitionBox(index=[0, 1, 2, 3],
channel_type=[0, 0, 0, 1],
association=[1, 2, 3, 0])
boxes = [self.ihdr, self.colr_rgb, cdef]
self.jp2h.box = boxes
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
jp2 = Jp2k(tfile.name)
jp2h = jp2.box[2]
boxes = [box.id for box in jp2h.box]
self.assertEqual(boxes, ['ihdr', 'colr', 'cdef'])
self.assertEqual(jp2h.box[2].index, (0, 1, 2, 3))
self.assertEqual(jp2h.box[2].channel_type, (0, 0, 0, 1))
self.assertEqual(jp2h.box[2].association, (1, 2, 3, 0))
def test_bad_rgba(self):
"""R, G, and B must be specified."""
j2k = Jp2k(self.four_planes)
cdef = glymur.jp2box.ChannelDefinitionBox(index=[0, 1, 2, 3],
channel_type=[0, 0, 1, 1],
association=[1, 2, 3, 0])
boxes = [self.ihdr, self.colr_rgb, cdef]
self.jp2h.box = boxes
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError) as ce:
j2k.wrap(tfile.name, boxes=boxes)
def test_grey(self):
"""Just regular greyscale."""
j2k = Jp2k(self.one_plane)
cdef = glymur.jp2box.ChannelDefinitionBox(index=[0],
channel_type=[0],
association=[1])
boxes = [self.ihdr, self.colr_gr, cdef]
self.jp2h.box = boxes
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
jp2 = Jp2k(tfile.name)
jp2h = jp2.box[2]
boxes = [box.id for box in jp2h.box]
self.assertEqual(boxes, ['ihdr', 'colr', 'cdef'])
self.assertEqual(jp2h.box[2].index, (0,))
self.assertEqual(jp2h.box[2].channel_type, (0,))
self.assertEqual(jp2h.box[2].association, (1,))
def test_grey_alpha(self):
"""Just regular greyscale plus alpha."""
j2k = Jp2k(self.two_planes)
cdef = glymur.jp2box.ChannelDefinitionBox(index=[0, 1],
channel_type=[0, 1],
association=[1, 0])
boxes = [self.ihdr, self.colr_gr, cdef]
self.jp2h.box = boxes
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
jp2 = Jp2k(tfile.name)
jp2h = jp2.box[2]
boxes = [box.id for box in jp2h.box]
self.assertEqual(boxes, ['ihdr', 'colr', 'cdef'])
self.assertEqual(jp2h.box[2].index, (0, 1))
self.assertEqual(jp2h.box[2].channel_type, (0, 1))
self.assertEqual(jp2h.box[2].association, (1, 0))
def test_bad_grey_alpha(self):
"""A greyscale image with alpha layer must specify Y"""
j2k = Jp2k(self.two_planes)
# This cdef box
cdef = glymur.jp2box.ChannelDefinitionBox(index=[0, 1],
channel_type=[1, 1],
association=[0, 1])
boxes = [self.ihdr, self.colr_gr, cdef]
self.jp2h.box = boxes
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises((OSError, IOError)) as ce:
j2k.wrap(tfile.name, boxes=boxes)
def test_only_one_cdef_in_jp2_header(self):
"""There can only be one channel definition box in the jp2 header."""
j2k = Jp2k(self.j2kfile)
cdef = glymur.jp2box.ChannelDefinitionBox(index=[0, 1, 2],
channel_type=[0, 0, 0],
association=[1, 2, 3])
boxes = [self.ihdr, cdef, self.colr_rgb, cdef]
self.jp2h.box = boxes
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
def test_not_in_jp2_header(self):
j2k = Jp2k(self.j2kfile)
boxes = [self.ihdr, self.colr_rgb]
self.jp2h.box = boxes
cdef = glymur.jp2box.ChannelDefinitionBox(index=[0, 1, 2],
channel_type=[0, 0, 0],
association=[1, 2, 3])
boxes = [self.jP, self.ftyp, self.jp2h, cdef, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
def test_bad_type(self):
# Channel types are limited to 0, 1, 2, 65535
# Should reject if not all of index, channel_type, association the
# same length.
with self.assertRaises(IOError):
box = glymur.jp2box.ChannelDefinitionBox(index=[0, 1, 2],
channel_type=[0, 0, 3],
association=[1, 2, 3])
def test_wrong_lengths(self):
# Should reject if not all of index, channel_type, association the
# same length.
with self.assertRaises(IOError):
box = glymur.jp2box.ChannelDefinitionBox(index=[0, 1, 2],
channel_type=[0, 0],
association=[1, 2, 3])
class TestXML(unittest.TestCase):
def setUp(self):
self.jp2file = glymur.data.nemo()
self.j2kfile = glymur.data.goodstuff()
raw_xml = b"""<?xml version="1.0"?>
<data>
<country name="Liechtenstein">
<rank>1</rank>
<year>2008</year>
<gdppc>141100</gdppc>
<neighbor name="Austria" direction="E"/>
<neighbor name="Switzerland" direction="W"/>
</country>
<country name="Singapore">
<rank>4</rank>
<year>2011</year>
<gdppc>59900</gdppc>
<neighbor name="Malaysia" direction="N"/>
</country>
<country name="Panama">
<rank>68</rank>
<year>2011</year>
<gdppc>13600</gdppc>
<neighbor name="Costa Rica" direction="W"/>
<neighbor name="Colombia" direction="E"/>
</country>
</data>"""
with tempfile.NamedTemporaryFile(suffix=".xml", delete=False) as tfile:
tfile.write(raw_xml)
tfile.flush()
self.xmlfile = tfile.name
j2k = Jp2k(self.j2kfile)
c = j2k.get_codestream()
height = c.segment[1].Ysiz
width = c.segment[1].Xsiz
num_components = len(c.segment[1].XRsiz)
self.jP = JPEG2000SignatureBox()
self.ftyp = FileTypeBox()
self.jp2h = JP2HeaderBox()
self.jp2c = ContiguousCodestreamBox()
self.ihdr = ImageHeaderBox(height=height, width=width,
num_components=num_components)
self.colr = ColourSpecificationBox(colorspace=glymur.core.SRGB)
def tearDown(self):
os.unlink(self.xmlfile)
pass
def test_negative_both_file_and_xml_provided(self):
"""The XML should come from only one source."""
j2k = Jp2k(self.j2kfile)
xml_object = ET.parse(self.xmlfile)
with self.assertRaises((IOError, OSError)) as ce:
xmlb = glymur.jp2box.XMLBox(filename=self.xmlfile, xml=xml_object)
def test_basic_xml(self):
# Should be able to write an XMLBox.
j2k = Jp2k(self.j2kfile)
self.jp2h.box = [self.ihdr, self.colr]
the_xml = ET.fromstring('<?xml version="1.0"?><data>0</data>')
xmlb = glymur.jp2box.XMLBox(xml=the_xml)
self.assertEqual(ET.tostring(xmlb.xml),
b'<data>0</data>')
boxes = [self.jP, self.ftyp, self.jp2h, xmlb, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
jp2 = Jp2k(tfile.name)
self.assertEqual(jp2.box[3].id, 'xml ')
self.assertEqual(ET.tostring(jp2.box[3].xml),
b'<data>0</data>')
def test_xml_from_file(self):
j2k = Jp2k(self.j2kfile)
self.jp2h.box = [self.ihdr, self.colr]
xmlb = glymur.jp2box.XMLBox(filename=self.xmlfile)
boxes = [self.jP, self.ftyp, self.jp2h, xmlb, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
jp2 = Jp2k(tfile.name)
output_boxes = [box.id for box in jp2.box]
self.assertEqual(output_boxes, ['jP ', 'ftyp', 'jp2h', 'xml ',
'jp2c'])
elts = jp2.box[3].xml.findall('country')
self.assertEqual(len(elts), 3)
neighbor = elts[1].find('neighbor')
self.assertEqual(neighbor.attrib['name'], 'Malaysia')
self.assertEqual(neighbor.attrib['direction'], 'N')
class TestColourSpecificationBox(unittest.TestCase):
def setUp(self):
self.j2kfile = glymur.data.goodstuff()
j2k = Jp2k(self.j2kfile)
c = j2k.get_codestream()
height = c.segment[1].Ysiz
width = c.segment[1].Xsiz
num_components = len(c.segment[1].XRsiz)
self.jP = JPEG2000SignatureBox()
self.ftyp = FileTypeBox()
self.jp2h = JP2HeaderBox()
self.jp2c = ContiguousCodestreamBox()
self.ihdr = ImageHeaderBox(height=height, width=width,
num_components=num_components)
def tearDown(self):
pass
def test_color_specification_box_with_out_enumerated_colorspace(self):
j2k = Jp2k(self.j2kfile)
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
boxes[2].box = [self.ihdr, ColourSpecificationBox(colorspace=None)]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(NotImplementedError):
j2k.wrap(tfile.name, boxes=boxes)
def test_missing_colr_box(self):
j2k = Jp2k(self.j2kfile)
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
boxes[2].box = [self.ihdr]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
def test_default_ColourSpecificationBox(self):
b = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.SRGB)
self.assertEqual(b.method, glymur.core.ENUMERATED_COLORSPACE)
self.assertEqual(b.precedence, 0)
self.assertEqual(b.approximation, 0)
self.assertEqual(b.colorspace, glymur.core.SRGB)
self.assertIsNone(b.icc_profile)
def test_ColourSpecificationBox_with_colorspace_and_icc(self):
# Colour specification boxes can't have both.
with self.assertRaises((OSError, IOError)):
colorspace = glymur.core.SRGB
icc_profile = b'\x01\x02\x03\x04'
b = glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
icc_profile=icc_profile)
def test_ColourSpecificationBox_with_bad_method(self):
colorspace = glymur.core.SRGB
method = -1
with self.assertRaises(IOError):
b = glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
method=method)
def test_ColourSpecificationBox_with_bad_approximation(self):
colorspace = glymur.core.SRGB
approx = -1
with self.assertRaises(IOError):
b = glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
approximation=approx)
@unittest.skipIf(glymur.lib.openjp2._OPENJP2 is None,
"Missing openjp2 library.")
class TestJp2Boxes(unittest.TestCase):
def setUp(self):
self.j2kfile = glymur.data.goodstuff()
def tearDown(self):
pass
def test_default_JPEG2000SignatureBox(self):
# Should be able to instantiate a JPEG2000SignatureBox
b = glymur.jp2box.JPEG2000SignatureBox()
self.assertEqual(b.signature, (13, 10, 135, 10))
def test_default_FileTypeBox(self):
# Should be able to instantiate a FileTypeBox
b = glymur.jp2box.FileTypeBox()
self.assertEqual(b.brand, 'jp2 ')
self.assertEqual(b.minor_version, 0)
self.assertEqual(b.compatibility_list, ['jp2 '])
def test_default_ImageHeaderBox(self):
# Should be able to instantiate an image header box.
b = glymur.jp2box.ImageHeaderBox(height=512, width=256,
num_components=3)
self.assertEqual(b.height, 512)
self.assertEqual(b.width, 256)
self.assertEqual(b.num_components, 3)
self.assertEqual(b.bits_per_component, 8)
self.assertFalse(b.signed)
self.assertFalse(b.colorspace_unknown)
def test_default_JP2HeaderBox(self):
b1 = JP2HeaderBox()
b1.box = [ImageHeaderBox(height=512, width=256),
ColourSpecificationBox(colorspace=glymur.core.GREYSCALE)]
def test_default_ContiguousCodestreamBox(self):
b = ContiguousCodestreamBox()
self.assertEqual(b.id, 'jp2c')
self.assertEqual(b.main_header, [])
def verify_wrapped_raw(self, jp2file):
# Shared method by at least two tests.
jp2 = Jp2k(jp2file)
self.assertEqual(len(jp2.box), 4)
self.assertEqual(jp2.box[0].id, 'jP ')
self.assertEqual(jp2.box[0].offset, 0)
self.assertEqual(jp2.box[0].length, 12)
self.assertEqual(jp2.box[0].longname, 'JPEG 2000 Signature')
self.assertEqual(jp2.box[1].id, 'ftyp')
self.assertEqual(jp2.box[1].offset, 12)
self.assertEqual(jp2.box[1].length, 20)
self.assertEqual(jp2.box[1].longname, 'File Type')
self.assertEqual(jp2.box[2].id, 'jp2h')
self.assertEqual(jp2.box[2].offset, 32)
self.assertEqual(jp2.box[2].length, 45)
self.assertEqual(jp2.box[2].longname, 'JP2 Header')
self.assertEqual(jp2.box[3].id, 'jp2c')
self.assertEqual(jp2.box[3].offset, 77)
self.assertEqual(jp2.box[3].length, 115228)
# jp2h super box
self.assertEqual(len(jp2.box[2].box), 2)
self.assertEqual(jp2.box[2].box[0].id, 'ihdr')
self.assertEqual(jp2.box[2].box[0].offset, 40)
self.assertEqual(jp2.box[2].box[0].length, 22)
self.assertEqual(jp2.box[2].box[0].longname, 'Image Header')
self.assertEqual(jp2.box[2].box[0].height, 800)
self.assertEqual(jp2.box[2].box[0].width, 480)
self.assertEqual(jp2.box[2].box[0].num_components, 3)
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False)
self.assertEqual(jp2.box[2].box[0].compression, 7)
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
self.assertEqual(jp2.box[2].box[1].id, 'colr')
self.assertEqual(jp2.box[2].box[1].offset, 62)
self.assertEqual(jp2.box[2].box[1].length, 15)
self.assertEqual(jp2.box[2].box[1].longname, 'Colour Specification')
self.assertEqual(jp2.box[2].box[1].precedence, 0)
self.assertEqual(jp2.box[2].box[1].approximation, 0)
self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB)
self.assertIsNone(jp2.box[2].box[1].icc_profile)
def test_wrap(self):
j2k = Jp2k(self.j2kfile)
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name)
self.verify_wrapped_raw(tfile.name)
def test_wrap_jp2(self):
j2k = Jp2k(self.j2kfile)
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
jp2 = j2k.wrap(tfile.name)
boxes = [box.id for box in jp2.box]
self.assertEqual(boxes, ['jP ', 'ftyp', 'jp2h', 'jp2c'])
def test_default_layout_but_with_specified_boxes(self):
j2k = Jp2k(self.j2kfile)
boxes = [JPEG2000SignatureBox(),
FileTypeBox(),
JP2HeaderBox(),
ContiguousCodestreamBox()]
c = j2k.get_codestream()
height = c.segment[1].Ysiz
width = c.segment[1].Xsiz
num_components = len(c.segment[1].XRsiz)
boxes[2].box = [ImageHeaderBox(height=height,
width=width,
num_components=num_components),
ColourSpecificationBox(colorspace=glymur.core.SRGB)]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
self.verify_wrapped_raw(tfile.name)
def test_image_header_box_not_first_in_jp2_header(self):
# The specification says that ihdr must be the first box in jp2h.
j2k = Jp2k(self.j2kfile)
boxes = [JPEG2000SignatureBox(),
FileTypeBox(),
JP2HeaderBox(),
ContiguousCodestreamBox()]
c = j2k.get_codestream()
height = c.segment[1].Ysiz
width = c.segment[1].Xsiz
num_components = len(c.segment[1].XRsiz)
boxes[2].box = [ColourSpecificationBox(colorspace=glymur.core.SRGB),
ImageHeaderBox(height=height,
width=width,
num_components=num_components)]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
def test_first_2_boxes_not_jP_and_ftyp(self):
j2k = Jp2k(self.j2kfile)
c = j2k.get_codestream()
height = c.segment[1].Ysiz
width = c.segment[1].Xsiz
num_components = len(c.segment[1].XRsiz)
jP = JPEG2000SignatureBox()
ftyp = FileTypeBox()
jp2h = JP2HeaderBox()
jp2c = ContiguousCodestreamBox()
colr = ColourSpecificationBox(colorspace=glymur.core.SRGB)
ihdr = ImageHeaderBox(height=height, width=width,
num_components=num_components)
jp2h.box = [ihdr, colr]
boxes = [ftyp, jP, jp2h, jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
def test_jp2h_not_preceeding_jp2c(self):
j2k = Jp2k(self.j2kfile)
c = j2k.get_codestream()
height = c.segment[1].Ysiz
width = c.segment[1].Xsiz
num_components = len(c.segment[1].XRsiz)
jP = JPEG2000SignatureBox()
ftyp = FileTypeBox()
jp2h = JP2HeaderBox()
jp2c = ContiguousCodestreamBox()
colr = ColourSpecificationBox(colorspace=glymur.core.SRGB)
ihdr = ImageHeaderBox(height=height, width=width,
num_components=num_components)
jp2h.box = [ihdr, colr]
boxes = [jP, ftyp, jp2c, jp2h]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
def test_missing_codestream(self):
j2k = Jp2k(self.j2kfile)
c = j2k.get_codestream()
height = c.segment[1].Ysiz
width = c.segment[1].Xsiz
num_components = len(c.segment[1].XRsiz)
jP = JPEG2000SignatureBox()
ftyp = FileTypeBox()
jp2h = JP2HeaderBox()
ihdr = ImageHeaderBox(height=height, width=width,
num_components=num_components)
jp2h.box = [ihdr]
boxes = [jP, ftyp, jp2h]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
if __name__ == "__main__":
unittest.main()

View file

@ -15,14 +15,13 @@ if sys.hexversion <= 0x03030000:
else:
from unittest.mock import patch
import warnings
from xml.etree import cElementTree as ET
import numpy as np
import pkg_resources
import glymur
from glymur import Jp2k
from glymur.lib import openjp2 as opj2
import glymur
try:
data_root = os.environ['OPJ_DATA_ROOT']
@ -77,6 +76,8 @@ class TestJp2k(unittest.TestCase):
def setUp(self):
self.jp2file = pkg_resources.resource_filename(glymur.__name__,
"data/nemo.jp2")
self.j2kfile = pkg_resources.resource_filename(glymur.__name__,
"data/goodstuff.j2k")
def tearDown(self):
pass
@ -153,6 +154,27 @@ class TestJp2k(unittest.TestCase):
subsetdata = j.read(area=(0, 0, 512, 512))
np.testing.assert_array_equal(tiledata, subsetdata)
def test_write_srgb_without_mct(self):
j2k = Jp2k(self.j2kfile)
expdata = j2k.read()
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
ofile = Jp2k(tfile.name, 'wb')
ofile.write(expdata, mct=False)
actdata = ofile.read()
np.testing.assert_array_equal(actdata, expdata)
c = ofile.get_codestream()
self.assertEqual(c.segment[2].SPcod[3], 0) # no mct
def test_write_grayscale_with_mct(self):
# MCT usage makes no sense for grayscale images.
j2k = Jp2k(self.j2kfile)
expdata = j2k.read()
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
ofile = Jp2k(tfile.name, 'wb')
with self.assertRaises(IOError):
ofile.write(expdata[:, :, 0], mct=True)
def test_write_cprl(self):
# Issue 17
j = Jp2k(self.jp2file)
@ -213,7 +235,7 @@ class TestJp2k(unittest.TestCase):
self.assertEqual(jp2k.box[2].box[0].bits_per_component, 8)
self.assertEqual(jp2k.box[2].box[0].signed, False)
self.assertEqual(jp2k.box[2].box[0].compression, 7)
self.assertEqual(jp2k.box[2].box[0].cspace_unknown, False)
self.assertEqual(jp2k.box[2].box[0].colorspace_unknown, False)
self.assertEqual(jp2k.box[2].box[0].ip_provided, False)
self.assertEqual(jp2k.box[2].box[1].id, 'colr')
@ -594,83 +616,6 @@ class TestJp2k(unittest.TestCase):
with self.assertRaisesRegex(IOError, regexp) as ce:
d = j.read(reduce=3)
@unittest.skipIf(sys.hexversion < 0x03020000,
"Uses features introduced in 3.2.")
def test_config_file_via_environ(self):
"""Verify that we can read a configuration file set via environ var."""
with tempfile.TemporaryDirectory() as tdir:
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', {'XDG_CONFIG_HOME': tdir}):
imp.reload(glymur)
j = Jp2k(self.jp2file)
@unittest.skipIf(sys.hexversion < 0x03020000,
"Uses features introduced in 3.2.")
def test_config_file_via_environ_is_wrong(self):
# A non-existant library location should be rejected.
with tempfile.TemporaryDirectory() as tdir:
configdir = os.path.join(tdir, 'glymur')
os.mkdir(configdir)
fname = os.path.join(configdir, 'glymurrc')
with open(fname, 'w') as fp:
with 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', {'XDG_CONFIG_HOME': tdir}):
# Misconfigured new configuration file should
# be rejected.
with self.assertWarns(UserWarning) as cw:
imp.reload(glymur)
@unittest.skipIf(sys.hexversion < 0x03020000,
"Uses features introduced in 3.2.")
def test_missing_config_file_via_environ(self):
# 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', {'XDG_CONFIG_HOME': tdir}):
# Misconfigured new configuration file should
# be rejected.
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_config_dir(self):
# 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
# be rejected.
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)

View file

@ -4116,7 +4116,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[3].box[0].bits_per_component, 8)
self.assertEqual(jp2.box[3].box[0].signed, False)
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[3].box[0].cspace_unknown, False)
self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False)
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
# Jp2 Header
@ -4158,7 +4158,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False)
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header
@ -4170,8 +4170,8 @@ class TestSuiteDump(unittest.TestCase):
# Jp2 Header
# Channel Definition
self.assertEqual(jp2.box[2].box[2].component_number, (0, 1, 2))
self.assertEqual(jp2.box[2].box[2].component_type, (0, 0, 0)) # color
self.assertEqual(jp2.box[2].box[2].index, (0, 1, 2))
self.assertEqual(jp2.box[2].box[2].channel_type, (0, 0, 0)) # color
self.assertEqual(jp2.box[2].box[2].association, (3, 2, 1)) # reverse
def test_NR_file3_dump(self):
@ -4204,7 +4204,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False)
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header
@ -4250,7 +4250,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False)
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header
@ -4292,7 +4292,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[3].box[0].num_components, 3)
self.assertEqual(jp2.box[3].box[0].signed, False)
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[3].box[0].cspace_unknown, False)
self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False)
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
# Jp2 Header
@ -4338,7 +4338,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[2].box[0].bits_per_component, 12)
self.assertEqual(jp2.box[2].box[0].signed, False)
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header
@ -4387,7 +4387,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[3].box[0].bits_per_component, 16)
self.assertEqual(jp2.box[3].box[0].signed, False)
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[3].box[0].cspace_unknown, False)
self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False)
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
# Jp2 Header
@ -4436,7 +4436,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False)
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header
@ -4488,7 +4488,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False)
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Palette box.
@ -5985,7 +5985,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False)
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header
@ -6118,7 +6118,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False)
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header
@ -6246,7 +6246,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False)
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header
@ -6386,7 +6386,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[3].box[0].bits_per_component, 8)
self.assertEqual(jp2.box[3].box[0].signed, False)
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[3].box[0].cspace_unknown, True)
self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True)
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
# Jp2 Header
@ -6497,7 +6497,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False)
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[2].box[0].cspace_unknown, True)
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, True)
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header
@ -6597,7 +6597,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[3].box[0].bits_per_component, 8)
self.assertEqual(jp2.box[3].box[0].signed, False)
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[3].box[0].cspace_unknown, True)
self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True)
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
# Jp2 Header
@ -6694,7 +6694,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False)
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[2].box[0].cspace_unknown, True)
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, True)
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header
@ -6706,8 +6706,8 @@ class TestSuiteDump(unittest.TestCase):
# Jp2 Header
# Channel Definition
self.assertEqual(jp2.box[2].box[2].component_number, (0, 1))
self.assertEqual(jp2.box[2].box[2].component_type, (0, 1)) # opacity
self.assertEqual(jp2.box[2].box[2].index, (0, 1))
self.assertEqual(jp2.box[2].box[2].channel_type, (0, 1)) # opacity
self.assertEqual(jp2.box[2].box[2].association, (0, 0)) # both main
c = jp2.box[3].main_header
@ -6813,7 +6813,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[3].box[0].bits_per_component, 1)
self.assertEqual(jp2.box[3].box[0].signed, False)
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[3].box[0].cspace_unknown, True)
self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True)
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
# Jp2 Header
@ -6927,7 +6927,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[3].box[0].bits_per_component, 4)
self.assertEqual(jp2.box[3].box[0].signed, False)
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[3].box[0].cspace_unknown, True)
self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True)
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
# Jp2 Header
@ -7035,7 +7035,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False)
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header
@ -7142,7 +7142,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False)
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header
@ -7241,7 +7241,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False)
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header
@ -7343,7 +7343,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[3].box[0].bits_per_component, 8)
self.assertEqual(jp2.box[3].box[0].signed, False)
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[3].box[0].cspace_unknown, True)
self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True)
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
# Jp2 Header

View file

@ -11,7 +11,7 @@ import warnings
import numpy as np
import pkg_resources
from ..lib import openjp2 as opj2
from glymur.lib import openjp2 as opj2
# Need some combination of matplotlib, PIL, or scikits-image for reading
# other image formats.

View file

@ -11,7 +11,7 @@ import warnings
import numpy as np
from ..lib import openjp2 as opj2
from glymur.lib import openjp2 as opj2
# Need some combination of matplotlib, PIL, or scikits-image for reading
# other image formats.
@ -686,7 +686,7 @@ class TestSuiteWrite(unittest.TestCase):
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False)
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header

View file

@ -30,8 +30,7 @@ class TestPrintingNeedsLib(unittest.TestCase):
@classmethod
def setUpClass(cls):
# Setup a plain JP2 file without the two UUID boxes.
jp2file = pkg_resources.resource_filename(glymur.__name__,
"data/nemo.jp2")
jp2file = glymur.data.nemo()
with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile:
cls._plain_nemo_file = tfile.name
ijfile = Jp2k(jp2file)
@ -676,15 +675,15 @@ class TestPrinting(unittest.TestCase):
@unittest.skipIf(data_root is None,
"OPJ_DATA_ROOT environment variable not set")
def test_component_definition(self):
def test_channel_definition(self):
filename = os.path.join(data_root, 'input/conformance/file2.jp2')
j = glymur.Jp2k(filename)
print(j.box[2].box[2])
actual = sys.stdout.getvalue().strip()
lines = ['Component Definition Box (cdef) @ (81, 28)',
' Component 0 (color) ==> (3)',
' Component 1 (color) ==> (2)',
' Component 2 (color) ==> (1)']
lines = ['Channel Definition Box (cdef) @ (81, 28)',
' Channel 0 (color) ==> (3)',
' Channel 1 (color) ==> (2)',
' Channel 2 (color) ==> (1)']
expected = '\n'.join(lines)
self.assertEqual(actual, expected)

View file

@ -2,7 +2,7 @@ from setuptools import setup, find_packages
import sys
kwargs = {'name': 'Glymur',
'version': '0.1.9',
'version': '0.1.10',
'description': 'Tools for accessing JPEG2000 files',
'long_description': open('README.md').read(),
'author': 'John Evans',
@ -10,7 +10,7 @@ kwargs = {'name': 'Glymur',
'url': 'https://github.com/quintusdias/glymur',
'packages': ['glymur', 'glymur.data', 'glymur.test', 'glymur.lib',
'glymur.lib.test'],
'package_data': {'glymur': ['data/*.jp2']},
'package_data': {'glymur': ['data/*.jp2', 'data/*.j2k']},
'scripts': ['bin/jp2dump'],
'license': 'LICENSE.txt',
'platforms': ['darwin']}