Merge branch 'issue41' into devel
This commit is contained in:
commit
b946d9b519
15 changed files with 1163 additions and 103 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -34,6 +34,91 @@ codestream box, only the main header is printed. It is possible to print
|
|||
|
||||
>>> print(j.get_codestream())
|
||||
|
||||
Add XML Metadata?
|
||||
=================
|
||||
An existing raw codestream (or JP2 file) can be wrapped (re-wrapped) in a
|
||||
user-defined set of JP2 boxes. To get just a minimal JP2 jacket on the
|
||||
codestream provided by `goodstuff.j2k`, you can use the **wrap** method with
|
||||
no box argument: ::
|
||||
|
||||
>>> import glymur
|
||||
>>> jfile = glymur.data.goodstuff()
|
||||
>>> j2k = glymur.Jp2k(jfile)
|
||||
>>> jp2 = j2k.wrap("newfile.jp2")
|
||||
>>> print(jp2)
|
||||
File: newfile.jp2
|
||||
JPEG 2000 Signature Box (jP ) @ (0, 12)
|
||||
Signature: 0d0a870a
|
||||
File Type Box (ftyp) @ (12, 20)
|
||||
Brand: jp2
|
||||
Compatibility: ['jp2 ']
|
||||
JP2 Header Box (jp2h) @ (32, 45)
|
||||
Image Header Box (ihdr) @ (40, 22)
|
||||
Size: [800 480 3]
|
||||
Bitdepth: 8
|
||||
Signed: False
|
||||
Compression: wavelet
|
||||
Colorspace Unknown: False
|
||||
Colour Specification Box (colr) @ (62, 15)
|
||||
Method: enumerated colorspace
|
||||
Precedence: 0
|
||||
Colorspace: sRGB
|
||||
Contiguous Codestream Box (jp2c) @ (77, 115228)
|
||||
Main header:
|
||||
.
|
||||
. (truncated)
|
||||
.
|
||||
|
||||
The raw codestream was wrapped in a JP2 jacket with four boxes in the outer
|
||||
layer (the signature, file type, JP2 header, and contiguous codestream), with
|
||||
two additional boxes (image header and color specification) contained in the
|
||||
JP2 header superbox.
|
||||
|
||||
XML boxes are not in the minimal set of box requirements for the JP2 format, so
|
||||
in order to add an XML box into the mix, we'll need to specify all of the
|
||||
boxes. If you already have a JP2 jacket in place, you can just reuse it,
|
||||
though. Take the following example content in an XML file `favorites.xml` : ::
|
||||
|
||||
<?xml version="1.0"?>
|
||||
<favorite_things>
|
||||
<category>Light Ale</category>
|
||||
</favorite_things>
|
||||
|
||||
and add it after the JP2 header box, but before the codestream box ::
|
||||
|
||||
>>> boxes = jp2.box # The box attribute is the list of JP2 boxes
|
||||
>>> xmlbox = glymur.jp2box.XMLBox(file='favorites.xml')
|
||||
>>> boxes.insert(3, xmlbox)
|
||||
>>> jp2_xml = jp2.wrap("newfile_with_xml.jp2", boxes=boxes)
|
||||
>>> print(jp2_xml)
|
||||
File: newfile_with_xml.jp2
|
||||
JPEG 2000 Signature Box (jP ) @ (0, 12)
|
||||
Signature: 0d0a870a
|
||||
File Type Box (ftyp) @ (12, 20)
|
||||
Brand: jp2
|
||||
Compatibility: ['jp2 ']
|
||||
JP2 Header Box (jp2h) @ (32, 45)
|
||||
Image Header Box (ihdr) @ (40, 22)
|
||||
Size: [800 480 3]
|
||||
Bitdepth: 8
|
||||
Signed: False
|
||||
Compression: wavelet
|
||||
Colorspace Unknown: False
|
||||
Colour Specification Box (colr) @ (62, 15)
|
||||
Method: enumerated colorspace
|
||||
Precedence: 0
|
||||
Colorspace: sRGB
|
||||
XML Box (xml ) @ (77, 76)
|
||||
<favorite_things>
|
||||
<category>Light Ale</category>
|
||||
</favorite_things>
|
||||
|
||||
Contiguous Codestream Box (jp2c) @ (153, 115236)
|
||||
Main header:
|
||||
.
|
||||
. (truncated)
|
||||
.
|
||||
|
||||
Work with XMP UUIDs?
|
||||
====================
|
||||
The example JP2 file shipped with glymur has an XMP UUID. ::
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
"""Shipping JPEG 2000 files.
|
||||
|
||||
There is only one JP2 file at the moment, "nemo.jp2", converted from the
|
||||
original JPEG photo of the aftermath of NEMO, the nor'easter that shutdown
|
||||
Boston in February of 2013.
|
||||
These include:
|
||||
nemo.jp2: converted from the original JPEG photo of the aftermath of NEMO,
|
||||
the nor'easter that shutdown Boston in February of 2013.
|
||||
goodstuff.j2k: my favorite bevorage.
|
||||
|
||||
"""
|
||||
import pkg_resources
|
||||
|
||||
|
|
@ -17,3 +19,15 @@ def nemo():
|
|||
"""
|
||||
file = pkg_resources.resource_filename(__name__, "nemo.jp2")
|
||||
return file
|
||||
|
||||
|
||||
def goodstuff():
|
||||
"""Shortcut for specifying path to goodstuff.j2k.
|
||||
|
||||
Returns
|
||||
-------
|
||||
file : str
|
||||
Platform-independent path to goodstuff.j2k.
|
||||
"""
|
||||
file = pkg_resources.resource_filename(__name__, "goodstuff.j2k")
|
||||
return file
|
||||
|
|
|
|||
BIN
glymur/data/goodstuff.j2k
Normal file
BIN
glymur/data/goodstuff.j2k
Normal file
Binary file not shown.
281
glymur/jp2box.py
281
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,
|
||||
|
|
|
|||
164
glymur/jp2k.py
164
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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
604
glymur/test/test_jp2box.py
Normal file
604
glymur/test/test_jp2box.py
Normal 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()
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue