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
|
Jun 16, 2013 - v0.1.9 Reading ICC profile headers as ordered dicts. Exif
|
||||||
dictionaries changed to ordered dicts. Honoring XDG_CONFIG_HOME
|
dictionaries changed to ordered dicts. Honoring XDG_CONFIG_HOME
|
||||||
environment variable.
|
environment variable.
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ API
|
||||||
Jp2k
|
Jp2k
|
||||||
----
|
----
|
||||||
.. autoclass:: glymur.Jp2k
|
.. autoclass:: glymur.Jp2k
|
||||||
:members: read, write, read_bands, get_codestream
|
:members: read, write, wrap, read_bands, get_codestream
|
||||||
|
|
||||||
Individual Boxes
|
Individual Boxes
|
||||||
----------------
|
----------------
|
||||||
|
|
@ -24,9 +24,9 @@ ColourSpecificationBox
|
||||||
.. autoclass:: glymur.jp2box.ColourSpecificationBox
|
.. autoclass:: glymur.jp2box.ColourSpecificationBox
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
ComponentDefinitionBox
|
ChannelDefinitionBox
|
||||||
''''''''''''''''''''''
|
''''''''''''''''''''''
|
||||||
.. autoclass:: glymur.jp2box.ComponentDefinitionBox
|
.. autoclass:: glymur.jp2box.ChannelDefinitionBox
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
ComponentMappingBox
|
ComponentMappingBox
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,91 @@ codestream box, only the main header is printed. It is possible to print
|
||||||
|
|
||||||
>>> print(j.get_codestream())
|
>>> print(j.get_codestream())
|
||||||
|
|
||||||
|
Add XML Metadata?
|
||||||
|
=================
|
||||||
|
An existing raw codestream (or JP2 file) can be wrapped (re-wrapped) in a
|
||||||
|
user-defined set of JP2 boxes. To get just a minimal JP2 jacket on the
|
||||||
|
codestream provided by `goodstuff.j2k`, you can use the **wrap** method with
|
||||||
|
no box argument: ::
|
||||||
|
|
||||||
|
>>> import glymur
|
||||||
|
>>> jfile = glymur.data.goodstuff()
|
||||||
|
>>> j2k = glymur.Jp2k(jfile)
|
||||||
|
>>> jp2 = j2k.wrap("newfile.jp2")
|
||||||
|
>>> print(jp2)
|
||||||
|
File: newfile.jp2
|
||||||
|
JPEG 2000 Signature Box (jP ) @ (0, 12)
|
||||||
|
Signature: 0d0a870a
|
||||||
|
File Type Box (ftyp) @ (12, 20)
|
||||||
|
Brand: jp2
|
||||||
|
Compatibility: ['jp2 ']
|
||||||
|
JP2 Header Box (jp2h) @ (32, 45)
|
||||||
|
Image Header Box (ihdr) @ (40, 22)
|
||||||
|
Size: [800 480 3]
|
||||||
|
Bitdepth: 8
|
||||||
|
Signed: False
|
||||||
|
Compression: wavelet
|
||||||
|
Colorspace Unknown: False
|
||||||
|
Colour Specification Box (colr) @ (62, 15)
|
||||||
|
Method: enumerated colorspace
|
||||||
|
Precedence: 0
|
||||||
|
Colorspace: sRGB
|
||||||
|
Contiguous Codestream Box (jp2c) @ (77, 115228)
|
||||||
|
Main header:
|
||||||
|
.
|
||||||
|
. (truncated)
|
||||||
|
.
|
||||||
|
|
||||||
|
The raw codestream was wrapped in a JP2 jacket with four boxes in the outer
|
||||||
|
layer (the signature, file type, JP2 header, and contiguous codestream), with
|
||||||
|
two additional boxes (image header and color specification) contained in the
|
||||||
|
JP2 header superbox.
|
||||||
|
|
||||||
|
XML boxes are not in the minimal set of box requirements for the JP2 format, so
|
||||||
|
in order to add an XML box into the mix, we'll need to specify all of the
|
||||||
|
boxes. If you already have a JP2 jacket in place, you can just reuse it,
|
||||||
|
though. Take the following example content in an XML file `favorites.xml` : ::
|
||||||
|
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<favorite_things>
|
||||||
|
<category>Light Ale</category>
|
||||||
|
</favorite_things>
|
||||||
|
|
||||||
|
and add it after the JP2 header box, but before the codestream box ::
|
||||||
|
|
||||||
|
>>> boxes = jp2.box # The box attribute is the list of JP2 boxes
|
||||||
|
>>> xmlbox = glymur.jp2box.XMLBox(file='favorites.xml')
|
||||||
|
>>> boxes.insert(3, xmlbox)
|
||||||
|
>>> jp2_xml = jp2.wrap("newfile_with_xml.jp2", boxes=boxes)
|
||||||
|
>>> print(jp2_xml)
|
||||||
|
File: newfile_with_xml.jp2
|
||||||
|
JPEG 2000 Signature Box (jP ) @ (0, 12)
|
||||||
|
Signature: 0d0a870a
|
||||||
|
File Type Box (ftyp) @ (12, 20)
|
||||||
|
Brand: jp2
|
||||||
|
Compatibility: ['jp2 ']
|
||||||
|
JP2 Header Box (jp2h) @ (32, 45)
|
||||||
|
Image Header Box (ihdr) @ (40, 22)
|
||||||
|
Size: [800 480 3]
|
||||||
|
Bitdepth: 8
|
||||||
|
Signed: False
|
||||||
|
Compression: wavelet
|
||||||
|
Colorspace Unknown: False
|
||||||
|
Colour Specification Box (colr) @ (62, 15)
|
||||||
|
Method: enumerated colorspace
|
||||||
|
Precedence: 0
|
||||||
|
Colorspace: sRGB
|
||||||
|
XML Box (xml ) @ (77, 76)
|
||||||
|
<favorite_things>
|
||||||
|
<category>Light Ale</category>
|
||||||
|
</favorite_things>
|
||||||
|
|
||||||
|
Contiguous Codestream Box (jp2c) @ (153, 115236)
|
||||||
|
Main header:
|
||||||
|
.
|
||||||
|
. (truncated)
|
||||||
|
.
|
||||||
|
|
||||||
Work with XMP UUIDs?
|
Work with XMP UUIDs?
|
||||||
====================
|
====================
|
||||||
The example JP2 file shipped with glymur has an XMP UUID. ::
|
The example JP2 file shipped with glymur has an XMP UUID. ::
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,12 @@ _color_type_map_display = {
|
||||||
_PRE_MULTIPLIED_OPACITY: 'pre-multiplied opacity',
|
_PRE_MULTIPLIED_OPACITY: 'pre-multiplied opacity',
|
||||||
_UNSPECIFIED: 'unspecified'}
|
_UNSPECIFIED: 'unspecified'}
|
||||||
|
|
||||||
|
# color channel definitions.
|
||||||
|
RED = 1
|
||||||
|
GREEN = 2
|
||||||
|
BLUE = 3
|
||||||
|
GREY = 1
|
||||||
|
|
||||||
# enumerated color channel associations
|
# enumerated color channel associations
|
||||||
_rgb_colorspace = {"R": 1, "G": 2, "B": 3}
|
_rgb_colorspace = {"R": 1, "G": 2, "B": 3}
|
||||||
_greyscale_colorspace = {"Y": 1}
|
_greyscale_colorspace = {"Y": 1}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
"""Shipping JPEG 2000 files.
|
"""Shipping JPEG 2000 files.
|
||||||
|
|
||||||
There is only one JP2 file at the moment, "nemo.jp2", converted from the
|
These include:
|
||||||
original JPEG photo of the aftermath of NEMO, the nor'easter that shutdown
|
nemo.jp2: converted from the original JPEG photo of the aftermath of NEMO,
|
||||||
Boston in February of 2013.
|
the nor'easter that shutdown Boston in February of 2013.
|
||||||
|
goodstuff.j2k: my favorite bevorage.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
|
||||||
|
|
@ -17,3 +19,15 @@ def nemo():
|
||||||
"""
|
"""
|
||||||
file = pkg_resources.resource_filename(__name__, "nemo.jp2")
|
file = pkg_resources.resource_filename(__name__, "nemo.jp2")
|
||||||
return file
|
return file
|
||||||
|
|
||||||
|
|
||||||
|
def goodstuff():
|
||||||
|
"""Shortcut for specifying path to goodstuff.j2k.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
file : str
|
||||||
|
Platform-independent path to goodstuff.j2k.
|
||||||
|
"""
|
||||||
|
file = pkg_resources.resource_filename(__name__, "goodstuff.j2k")
|
||||||
|
return file
|
||||||
|
|
|
||||||
BIN
glymur/data/goodstuff.j2k
Normal file
BIN
glymur/data/goodstuff.j2k
Normal file
Binary file not shown.
279
glymur/jp2box.py
279
glymur/jp2box.py
|
|
@ -31,6 +31,7 @@ from .core import _colorspace_map_display
|
||||||
from .core import _color_type_map_display
|
from .core import _color_type_map_display
|
||||||
from .core import _method_display
|
from .core import _method_display
|
||||||
from .core import _reader_requirements_display
|
from .core import _reader_requirements_display
|
||||||
|
from .core import *
|
||||||
|
|
||||||
|
|
||||||
class Jp2kBox(object):
|
class Jp2kBox(object):
|
||||||
|
|
@ -59,6 +60,12 @@ class Jp2kBox(object):
|
||||||
msg += " @ ({0}, {1})".format(self.offset, self.length)
|
msg += " @ ({0}, {1})".format(self.offset, self.length)
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
def _write(self, f):
|
||||||
|
"""Must be implemented in a subclass.
|
||||||
|
"""
|
||||||
|
msg = "Not supported for {0} box.".format(self.longname)
|
||||||
|
raise NotImplementedError(msg)
|
||||||
|
|
||||||
def _parse_superbox(self, f):
|
def _parse_superbox(self, f):
|
||||||
"""Parse a superbox (box consisting of nothing but other boxes.
|
"""Parse a superbox (box consisting of nothing but other boxes.
|
||||||
|
|
||||||
|
|
@ -159,9 +166,22 @@ class ColourSpecificationBox(Jp2kBox):
|
||||||
ICC profile header according to ICC profile specification. If
|
ICC profile header according to ICC profile specification. If
|
||||||
colorspace is not None, then icc_profile must be empty.
|
colorspace is not None, then icc_profile must be empty.
|
||||||
"""
|
"""
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, method=ENUMERATED_COLORSPACE, precedence=0,
|
||||||
Jp2kBox.__init__(self, id='', longname='Colour Specification')
|
approximation=0, colorspace=None, icc_profile=None, **kwargs):
|
||||||
|
Jp2kBox.__init__(self, id='colr', longname='Colour Specification')
|
||||||
|
|
||||||
|
if colorspace is not None and icc_profile is not None:
|
||||||
|
raise IOError("colorspace and icc_profile cannot both be set.")
|
||||||
|
if method not in (1, 2, 3, 4):
|
||||||
|
raise IOError("Invalid method.")
|
||||||
|
if approximation not in (0, 1, 2, 3, 4):
|
||||||
|
raise IOError("Invalid approximation.")
|
||||||
self.__dict__.update(**kwargs)
|
self.__dict__.update(**kwargs)
|
||||||
|
self.method = method
|
||||||
|
self.precedence = precedence
|
||||||
|
self.approximation = approximation
|
||||||
|
self.colorspace = colorspace
|
||||||
|
self.icc_profile = icc_profile
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = Jp2kBox.__str__(self)
|
msg = Jp2kBox.__str__(self)
|
||||||
|
|
@ -188,6 +208,24 @@ class ColourSpecificationBox(Jp2kBox):
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
def _write(self, f):
|
||||||
|
"""Write an Colour Specification box to file.
|
||||||
|
"""
|
||||||
|
if self.colorspace is None:
|
||||||
|
msg = "Writing Colour Specification boxes without enumerated "
|
||||||
|
msg += "colorspaces is not supported at this time."
|
||||||
|
raise NotImplementedError(msg)
|
||||||
|
length = 15 if self.icc_profile is None else 11 + len(self.icc_profile)
|
||||||
|
f.write(struct.pack('>I', length))
|
||||||
|
f.write('colr'.encode())
|
||||||
|
|
||||||
|
buffer = struct.pack('>BBBI',
|
||||||
|
self.method,
|
||||||
|
self.precedence,
|
||||||
|
self.approximation,
|
||||||
|
self.colorspace)
|
||||||
|
f.write(buffer)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(f, id, offset, length):
|
def _parse(f, id, offset, length):
|
||||||
"""Parse JPEG 2000 color specification box.
|
"""Parse JPEG 2000 color specification box.
|
||||||
|
|
@ -354,7 +392,7 @@ class _ICCProfile(object):
|
||||||
self.header = header
|
self.header = header
|
||||||
|
|
||||||
|
|
||||||
class ComponentDefinitionBox(Jp2kBox):
|
class ChannelDefinitionBox(Jp2kBox):
|
||||||
"""Container for component definition box information.
|
"""Container for component definition box information.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
|
|
@ -367,29 +405,58 @@ class ComponentDefinitionBox(Jp2kBox):
|
||||||
offset of the box from the start of the file.
|
offset of the box from the start of the file.
|
||||||
longname : str
|
longname : str
|
||||||
more verbose description of the box.
|
more verbose description of the box.
|
||||||
component_number : int
|
index : int
|
||||||
number of the component
|
number of the channel
|
||||||
component_type : int
|
channel_type : int
|
||||||
type of the component
|
type of the channel
|
||||||
association : int
|
association : int
|
||||||
number of the associated color
|
index of the associated color
|
||||||
"""
|
"""
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, index, channel_type, association, **kwargs):
|
||||||
Jp2kBox.__init__(self, id='', longname='Component Definition')
|
Jp2kBox.__init__(self, id='cdef', longname='Channel Definition')
|
||||||
|
if len(index) != len(channel_type) or len(index) != len(association):
|
||||||
|
msg = "Length of channel definition box inputs must be the same."
|
||||||
|
raise IOError(msg)
|
||||||
|
|
||||||
|
# channel types must be one of 0, 1, 2, 65535
|
||||||
|
if any(x not in [0, 1, 2, 65535] for x in channel_type):
|
||||||
|
msg = "Channel types must be in the set of\n\n"
|
||||||
|
msg += " 0 - colour image data for associated color\n"
|
||||||
|
msg += " 1 - opacity\n"
|
||||||
|
msg += " 2 - premultiplied opacity\n"
|
||||||
|
msg += " 65535 - unspecified"
|
||||||
|
raise IOError(msg)
|
||||||
|
|
||||||
|
self.index = index
|
||||||
|
self.channel_type = channel_type
|
||||||
|
self.association = association
|
||||||
self.__dict__.update(**kwargs)
|
self.__dict__.update(**kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = Jp2kBox.__str__(self)
|
msg = Jp2kBox.__str__(self)
|
||||||
for j in range(len(self.association)):
|
for j in range(len(self.association)):
|
||||||
color_type_string = _color_type_map_display[self.component_type[j]]
|
color_type_string = _color_type_map_display[self.channel_type[j]]
|
||||||
if self.association[j] == 0:
|
if self.association[j] == 0:
|
||||||
assn = 'whole image'
|
assn = 'whole image'
|
||||||
else:
|
else:
|
||||||
assn = str(self.association[j])
|
assn = str(self.association[j])
|
||||||
msg += '\n Component {0} ({1}) ==> ({2})'
|
msg += '\n Channel {0} ({1}) ==> ({2})'
|
||||||
msg = msg.format(self.component_number[j], color_type_string, assn)
|
msg = msg.format(self.index[j], color_type_string, assn)
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
def _write(self, f):
|
||||||
|
"""Write a channel definition box to file.
|
||||||
|
"""
|
||||||
|
N = len(self.association)
|
||||||
|
f.write(struct.pack('>I', 8 + 2 + N * 6))
|
||||||
|
f.write('cdef'.encode('utf-8'))
|
||||||
|
f.write(struct.pack('>H', N))
|
||||||
|
for j in range(N):
|
||||||
|
f.write(struct.pack('>' + 'H' * 3,
|
||||||
|
self.index[j],
|
||||||
|
self.channel_type[j],
|
||||||
|
self.association[j]))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(f, id, offset, length):
|
def _parse(f, id, offset, length):
|
||||||
"""Parse component definition box.
|
"""Parse component definition box.
|
||||||
|
|
@ -418,17 +485,17 @@ class ComponentDefinitionBox(Jp2kBox):
|
||||||
buffer = f.read(2)
|
buffer = f.read(2)
|
||||||
N, = struct.unpack('>H', buffer)
|
N, = struct.unpack('>H', buffer)
|
||||||
|
|
||||||
component_number = []
|
index = []
|
||||||
component_type = []
|
chan_type = []
|
||||||
association = []
|
association = []
|
||||||
|
|
||||||
buffer = f.read(N * 6)
|
buffer = f.read(N * 6)
|
||||||
data = struct.unpack('>' + 'HHH' * N, buffer)
|
data = struct.unpack('>' + 'HHH' * N, buffer)
|
||||||
kwargs['component_number'] = data[0:N * 6:3]
|
index = data[0:N * 6:3]
|
||||||
kwargs['component_type'] = data[1:N * 6:3]
|
channel_type = data[1:N * 6:3]
|
||||||
kwargs['association'] = data[2:N * 6:3]
|
association = data[2:N * 6:3]
|
||||||
|
|
||||||
box = ComponentDefinitionBox(**kwargs)
|
box = ChannelDefinitionBox(index, channel_type, association, **kwargs)
|
||||||
return box
|
return box
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -524,9 +591,10 @@ class ContiguousCodestreamBox(Jp2kBox):
|
||||||
List of segments in the codestream header.
|
List of segments in the codestream header.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, main_header=[], **kwargs):
|
||||||
Jp2kBox.__init__(self, id='jp2c', longname='Contiguous Codestream')
|
Jp2kBox.__init__(self, id='jp2c', longname='Contiguous Codestream')
|
||||||
self.__dict__.update(**kwargs)
|
self.__dict__.update(**kwargs)
|
||||||
|
self.main_header = main_header
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = Jp2kBox.__str__(self)
|
msg = Jp2kBox.__str__(self)
|
||||||
|
|
@ -591,8 +659,14 @@ class FileTypeBox(Jp2kBox):
|
||||||
List of file conformance profiles.
|
List of file conformance profiles.
|
||||||
"""
|
"""
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
Jp2kBox.__init__(self, id='', longname='File Type')
|
Jp2kBox.__init__(self, id='ftyp', longname='File Type')
|
||||||
self.__dict__.update(**kwargs)
|
self.__dict__.update(**kwargs)
|
||||||
|
if 'brand' not in kwargs.keys():
|
||||||
|
self.brand = 'jp2 '
|
||||||
|
if 'minor_version' not in kwargs.keys():
|
||||||
|
self.minor_version = 0
|
||||||
|
if 'compatibility_list' not in kwargs.keys():
|
||||||
|
self.compatibility_list = ['jp2 ']
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
lst = [Jp2kBox.__str__(self),
|
lst = [Jp2kBox.__str__(self),
|
||||||
|
|
@ -603,6 +677,18 @@ class FileTypeBox(Jp2kBox):
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
def _write(self, f):
|
||||||
|
"""Write a File Type box to file.
|
||||||
|
"""
|
||||||
|
length = 16 + 4*len(self.compatibility_list)
|
||||||
|
f.write(struct.pack('>I', length))
|
||||||
|
f.write('ftyp'.encode())
|
||||||
|
f.write(self.brand.encode())
|
||||||
|
f.write(struct.pack('>I', self.minor_version))
|
||||||
|
|
||||||
|
for item in self.compatibility_list:
|
||||||
|
f.write(item.encode())
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(f, id, offset, length):
|
def _parse(f, id, offset, length):
|
||||||
"""Parse JPEG 2000 file type box.
|
"""Parse JPEG 2000 file type box.
|
||||||
|
|
@ -673,30 +759,68 @@ class ImageHeaderBox(Jp2kBox):
|
||||||
Bits per component.
|
Bits per component.
|
||||||
signed : bool
|
signed : bool
|
||||||
False if the image components are unsigned.
|
False if the image components are unsigned.
|
||||||
compression : nt
|
compression : int
|
||||||
The compression type, should be 7 if JP2.
|
The compression type, should be 7 if JP2.
|
||||||
cspace_unknown : int
|
colorspace_unknown : bool
|
||||||
0 if the color space is known and correctly specified.
|
False if the color space is known and correctly specified.
|
||||||
ip_provided : int
|
ip_provided : bool
|
||||||
0 if the file does not contain intellectual propery rights information.
|
False if the file does not contain intellectual propery rights
|
||||||
|
information.
|
||||||
"""
|
"""
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, height, width, num_components=1, signed=False,
|
||||||
Jp2kBox.__init__(self, id='', longname='Image Header')
|
bits_per_component=8, compression=7, colorspace_unknown=False,
|
||||||
|
ip_provided=False, **kwargs):
|
||||||
|
"""
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
>>> import glymur
|
||||||
|
>>> box = glymur.jp2box.ImageHeaderBox(height=512, width=256)
|
||||||
|
"""
|
||||||
|
Jp2kBox.__init__(self, id='ihdr', longname='Image Header')
|
||||||
|
self.height = height
|
||||||
|
self.width = width
|
||||||
|
self.num_components = num_components
|
||||||
|
self.signed = signed
|
||||||
|
self.bits_per_component = bits_per_component
|
||||||
|
self.compression = compression
|
||||||
|
self.colorspace_unknown = colorspace_unknown
|
||||||
|
self.ip_provided = False
|
||||||
self.__dict__.update(**kwargs)
|
self.__dict__.update(**kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
lst = [Jp2kBox.__str__(self)]
|
msg = Jp2kBox.__str__(self)
|
||||||
lst.append('Size: [{0} {1} {2}]'.format(self.height, self.width,
|
msg = "{0}"
|
||||||
self.num_components))
|
msg += '\n Size: [{1} {2} {3}]'
|
||||||
lst.append('Bitdepth: {0}'.format(self.bits_per_component))
|
msg += '\n Bitdepth: {4}'
|
||||||
lst.append('Signed: {0}'.format(self.signed))
|
msg += '\n Signed: {5}'
|
||||||
if self.compression == 7:
|
msg += '\n Compression: {6}'
|
||||||
lst.append('Compression: wavelet')
|
msg += '\n Colorspace Unknown: {7}'
|
||||||
if self.cspace_unknown:
|
msg = msg.format(Jp2kBox.__str__(self),
|
||||||
lst.append('Colorspace Unknown: True')
|
self.height, self.width, self.num_components,
|
||||||
else:
|
self.bits_per_component,
|
||||||
lst.append('Colorspace Unknown: False')
|
self.signed,
|
||||||
return '\n '.join(lst)
|
'wavelet' if self.compression == 7 else 'unknown',
|
||||||
|
self.colorspace_unknown)
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def _write(self, f):
|
||||||
|
"""Write an Image Header box to file.
|
||||||
|
"""
|
||||||
|
f.write(struct.pack('>I', 22))
|
||||||
|
f.write('ihdr'.encode())
|
||||||
|
|
||||||
|
# signedness and bps are stored together in a single byte
|
||||||
|
bit_depth_signedness = 0x80 if self.signed else 0x00
|
||||||
|
bit_depth_signedness |= self.bits_per_component - 1
|
||||||
|
buffer = struct.pack('>IIHBBBB',
|
||||||
|
self.height,
|
||||||
|
self.width,
|
||||||
|
self.num_components,
|
||||||
|
bit_depth_signedness,
|
||||||
|
self.compression,
|
||||||
|
1 if self.colorspace_unknown else 0,
|
||||||
|
1 if self.ip_provided else 0)
|
||||||
|
f.write(buffer)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(f, id, offset, length):
|
def _parse(f, id, offset, length):
|
||||||
|
|
@ -724,16 +848,16 @@ class ImageHeaderBox(Jp2kBox):
|
||||||
# Read the box information
|
# Read the box information
|
||||||
buffer = f.read(14)
|
buffer = f.read(14)
|
||||||
params = struct.unpack('>IIHBBBB', buffer)
|
params = struct.unpack('>IIHBBBB', buffer)
|
||||||
kwargs['height'] = params[0]
|
height = params[0]
|
||||||
kwargs['width'] = params[1]
|
width = params[1]
|
||||||
kwargs['num_components'] = params[2]
|
kwargs['num_components'] = params[2]
|
||||||
kwargs['bits_per_component'] = (params[3] & 0x7f) + 1
|
kwargs['bits_per_component'] = (params[3] & 0x7f) + 1
|
||||||
kwargs['signed'] = (params[3] & 0x80) > 1
|
kwargs['signed'] = (params[3] & 0x80) > 1
|
||||||
kwargs['compression'] = params[4]
|
kwargs['compression'] = params[4]
|
||||||
kwargs['cspace_unknown'] = params[5]
|
kwargs['colorspace_unknown'] = True if params[5] else False
|
||||||
kwargs['ip_provided'] = params[6]
|
kwargs['ip_provided'] = True if params[6] else False
|
||||||
|
|
||||||
box = ImageHeaderBox(**kwargs)
|
box = ImageHeaderBox(height, width, **kwargs)
|
||||||
return box
|
return box
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -818,7 +942,7 @@ class JP2HeaderBox(Jp2kBox):
|
||||||
List of boxes contained in this superbox.
|
List of boxes contained in this superbox.
|
||||||
"""
|
"""
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
Jp2kBox.__init__(self, id='', longname='JP2 Header')
|
Jp2kBox.__init__(self, id='jp2h', longname='JP2 Header')
|
||||||
self.__dict__.update(**kwargs)
|
self.__dict__.update(**kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
@ -831,6 +955,21 @@ class JP2HeaderBox(Jp2kBox):
|
||||||
msg += ''.join(strs)
|
msg += ''.join(strs)
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
def _write(self, f):
|
||||||
|
"""Write a JP2 Header box to file.
|
||||||
|
"""
|
||||||
|
# Write the contained boxes, then come back and write the length.
|
||||||
|
orig_pos = f.tell()
|
||||||
|
f.write(struct.pack('>I', 0))
|
||||||
|
f.write('jp2h'.encode())
|
||||||
|
for box in self.box:
|
||||||
|
box._write(f)
|
||||||
|
|
||||||
|
end_pos = f.tell()
|
||||||
|
f.seek(orig_pos)
|
||||||
|
f.write(struct.pack('>I', end_pos - orig_pos))
|
||||||
|
f.seek(end_pos)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(f, id, offset, length):
|
def _parse(f, id, offset, length):
|
||||||
"""Parse JPEG 2000 header box.
|
"""Parse JPEG 2000 header box.
|
||||||
|
|
@ -882,8 +1021,10 @@ class JPEG2000SignatureBox(Jp2kBox):
|
||||||
Four-byte tuple identifying the file as JPEG 2000.
|
Four-byte tuple identifying the file as JPEG 2000.
|
||||||
"""
|
"""
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
Jp2kBox.__init__(self, id='', longname='JPEG 2000 Signature')
|
Jp2kBox.__init__(self, id='jP ', longname='JPEG 2000 Signature')
|
||||||
self.__dict__.update(**kwargs)
|
self.__dict__.update(**kwargs)
|
||||||
|
if 'signature' not in kwargs.keys():
|
||||||
|
self.signature = (13, 10, 135, 10)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = Jp2kBox.__str__(self)
|
msg = Jp2kBox.__str__(self)
|
||||||
|
|
@ -891,6 +1032,13 @@ class JPEG2000SignatureBox(Jp2kBox):
|
||||||
msg = msg.format(*self.signature)
|
msg = msg.format(*self.signature)
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
def _write(self, f):
|
||||||
|
"""Write a JPEG 2000 Signature box to file.
|
||||||
|
"""
|
||||||
|
f.write(struct.pack('>I', 12))
|
||||||
|
f.write(self.id.encode())
|
||||||
|
f.write(struct.pack('>BBBB', *self.signature))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(f, id, offset, length):
|
def _parse(f, id, offset, length):
|
||||||
"""Parse JPEG 2000 signature box.
|
"""Parse JPEG 2000 signature box.
|
||||||
|
|
@ -1412,19 +1560,50 @@ class XMLBox(Jp2kBox):
|
||||||
xml : ElementTree.Element
|
xml : ElementTree.Element
|
||||||
XML section.
|
XML section.
|
||||||
"""
|
"""
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, xml=None, filename=None, **kwargs):
|
||||||
Jp2kBox.__init__(self, id='', longname='XML')
|
"""
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
xml : ElementTree
|
||||||
|
An ElementTree object already existing in python.
|
||||||
|
filename : str
|
||||||
|
File from which to read XML. If filename is not None, then the xml
|
||||||
|
keyword argument must be None.
|
||||||
|
"""
|
||||||
|
Jp2kBox.__init__(self, id='xml ', longname='XML')
|
||||||
|
if filename is not None and xml is not None:
|
||||||
|
msg = "Only one of either filename or xml should be provided."
|
||||||
|
raise IOError(msg)
|
||||||
|
if filename is not None:
|
||||||
|
self.xml = ET.parse(filename)
|
||||||
|
else:
|
||||||
|
self.xml = xml
|
||||||
self.__dict__.update(**kwargs)
|
self.__dict__.update(**kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = Jp2kBox.__str__(self)
|
msg = Jp2kBox.__str__(self)
|
||||||
xml = self.xml
|
xml = self.xml
|
||||||
if self.xml is not None:
|
if self.xml is not None:
|
||||||
|
try:
|
||||||
msg += _pretty_print_xml(self.xml)
|
msg += _pretty_print_xml(self.xml)
|
||||||
|
except TypeError:
|
||||||
|
msg += _pretty_print_xml(self.xml.getroot())
|
||||||
else:
|
else:
|
||||||
msg += '\n {0}'.format(xml)
|
msg += '\n {0}'.format(xml)
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
def _write(self, f):
|
||||||
|
"""Write an XML box to file.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
buffer = ET.tostring(self.xml, encoding='utf-8')
|
||||||
|
except AttributeError:
|
||||||
|
buffer = ET.tostring(self.xml.getroot(), encoding='utf-8')
|
||||||
|
|
||||||
|
f.write(struct.pack('>I', len(buffer) + 8))
|
||||||
|
f.write(self.id.encode())
|
||||||
|
f.write(buffer)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(f, id, offset, length):
|
def _parse(f, id, offset, length):
|
||||||
"""Parse XML box.
|
"""Parse XML box.
|
||||||
|
|
@ -2273,7 +2452,7 @@ class _ExifInteroperabilityIfd(_Ifd):
|
||||||
# Map each box ID to the corresponding class.
|
# Map each box ID to the corresponding class.
|
||||||
_box_with_id = {
|
_box_with_id = {
|
||||||
'asoc': AssociationBox,
|
'asoc': AssociationBox,
|
||||||
'cdef': ComponentDefinitionBox,
|
'cdef': ChannelDefinitionBox,
|
||||||
'cmap': ComponentMappingBox,
|
'cmap': ComponentMappingBox,
|
||||||
'colr': ColourSpecificationBox,
|
'colr': ColourSpecificationBox,
|
||||||
'jP ': JPEG2000SignatureBox,
|
'jP ': JPEG2000SignatureBox,
|
||||||
|
|
|
||||||
164
glymur/jp2k.py
164
glymur/jp2k.py
|
|
@ -16,8 +16,8 @@ import warnings
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from .codestream import Codestream
|
from .codestream import Codestream
|
||||||
from .core import progression_order
|
from .core import *
|
||||||
from .jp2box import Jp2kBox
|
from .jp2box import *
|
||||||
from .lib import openjp2 as opj2
|
from .lib import openjp2 as opj2
|
||||||
|
|
||||||
_cspace_map = {'rgb': opj2._CLRSPC_SRGB,
|
_cspace_map = {'rgb': opj2._CLRSPC_SRGB,
|
||||||
|
|
@ -141,7 +141,7 @@ class Jp2k(Jp2kBox):
|
||||||
def write(self, img_array, cratios=None, eph=False, psnr=None, numres=None,
|
def write(self, img_array, cratios=None, eph=False, psnr=None, numres=None,
|
||||||
cbsize=None, psizes=None, grid_offset=None, sop=False,
|
cbsize=None, psizes=None, grid_offset=None, sop=False,
|
||||||
subsam=None, tilesize=None, prog=None, modesw=None,
|
subsam=None, tilesize=None, prog=None, modesw=None,
|
||||||
colorspace=None, verbose=False):
|
colorspace=None, verbose=False, mct=None):
|
||||||
"""Write image data to a JP2/JPX/J2k file. Intended usage of the
|
"""Write image data to a JP2/JPX/J2k file. Intended usage of the
|
||||||
various parameters follows that of OpenJPEG's opj_compress utility.
|
various parameters follows that of OpenJPEG's opj_compress utility.
|
||||||
|
|
||||||
|
|
@ -167,6 +167,9 @@ class Jp2k(Jp2kBox):
|
||||||
If true, write SOP marker after each header packet.
|
If true, write SOP marker after each header packet.
|
||||||
grid_offset : tuple, optional
|
grid_offset : tuple, optional
|
||||||
Offset (DY, DX) of the origin of the image in the reference grid.
|
Offset (DY, DX) of the origin of the image in the reference grid.
|
||||||
|
mct : bool, optional
|
||||||
|
Specifies usage of the multi component transform. If not
|
||||||
|
specified, defaults to True if the colorspace is RGB.
|
||||||
modesw : int, optional
|
modesw : int, optional
|
||||||
Mode switch.
|
Mode switch.
|
||||||
1 = BYPASS(LAZY)
|
1 = BYPASS(LAZY)
|
||||||
|
|
@ -336,6 +339,18 @@ class Jp2k(Jp2kBox):
|
||||||
else:
|
else:
|
||||||
colorspace = _cspace_map[colorspace]
|
colorspace = _cspace_map[colorspace]
|
||||||
|
|
||||||
|
if mct is None:
|
||||||
|
if colorspace == opj2._CLRSPC_SRGB:
|
||||||
|
cparams.tcp_mct = 1
|
||||||
|
else:
|
||||||
|
cparams.tcp_mct = 0
|
||||||
|
else:
|
||||||
|
if mct and colorspace == opj2._CLRSPC_GRAY:
|
||||||
|
msg = "Cannot specify usage of the multi component transform "
|
||||||
|
msg += "if the colorspace is gray."
|
||||||
|
raise IOError(msg)
|
||||||
|
cparams.tcp_mct = 1 if mct else 0
|
||||||
|
|
||||||
if img_array.dtype == np.uint8:
|
if img_array.dtype == np.uint8:
|
||||||
comp_prec = 8
|
comp_prec = 8
|
||||||
elif img_array.dtype == np.uint16:
|
elif img_array.dtype == np.uint16:
|
||||||
|
|
@ -372,12 +387,6 @@ class Jp2k(Jp2kBox):
|
||||||
src = layer.ctypes.data
|
src = layer.ctypes.data
|
||||||
ctypes.memmove(dest, src, layer.nbytes)
|
ctypes.memmove(dest, src, layer.nbytes)
|
||||||
|
|
||||||
# set multi-component transform?
|
|
||||||
if image.contents.numcomps == 3:
|
|
||||||
cparams.tcp_mct = 1
|
|
||||||
else:
|
|
||||||
cparams.tcp_mct = 0
|
|
||||||
|
|
||||||
codec = opj2._create_compress(codec_fmt)
|
codec = opj2._create_compress(codec_fmt)
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
|
|
@ -398,6 +407,143 @@ class Jp2k(Jp2kBox):
|
||||||
|
|
||||||
self._parse()
|
self._parse()
|
||||||
|
|
||||||
|
def wrap(self, filename, boxes=None):
|
||||||
|
"""Write the codestream back out to file, wrapped in new JP2 jacket.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
filename : str
|
||||||
|
JP2 file to be created from a raw codestream.
|
||||||
|
boxes : list
|
||||||
|
JP2 box definitions to define the JP2 file format. If not
|
||||||
|
provided, a default ""jacket" is assumed, consisting of JP2
|
||||||
|
signature, file type, JP2 header, and contiguous codestream boxes.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
jp2 : Jp2k object
|
||||||
|
Newly wrapped Jp2k object.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
>>> import glymur, tempfile
|
||||||
|
>>> jfile = glymur.data.goodstuff()
|
||||||
|
>>> j2k = glymur.Jp2k(jfile)
|
||||||
|
>>> tfile = tempfile.NamedTemporaryFile(suffix='jp2')
|
||||||
|
>>> jp2 = j2k.wrap(tfile.name)
|
||||||
|
"""
|
||||||
|
if boxes is None:
|
||||||
|
# Try to create a reasonable default.
|
||||||
|
boxes = [JPEG2000SignatureBox(),
|
||||||
|
FileTypeBox(),
|
||||||
|
JP2HeaderBox(),
|
||||||
|
ContiguousCodestreamBox()]
|
||||||
|
c = self.get_codestream()
|
||||||
|
height = c.segment[1].Ysiz
|
||||||
|
width = c.segment[1].Xsiz
|
||||||
|
num_components = len(c.segment[1].XRsiz)
|
||||||
|
boxes[2].box = [ImageHeaderBox(height=height,
|
||||||
|
width=width,
|
||||||
|
num_components=num_components),
|
||||||
|
ColourSpecificationBox(colorspace=SRGB)]
|
||||||
|
|
||||||
|
# Check for a bad sequence of boxes.
|
||||||
|
# 1st two boxes must be 'jP ' and 'ftyp'
|
||||||
|
if boxes[0].id != 'jP ' or boxes[1].id != 'ftyp':
|
||||||
|
msg = "The first box must be the signature box and the second "
|
||||||
|
msg += "must be the file type box."
|
||||||
|
raise IOError(msg)
|
||||||
|
|
||||||
|
# jp2c must be preceeded by jp2h
|
||||||
|
jp2h_lst = [idx for (idx, box) in enumerate(boxes) if box.id == 'jp2h']
|
||||||
|
jp2h_idx = jp2h_lst[0]
|
||||||
|
jp2c_lst = [idx for (idx, box) in enumerate(boxes) if box.id == 'jp2c']
|
||||||
|
if len(jp2c_lst) == 0:
|
||||||
|
msg = "A codestream box must be defined in the outermost "
|
||||||
|
msg += "list of boxes."
|
||||||
|
raise IOError(msg)
|
||||||
|
|
||||||
|
jp2c_idx = jp2c_lst[0]
|
||||||
|
if jp2h_idx >= jp2c_idx:
|
||||||
|
msg = "The codestream box must be preceeded by a jp2 header box."
|
||||||
|
raise IOError(msg)
|
||||||
|
|
||||||
|
# 1st jp2 header box must be ihdr
|
||||||
|
jp2h = boxes[jp2h_idx]
|
||||||
|
if jp2h.box[0].id != 'ihdr':
|
||||||
|
msg = "The first box in the jp2 header box must be the image "
|
||||||
|
msg += "header box."
|
||||||
|
raise IOError(msg)
|
||||||
|
|
||||||
|
# colr must be present in jp2 header box.
|
||||||
|
jp2hb = jp2h.box
|
||||||
|
colr_lst = [j for (j, box) in enumerate(jp2h.box) if box.id == 'colr']
|
||||||
|
if len(colr_lst) == 0:
|
||||||
|
msg = "The jp2 header box must contain a color definition box."
|
||||||
|
raise IOError(msg)
|
||||||
|
colr = jp2h.box[colr_lst[0]]
|
||||||
|
|
||||||
|
# Any cdef box must be in the jp2 header following the image header.
|
||||||
|
cdef_lst = [j for (j, box) in enumerate(boxes) if box.id == 'cdef']
|
||||||
|
if len(cdef_lst) != 0:
|
||||||
|
msg = "Any channel defintion box must be in the JP2 header "
|
||||||
|
msg += "following the image header."
|
||||||
|
raise IOError(msg)
|
||||||
|
|
||||||
|
cdef_lst = [j for (j, box) in enumerate(jp2h.box) if box.id == 'cdef']
|
||||||
|
if len(cdef_lst) > 1:
|
||||||
|
msg = "Only one channel definition box is allowed in the "
|
||||||
|
msg += "JP2 header."
|
||||||
|
raise IOError(msg)
|
||||||
|
elif len(cdef_lst) == 1:
|
||||||
|
cdef = jp2h.box[cdef_lst[0]]
|
||||||
|
assn = cdef.association
|
||||||
|
typ = cdef.channel_type
|
||||||
|
index = cdef.index
|
||||||
|
if colr.colorspace == SRGB:
|
||||||
|
if any([chan + 1 not in assn or typ[chan] != 0
|
||||||
|
for chan in [0, 1, 2]]):
|
||||||
|
msg = "All color channels must be defined in the "
|
||||||
|
msg += "channel definition box."
|
||||||
|
raise IOError(msg)
|
||||||
|
elif colr.colorspace == GREYSCALE:
|
||||||
|
if 0 not in typ:
|
||||||
|
msg = "All color channels must be defined in the "
|
||||||
|
msg += "channel definition box."
|
||||||
|
raise IOError(msg)
|
||||||
|
|
||||||
|
with open(filename, 'wb') as ofile:
|
||||||
|
for box in boxes:
|
||||||
|
if box.id != 'jp2c':
|
||||||
|
box._write(ofile)
|
||||||
|
else:
|
||||||
|
# The codestream gets written last.
|
||||||
|
if len(self.box) == 0:
|
||||||
|
# Am I a raw codestream? If so, then it is pretty
|
||||||
|
# easy, just write the codestream box header plus all
|
||||||
|
# of myself out to file.
|
||||||
|
ofile.write(struct.pack('>I', self.length + 8))
|
||||||
|
ofile.write('jp2c'.encode())
|
||||||
|
with open(self.filename, 'rb') as ifile:
|
||||||
|
ofile.write(ifile.read())
|
||||||
|
else:
|
||||||
|
# OK, I'm a jp2 file. Need to find out where the
|
||||||
|
# raw codestream actually starts.
|
||||||
|
jp2c = [box for box in self.box if box.id == 'jp2c']
|
||||||
|
jp2c = jp2c[0]
|
||||||
|
ofile.write(struct.pack('>I', jp2c.length + 8))
|
||||||
|
ofile.write('jp2c'.encode())
|
||||||
|
with open(self.filename, 'rb') as ifile:
|
||||||
|
# Seek 8 bytes past the L, T fields to get to the
|
||||||
|
# raw codestream.
|
||||||
|
ifile.seek(jp2c.offset + 8)
|
||||||
|
ofile.write(ifile.read(jp2c.length - 8))
|
||||||
|
|
||||||
|
ofile.flush()
|
||||||
|
|
||||||
|
jp2 = Jp2k(filename)
|
||||||
|
return jp2
|
||||||
|
|
||||||
def read(self, reduce=0, layer=0, area=None, tile=None, verbose=False):
|
def read(self, reduce=0, layer=0, area=None, tile=None, verbose=False):
|
||||||
"""Read a JPEG 2000 image.
|
"""Read a JPEG 2000 image.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,4 @@ from .test_printing import TestPrinting as printing
|
||||||
from .test_opj_suite import TestSuite as suite
|
from .test_opj_suite import TestSuite as suite
|
||||||
from .test_opj_suite_write import TestSuiteWrite as suitew
|
from .test_opj_suite_write import TestSuiteWrite as suitew
|
||||||
from .test_opj_suite_neg import TestSuiteNegative as suiteneg
|
from .test_opj_suite_neg import TestSuiteNegative as suiteneg
|
||||||
|
from .test_jp2box import TestJp2Boxes as box
|
||||||
|
|
|
||||||
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:
|
else:
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
import warnings
|
import warnings
|
||||||
from xml.etree import cElementTree as ET
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
|
@ -77,6 +76,8 @@ class TestJp2k(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.jp2file = pkg_resources.resource_filename(glymur.__name__,
|
self.jp2file = pkg_resources.resource_filename(glymur.__name__,
|
||||||
"data/nemo.jp2")
|
"data/nemo.jp2")
|
||||||
|
self.j2kfile = pkg_resources.resource_filename(glymur.__name__,
|
||||||
|
"data/goodstuff.j2k")
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
@ -153,6 +154,27 @@ class TestJp2k(unittest.TestCase):
|
||||||
subsetdata = j.read(area=(0, 0, 512, 512))
|
subsetdata = j.read(area=(0, 0, 512, 512))
|
||||||
np.testing.assert_array_equal(tiledata, subsetdata)
|
np.testing.assert_array_equal(tiledata, subsetdata)
|
||||||
|
|
||||||
|
def test_write_srgb_without_mct(self):
|
||||||
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
expdata = j2k.read()
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
ofile = Jp2k(tfile.name, 'wb')
|
||||||
|
ofile.write(expdata, mct=False)
|
||||||
|
actdata = ofile.read()
|
||||||
|
np.testing.assert_array_equal(actdata, expdata)
|
||||||
|
|
||||||
|
c = ofile.get_codestream()
|
||||||
|
self.assertEqual(c.segment[2].SPcod[3], 0) # no mct
|
||||||
|
|
||||||
|
def test_write_grayscale_with_mct(self):
|
||||||
|
# MCT usage makes no sense for grayscale images.
|
||||||
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
expdata = j2k.read()
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
ofile = Jp2k(tfile.name, 'wb')
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
ofile.write(expdata[:, :, 0], mct=True)
|
||||||
|
|
||||||
def test_write_cprl(self):
|
def test_write_cprl(self):
|
||||||
# Issue 17
|
# Issue 17
|
||||||
j = Jp2k(self.jp2file)
|
j = Jp2k(self.jp2file)
|
||||||
|
|
@ -213,7 +235,7 @@ class TestJp2k(unittest.TestCase):
|
||||||
self.assertEqual(jp2k.box[2].box[0].bits_per_component, 8)
|
self.assertEqual(jp2k.box[2].box[0].bits_per_component, 8)
|
||||||
self.assertEqual(jp2k.box[2].box[0].signed, False)
|
self.assertEqual(jp2k.box[2].box[0].signed, False)
|
||||||
self.assertEqual(jp2k.box[2].box[0].compression, 7)
|
self.assertEqual(jp2k.box[2].box[0].compression, 7)
|
||||||
self.assertEqual(jp2k.box[2].box[0].cspace_unknown, False)
|
self.assertEqual(jp2k.box[2].box[0].colorspace_unknown, False)
|
||||||
self.assertEqual(jp2k.box[2].box[0].ip_provided, False)
|
self.assertEqual(jp2k.box[2].box[0].ip_provided, False)
|
||||||
|
|
||||||
self.assertEqual(jp2k.box[2].box[1].id, 'colr')
|
self.assertEqual(jp2k.box[2].box[1].id, 'colr')
|
||||||
|
|
|
||||||
|
|
@ -4116,7 +4116,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[3].box[0].bits_per_component, 8)
|
self.assertEqual(jp2.box[3].box[0].bits_per_component, 8)
|
||||||
self.assertEqual(jp2.box[3].box[0].signed, False)
|
self.assertEqual(jp2.box[3].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[3].box[0].cspace_unknown, False)
|
self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False)
|
||||||
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -4158,7 +4158,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
||||||
self.assertEqual(jp2.box[2].box[0].signed, False)
|
self.assertEqual(jp2.box[2].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
|
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -4170,8 +4170,8 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
# Channel Definition
|
# Channel Definition
|
||||||
self.assertEqual(jp2.box[2].box[2].component_number, (0, 1, 2))
|
self.assertEqual(jp2.box[2].box[2].index, (0, 1, 2))
|
||||||
self.assertEqual(jp2.box[2].box[2].component_type, (0, 0, 0)) # color
|
self.assertEqual(jp2.box[2].box[2].channel_type, (0, 0, 0)) # color
|
||||||
self.assertEqual(jp2.box[2].box[2].association, (3, 2, 1)) # reverse
|
self.assertEqual(jp2.box[2].box[2].association, (3, 2, 1)) # reverse
|
||||||
|
|
||||||
def test_NR_file3_dump(self):
|
def test_NR_file3_dump(self):
|
||||||
|
|
@ -4204,7 +4204,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
||||||
self.assertEqual(jp2.box[2].box[0].signed, False)
|
self.assertEqual(jp2.box[2].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
|
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -4250,7 +4250,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
||||||
self.assertEqual(jp2.box[2].box[0].signed, False)
|
self.assertEqual(jp2.box[2].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
|
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -4292,7 +4292,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[3].box[0].num_components, 3)
|
self.assertEqual(jp2.box[3].box[0].num_components, 3)
|
||||||
self.assertEqual(jp2.box[3].box[0].signed, False)
|
self.assertEqual(jp2.box[3].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[3].box[0].cspace_unknown, False)
|
self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False)
|
||||||
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -4338,7 +4338,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[2].box[0].bits_per_component, 12)
|
self.assertEqual(jp2.box[2].box[0].bits_per_component, 12)
|
||||||
self.assertEqual(jp2.box[2].box[0].signed, False)
|
self.assertEqual(jp2.box[2].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
|
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -4387,7 +4387,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[3].box[0].bits_per_component, 16)
|
self.assertEqual(jp2.box[3].box[0].bits_per_component, 16)
|
||||||
self.assertEqual(jp2.box[3].box[0].signed, False)
|
self.assertEqual(jp2.box[3].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[3].box[0].cspace_unknown, False)
|
self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False)
|
||||||
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -4436,7 +4436,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
||||||
self.assertEqual(jp2.box[2].box[0].signed, False)
|
self.assertEqual(jp2.box[2].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
|
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -4488,7 +4488,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
||||||
self.assertEqual(jp2.box[2].box[0].signed, False)
|
self.assertEqual(jp2.box[2].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
|
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Palette box.
|
# Palette box.
|
||||||
|
|
@ -5985,7 +5985,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
||||||
self.assertEqual(jp2.box[2].box[0].signed, False)
|
self.assertEqual(jp2.box[2].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
|
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -6118,7 +6118,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
||||||
self.assertEqual(jp2.box[2].box[0].signed, False)
|
self.assertEqual(jp2.box[2].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
|
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -6246,7 +6246,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
||||||
self.assertEqual(jp2.box[2].box[0].signed, False)
|
self.assertEqual(jp2.box[2].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
|
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -6386,7 +6386,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[3].box[0].bits_per_component, 8)
|
self.assertEqual(jp2.box[3].box[0].bits_per_component, 8)
|
||||||
self.assertEqual(jp2.box[3].box[0].signed, False)
|
self.assertEqual(jp2.box[3].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[3].box[0].cspace_unknown, True)
|
self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True)
|
||||||
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -6497,7 +6497,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
||||||
self.assertEqual(jp2.box[2].box[0].signed, False)
|
self.assertEqual(jp2.box[2].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[2].box[0].cspace_unknown, True)
|
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, True)
|
||||||
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -6597,7 +6597,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[3].box[0].bits_per_component, 8)
|
self.assertEqual(jp2.box[3].box[0].bits_per_component, 8)
|
||||||
self.assertEqual(jp2.box[3].box[0].signed, False)
|
self.assertEqual(jp2.box[3].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[3].box[0].cspace_unknown, True)
|
self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True)
|
||||||
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -6694,7 +6694,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
||||||
self.assertEqual(jp2.box[2].box[0].signed, False)
|
self.assertEqual(jp2.box[2].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[2].box[0].cspace_unknown, True)
|
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, True)
|
||||||
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -6706,8 +6706,8 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
# Channel Definition
|
# Channel Definition
|
||||||
self.assertEqual(jp2.box[2].box[2].component_number, (0, 1))
|
self.assertEqual(jp2.box[2].box[2].index, (0, 1))
|
||||||
self.assertEqual(jp2.box[2].box[2].component_type, (0, 1)) # opacity
|
self.assertEqual(jp2.box[2].box[2].channel_type, (0, 1)) # opacity
|
||||||
self.assertEqual(jp2.box[2].box[2].association, (0, 0)) # both main
|
self.assertEqual(jp2.box[2].box[2].association, (0, 0)) # both main
|
||||||
|
|
||||||
c = jp2.box[3].main_header
|
c = jp2.box[3].main_header
|
||||||
|
|
@ -6813,7 +6813,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[3].box[0].bits_per_component, 1)
|
self.assertEqual(jp2.box[3].box[0].bits_per_component, 1)
|
||||||
self.assertEqual(jp2.box[3].box[0].signed, False)
|
self.assertEqual(jp2.box[3].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[3].box[0].cspace_unknown, True)
|
self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True)
|
||||||
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -6927,7 +6927,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[3].box[0].bits_per_component, 4)
|
self.assertEqual(jp2.box[3].box[0].bits_per_component, 4)
|
||||||
self.assertEqual(jp2.box[3].box[0].signed, False)
|
self.assertEqual(jp2.box[3].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[3].box[0].cspace_unknown, True)
|
self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True)
|
||||||
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -7035,7 +7035,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
||||||
self.assertEqual(jp2.box[2].box[0].signed, False)
|
self.assertEqual(jp2.box[2].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
|
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -7142,7 +7142,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
||||||
self.assertEqual(jp2.box[2].box[0].signed, False)
|
self.assertEqual(jp2.box[2].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
|
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -7241,7 +7241,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
||||||
self.assertEqual(jp2.box[2].box[0].signed, False)
|
self.assertEqual(jp2.box[2].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
|
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -7343,7 +7343,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[3].box[0].bits_per_component, 8)
|
self.assertEqual(jp2.box[3].box[0].bits_per_component, 8)
|
||||||
self.assertEqual(jp2.box[3].box[0].signed, False)
|
self.assertEqual(jp2.box[3].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[3].box[0].cspace_unknown, True)
|
self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True)
|
||||||
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
|
||||||
|
|
@ -686,7 +686,7 @@ class TestSuiteWrite(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
||||||
self.assertEqual(jp2.box[2].box[0].signed, False)
|
self.assertEqual(jp2.box[2].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[2].box[0].cspace_unknown, False)
|
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,7 @@ class TestPrintingNeedsLib(unittest.TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
# Setup a plain JP2 file without the two UUID boxes.
|
# Setup a plain JP2 file without the two UUID boxes.
|
||||||
jp2file = pkg_resources.resource_filename(glymur.__name__,
|
jp2file = glymur.data.nemo()
|
||||||
"data/nemo.jp2")
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile:
|
with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile:
|
||||||
cls._plain_nemo_file = tfile.name
|
cls._plain_nemo_file = tfile.name
|
||||||
ijfile = Jp2k(jp2file)
|
ijfile = Jp2k(jp2file)
|
||||||
|
|
@ -676,15 +675,15 @@ class TestPrinting(unittest.TestCase):
|
||||||
|
|
||||||
@unittest.skipIf(data_root is None,
|
@unittest.skipIf(data_root is None,
|
||||||
"OPJ_DATA_ROOT environment variable not set")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_component_definition(self):
|
def test_channel_definition(self):
|
||||||
filename = os.path.join(data_root, 'input/conformance/file2.jp2')
|
filename = os.path.join(data_root, 'input/conformance/file2.jp2')
|
||||||
j = glymur.Jp2k(filename)
|
j = glymur.Jp2k(filename)
|
||||||
print(j.box[2].box[2])
|
print(j.box[2].box[2])
|
||||||
actual = sys.stdout.getvalue().strip()
|
actual = sys.stdout.getvalue().strip()
|
||||||
lines = ['Component Definition Box (cdef) @ (81, 28)',
|
lines = ['Channel Definition Box (cdef) @ (81, 28)',
|
||||||
' Component 0 (color) ==> (3)',
|
' Channel 0 (color) ==> (3)',
|
||||||
' Component 1 (color) ==> (2)',
|
' Channel 1 (color) ==> (2)',
|
||||||
' Component 2 (color) ==> (1)']
|
' Channel 2 (color) ==> (1)']
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue