Merge branch 'issue175' into devel
This commit is contained in:
commit
15358326da
10 changed files with 643 additions and 173 deletions
21
CHANGES.txt
21
CHANGES.txt
|
|
@ -1,13 +1,14 @@
|
||||||
Feb 09, 2014 - Removed support for Python 2.6. Added write support for JP2
|
Feb 09, 2014 - Changed constructor for ChannelDefinition box. Removed support
|
||||||
UUID, DataEntryURL, Palette and Component Mapping boxes, JPX
|
for Python 2.6. Added write support for JP2 UUID, DataEntryURL,
|
||||||
Association, NumberList and DataReference boxes. Added read
|
Palette and Component Mapping boxes, JPX Association, NumberList
|
||||||
support for JPX free, number list, data reference, fragment
|
and DataReference boxes. Added read support for JPX free,
|
||||||
table, and fragment list boxes. Improved JPX Reader Requirements box
|
number list, data reference, fragment table, and fragment list
|
||||||
support. Added get_printoptions, set_printoptions functions.
|
boxes. Improved JPX Reader Requirements box support. Added
|
||||||
Palette box now a 2D numpy array instead of a list of 1D arrays.
|
get_printoptions, set_printoptions functions. Palette box now
|
||||||
JP2 super box constructors now take optional box list argument.
|
a 2D numpy array instead of a list of 1D arrays. JP2 super box
|
||||||
Fixed bug where JPX files with more than one codestream but
|
constructors now take optional box list argument. Fixed bug
|
||||||
advertising jp2 compatibility were not being read.
|
where JPX files with more than one codestream but advertising
|
||||||
|
jp2 compatibility were not being read.
|
||||||
|
|
||||||
Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask
|
Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask
|
||||||
length is unsupported.
|
length is unsupported.
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ ChangeLog
|
||||||
* added write support for JP2 UUID, dataEntryURL, palette, and component mapping boxes
|
* added write support for JP2 UUID, dataEntryURL, palette, and component mapping boxes
|
||||||
* added read/write support for JPX free, number list, and data reference boxes
|
* added read/write support for JPX free, number list, and data reference boxes
|
||||||
* Added read support for JPX fragment list and fragment table boxes
|
* Added read support for JPX fragment list and fragment table boxes
|
||||||
|
* incompatible change to channel definition box constructor, channel_type and association are no longer keyword arguments
|
||||||
* incompatible change to palette box constructor, it now takes a 2D numpy array instead of a list of 1D arrays
|
* incompatible change to palette box constructor, it now takes a 2D numpy array instead of a list of 1D arrays
|
||||||
|
|
||||||
0.5.0 (September 16, 2013)
|
0.5.0 (September 16, 2013)
|
||||||
|
|
|
||||||
|
|
@ -359,7 +359,7 @@ channel, but we aren't doing that). ::
|
||||||
|
|
||||||
>>> from glymur.core import RED, GREEN, BLUE, WHOLE_IMAGE
|
>>> from glymur.core import RED, GREEN, BLUE, WHOLE_IMAGE
|
||||||
>>> asoc = [RED, GREEN, BLUE, WHOLE_IMAGE]
|
>>> asoc = [RED, GREEN, BLUE, WHOLE_IMAGE]
|
||||||
>>> cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=ctype, association=asoc)
|
>>> cdef = glymur.jp2box.ChannelDefinitionBox(ctype, asoc)
|
||||||
>>> print(cdef)
|
>>> print(cdef)
|
||||||
Channel Definition Box (cdef) @ (0, 0)
|
Channel Definition Box (cdef) @ (0, 0)
|
||||||
Channel 0 (color) ==> (1)
|
Channel 0 (color) ==> (1)
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ class _Ifd(object):
|
||||||
tag_name = tagnum2name[tag]
|
tag_name = tagnum2name[tag]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# Ok, we don't recognize this tag. Just use the numeric id.
|
# Ok, we don't recognize this tag. Just use the numeric id.
|
||||||
msg = 'Unrecognized Exif tag "{0}".'.format(tag)
|
msg = 'Unrecognized Exif tag: {0}'.format(tag)
|
||||||
warnings.warn(msg, UserWarning)
|
warnings.warn(msg, UserWarning)
|
||||||
tag_name = tag
|
tag_name = tag
|
||||||
self.processed_ifd[tag_name] = value
|
self.processed_ifd[tag_name] = value
|
||||||
|
|
|
||||||
360
glymur/jp2box.py
360
glymur/jp2box.py
|
|
@ -29,6 +29,7 @@ import numpy as np
|
||||||
from .codestream import Codestream
|
from .codestream import Codestream
|
||||||
from .core import _COLORSPACE_MAP_DISPLAY
|
from .core import _COLORSPACE_MAP_DISPLAY
|
||||||
from .core import _COLOR_TYPE_MAP_DISPLAY
|
from .core import _COLOR_TYPE_MAP_DISPLAY
|
||||||
|
from .core import SRGB, GREYSCALE, YCC
|
||||||
from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE
|
from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE
|
||||||
from .core import ANY_ICC_PROFILE, VENDOR_COLOR_METHOD
|
from .core import ANY_ICC_PROFILE, VENDOR_COLOR_METHOD
|
||||||
from .core import _pretty_print_xml
|
from .core import _pretty_print_xml
|
||||||
|
|
@ -63,6 +64,8 @@ class Jp2kBox(object):
|
||||||
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.
|
||||||
|
box : list
|
||||||
|
List of JPEG 2000 boxes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, box_id='', offset=0, length=0, longname=''):
|
def __init__(self, box_id='', offset=0, length=0, longname=''):
|
||||||
|
|
@ -70,6 +73,7 @@ class Jp2kBox(object):
|
||||||
self.length = length
|
self.length = length
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
self.longname = longname
|
self.longname = longname
|
||||||
|
self.box = []
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
msg = "glymur.jp2box.Jp2kBox(box_id='{0}', offset={1}, length={2}, "
|
msg = "glymur.jp2box.Jp2kBox(box_id='{0}', offset={1}, length={2}, "
|
||||||
|
|
@ -88,6 +92,18 @@ class Jp2kBox(object):
|
||||||
msg = "Not supported for {0} box.".format(self.longname)
|
msg = "Not supported for {0} box.".format(self.longname)
|
||||||
raise NotImplementedError(msg)
|
raise NotImplementedError(msg)
|
||||||
|
|
||||||
|
def _str_superbox(self):
|
||||||
|
"""__str__ method for all superboxes."""
|
||||||
|
msg = Jp2kBox.__str__(self)
|
||||||
|
for box in self.box:
|
||||||
|
boxstr = str(box)
|
||||||
|
|
||||||
|
# Add indentation.
|
||||||
|
strs = [('\n ' + x) for x in boxstr.split('\n')]
|
||||||
|
msg += ''.join(strs)
|
||||||
|
return msg
|
||||||
|
|
||||||
|
|
||||||
def _write_superbox(self, fptr):
|
def _write_superbox(self, fptr):
|
||||||
"""Write a superbox.
|
"""Write a superbox.
|
||||||
|
|
||||||
|
|
@ -108,6 +124,54 @@ class Jp2kBox(object):
|
||||||
fptr.write(struct.pack('>I', end_pos - orig_pos))
|
fptr.write(struct.pack('>I', end_pos - orig_pos))
|
||||||
fptr.seek(end_pos)
|
fptr.seek(end_pos)
|
||||||
|
|
||||||
|
def _parse_this_box(self, fptr, box_id, start, num_bytes):
|
||||||
|
"""Parse the current box.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
fptr : file
|
||||||
|
Open file object.
|
||||||
|
box_id : str
|
||||||
|
4-letter identifier for the current box.
|
||||||
|
start, num_bytes: int
|
||||||
|
Byte offset and length of the current box.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
box : Jp2kBox
|
||||||
|
object corresponding to the current box
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
box = _BOX_WITH_ID[box_id].parse(fptr, start, num_bytes)
|
||||||
|
except KeyError:
|
||||||
|
msg = 'Unrecognized box ({0}) encountered.'.format(box_id)
|
||||||
|
warnings.warn(msg)
|
||||||
|
box = UnknownBox(box_id, offset=start, length=num_bytes,
|
||||||
|
longname='Unknown')
|
||||||
|
|
||||||
|
cpos = fptr.tell()
|
||||||
|
if not ((cpos == start + 8) or (cpos == start + 16)):
|
||||||
|
# If the file pointer has advanced, then the KeyError
|
||||||
|
# ocurred during the parsing of the box.
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Could it be a superbox with recognizable child boxes?
|
||||||
|
# Peek ahead to see.
|
||||||
|
pos = fptr.tell()
|
||||||
|
read_buffer = fptr.read(8)
|
||||||
|
_, sub_id = struct.unpack('>I4s', read_buffer)
|
||||||
|
sub_id = sub_id.decode('utf-8')
|
||||||
|
|
||||||
|
# Regardless of whether or not we recognize the box, rewind back
|
||||||
|
# to properly advance to the next box.
|
||||||
|
fptr.seek(pos)
|
||||||
|
|
||||||
|
# Now process any child boxes if we actually did recognize it.
|
||||||
|
if sub_id in _BOX_WITH_ID.keys():
|
||||||
|
box.box = box.parse_superbox(fptr)
|
||||||
|
|
||||||
|
return box
|
||||||
|
|
||||||
def parse_superbox(self, fptr):
|
def parse_superbox(self, fptr):
|
||||||
"""Parse a superbox (box consisting of nothing but other boxes.
|
"""Parse a superbox (box consisting of nothing but other boxes.
|
||||||
|
|
||||||
|
|
@ -147,16 +211,10 @@ class Jp2kBox(object):
|
||||||
num_bytes, = struct.unpack('>Q', read_buffer)
|
num_bytes, = struct.unpack('>Q', read_buffer)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
# The box_length value really is the length of the box!
|
||||||
num_bytes = box_length
|
num_bytes = box_length
|
||||||
|
|
||||||
# Call the proper parser for the given box with ID "T".
|
box = self._parse_this_box(fptr, box_id, start, num_bytes)
|
||||||
try:
|
|
||||||
box = _BOX_WITH_ID[box_id].parse(fptr, start, num_bytes)
|
|
||||||
except KeyError:
|
|
||||||
msg = 'Unrecognized box ({0}) encountered.'.format(box_id)
|
|
||||||
warnings.warn(msg)
|
|
||||||
box = Jp2kBox(box_id, offset=start, length=num_bytes,
|
|
||||||
longname='Unknown box')
|
|
||||||
|
|
||||||
superbox.append(box)
|
superbox.append(box)
|
||||||
|
|
||||||
|
|
@ -213,13 +271,6 @@ class ColourSpecificationBox(Jp2kBox):
|
||||||
approximation=0, colorspace=None, icc_profile=None,
|
approximation=0, colorspace=None, icc_profile=None,
|
||||||
length=0, offset=-1):
|
length=0, offset=-1):
|
||||||
Jp2kBox.__init__(self, box_id='colr', longname='Colour Specification')
|
Jp2kBox.__init__(self, box_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.method = method
|
self.method = method
|
||||||
self.precedence = precedence
|
self.precedence = precedence
|
||||||
self.approximation = approximation
|
self.approximation = approximation
|
||||||
|
|
@ -227,6 +278,33 @@ class ColourSpecificationBox(Jp2kBox):
|
||||||
self.icc_profile = icc_profile
|
self.icc_profile = icc_profile
|
||||||
self.length = length
|
self.length = length
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
|
self._validate()
|
||||||
|
|
||||||
|
def _validate(self):
|
||||||
|
"""Verify that the box obeys the specifications."""
|
||||||
|
if self.colorspace is not None and self.icc_profile is not None:
|
||||||
|
raise IOError("colorspace and icc_profile cannot both be set.")
|
||||||
|
if self.method not in (1, 2, 3, 4):
|
||||||
|
raise IOError("Invalid method.")
|
||||||
|
if self.approximation not in (0, 1, 2, 3, 4):
|
||||||
|
raise IOError("Invalid approximation.")
|
||||||
|
|
||||||
|
def _write_validate(self):
|
||||||
|
"""In addition to constructor validation steps, run validation steps
|
||||||
|
for writing."""
|
||||||
|
if self.colorspace is None:
|
||||||
|
msg = "Writing Colour Specification boxes without enumerated "
|
||||||
|
msg += "colorspaces is not supported at this time."
|
||||||
|
raise IOError(msg)
|
||||||
|
|
||||||
|
if self.icc_profile is None:
|
||||||
|
if self.colorspace not in [SRGB, GREYSCALE, YCC]:
|
||||||
|
msg = "Colorspace should correspond to one of SRGB, GREYSCALE, "
|
||||||
|
msg += "or YCC."
|
||||||
|
raise IOError(msg)
|
||||||
|
|
||||||
|
self._validate()
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
msg = "glymur.jp2box.ColourSpecificationBox("
|
msg = "glymur.jp2box.ColourSpecificationBox("
|
||||||
|
|
@ -270,10 +348,7 @@ class ColourSpecificationBox(Jp2kBox):
|
||||||
def write(self, fptr):
|
def write(self, fptr):
|
||||||
"""Write an Colour Specification box to file.
|
"""Write an Colour Specification box to file.
|
||||||
"""
|
"""
|
||||||
if self.colorspace is None:
|
self._write_validate()
|
||||||
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)
|
length = 15 if self.icc_profile is None else 11 + len(self.icc_profile)
|
||||||
fptr.write(struct.pack('>I', length))
|
fptr.write(struct.pack('>I', length))
|
||||||
fptr.write('colr'.encode())
|
fptr.write('colr'.encode())
|
||||||
|
|
@ -469,23 +544,29 @@ class ChannelDefinitionBox(Jp2kBox):
|
||||||
association : list
|
association : list
|
||||||
index of the associated color
|
index of the associated color
|
||||||
"""
|
"""
|
||||||
def __init__(self, index=None, channel_type=None, association=None,
|
def __init__(self, channel_type, association, index=None, **kwargs):
|
||||||
**kwargs):
|
|
||||||
Jp2kBox.__init__(self, box_id='cdef', longname='Channel Definition')
|
Jp2kBox.__init__(self, box_id='cdef', longname='Channel Definition')
|
||||||
|
|
||||||
# channel type and association must be specified.
|
|
||||||
if channel_type is None or association is None:
|
|
||||||
raise IOError("channel_type and association must be specified.")
|
|
||||||
|
|
||||||
if index is None:
|
if index is None:
|
||||||
index = list(range(len(channel_type)))
|
self.index = tuple(range(len(channel_type)))
|
||||||
|
else:
|
||||||
|
self.index = tuple(index)
|
||||||
|
|
||||||
if len(index) != len(channel_type) or len(index) != len(association):
|
self.channel_type = tuple(channel_type)
|
||||||
|
self.association = tuple(association)
|
||||||
|
self.__dict__.update(**kwargs)
|
||||||
|
self._validate()
|
||||||
|
|
||||||
|
def _validate(self):
|
||||||
|
"""Verify that the box obeys the specifications."""
|
||||||
|
# channel type and association must be specified.
|
||||||
|
if not ((len(self.index) == len(self.channel_type)) and
|
||||||
|
(len(self.channel_type) == len(self.association))):
|
||||||
msg = "Length of channel definition box inputs must be the same."
|
msg = "Length of channel definition box inputs must be the same."
|
||||||
raise IOError(msg)
|
raise IOError(msg)
|
||||||
|
|
||||||
# channel types must be one of 0, 1, 2, 65535
|
# channel types must be one of 0, 1, 2, 65535
|
||||||
if any(x not in [0, 1, 2, 65535] for x in channel_type):
|
if any(x not in [0, 1, 2, 65535] for x in self.channel_type):
|
||||||
msg = "Channel types must be in the set of\n\n"
|
msg = "Channel types must be in the set of\n\n"
|
||||||
msg += " 0 - colour image data for associated color\n"
|
msg += " 0 - colour image data for associated color\n"
|
||||||
msg += " 1 - opacity\n"
|
msg += " 1 - opacity\n"
|
||||||
|
|
@ -493,10 +574,6 @@ class ChannelDefinitionBox(Jp2kBox):
|
||||||
msg += " 65535 - unspecified"
|
msg += " 65535 - unspecified"
|
||||||
raise IOError(msg)
|
raise IOError(msg)
|
||||||
|
|
||||||
self.index = tuple(index)
|
|
||||||
self.channel_type = tuple(channel_type)
|
|
||||||
self.association = tuple(association)
|
|
||||||
self.__dict__.update(**kwargs)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = Jp2kBox.__str__(self)
|
msg = Jp2kBox.__str__(self)
|
||||||
|
|
@ -522,6 +599,7 @@ class ChannelDefinitionBox(Jp2kBox):
|
||||||
def write(self, fptr):
|
def write(self, fptr):
|
||||||
"""Write a channel definition box to file.
|
"""Write a channel definition box to file.
|
||||||
"""
|
"""
|
||||||
|
self._validate()
|
||||||
num_components = len(self.association)
|
num_components = len(self.association)
|
||||||
fptr.write(struct.pack('>I', 8 + 2 + num_components * 6))
|
fptr.write(struct.pack('>I', 8 + 2 + num_components * 6))
|
||||||
fptr.write('cdef'.encode('utf-8'))
|
fptr.write('cdef'.encode('utf-8'))
|
||||||
|
|
@ -559,9 +637,10 @@ class ChannelDefinitionBox(Jp2kBox):
|
||||||
channel_type = data[1:num_components * 6:3]
|
channel_type = data[1:num_components * 6:3]
|
||||||
association = data[2:num_components * 6:3]
|
association = data[2:num_components * 6:3]
|
||||||
|
|
||||||
box = ChannelDefinitionBox(index=index, channel_type=channel_type,
|
box = ChannelDefinitionBox(index=tuple(index),
|
||||||
association=association, length=length,
|
channel_type=tuple(channel_type),
|
||||||
offset=offset)
|
association=tuple(association),
|
||||||
|
length=length, offset=offset)
|
||||||
return box
|
return box
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -592,16 +671,7 @@ class CodestreamHeaderBox(Jp2kBox):
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = Jp2kBox.__str__(self)
|
msg = self._str_superbox()
|
||||||
if _printoptions['short'] == True:
|
|
||||||
return msg
|
|
||||||
|
|
||||||
for box in self.box:
|
|
||||||
boxstr = str(box)
|
|
||||||
|
|
||||||
# Add indentation.
|
|
||||||
strs = [('\n ' + x) for x in boxstr.split('\n')]
|
|
||||||
msg += ''.join(strs)
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -659,16 +729,7 @@ class CompositingLayerHeaderBox(Jp2kBox):
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = Jp2kBox.__str__(self)
|
msg = self._str_superbox()
|
||||||
if _printoptions['short'] == True:
|
|
||||||
return msg
|
|
||||||
|
|
||||||
for box in self.box:
|
|
||||||
boxstr = str(box)
|
|
||||||
|
|
||||||
# Add indentation.
|
|
||||||
strs = [('\n ' + x) for x in boxstr.split('\n')]
|
|
||||||
msg += ''.join(strs)
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -738,7 +799,6 @@ class ComponentMappingBox(Jp2kBox):
|
||||||
if _printoptions['short'] == True:
|
if _printoptions['short'] == True:
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
for k in range(len(self.component_index)):
|
for k in range(len(self.component_index)):
|
||||||
if self.mapping_type[k] == 1:
|
if self.mapping_type[k] == 1:
|
||||||
msg += '\n Component {0} ==> palette column {1}'
|
msg += '\n Component {0} ==> palette column {1}'
|
||||||
|
|
@ -831,10 +891,10 @@ class ContiguousCodestreamBox(Jp2kBox):
|
||||||
msg += '\n Main header:'
|
msg += '\n Main header:'
|
||||||
for segment in self.main_header.segment:
|
for segment in self.main_header.segment:
|
||||||
segstr = str(segment)
|
segstr = str(segment)
|
||||||
|
|
||||||
# Add indentation.
|
# Add indentation.
|
||||||
strs = [('\n ' + x) for x in segstr.split('\n')]
|
strs = [('\n ' + x) for x in segstr.split('\n')]
|
||||||
msg += ''.join(strs)
|
msg += ''.join(strs)
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -876,15 +936,36 @@ class DataReferenceBox(Jp2kBox):
|
||||||
DR : list
|
DR : list
|
||||||
Data Entry URL boxes.
|
Data Entry URL boxes.
|
||||||
"""
|
"""
|
||||||
def __init__(self, data_entry_url_boxes, length=0, offset=-1):
|
def __init__(self, data_entry_url_boxes=None, length=0, offset=-1):
|
||||||
Jp2kBox.__init__(self, box_id='dtbl', longname='Data Reference')
|
Jp2kBox.__init__(self, box_id='dtbl', longname='Data Reference')
|
||||||
self.DR = data_entry_url_boxes
|
if data_entry_url_boxes is None:
|
||||||
|
self.DR = []
|
||||||
|
else:
|
||||||
|
self.DR = data_entry_url_boxes
|
||||||
self.length = length
|
self.length = length
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
|
self._validate()
|
||||||
|
|
||||||
|
def _validate(self):
|
||||||
|
"""Verify that the box obeys the specifications."""
|
||||||
|
for box in self.DR:
|
||||||
|
if box.box_id != 'url ':
|
||||||
|
msg = 'All child boxes of a data reference box must be data '
|
||||||
|
msg += 'entry URL boxes.'
|
||||||
|
raise IOError(msg)
|
||||||
|
|
||||||
|
def _write_validate(self):
|
||||||
|
"""Verify that the box obeys the specifications for writing.
|
||||||
|
"""
|
||||||
|
if len(self.DR) == 0:
|
||||||
|
msg = "A data reference box cannot be empty when written to a file."
|
||||||
|
raise IOError(msg)
|
||||||
|
self._validate()
|
||||||
|
|
||||||
def write(self, fptr):
|
def write(self, fptr):
|
||||||
"""Write a Data Reference box to file.
|
"""Write a Data Reference box to file.
|
||||||
"""
|
"""
|
||||||
|
self._write_validate()
|
||||||
|
|
||||||
# Very similar to the say a superbox is written.
|
# Very similar to the say a superbox is written.
|
||||||
orig_pos = fptr.tell()
|
orig_pos = fptr.tell()
|
||||||
|
|
@ -980,7 +1061,6 @@ class FileTypeBox(Jp2kBox):
|
||||||
self.brand = brand
|
self.brand = brand
|
||||||
self.minor_version = minor_version
|
self.minor_version = minor_version
|
||||||
if compatibility_list is None:
|
if compatibility_list is None:
|
||||||
# see W0102, pylint
|
|
||||||
self.compatibility_list = ['jp2 ']
|
self.compatibility_list = ['jp2 ']
|
||||||
else:
|
else:
|
||||||
self.compatibility_list = compatibility_list
|
self.compatibility_list = compatibility_list
|
||||||
|
|
@ -1007,9 +1087,22 @@ class FileTypeBox(Jp2kBox):
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
def _validate(self):
|
||||||
|
"""Validate the box before writing to file."""
|
||||||
|
if self.brand not in ['jp2 ', 'jpx ']:
|
||||||
|
msg = "The file type brand must be either 'jp2 ' or 'jpx '."
|
||||||
|
raise IOError(msg)
|
||||||
|
valid_cls = ['jp2 ', 'jpx ', 'jpxb']
|
||||||
|
for item in self.compatibility_list:
|
||||||
|
if item not in valid_cls:
|
||||||
|
msg = "The file type compatibility list item '{0}' is not "
|
||||||
|
msg += "valid: valid entries are {1}"
|
||||||
|
raise IOError(msg.format(item, valid_cls))
|
||||||
|
|
||||||
def write(self, fptr):
|
def write(self, fptr):
|
||||||
"""Write a File Type box to file.
|
"""Write a File Type box to file.
|
||||||
"""
|
"""
|
||||||
|
self._validate()
|
||||||
length = 16 + 4*len(self.compatibility_list)
|
length = 16 + 4*len(self.compatibility_list)
|
||||||
fptr.write(struct.pack('>I', length))
|
fptr.write(struct.pack('>I', length))
|
||||||
fptr.write('ftyp'.encode())
|
fptr.write('ftyp'.encode())
|
||||||
|
|
@ -1084,6 +1177,18 @@ class FragmentListBox(Jp2kBox):
|
||||||
self.length = length
|
self.length = length
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
|
|
||||||
|
def _validate(self):
|
||||||
|
"""Validate internal correctness."""
|
||||||
|
if (((len(self.fragment_offset) != len(self.fragment_length)) or
|
||||||
|
(len(self.fragment_length) != len(self.data_reference)))):
|
||||||
|
msg = "The lengths of the fragment offsets, fragment lengths, and "
|
||||||
|
msg += "data reference items must be the same."
|
||||||
|
raise IOError(msg)
|
||||||
|
if any([x <= 0 for x in self.fragment_offset]):
|
||||||
|
raise IOError("Fragment offsets must all be positive.")
|
||||||
|
if any([x <= 0 for x in self.fragment_length]):
|
||||||
|
raise IOError("Fragment lengths must all be positive.")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
msg = "glymur.jp2box.FragmentListBox()"
|
msg = "glymur.jp2box.FragmentListBox()"
|
||||||
return msg
|
return msg
|
||||||
|
|
@ -1103,6 +1208,22 @@ class FragmentListBox(Jp2kBox):
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
def write(self, fptr):
|
||||||
|
"""Write a fragment list box to file.
|
||||||
|
"""
|
||||||
|
self._validate()
|
||||||
|
num_items = len(self.fragment_offset)
|
||||||
|
length = 8 + 2 + num_items * 14
|
||||||
|
fptr.write(struct.pack('>I', length))
|
||||||
|
fptr.write(self.box_id.encode())
|
||||||
|
fptr.write(struct.pack('>H', num_items))
|
||||||
|
for j in range(num_items):
|
||||||
|
write_buffer = struct.pack('>QIH',
|
||||||
|
self.fragment_offset[j],
|
||||||
|
self.fragment_length[j],
|
||||||
|
self.data_reference[j])
|
||||||
|
fptr.write(write_buffer)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse JPX free box.
|
"""Parse JPX free box.
|
||||||
|
|
@ -1146,26 +1267,18 @@ class FragmentTableBox(Jp2kBox):
|
||||||
longname : str
|
longname : str
|
||||||
more verbose description of the box.
|
more verbose description of the box.
|
||||||
"""
|
"""
|
||||||
def __init__(self, length=0, offset=-1):
|
def __init__(self, box=None, length=0, offset=-1):
|
||||||
Jp2kBox.__init__(self, box_id='ftbl', longname='Fragment Table')
|
Jp2kBox.__init__(self, box_id='ftbl', longname='Fragment Table')
|
||||||
self.length = length
|
self.length = length
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
|
self.box = box if box is not None else []
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
msg = "glymur.jp2box.FragmentTableBox()"
|
msg = "glymur.jp2box.FragmentTableBox()"
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = Jp2kBox.__str__(self)
|
msg = self._str_superbox()
|
||||||
if _printoptions['short'] == True:
|
|
||||||
return msg
|
|
||||||
|
|
||||||
for box in self.box:
|
|
||||||
boxstr = str(box)
|
|
||||||
|
|
||||||
# Add indentation.
|
|
||||||
strs = [('\n ' + x) for x in boxstr.split('\n')]
|
|
||||||
msg += ''.join(strs)
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -1183,7 +1296,7 @@ class FragmentTableBox(Jp2kBox):
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
FreeBox instance
|
FragmentTableBox instance
|
||||||
"""
|
"""
|
||||||
box = FragmentTableBox(length=length, offset=offset)
|
box = FragmentTableBox(length=length, offset=offset)
|
||||||
|
|
||||||
|
|
@ -1193,6 +1306,20 @@ class FragmentTableBox(Jp2kBox):
|
||||||
|
|
||||||
return box
|
return box
|
||||||
|
|
||||||
|
def _validate(self):
|
||||||
|
"""Self-validate the box before writing."""
|
||||||
|
box_ids = [box.box_id for box in self.box]
|
||||||
|
if len(box_ids) != 1 or box_ids[0] != 'flst':
|
||||||
|
msg = "Fragment table boxes must have a single fragment list "
|
||||||
|
msg += "box as a child box."
|
||||||
|
raise IOError(msg)
|
||||||
|
|
||||||
|
def write(self, fptr):
|
||||||
|
"""Write a fragment table box to file.
|
||||||
|
"""
|
||||||
|
self._validate()
|
||||||
|
self._write_superbox(fptr)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class FreeBox(Jp2kBox):
|
class FreeBox(Jp2kBox):
|
||||||
|
|
@ -1415,16 +1542,7 @@ class AssociationBox(Jp2kBox):
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = Jp2kBox.__str__(self)
|
msg = self._str_superbox()
|
||||||
if _printoptions['short'] == True:
|
|
||||||
return msg
|
|
||||||
|
|
||||||
for box in self.box:
|
|
||||||
boxstr = str(box)
|
|
||||||
|
|
||||||
# Add indentation.
|
|
||||||
strs = [('\n ' + x) for x in boxstr.split('\n')]
|
|
||||||
msg += ''.join(strs)
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -1485,13 +1603,7 @@ class JP2HeaderBox(Jp2kBox):
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = Jp2kBox.__str__(self)
|
msg = self._str_superbox()
|
||||||
for box in self.box:
|
|
||||||
boxstr = str(box)
|
|
||||||
|
|
||||||
# Add indentation.
|
|
||||||
strs = [('\n ' + x) for x in boxstr.split('\n')]
|
|
||||||
msg += ''.join(strs)
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def write(self, fptr):
|
def write(self, fptr):
|
||||||
|
|
@ -1616,6 +1728,15 @@ class PaletteBox(Jp2kBox):
|
||||||
self.signed = signed
|
self.signed = signed
|
||||||
self.length = length
|
self.length = length
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
|
self._validate()
|
||||||
|
|
||||||
|
def _validate(self):
|
||||||
|
"""Verify that the box obeys the specifications."""
|
||||||
|
if ((len(self.bits_per_component) != len(self.signed)) or
|
||||||
|
(len(self.signed) != self.palette.shape[1])):
|
||||||
|
msg = "The length of the 'bits_per_component' and the 'signed' "
|
||||||
|
msg += "members must equal the number of columns of the palette."
|
||||||
|
raise IOError(msg)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
msg = "glymur.jp2box.PaletteBox({0}, bits_per_component={1}, "
|
msg = "glymur.jp2box.PaletteBox({0}, bits_per_component={1}, "
|
||||||
|
|
@ -1635,6 +1756,7 @@ class PaletteBox(Jp2kBox):
|
||||||
def write(self, fptr):
|
def write(self, fptr):
|
||||||
"""Write a Palette box to file.
|
"""Write a Palette box to file.
|
||||||
"""
|
"""
|
||||||
|
self._validate()
|
||||||
bytes_per_row = sum(self.bits_per_component) / 8
|
bytes_per_row = sum(self.bits_per_component) / 8
|
||||||
bytes_per_palette = bytes_per_row * self.palette.shape[0]
|
bytes_per_palette = bytes_per_row * self.palette.shape[0]
|
||||||
box_length = 8 + 3 + self.palette.shape[1] + bytes_per_palette
|
box_length = 8 + 3 + self.palette.shape[1] + bytes_per_palette
|
||||||
|
|
@ -1714,8 +1836,7 @@ class PaletteBox(Jp2kBox):
|
||||||
palette[j] = struct.unpack_from(fmt, read_buffer,
|
palette[j] = struct.unpack_from(fmt, read_buffer,
|
||||||
offset=j * row_nbytes)
|
offset=j * row_nbytes)
|
||||||
|
|
||||||
box = PaletteBox(palette, bps, signed, length=length, offset=offset)
|
return PaletteBox(palette, bps, signed, length=length, offset=offset)
|
||||||
return box
|
|
||||||
|
|
||||||
|
|
||||||
# Map rreq codes to display text.
|
# Map rreq codes to display text.
|
||||||
|
|
@ -2086,13 +2207,7 @@ class ResolutionBox(Jp2kBox):
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = Jp2kBox.__str__(self)
|
msg = self._str_superbox()
|
||||||
for box in self.box:
|
|
||||||
boxstr = str(box)
|
|
||||||
|
|
||||||
# Add indentation.
|
|
||||||
strs = [('\n ' + x) for x in boxstr.split('\n')]
|
|
||||||
msg += ''.join(strs)
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -2612,14 +2727,7 @@ class UUIDInfoBox(Jp2kBox):
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = Jp2kBox.__str__(self)
|
msg = self._str_superbox()
|
||||||
for box in self.box:
|
|
||||||
box_str = str(box)
|
|
||||||
|
|
||||||
# Add indentation.
|
|
||||||
lst = [('\n ' + x) for x in box_str.split('\n')]
|
|
||||||
msg += ''.join(lst)
|
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -2680,13 +2788,18 @@ class DataEntryURLBox(Jp2kBox):
|
||||||
def write(self, fptr):
|
def write(self, fptr):
|
||||||
"""Write a data entry url box to file.
|
"""Write a data entry url box to file.
|
||||||
"""
|
"""
|
||||||
length = 8 + 1 + 3 + len(self.url.encode())
|
# Make sure it is written out as null-terminated.
|
||||||
|
url = self.url
|
||||||
|
if self.url[-1] != chr(0):
|
||||||
|
url = url + chr(0)
|
||||||
|
|
||||||
|
length = 8 + 1 + 3 + len(url.encode())
|
||||||
write_buffer = struct.pack('>I4sBBBB',
|
write_buffer = struct.pack('>I4sBBBB',
|
||||||
length, self.box_id.encode(),
|
length, self.box_id.encode(),
|
||||||
self.version,
|
self.version,
|
||||||
self.flag[0], self.flag[1], self.flag[2])
|
self.flag[0], self.flag[1], self.flag[2])
|
||||||
fptr.write(write_buffer)
|
fptr.write(write_buffer)
|
||||||
fptr.write(self.url.encode())
|
fptr.write(url.encode())
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
|
@ -2739,6 +2852,37 @@ class DataEntryURLBox(Jp2kBox):
|
||||||
return box
|
return box
|
||||||
|
|
||||||
|
|
||||||
|
class UnknownBox(Jp2kBox):
|
||||||
|
"""Container for unrecognized boxes.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
box_id : str
|
||||||
|
4-character identifier for the box.
|
||||||
|
length : int
|
||||||
|
length of the box in bytes.
|
||||||
|
offset : int
|
||||||
|
offset of the box from the start of the file.
|
||||||
|
longname : str
|
||||||
|
more verbose description of the box.
|
||||||
|
"""
|
||||||
|
def __init__(self, box_id, length=0, offset=-1, longname=''):
|
||||||
|
Jp2kBox.__init__(self, box_id=box_id, longname=longname)
|
||||||
|
self.length = length
|
||||||
|
self.offset = offset
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
msg = "glymur.jp2box.UnknownBox({0})".format(self.box_id)
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if len(self.box) > 0:
|
||||||
|
msg = self._str_superbox()
|
||||||
|
else:
|
||||||
|
msg = Jp2kBox.__str__(self)
|
||||||
|
return msg
|
||||||
|
|
||||||
|
|
||||||
class UUIDBox(Jp2kBox):
|
class UUIDBox(Jp2kBox):
|
||||||
"""Container for UUID box information.
|
"""Container for UUID box information.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1110,6 +1110,10 @@ def _validate_nonzero_image_size(nrows, ncols, component_index):
|
||||||
raise IOError(msg)
|
raise IOError(msg)
|
||||||
|
|
||||||
|
|
||||||
|
JP2_IDS = ['colr', 'cdef', 'cmap', 'jp2c', 'ftyp', 'ihdr', 'jp2h', 'jP ',
|
||||||
|
'pclr', 'res ', 'resc', 'resd', 'xml ', 'ulst', 'uinf', 'url ',
|
||||||
|
'uuid']
|
||||||
|
|
||||||
def _validate_jp2_box_sequence(boxes):
|
def _validate_jp2_box_sequence(boxes):
|
||||||
"""Run through series of tests for JP2 box legality.
|
"""Run through series of tests for JP2 box legality.
|
||||||
|
|
||||||
|
|
@ -1118,14 +1122,24 @@ def _validate_jp2_box_sequence(boxes):
|
||||||
_validate_signature_compatibility(boxes)
|
_validate_signature_compatibility(boxes)
|
||||||
_validate_jp2h(boxes)
|
_validate_jp2h(boxes)
|
||||||
_validate_jp2c(boxes)
|
_validate_jp2c(boxes)
|
||||||
_validate_association(boxes)
|
if boxes[1].brand == 'jpx ':
|
||||||
|
_validate_jpx_box_sequence(boxes)
|
||||||
|
else:
|
||||||
|
count = _collect_box_count(boxes)
|
||||||
|
for id in count.keys():
|
||||||
|
if id not in JP2_IDS:
|
||||||
|
msg = "The presence of a '{0}' box requires that the file type "
|
||||||
|
msg += "brand be set to 'jpx '."
|
||||||
|
raise IOError(msg.format(id))
|
||||||
|
|
||||||
|
def _validate_jpx_box_sequence(boxes):
|
||||||
|
"""Run through series of tests for JPX box legality."""
|
||||||
_validate_label(boxes)
|
_validate_label(boxes)
|
||||||
_validate_jpx_brand(boxes, boxes[1].brand)
|
_validate_jpx_brand(boxes, boxes[1].brand)
|
||||||
_validate_jpx_compatibility(boxes, boxes[1].compatibility_list)
|
_validate_jpx_compatibility(boxes, boxes[1].compatibility_list)
|
||||||
_validate_singletons(boxes)
|
_validate_singletons(boxes)
|
||||||
_validate_top_level(boxes)
|
_validate_top_level(boxes)
|
||||||
|
|
||||||
|
|
||||||
def _validate_signature_compatibility(boxes):
|
def _validate_signature_compatibility(boxes):
|
||||||
"""Validate the file signature and compatibility status."""
|
"""Validate the file signature and compatibility status."""
|
||||||
# Check for a bad sequence of boxes.
|
# Check for a bad sequence of boxes.
|
||||||
|
|
@ -1268,6 +1282,12 @@ def _validate_top_level(boxes):
|
||||||
if 'dtbl' in multiples:
|
if 'dtbl' in multiples:
|
||||||
raise IOError('There can only be one dtbl box in a file.')
|
raise IOError('There can only be one dtbl box in a file.')
|
||||||
|
|
||||||
|
# If there is one data reference box, then there must also be one ftbl.
|
||||||
|
if 'dtbl' in count and 'ftbl' not in count:
|
||||||
|
msg = 'The presence of a data reference box requires the presence of '
|
||||||
|
msg += 'a fragment table box as well.'
|
||||||
|
raise IOError(msg)
|
||||||
|
|
||||||
def _validate_singletons(boxes):
|
def _validate_singletons(boxes):
|
||||||
"""Several boxes can only occur once."""
|
"""Several boxes can only occur once."""
|
||||||
count = _collect_box_count(boxes)
|
count = _collect_box_count(boxes)
|
||||||
|
|
@ -1321,23 +1341,6 @@ def _validate_label(boxes):
|
||||||
# Same set of checks on any child boxes.
|
# Same set of checks on any child boxes.
|
||||||
_validate_label(box.box)
|
_validate_label(box.box)
|
||||||
|
|
||||||
def _validate_association(boxes):
|
|
||||||
"""
|
|
||||||
Association boxes can only contain number list boxes and xml boxes, as far
|
|
||||||
as we know.
|
|
||||||
"""
|
|
||||||
for box in boxes:
|
|
||||||
if box.box_id == 'asoc':
|
|
||||||
if box.box[0].box_id != 'nlst' or box.box[1].box_id != 'xml ':
|
|
||||||
msg = "An Association box can only contain a NumberList box "
|
|
||||||
msg += "followed by an XML box."
|
|
||||||
raise RuntimeError(msg)
|
|
||||||
if hasattr(box, 'box') != 0:
|
|
||||||
# Same set of checks on any child boxes.
|
|
||||||
_validate_association(box.box)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def extract_image_cube(image):
|
def extract_image_cube(image):
|
||||||
"""Extract 3D image from openjpeg data structure.
|
"""Extract 3D image from openjpeg data structure.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -690,3 +690,11 @@ file7_rreq = r"""Reader Requirements Box (rreq) @ (44, 24)
|
||||||
Feature 060: 0x60 e-sRGB enumerated colorspace
|
Feature 060: 0x60 e-sRGB enumerated colorspace
|
||||||
Feature 043: 0x40 Deprecated - compositing layer uses restricted ICC profile
|
Feature 043: 0x40 Deprecated - compositing layer uses restricted ICC profile
|
||||||
Vendor Features:"""
|
Vendor Features:"""
|
||||||
|
|
||||||
|
file1_xml = r"""XML Box (xml ) @ (36, 439)
|
||||||
|
<ns0:IMAGE_CREATION xmlns:ns0="http://www.jpeg.org/jpx/1.0/xml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.jpeg.org/jpx/1.0/xml http://www.jpeg.org/metadata/15444-2.xsd">
|
||||||
|
<ns0:GENERAL_CREATION_INFO>
|
||||||
|
<ns0:CREATION_TIME>2001-11-01T13:45:00.000-06:00</ns0:CREATION_TIME>
|
||||||
|
<ns0:IMAGE_SOURCE>Professional 120 Image</ns0:IMAGE_SOURCE>
|
||||||
|
</ns0:GENERAL_CREATION_INFO>
|
||||||
|
</ns0:IMAGE_CREATION>"""
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,28 @@ class TestDataEntryURL(unittest.TestCase):
|
||||||
self.assertEqual(jp22.box[4].flag, (0, 0, 0))
|
self.assertEqual(jp22.box[4].flag, (0, 0, 0))
|
||||||
self.assertEqual(jp22.box[4].url, url)
|
self.assertEqual(jp22.box[4].url, url)
|
||||||
|
|
||||||
|
def test_null_termination(self):
|
||||||
|
"""I.9.3.2 specifies that the location field must be null terminated."""
|
||||||
|
jp2 = Jp2k(self.jp2file)
|
||||||
|
|
||||||
|
url = 'http://glymur.readthedocs.org'
|
||||||
|
deurl = glymur.jp2box.DataEntryURLBox(0, (0, 0, 0), url)
|
||||||
|
boxes = [box for box in jp2.box if box.box_id != 'uuid']
|
||||||
|
boxes.append(deurl)
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
|
jp22 = jp2.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
self.assertEqual(jp22.box[-1].length, 42)
|
||||||
|
|
||||||
|
# Go to the last box. Seek past the L, T, version, and flag fields.
|
||||||
|
with open(tfile.name, 'rb') as fptr:
|
||||||
|
fptr.seek(jp22.box[-1].offset + 4 + 4 + 1 + 3)
|
||||||
|
|
||||||
|
nbytes = jp22.box[-1].offset + jp22.box[-1].length - fptr.tell()
|
||||||
|
read_buffer = fptr.read(nbytes)
|
||||||
|
read_url = read_buffer.decode('utf-8')
|
||||||
|
self.assertEqual(url + chr(0), read_url)
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2 or
|
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2 or
|
||||||
OPENJP2_IS_V2_OFFICIAL,
|
OPENJP2_IS_V2_OFFICIAL,
|
||||||
|
|
@ -139,7 +161,7 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
|
|
||||||
def test_cdef_no_inputs(self):
|
def test_cdef_no_inputs(self):
|
||||||
"""channel_type and association are required inputs."""
|
"""channel_type and association are required inputs."""
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(TypeError):
|
||||||
glymur.jp2box.ChannelDefinitionBox()
|
glymur.jp2box.ChannelDefinitionBox()
|
||||||
|
|
||||||
def test_rgb_with_index(self):
|
def test_rgb_with_index(self):
|
||||||
|
|
@ -340,6 +362,29 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
association=association)
|
association=association)
|
||||||
|
|
||||||
|
|
||||||
|
class TestFileTypeBox(unittest.TestCase):
|
||||||
|
"""Test suite for ftyp box issues."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_brand_unknown(self):
|
||||||
|
"""A ftyp box brand must be 'jp2 ' or 'jpx '."""
|
||||||
|
ftyp = glymur.jp2box.FileTypeBox(brand='jp3')
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
with tempfile.TemporaryFile() as tfile:
|
||||||
|
ftyp.write(tfile)
|
||||||
|
|
||||||
|
def test_cl_entry_unknown(self):
|
||||||
|
"""A ftyp box cl list can only contain 'jp2 ', 'jpx ', or 'jpxb'."""
|
||||||
|
ftyp = glymur.jp2box.FileTypeBox(compatibility_list=['jp3'])
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
with tempfile.TemporaryFile() as tfile:
|
||||||
|
ftyp.write(tfile)
|
||||||
|
|
||||||
class TestColourSpecificationBox(unittest.TestCase):
|
class TestColourSpecificationBox(unittest.TestCase):
|
||||||
"""Test suite for colr box instantiation."""
|
"""Test suite for colr box instantiation."""
|
||||||
|
|
||||||
|
|
@ -371,7 +416,7 @@ class TestColourSpecificationBox(unittest.TestCase):
|
||||||
boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
|
boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
|
||||||
boxes[2].box = [self.ihdr, ColourSpecificationBox(colorspace=None)]
|
boxes[2].box = [self.ihdr, ColourSpecificationBox(colorspace=None)]
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
with self.assertRaises(NotImplementedError):
|
with self.assertRaises(IOError):
|
||||||
j2k.wrap(tfile.name, boxes=boxes)
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
|
|
@ -417,6 +462,46 @@ class TestColourSpecificationBox(unittest.TestCase):
|
||||||
glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
|
glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
|
||||||
approximation=approx)
|
approximation=approx)
|
||||||
|
|
||||||
|
def test_colr_with_bad_color(self):
|
||||||
|
"""colr must have a valid color, strange as though that may sound."""
|
||||||
|
colorspace = -1
|
||||||
|
approx = 0
|
||||||
|
colr = glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
|
||||||
|
approximation=approx)
|
||||||
|
with tempfile.TemporaryFile() as tfile:
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
colr.write(tfile)
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt",
|
||||||
|
"Problems using NamedTemporaryFile on windows.")
|
||||||
|
class TestPaletteBox(unittest.TestCase):
|
||||||
|
"""Test suite for pclr box instantiation."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_mismatched_bitdepth_signed(self):
|
||||||
|
"""bitdepth and signed arguments must have equal length"""
|
||||||
|
palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint8)
|
||||||
|
bps = (8, 8, 8)
|
||||||
|
signed = (False, False)
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps,
|
||||||
|
signed=signed)
|
||||||
|
|
||||||
|
def test_mismatched_signed_palette(self):
|
||||||
|
"""bitdepth and signed arguments must have equal length"""
|
||||||
|
palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint8)
|
||||||
|
bps = (8, 8, 8, 8)
|
||||||
|
signed = (False, False, False, False)
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps,
|
||||||
|
signed=signed)
|
||||||
|
|
||||||
|
|
||||||
class TestAppend(unittest.TestCase):
|
class TestAppend(unittest.TestCase):
|
||||||
"""Tests for append method."""
|
"""Tests for append method."""
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,12 @@ import struct
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
import warnings
|
|
||||||
import xml.etree.cElementTree as ET
|
import xml.etree.cElementTree as ET
|
||||||
|
|
||||||
import glymur
|
import glymur
|
||||||
from glymur import Jp2k
|
from glymur import Jp2k
|
||||||
|
from glymur.jp2box import DataEntryURLBox, FileTypeBox, JPEG2000SignatureBox
|
||||||
|
from glymur.jp2box import DataReferenceBox, FragmentListBox, FragmentTableBox
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
class TestJPXWrap(unittest.TestCase):
|
class TestJPXWrap(unittest.TestCase):
|
||||||
|
|
@ -21,6 +21,7 @@ class TestJPXWrap(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.jp2file = glymur.data.nemo()
|
self.jp2file = glymur.data.nemo()
|
||||||
|
self.j2kfile = glymur.data.goodstuff()
|
||||||
|
|
||||||
raw_xml = b"""<?xml version="1.0"?>
|
raw_xml = b"""<?xml version="1.0"?>
|
||||||
<data>
|
<data>
|
||||||
|
|
@ -40,6 +41,100 @@ class TestJPXWrap(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
os.unlink(self.xmlfile)
|
os.unlink(self.xmlfile)
|
||||||
|
|
||||||
|
def test_jpx_ftbl_no_codestream(self):
|
||||||
|
"""Can have a jpx with no codestream."""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1:
|
||||||
|
with open(self.jp2file, 'rb') as fptr:
|
||||||
|
tfile1.write(fptr.read())
|
||||||
|
tfile1.flush()
|
||||||
|
jp2_1 = Jp2k(tfile1.name)
|
||||||
|
|
||||||
|
jp2c = [box for box in jp2_1.box if box.box_id == 'jp2c'][0]
|
||||||
|
|
||||||
|
# coff and clen will be the offset and length input arguments
|
||||||
|
# to the fragment list box. dr_idx is the data reference index.
|
||||||
|
coff = []
|
||||||
|
clen = []
|
||||||
|
dr_idx = []
|
||||||
|
|
||||||
|
coff.append(jp2c.offset + 8)
|
||||||
|
clen.append(jp2c.length - (coff[0] - jp2c.offset))
|
||||||
|
dr_idx.append(1)
|
||||||
|
|
||||||
|
# Make the url box for this codestream.
|
||||||
|
url1 = DataEntryURLBox(0, [0, 0, 0], 'file://' + tfile1.name)
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2:
|
||||||
|
|
||||||
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
jp2_2 = j2k.wrap(tfile2.name)
|
||||||
|
|
||||||
|
jp2c = [box for box in jp2_2.box if box.box_id == 'jp2c'][0]
|
||||||
|
coff.append(jp2c.offset + 8)
|
||||||
|
clen.append(jp2c.length - (coff[0] - jp2c.offset))
|
||||||
|
dr_idx.append(2)
|
||||||
|
|
||||||
|
# Make the url box for this codestream.
|
||||||
|
url2 = DataEntryURLBox(0, [0, 0, 0], 'file://' + tfile2.name)
|
||||||
|
|
||||||
|
boxes = [JPEG2000SignatureBox(),
|
||||||
|
FileTypeBox(brand='jpx ',
|
||||||
|
compatibility_list=['jpx ',
|
||||||
|
'jp2 ', 'jpxb']),
|
||||||
|
jp2_1.box[2]]
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jpx') as tjpx:
|
||||||
|
for box in boxes:
|
||||||
|
box.write(tjpx)
|
||||||
|
|
||||||
|
flst = FragmentListBox(coff, clen, dr_idx)
|
||||||
|
ftbl = FragmentTableBox([flst])
|
||||||
|
ftbl.write(tjpx)
|
||||||
|
|
||||||
|
boxes = [url1, url2]
|
||||||
|
dtbl = DataReferenceBox(data_entry_url_boxes=boxes)
|
||||||
|
dtbl.write(tjpx)
|
||||||
|
tjpx.flush()
|
||||||
|
|
||||||
|
def test_jp2_with_jpx_box(self):
|
||||||
|
"""If the brand is jp2, then no jpx boxes are allowed."""
|
||||||
|
jp2 = Jp2k(self.jp2file)
|
||||||
|
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
||||||
|
boxes = jp2.box
|
||||||
|
|
||||||
|
boxes.append(glymur.jp2box.AssociationBox())
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
jp2.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
def test_ftbl(self):
|
||||||
|
"""Write a fragment table box."""
|
||||||
|
# Add a negative test where offset < 0
|
||||||
|
# Add a negative test where length < 0
|
||||||
|
# Add a negative test where ref > 0 but no data reference box.
|
||||||
|
# Add a negative test where more than one flst
|
||||||
|
# Add negative test where ftbl contained in a superbox.
|
||||||
|
jp2 = Jp2k(self.jp2file)
|
||||||
|
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
||||||
|
|
||||||
|
# The ftyp box must be modified to jpx.
|
||||||
|
boxes[1].brand = 'jpx '
|
||||||
|
boxes[1].compatibility_list = ['jp2 ', 'jpxb']
|
||||||
|
|
||||||
|
offset = [89]
|
||||||
|
length = [1132288]
|
||||||
|
reference = [0]
|
||||||
|
flst = glymur.jp2box.FragmentListBox(offset, length, reference)
|
||||||
|
ftbl = glymur.jp2box.FragmentTableBox(box=[flst])
|
||||||
|
boxes.append(ftbl)
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
||||||
|
jpx = jp2.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
self.assertEqual(jpx.box[1].compatibility_list, ['jp2 ', 'jpxb'])
|
||||||
|
self.assertEqual(jpx.box[-1].box_id, 'ftbl')
|
||||||
|
self.assertEqual(jpx.box[-1].box[0].box_id, 'flst')
|
||||||
|
|
||||||
def test_jpxb_compatibility(self):
|
def test_jpxb_compatibility(self):
|
||||||
"""Wrap JP2 to JPX, state jpxb compatibility"""
|
"""Wrap JP2 to JPX, state jpxb compatibility"""
|
||||||
jp2 = Jp2k(self.jp2file)
|
jp2 = Jp2k(self.jp2file)
|
||||||
|
|
@ -98,11 +193,47 @@ class TestJPXWrap(unittest.TestCase):
|
||||||
self.assertEqual(jpx.box[-1].box[2].box_id, 'lbl ')
|
self.assertEqual(jpx.box[-1].box[2].box_id, 'lbl ')
|
||||||
self.assertEqual(jpx.box[-1].box[2].label, label)
|
self.assertEqual(jpx.box[-1].box[2].label, label)
|
||||||
|
|
||||||
|
def test_empty_data_reference(self):
|
||||||
|
"""Empty data reference boxes can be created, but not written."""
|
||||||
|
jp2 = Jp2k(self.jp2file)
|
||||||
|
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
||||||
|
|
||||||
|
boxes[1].brand = 'jpx '
|
||||||
|
|
||||||
|
dref = glymur.jp2box.DataReferenceBox()
|
||||||
|
boxes.append(dref)
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
jp2.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
def test_deurl_child_of_dtbl(self):
|
||||||
|
"""Data reference boxes can only contain data entry url boxes."""
|
||||||
|
jp2 = Jp2k(self.jp2file)
|
||||||
|
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
||||||
|
|
||||||
|
ftyp = glymur.jp2box.FileTypeBox()
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
dref = glymur.jp2box.DataReferenceBox([ftyp])
|
||||||
|
|
||||||
|
# Try to get around it by appending the ftyp box after creation.
|
||||||
|
dref = glymur.jp2box.DataReferenceBox()
|
||||||
|
dref.DR.append(ftyp)
|
||||||
|
|
||||||
|
boxes.append(dref)
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
jp2.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
def test_only_one_data_reference(self):
|
def test_only_one_data_reference(self):
|
||||||
"""Data reference boxes cannot be inside a superbox ."""
|
"""Data reference boxes cannot be inside a superbox ."""
|
||||||
jp2 = Jp2k(self.jp2file)
|
jp2 = Jp2k(self.jp2file)
|
||||||
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
||||||
|
|
||||||
|
# Have to make the ftyp brand jpx.
|
||||||
|
boxes[1].brand = 'jpx '
|
||||||
|
|
||||||
flag = 0
|
flag = 0
|
||||||
version = (0, 0, 0)
|
version = (0, 0, 0)
|
||||||
url = 'file:////usr/local/bin'
|
url = 'file:////usr/local/bin'
|
||||||
|
|
@ -113,16 +244,16 @@ class TestJPXWrap(unittest.TestCase):
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
jpx = jp2.wrap(tfile.name, boxes=boxes)
|
jp2.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
def test_lbl_at_top_level(self):
|
def test_lbl_at_top_level(self):
|
||||||
"""Label boxes can only be inside a asoc box ."""
|
"""Label boxes can only be inside a asoc box ."""
|
||||||
jp2 = Jp2k(self.jp2file)
|
jp2 = Jp2k(self.jp2file)
|
||||||
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
||||||
|
|
||||||
flag = 0
|
# Have to make the ftyp brand jpx.
|
||||||
version = (0, 0, 0)
|
boxes[1].brand = 'jpx '
|
||||||
url = 'file:////usr/local/bin'
|
|
||||||
lblb = glymur.jp2box.LabelBox('hi there')
|
lblb = glymur.jp2box.LabelBox('hi there')
|
||||||
|
|
||||||
# Put it inside the jp2 header box.
|
# Put it inside the jp2 header box.
|
||||||
|
|
@ -130,13 +261,16 @@ class TestJPXWrap(unittest.TestCase):
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
jpx = jp2.wrap(tfile.name, boxes=boxes)
|
jp2.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
def test_data_reference_not_at_top_level(self):
|
def test_data_reference_in_subbox(self):
|
||||||
"""Data reference boxes cannot be inside a superbox ."""
|
"""Data reference boxes cannot be inside a superbox ."""
|
||||||
jp2 = Jp2k(self.jp2file)
|
jp2 = Jp2k(self.jp2file)
|
||||||
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
||||||
|
|
||||||
|
# Have to make the ftyp brand jpx.
|
||||||
|
boxes[1].brand = 'jpx '
|
||||||
|
|
||||||
flag = 0
|
flag = 0
|
||||||
version = (0, 0, 0)
|
version = (0, 0, 0)
|
||||||
url = 'file:////usr/local/bin'
|
url = 'file:////usr/local/bin'
|
||||||
|
|
@ -148,13 +282,17 @@ class TestJPXWrap(unittest.TestCase):
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
jpx = jp2.wrap(tfile.name, boxes=boxes)
|
jp2.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
def test_jp2_to_jpx_sans_jp2_compatibility(self):
|
def test_jp2_to_jpx_sans_jp2_compatibility(self):
|
||||||
"""jp2 wrapped to jpx not including jp2 compatibility is wrong."""
|
"""jp2 wrapped to jpx not including jp2 compatibility is wrong."""
|
||||||
jp2 = Jp2k(self.jp2file)
|
jp2 = Jp2k(self.jp2file)
|
||||||
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
||||||
|
|
||||||
|
# Have to make the ftyp brand jpx.
|
||||||
|
boxes[1].brand = 'jpx '
|
||||||
boxes[1].compatibility_list.append('jp2 ')
|
boxes[1].compatibility_list.append('jp2 ')
|
||||||
|
|
||||||
numbers = [0, 1]
|
numbers = [0, 1]
|
||||||
nlst = glymur.jp2box.NumberListBox(numbers)
|
nlst = glymur.jp2box.NumberListBox(numbers)
|
||||||
the_xml = ET.fromstring('<?xml version="1.0"?><data>0</data>')
|
the_xml = ET.fromstring('<?xml version="1.0"?><data>0</data>')
|
||||||
|
|
@ -164,7 +302,7 @@ class TestJPXWrap(unittest.TestCase):
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
||||||
with self.assertRaises(RuntimeError):
|
with self.assertRaises(RuntimeError):
|
||||||
jpx = jp2.wrap(tfile.name, boxes=boxes)
|
jp2.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
def test_jp2_to_jpx_sans_jpx_brand(self):
|
def test_jp2_to_jpx_sans_jpx_brand(self):
|
||||||
"""Verify error when jp2 wrapped to jpx does not include jpx brand."""
|
"""Verify error when jp2 wrapped to jpx does not include jpx brand."""
|
||||||
|
|
@ -180,7 +318,7 @@ class TestJPXWrap(unittest.TestCase):
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
||||||
with self.assertRaises(RuntimeError):
|
with self.assertRaises(RuntimeError):
|
||||||
jpx = jp2.wrap(tfile.name, boxes=boxes)
|
jp2.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
|
|
@ -188,11 +326,57 @@ class TestJPX(unittest.TestCase):
|
||||||
"""Test suite for other JPX boxes."""
|
"""Test suite for other JPX boxes."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
self.jp2file = glymur.data.nemo()
|
||||||
self.jpxfile = glymur.data.jpxfile()
|
self.jpxfile = glymur.data.jpxfile()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def test_flst_lens_not_the_same(self):
|
||||||
|
"""A fragment list box items must be the same length."""
|
||||||
|
offset = [89]
|
||||||
|
length = [1132288]
|
||||||
|
reference = [0, 0]
|
||||||
|
flst = glymur.jp2box.FragmentListBox(offset, length, reference)
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
with tempfile.TemporaryFile() as tfile:
|
||||||
|
flst.write(tfile)
|
||||||
|
|
||||||
|
def test_flst_offsets_not_positive(self):
|
||||||
|
"""A fragment list box offsets must be positive."""
|
||||||
|
offset = [0]
|
||||||
|
length = [1132288]
|
||||||
|
reference = [0]
|
||||||
|
flst = glymur.jp2box.FragmentListBox(offset, length, reference)
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
with tempfile.TemporaryFile() as tfile:
|
||||||
|
flst.write(tfile)
|
||||||
|
|
||||||
|
def test_flst_lengths_not_positive(self):
|
||||||
|
"""A fragment list box lengths must be positive."""
|
||||||
|
offset = [89]
|
||||||
|
length = [0]
|
||||||
|
reference = [0]
|
||||||
|
flst = glymur.jp2box.FragmentListBox(offset, length, reference)
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
with tempfile.TemporaryFile() as tfile:
|
||||||
|
flst.write(tfile)
|
||||||
|
|
||||||
|
def test_ftbl_boxes_empty(self):
|
||||||
|
"""A fragment table box must have at least one child box."""
|
||||||
|
ftbl = glymur.jp2box.FragmentTableBox()
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
with tempfile.TemporaryFile() as tfile:
|
||||||
|
ftbl.write(tfile)
|
||||||
|
|
||||||
|
def test_ftbl_child_not_flst(self):
|
||||||
|
"""A fragment table box can only contain a fragment list."""
|
||||||
|
free = glymur.jp2box.FreeBox()
|
||||||
|
ftbl = glymur.jp2box.FragmentTableBox(box=[free])
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
with tempfile.TemporaryFile() as tfile:
|
||||||
|
ftbl.write(tfile)
|
||||||
|
|
||||||
def test_jpx_rreq_mask_length_3(self):
|
def test_jpx_rreq_mask_length_3(self):
|
||||||
"""There are some JPX files with rreq mask length of 3."""
|
"""There are some JPX files with rreq mask length of 3."""
|
||||||
jpx = Jp2k(self.jpxfile)
|
jpx = Jp2k(self.jpxfile)
|
||||||
|
|
@ -202,12 +386,50 @@ class TestJPX(unittest.TestCase):
|
||||||
self.assertEqual(jpx.box[2].standard_flag,
|
self.assertEqual(jpx.box[2].standard_flag,
|
||||||
(5, 42, 45, 2, 18, 19, 1, 8, 12, 31, 20))
|
(5, 42, 45, 2, 18, 19, 1, 8, 12, 31, 20))
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.")
|
||||||
|
def test_unknown_superbox(self):
|
||||||
|
"""Verify that we can handle an unknown superbox."""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile:
|
||||||
|
with open(self.jpxfile, 'rb') as ifile:
|
||||||
|
tfile.write(ifile.read())
|
||||||
|
|
||||||
|
# Add the header for an unknwon superbox.
|
||||||
|
write_buffer = struct.pack('>I4s', 20, 'grp '.encode())
|
||||||
|
tfile.write(write_buffer)
|
||||||
|
write_buffer = struct.pack('>I4sI', 12, 'free'.encode(), 0)
|
||||||
|
tfile.write(write_buffer)
|
||||||
|
tfile.flush()
|
||||||
|
|
||||||
|
with self.assertWarns(UserWarning):
|
||||||
|
jpx = Jp2k(tfile.name)
|
||||||
|
self.assertEqual(jpx.box[-1].box_id, 'grp ')
|
||||||
|
self.assertEqual(jpx.box[-1].box[0].box_id, 'free')
|
||||||
|
|
||||||
def test_free_box(self):
|
def test_free_box(self):
|
||||||
"""Verify that we can handle a free box."""
|
"""Verify that we can handle a free box."""
|
||||||
j = Jp2k(self.jpxfile)
|
j = Jp2k(self.jpxfile)
|
||||||
self.assertEqual(j.box[16].box[0].box_id, 'free')
|
self.assertEqual(j.box[16].box[0].box_id, 'free')
|
||||||
self.assertEqual(type(j.box[16].box[0]), glymur.jp2box.FreeBox)
|
self.assertEqual(type(j.box[16].box[0]), glymur.jp2box.FreeBox)
|
||||||
|
|
||||||
|
def test_data_reference_requires_dtbl(self):
|
||||||
|
"""The existance of a data reference box requires a ftbl box as well."""
|
||||||
|
flag = 0
|
||||||
|
version = (0, 0, 0)
|
||||||
|
url1 = 'file:////usr/local/bin'
|
||||||
|
url2 = 'http://glymur.readthedocs.org'
|
||||||
|
jpx1 = glymur.Jp2k(self.jp2file)
|
||||||
|
boxes = jpx1.box
|
||||||
|
boxes[1].brand = 'jpx '
|
||||||
|
|
||||||
|
deurl1 = glymur.jp2box.DataEntryURLBox(flag, version, url1)
|
||||||
|
deurl2 = glymur.jp2box.DataEntryURLBox(flag, version, url2)
|
||||||
|
dref = glymur.jp2box.DataReferenceBox([deurl1, deurl2])
|
||||||
|
boxes.append(dref)
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile:
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
jpx1.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
def test_dtbl(self):
|
def test_dtbl(self):
|
||||||
"""Verify that we can interpret Data Reference boxes."""
|
"""Verify that we can interpret Data Reference boxes."""
|
||||||
# Copy the existing JPX file, add a data reference box onto the end.
|
# Copy the existing JPX file, add a data reference box onto the end.
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ from .fixtures import text_gbr_27, text_gbr_33, text_gbr_34
|
||||||
class TestPrinting(unittest.TestCase):
|
class TestPrinting(unittest.TestCase):
|
||||||
"""Tests for verifying how printing works."""
|
"""Tests for verifying how printing works."""
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
self.jpxfile = glymur.data.jpxfile()
|
||||||
self.jp2file = glymur.data.nemo()
|
self.jp2file = glymur.data.nemo()
|
||||||
self.j2kfile = glymur.data.goodstuff()
|
self.j2kfile = glymur.data.goodstuff()
|
||||||
|
|
||||||
|
|
@ -48,6 +49,31 @@ class TestPrinting(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.")
|
||||||
|
def test_unknown_superbox(self):
|
||||||
|
"""Verify that we can handle an unknown superbox."""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile:
|
||||||
|
with open(self.jpxfile, 'rb') as ifile:
|
||||||
|
tfile.write(ifile.read())
|
||||||
|
|
||||||
|
# Add the header for an unknwon superbox.
|
||||||
|
write_buffer = struct.pack('>I4s', 20, 'grp '.encode())
|
||||||
|
tfile.write(write_buffer)
|
||||||
|
write_buffer = struct.pack('>I4sI', 12, 'free'.encode(), 0)
|
||||||
|
tfile.write(write_buffer)
|
||||||
|
tfile.flush()
|
||||||
|
|
||||||
|
with self.assertWarns(UserWarning):
|
||||||
|
jpx = Jp2k(tfile.name)
|
||||||
|
glymur.set_printoptions(short=True)
|
||||||
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
print(jpx.box[-1])
|
||||||
|
actual = fake_out.getvalue().strip()
|
||||||
|
lines = ['Unknown Box (grp ) @ (695609, 20)',
|
||||||
|
' Free Box (free) @ (695617, 12)']
|
||||||
|
expected = '\n'.join(lines)
|
||||||
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
def test_printoptions_bad_argument(self):
|
def test_printoptions_bad_argument(self):
|
||||||
"""Verify error when bad parameter to set_printoptions"""
|
"""Verify error when bad parameter to set_printoptions"""
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
|
|
@ -602,27 +628,7 @@ class TestPrinting(unittest.TestCase):
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
print(j.box[2])
|
print(j.box[2])
|
||||||
actual = fake_out.getvalue().strip()
|
actual = fake_out.getvalue().strip()
|
||||||
|
self.assertEqual(actual, fixtures.file1_xml)
|
||||||
lines = ['XML Box (xml ) @ (36, 439)',
|
|
||||||
' <ns0:IMAGE_CREATION '
|
|
||||||
+ 'xmlns:ns0="http://www.jpeg.org/jpx/1.0/xml" '
|
|
||||||
+ 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
|
|
||||||
+ 'xsi:schemaLocation="http://www.jpeg.org/jpx/1.0/xml '
|
|
||||||
+ 'http://www.jpeg.org/metadata/15444-2.xsd">',
|
|
||||||
|
|
||||||
' <ns0:GENERAL_CREATION_INFO>',
|
|
||||||
' <ns0:CREATION_TIME>'
|
|
||||||
+ '2001-11-01T13:45:00.000-06:00'
|
|
||||||
+ '</ns0:CREATION_TIME>',
|
|
||||||
|
|
||||||
' <ns0:IMAGE_SOURCE>'
|
|
||||||
+ 'Professional 120 Image'
|
|
||||||
+ '</ns0:IMAGE_SOURCE>',
|
|
||||||
|
|
||||||
' </ns0:GENERAL_CREATION_INFO>',
|
|
||||||
' </ns0:IMAGE_CREATION>']
|
|
||||||
expected = '\n'.join(lines)
|
|
||||||
self.assertEqual(actual, expected)
|
|
||||||
|
|
||||||
@unittest.skipIf(sys.hexversion < 0x03000000,
|
@unittest.skipIf(sys.hexversion < 0x03000000,
|
||||||
"Only trusting python3 for printing non-ascii chars")
|
"Only trusting python3 for printing non-ascii chars")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue