Merge branch 'devel' of https://github.com/quintusdias/glymur into devel

This commit is contained in:
jevans 2013-06-27 19:20:59 -04:00
commit f11e37545a
21 changed files with 1379 additions and 270 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 Jun 16, 2013 - v0.1.9 Reading ICC profile headers as ordered dicts. Exif
dictionaries changed to ordered dicts. Honoring XDG_CONFIG_HOME dictionaries changed to ordered dicts. Honoring XDG_CONFIG_HOME
environment variable. environment variable.

View file

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

View file

@ -120,7 +120,13 @@ Not currently supported.
Testing 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 $ cd /to/where/you/unpacked/glymur
$ python -m unittest discover $ 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()) >>> 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? 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. ::

View file

@ -70,3 +70,11 @@ to take advantage of it. For example, if you install with pip's
$ export PYTHONPATH=$HOME/.local/lib/python3.3/site-packages $ export PYTHONPATH=$HOME/.local/lib/python3.3/site-packages
$ export PATH=$HOME/.local/bin:$PATH $ export PATH=$HOME/.local/bin:$PATH
You can run the tests from within python as follows::
>>> import glymur
>>> glymur.runtests()
Many tests are currently skipped; the important thing is whether or not any
tests fail.

View file

@ -1,92 +1,16 @@
"""glymur - read, write, and interrogate JPEG 2000 files """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 .jp2k import Jp2k
from .jp2dump import jp2dump from .jp2dump import jp2dump
from . import data from . import data
from . import test 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) segment = self._parseGenericSegment(f, marker_id)
else: 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 += 'in codestream: "0x{1:x}"'
msg = msg.format(offset, marker_id) msg = msg.format(offset, marker_id)
raise IOError(msg) raise IOError(msg)

View file

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

View file

@ -1,8 +1,10 @@
"""Shipping JPEG 2000 files. """Shipping JPEG 2000 files.
There is only one JP2 file at the moment, "nemo.jp2", converted from the These include:
original JPEG photo of the aftermath of NEMO, the nor'easter that shutdown nemo.jp2: converted from the original JPEG photo of the aftermath of NEMO,
Boston in February of 2013. the nor'easter that shutdown Boston in February of 2013.
goodstuff.j2k: my favorite bevorage.
""" """
import pkg_resources import pkg_resources
@ -17,3 +19,15 @@ def nemo():
""" """
file = pkg_resources.resource_filename(__name__, "nemo.jp2") file = pkg_resources.resource_filename(__name__, "nemo.jp2")
return file 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 _color_type_map_display
from .core import _method_display from .core import _method_display
from .core import _reader_requirements_display from .core import _reader_requirements_display
from .core import *
class Jp2kBox(object): class Jp2kBox(object):
@ -59,6 +60,12 @@ class Jp2kBox(object):
msg += " @ ({0}, {1})".format(self.offset, self.length) msg += " @ ({0}, {1})".format(self.offset, self.length)
return msg 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): def _parse_superbox(self, f):
"""Parse a superbox (box consisting of nothing but other boxes. """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 ICC profile header according to ICC profile specification. If
colorspace is not None, then icc_profile must be empty. colorspace is not None, then icc_profile must be empty.
""" """
def __init__(self, **kwargs): def __init__(self, method=ENUMERATED_COLORSPACE, precedence=0,
Jp2kBox.__init__(self, id='', longname='Colour Specification') 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.__dict__.update(**kwargs)
self.method = method
self.precedence = precedence
self.approximation = approximation
self.colorspace = colorspace
self.icc_profile = icc_profile
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
@ -188,6 +208,24 @@ class ColourSpecificationBox(Jp2kBox):
return msg 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 @staticmethod
def _parse(f, id, offset, length): def _parse(f, id, offset, length):
"""Parse JPEG 2000 color specification box. """Parse JPEG 2000 color specification box.
@ -354,7 +392,7 @@ class _ICCProfile(object):
self.header = header self.header = header
class ComponentDefinitionBox(Jp2kBox): class ChannelDefinitionBox(Jp2kBox):
"""Container for component definition box information. """Container for component definition box information.
Attributes Attributes
@ -367,29 +405,58 @@ class ComponentDefinitionBox(Jp2kBox):
offset of the box from the start of the file. offset of the box from the start of the file.
longname : str longname : str
more verbose description of the box. more verbose description of the box.
component_number : int index : int
number of the component number of the channel
component_type : int channel_type : int
type of the component type of the channel
association : int association : int
number of the associated color index of the associated color
""" """
def __init__(self, **kwargs): def __init__(self, index, channel_type, association, **kwargs):
Jp2kBox.__init__(self, id='', longname='Component Definition') 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) self.__dict__.update(**kwargs)
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
for j in range(len(self.association)): 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: if self.association[j] == 0:
assn = 'whole image' assn = 'whole image'
else: else:
assn = str(self.association[j]) assn = str(self.association[j])
msg += '\n Component {0} ({1}) ==> ({2})' msg += '\n Channel {0} ({1}) ==> ({2})'
msg = msg.format(self.component_number[j], color_type_string, assn) msg = msg.format(self.index[j], color_type_string, assn)
return msg 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 @staticmethod
def _parse(f, id, offset, length): def _parse(f, id, offset, length):
"""Parse component definition box. """Parse component definition box.
@ -418,17 +485,17 @@ class ComponentDefinitionBox(Jp2kBox):
buffer = f.read(2) buffer = f.read(2)
N, = struct.unpack('>H', buffer) N, = struct.unpack('>H', buffer)
component_number = [] index = []
component_type = [] chan_type = []
association = [] association = []
buffer = f.read(N * 6) buffer = f.read(N * 6)
data = struct.unpack('>' + 'HHH' * N, buffer) data = struct.unpack('>' + 'HHH' * N, buffer)
kwargs['component_number'] = data[0:N * 6:3] index = data[0:N * 6:3]
kwargs['component_type'] = data[1:N * 6:3] channel_type = data[1:N * 6:3]
kwargs['association'] = data[2:N * 6:3] association = data[2:N * 6:3]
box = ComponentDefinitionBox(**kwargs) box = ChannelDefinitionBox(index, channel_type, association, **kwargs)
return box return box
@ -524,9 +591,10 @@ class ContiguousCodestreamBox(Jp2kBox):
List of segments in the codestream header. 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') Jp2kBox.__init__(self, id='jp2c', longname='Contiguous Codestream')
self.__dict__.update(**kwargs) self.__dict__.update(**kwargs)
self.main_header = main_header
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
@ -591,8 +659,14 @@ class FileTypeBox(Jp2kBox):
List of file conformance profiles. List of file conformance profiles.
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
Jp2kBox.__init__(self, id='', longname='File Type') Jp2kBox.__init__(self, id='ftyp', longname='File Type')
self.__dict__.update(**kwargs) 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): def __str__(self):
lst = [Jp2kBox.__str__(self), lst = [Jp2kBox.__str__(self),
@ -603,6 +677,18 @@ class FileTypeBox(Jp2kBox):
return msg 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 @staticmethod
def _parse(f, id, offset, length): def _parse(f, id, offset, length):
"""Parse JPEG 2000 file type box. """Parse JPEG 2000 file type box.
@ -673,30 +759,68 @@ class ImageHeaderBox(Jp2kBox):
Bits per component. Bits per component.
signed : bool signed : bool
False if the image components are unsigned. False if the image components are unsigned.
compression : nt compression : int
The compression type, should be 7 if JP2. The compression type, should be 7 if JP2.
cspace_unknown : int colorspace_unknown : bool
0 if the color space is known and correctly specified. False if the color space is known and correctly specified.
ip_provided : int ip_provided : bool
0 if the file does not contain intellectual propery rights information. False if the file does not contain intellectual propery rights
information.
""" """
def __init__(self, **kwargs): def __init__(self, height, width, num_components=1, signed=False,
Jp2kBox.__init__(self, id='', longname='Image Header') 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) self.__dict__.update(**kwargs)
def __str__(self): def __str__(self):
lst = [Jp2kBox.__str__(self)] msg = Jp2kBox.__str__(self)
lst.append('Size: [{0} {1} {2}]'.format(self.height, self.width, msg = "{0}"
self.num_components)) msg += '\n Size: [{1} {2} {3}]'
lst.append('Bitdepth: {0}'.format(self.bits_per_component)) msg += '\n Bitdepth: {4}'
lst.append('Signed: {0}'.format(self.signed)) msg += '\n Signed: {5}'
if self.compression == 7: msg += '\n Compression: {6}'
lst.append('Compression: wavelet') msg += '\n Colorspace Unknown: {7}'
if self.cspace_unknown: msg = msg.format(Jp2kBox.__str__(self),
lst.append('Colorspace Unknown: True') self.height, self.width, self.num_components,
else: self.bits_per_component,
lst.append('Colorspace Unknown: False') self.signed,
return '\n '.join(lst) '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 @staticmethod
def _parse(f, id, offset, length): def _parse(f, id, offset, length):
@ -724,16 +848,16 @@ class ImageHeaderBox(Jp2kBox):
# Read the box information # Read the box information
buffer = f.read(14) buffer = f.read(14)
params = struct.unpack('>IIHBBBB', buffer) params = struct.unpack('>IIHBBBB', buffer)
kwargs['height'] = params[0] height = params[0]
kwargs['width'] = params[1] width = params[1]
kwargs['num_components'] = params[2] kwargs['num_components'] = params[2]
kwargs['bits_per_component'] = (params[3] & 0x7f) + 1 kwargs['bits_per_component'] = (params[3] & 0x7f) + 1
kwargs['signed'] = (params[3] & 0x80) > 1 kwargs['signed'] = (params[3] & 0x80) > 1
kwargs['compression'] = params[4] kwargs['compression'] = params[4]
kwargs['cspace_unknown'] = params[5] kwargs['colorspace_unknown'] = True if params[5] else False
kwargs['ip_provided'] = params[6] kwargs['ip_provided'] = True if params[6] else False
box = ImageHeaderBox(**kwargs) box = ImageHeaderBox(height, width, **kwargs)
return box return box
@ -818,7 +942,7 @@ class JP2HeaderBox(Jp2kBox):
List of boxes contained in this superbox. List of boxes contained in this superbox.
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
Jp2kBox.__init__(self, id='', longname='JP2 Header') Jp2kBox.__init__(self, id='jp2h', longname='JP2 Header')
self.__dict__.update(**kwargs) self.__dict__.update(**kwargs)
def __str__(self): def __str__(self):
@ -831,6 +955,21 @@ class JP2HeaderBox(Jp2kBox):
msg += ''.join(strs) msg += ''.join(strs)
return msg 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 @staticmethod
def _parse(f, id, offset, length): def _parse(f, id, offset, length):
"""Parse JPEG 2000 header box. """Parse JPEG 2000 header box.
@ -882,8 +1021,10 @@ class JPEG2000SignatureBox(Jp2kBox):
Four-byte tuple identifying the file as JPEG 2000. Four-byte tuple identifying the file as JPEG 2000.
""" """
def __init__(self, **kwargs): 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) self.__dict__.update(**kwargs)
if 'signature' not in kwargs.keys():
self.signature = (13, 10, 135, 10)
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
@ -891,6 +1032,13 @@ class JPEG2000SignatureBox(Jp2kBox):
msg = msg.format(*self.signature) msg = msg.format(*self.signature)
return msg 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 @staticmethod
def _parse(f, id, offset, length): def _parse(f, id, offset, length):
"""Parse JPEG 2000 signature box. """Parse JPEG 2000 signature box.
@ -1412,19 +1560,50 @@ class XMLBox(Jp2kBox):
xml : ElementTree.Element xml : ElementTree.Element
XML section. XML section.
""" """
def __init__(self, **kwargs): def __init__(self, xml=None, filename=None, **kwargs):
Jp2kBox.__init__(self, id='', longname='XML') """
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) self.__dict__.update(**kwargs)
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
xml = self.xml xml = self.xml
if self.xml is not None: 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: else:
msg += '\n {0}'.format(xml) msg += '\n {0}'.format(xml)
return msg 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 @staticmethod
def _parse(f, id, offset, length): def _parse(f, id, offset, length):
"""Parse XML box. """Parse XML box.
@ -2273,7 +2452,7 @@ class _ExifInteroperabilityIfd(_Ifd):
# Map each box ID to the corresponding class. # Map each box ID to the corresponding class.
_box_with_id = { _box_with_id = {
'asoc': AssociationBox, 'asoc': AssociationBox,
'cdef': ComponentDefinitionBox, 'cdef': ChannelDefinitionBox,
'cmap': ComponentMappingBox, 'cmap': ComponentMappingBox,
'colr': ColourSpecificationBox, 'colr': ColourSpecificationBox,
'jP ': JPEG2000SignatureBox, 'jP ': JPEG2000SignatureBox,

View file

@ -16,8 +16,8 @@ import warnings
import numpy as np import numpy as np
from .codestream import Codestream from .codestream import Codestream
from .core import progression_order from .core import *
from .jp2box import Jp2kBox from .jp2box import *
from .lib import openjp2 as opj2 from .lib import openjp2 as opj2
_cspace_map = {'rgb': opj2._CLRSPC_SRGB, _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, def write(self, img_array, cratios=None, eph=False, psnr=None, numres=None,
cbsize=None, psizes=None, grid_offset=None, sop=False, cbsize=None, psizes=None, grid_offset=None, sop=False,
subsam=None, tilesize=None, prog=None, modesw=None, subsam=None, tilesize=None, prog=None, modesw=None,
colorspace=None, verbose=False): colorspace=None, verbose=False, mct=None):
"""Write image data to a JP2/JPX/J2k file. Intended usage of the """Write image data to a JP2/JPX/J2k file. Intended usage of the
various parameters follows that of OpenJPEG's opj_compress utility. 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. If true, write SOP marker after each header packet.
grid_offset : tuple, optional grid_offset : tuple, optional
Offset (DY, DX) of the origin of the image in the reference grid. 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 modesw : int, optional
Mode switch. Mode switch.
1 = BYPASS(LAZY) 1 = BYPASS(LAZY)
@ -336,6 +339,18 @@ class Jp2k(Jp2kBox):
else: else:
colorspace = _cspace_map[colorspace] 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: if img_array.dtype == np.uint8:
comp_prec = 8 comp_prec = 8
elif img_array.dtype == np.uint16: elif img_array.dtype == np.uint16:
@ -372,12 +387,6 @@ class Jp2k(Jp2kBox):
src = layer.ctypes.data src = layer.ctypes.data
ctypes.memmove(dest, src, layer.nbytes) 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) codec = opj2._create_compress(codec_fmt)
if verbose: if verbose:
@ -398,6 +407,143 @@ class Jp2k(Jp2kBox):
self._parse() 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): def read(self, reduce=0, layer=0, area=None, tile=None, verbose=False):
"""Read a JPEG 2000 image. """Read a JPEG 2000 image.

View file

@ -2,12 +2,99 @@
Wraps individual functions in openjp2 library. 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 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 import numpy as np
from .. import _OPENJP2 # Progression order
from ..core import LRCP, RLCP, RPCL, PCRL, CPRL LRCP = 0
RLCP = 1
RPCL = 2
PCRL = 3
CPRL = 4
_ERROR_MSG_LST = [] _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 import TestSuite as suite
from .test_opj_suite_write import TestSuiteWrite as suitew from .test_opj_suite_write import TestSuiteWrite as suitew
from .test_opj_suite_neg import TestSuiteNegative as suiteneg 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: else:
from unittest.mock import patch from unittest.mock import patch
import warnings import warnings
from xml.etree import cElementTree as ET
import numpy as np import numpy as np
import pkg_resources import pkg_resources
import glymur
from glymur import Jp2k from glymur import Jp2k
from glymur.lib import openjp2 as opj2 from glymur.lib import openjp2 as opj2
import glymur
try: try:
data_root = os.environ['OPJ_DATA_ROOT'] data_root = os.environ['OPJ_DATA_ROOT']
@ -77,6 +76,8 @@ class TestJp2k(unittest.TestCase):
def setUp(self): def setUp(self):
self.jp2file = pkg_resources.resource_filename(glymur.__name__, self.jp2file = pkg_resources.resource_filename(glymur.__name__,
"data/nemo.jp2") "data/nemo.jp2")
self.j2kfile = pkg_resources.resource_filename(glymur.__name__,
"data/goodstuff.j2k")
def tearDown(self): def tearDown(self):
pass pass
@ -153,6 +154,27 @@ class TestJp2k(unittest.TestCase):
subsetdata = j.read(area=(0, 0, 512, 512)) subsetdata = j.read(area=(0, 0, 512, 512))
np.testing.assert_array_equal(tiledata, subsetdata) 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): def test_write_cprl(self):
# Issue 17 # Issue 17
j = Jp2k(self.jp2file) 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].bits_per_component, 8)
self.assertEqual(jp2k.box[2].box[0].signed, False) 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].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[0].ip_provided, False)
self.assertEqual(jp2k.box[2].box[1].id, 'colr') self.assertEqual(jp2k.box[2].box[1].id, 'colr')
@ -594,83 +616,6 @@ class TestJp2k(unittest.TestCase):
with self.assertRaisesRegex(IOError, regexp) as ce: with self.assertRaisesRegex(IOError, regexp) as ce:
d = j.read(reduce=3) 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): def test_xmp_attribute(self):
# Verify that we can read the XMP packet in our shipping example file. # Verify that we can read the XMP packet in our shipping example file.
j = Jp2k(self.jp2file) j = Jp2k(self.jp2file)

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].bits_per_component, 8)
self.assertEqual(jp2.box[3].box[0].signed, False) 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].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) self.assertEqual(jp2.box[3].box[0].ip_provided, False)
# Jp2 Header # 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].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False) 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].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) self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header # Jp2 Header
@ -4170,8 +4170,8 @@ class TestSuiteDump(unittest.TestCase):
# Jp2 Header # Jp2 Header
# Channel Definition # Channel Definition
self.assertEqual(jp2.box[2].box[2].component_number, (0, 1, 2)) self.assertEqual(jp2.box[2].box[2].index, (0, 1, 2))
self.assertEqual(jp2.box[2].box[2].component_type, (0, 0, 0)) # color 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 self.assertEqual(jp2.box[2].box[2].association, (3, 2, 1)) # reverse
def test_NR_file3_dump(self): 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].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False) 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].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) self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header # 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].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False) 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].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) self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header # 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].num_components, 3)
self.assertEqual(jp2.box[3].box[0].signed, False) 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].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) self.assertEqual(jp2.box[3].box[0].ip_provided, False)
# Jp2 Header # 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].bits_per_component, 12)
self.assertEqual(jp2.box[2].box[0].signed, False) 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].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) self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header # 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].bits_per_component, 16)
self.assertEqual(jp2.box[3].box[0].signed, False) 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].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) self.assertEqual(jp2.box[3].box[0].ip_provided, False)
# Jp2 Header # 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].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False) 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].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) self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header # 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].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False) 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].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) self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Palette box. # 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].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False) 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].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) self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header # 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].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False) 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].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) self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header # 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].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False) 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].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) self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header # 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].bits_per_component, 8)
self.assertEqual(jp2.box[3].box[0].signed, False) 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].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) self.assertEqual(jp2.box[3].box[0].ip_provided, False)
# Jp2 Header # 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].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False) 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].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) self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header # 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].bits_per_component, 8)
self.assertEqual(jp2.box[3].box[0].signed, False) 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].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) self.assertEqual(jp2.box[3].box[0].ip_provided, False)
# Jp2 Header # 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].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False) 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].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) self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header # Jp2 Header
@ -6706,8 +6706,8 @@ class TestSuiteDump(unittest.TestCase):
# Jp2 Header # Jp2 Header
# Channel Definition # Channel Definition
self.assertEqual(jp2.box[2].box[2].component_number, (0, 1)) self.assertEqual(jp2.box[2].box[2].index, (0, 1))
self.assertEqual(jp2.box[2].box[2].component_type, (0, 1)) # opacity self.assertEqual(jp2.box[2].box[2].channel_type, (0, 1)) # opacity
self.assertEqual(jp2.box[2].box[2].association, (0, 0)) # both main self.assertEqual(jp2.box[2].box[2].association, (0, 0)) # both main
c = jp2.box[3].main_header 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].bits_per_component, 1)
self.assertEqual(jp2.box[3].box[0].signed, False) 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].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) self.assertEqual(jp2.box[3].box[0].ip_provided, False)
# Jp2 Header # 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].bits_per_component, 4)
self.assertEqual(jp2.box[3].box[0].signed, False) 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].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) self.assertEqual(jp2.box[3].box[0].ip_provided, False)
# Jp2 Header # 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].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False) 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].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) self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header # 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].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False) 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].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) self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header # 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].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False) 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].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) self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header # 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].bits_per_component, 8)
self.assertEqual(jp2.box[3].box[0].signed, False) 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].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) self.assertEqual(jp2.box[3].box[0].ip_provided, False)
# Jp2 Header # Jp2 Header

View file

@ -11,7 +11,7 @@ import warnings
import numpy as np import numpy as np
import pkg_resources 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 # Need some combination of matplotlib, PIL, or scikits-image for reading
# other image formats. # other image formats.

View file

@ -11,7 +11,7 @@ import warnings
import numpy as np 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 # Need some combination of matplotlib, PIL, or scikits-image for reading
# other image formats. # 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].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False) 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].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) self.assertEqual(jp2.box[2].box[0].ip_provided, False)
# Jp2 Header # Jp2 Header

View file

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