diff --git a/CHANGES.txt b/CHANGES.txt
index ef8ef6c..efc70fc 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -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.
diff --git a/docs/source/api.rst b/docs/source/api.rst
index e1efdac..7584718 100644
--- a/docs/source/api.rst
+++ b/docs/source/api.rst
@@ -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
diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst
index 355ff14..a15b865 100644
--- a/docs/source/how_do_i.rst
+++ b/docs/source/how_do_i.rst
@@ -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` : ::
+
+
+
+ Light Ale
+
+
+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)
+
+ Light Ale
+
+
+ Contiguous Codestream Box (jp2c) @ (153, 115236)
+ Main header:
+ .
+ . (truncated)
+ .
+
Work with XMP UUIDs?
====================
The example JP2 file shipped with glymur has an XMP UUID. ::
diff --git a/glymur/codestream.py b/glymur/codestream.py
index 04c208f..327dd19 100644
--- a/glymur/codestream.py
+++ b/glymur/codestream.py
@@ -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)
diff --git a/glymur/core.py b/glymur/core.py
index 73a286e..6483558 100644
--- a/glymur/core.py
+++ b/glymur/core.py
@@ -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}
diff --git a/glymur/data/__init__.py b/glymur/data/__init__.py
index ff39c0e..bebfb08 100644
--- a/glymur/data/__init__.py
+++ b/glymur/data/__init__.py
@@ -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
diff --git a/glymur/data/goodstuff.j2k b/glymur/data/goodstuff.j2k
new file mode 100644
index 0000000..3e3c1b6
Binary files /dev/null and b/glymur/data/goodstuff.j2k differ
diff --git a/glymur/jp2box.py b/glymur/jp2box.py
index d4a44e9..e0107f3 100644
--- a/glymur/jp2box.py
+++ b/glymur/jp2box.py
@@ -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,
diff --git a/glymur/jp2k.py b/glymur/jp2k.py
index b09271e..96ead49 100644
--- a/glymur/jp2k.py
+++ b/glymur/jp2k.py
@@ -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.
diff --git a/glymur/test/__init__.py b/glymur/test/__init__.py
index 1e19d88..aec2d72 100644
--- a/glymur/test/__init__.py
+++ b/glymur/test/__init__.py
@@ -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
diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py
new file mode 100644
index 0000000..e0500f5
--- /dev/null
+++ b/glymur/test/test_jp2box.py
@@ -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"""
+
+
+ 1
+ 2008
+ 141100
+
+
+
+
+ 4
+ 2011
+ 59900
+
+
+
+ 68
+ 2011
+ 13600
+
+
+
+ """
+ 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('0')
+ xmlb = glymur.jp2box.XMLBox(xml=the_xml)
+ self.assertEqual(ET.tostring(xmlb.xml),
+ b'0')
+
+ 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'0')
+
+ 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()
diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py
index 031b989..d9549c5 100644
--- a/glymur/test/test_jp2k.py
+++ b/glymur/test/test_jp2k.py
@@ -15,7 +15,6 @@ 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
@@ -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')
diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py
index 9a7efde..4e47971 100644
--- a/glymur/test/test_opj_suite.py
+++ b/glymur/test/test_opj_suite.py
@@ -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
diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py
index 74237c2..50e0e48 100644
--- a/glymur/test/test_opj_suite_write.py
+++ b/glymur/test/test_opj_suite_write.py
@@ -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
diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py
index f948c3e..4090a2d 100644
--- a/glymur/test/test_printing.py
+++ b/glymur/test/test_printing.py
@@ -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)