Merge branch 'issue86' into devel
This commit is contained in:
commit
ce46cfe710
19 changed files with 513 additions and 164 deletions
|
|
@ -1,3 +1,10 @@
|
||||||
|
Jul 23, 2013 - v0.2.5 Fixed inconsistency in XML handling, now all instances
|
||||||
|
are always ElementTree objects (issue82).
|
||||||
|
|
||||||
|
Jul 21, 2013 - v0.2.4 Fixed markdown bug for Fedora 17 information, fixed
|
||||||
|
out-of-date windows information (issue79). Fixed incorrect
|
||||||
|
interpretation of Psot parameter (issue78).
|
||||||
|
|
||||||
Jul 18, 2013 - v0.2.3 Support for Python 2.6, OpenJPEG 1.4. Incompatible
|
Jul 18, 2013 - v0.2.3 Support for Python 2.6, OpenJPEG 1.4. Incompatible
|
||||||
change to ChannelDefinitionBox constructor. Added RGBA example.
|
change to ChannelDefinitionBox constructor. Added RGBA example.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
glymur: a Python interface for JPEG 2000
|
glymur: a Python interface for JPEG 2000
|
||||||
=========================================
|
=========================================
|
||||||
|
|
||||||
**glymur** contains a Python interface to the OpenJPEG library
|
**glymur** contains a Python interface to the OpenJPEG library which
|
||||||
which allows linux and mac users to read and write JPEG 2000 files.
|
allows one to read and write JPEG 2000 files. **glymur** works on
|
||||||
**glymur** works on Python 2.6, 2.7 and 3.3. Python 3.3 is strongly
|
Python 2.6, 2.7 and 3.3. Python 3.3 is strongly
|
||||||
recommended.
|
recommended.
|
||||||
|
|
||||||
Please read the docs, https://glymur.readthedocs.org/en/latest/
|
Please read the docs, https://glymur.readthedocs.org/en/latest/
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ copyright = u'2013, John Evans'
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '0.1'
|
version = '0.1'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '0.2.3'
|
release = '0.2.5'
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ following RPMs installed.
|
||||||
* numpy
|
* numpy
|
||||||
* matplotlib (optional)
|
* matplotlib (optional)
|
||||||
|
|
||||||
In addition, you must install contextlib2 and Pillow via pip.
|
In addition, you must install contextlib2 and Pillow via pip. ::
|
||||||
|
|
||||||
$ yum install python-devel # pip needs this in order to compile Pillow
|
$ yum install python-devel # pip needs this in order to compile Pillow
|
||||||
$ pip-python install Pillow --user
|
$ pip-python install Pillow --user
|
||||||
|
|
@ -137,8 +137,17 @@ platforms.
|
||||||
Testing
|
Testing
|
||||||
'''''''
|
'''''''
|
||||||
|
|
||||||
If you wish to run the tests (strongly recommended :-), you can either run them
|
There are two environment variables you may wish to set before running the
|
||||||
from within python as follows ... ::
|
tests.
|
||||||
|
|
||||||
|
* **OPJ_DATA_ROOT** - points to directory for OpenJPEG test data
|
||||||
|
* **FORMAT_CORPUS_ROOT** - points to directory for format-corpus repository (see https://github.com/openplanets/format-corpus)
|
||||||
|
|
||||||
|
Setting these two environment variables is not required, as any tests using
|
||||||
|
either of them will be skipped.
|
||||||
|
|
||||||
|
In order to run the tests, you can either run them from within
|
||||||
|
python as follows ... ::
|
||||||
|
|
||||||
>>> import glymur
|
>>> import glymur
|
||||||
>>> glymur.runtests()
|
>>> glymur.runtests()
|
||||||
|
|
|
||||||
|
|
@ -185,9 +185,8 @@ 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. ::
|
||||||
|
|
||||||
>>> from glymur import Jp2k
|
>>> import glymur
|
||||||
>>> file = glymur.data.nemo()
|
>>> j = glymur.Jp2k(glymur.data.nemo())
|
||||||
>>> j = Jp2k(file)
|
|
||||||
>>> print(j.box[4])
|
>>> print(j.box[4])
|
||||||
UUID Box (uuid) @ (715, 2412)
|
UUID Box (uuid) @ (715, 2412)
|
||||||
UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP)
|
UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP)
|
||||||
|
|
@ -198,7 +197,7 @@ The example JP2 file shipped with glymur has an XMP UUID. ::
|
||||||
</rdf:RDF>
|
</rdf:RDF>
|
||||||
</ns0:xmpmeta>
|
</ns0:xmpmeta>
|
||||||
|
|
||||||
Since the UUID data in this case is returned as an ElementTree Element, one can
|
Since the UUID data in this case is returned as an ElementTree instance, one can
|
||||||
use ElementTree to access the data. For example, to extract the
|
use ElementTree to access the data. For example, to extract the
|
||||||
**CreatorTool** attribute value, the following would work::
|
**CreatorTool** attribute value, the following would work::
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ Here's an incomplete list of what I'd like to focus on in the near future.
|
||||||
|
|
||||||
* continue to monitor upstream changes in the openjp2 library
|
* continue to monitor upstream changes in the openjp2 library
|
||||||
* investigate using CFFI or cython instead of ctypes to wrap openjp2
|
* investigate using CFFI or cython instead of ctypes to wrap openjp2
|
||||||
* investigate swapping out ElementTree for LXML
|
|
||||||
* eventually expose the openjp2 API
|
* eventually expose the openjp2 API
|
||||||
* investigate JPIP
|
* investigate JPIP
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ from .jp2k import Jp2k
|
||||||
from .jp2dump import jp2dump
|
from .jp2dump import jp2dump
|
||||||
|
|
||||||
from . import data
|
from . import data
|
||||||
from . import test
|
|
||||||
|
|
||||||
|
|
||||||
def runtests():
|
def runtests():
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ codestreams.
|
||||||
"""
|
"""
|
||||||
# pylint: disable=C0302,R0902,R0903,R0913
|
# pylint: disable=C0302,R0902,R0903,R0913
|
||||||
|
|
||||||
from itertools import takewhile
|
|
||||||
import math
|
import math
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -43,16 +42,6 @@ for _marker in range(0xff90, 0xff94):
|
||||||
_VALID_MARKERS.append(_marker)
|
_VALID_MARKERS.append(_marker)
|
||||||
|
|
||||||
|
|
||||||
class InconsistentStartOfTileError(IOError):
|
|
||||||
"""To be raised if bad SOT segment encountered.
|
|
||||||
|
|
||||||
SOT segment offsets are recorded as encountered. The offsets should all be
|
|
||||||
different.
|
|
||||||
"""
|
|
||||||
def __init__(self, msg):
|
|
||||||
IOError.__init__(self, msg)
|
|
||||||
|
|
||||||
|
|
||||||
class Codestream(object):
|
class Codestream(object):
|
||||||
"""Container for codestream information.
|
"""Container for codestream information.
|
||||||
|
|
||||||
|
|
@ -113,7 +102,19 @@ class Codestream(object):
|
||||||
while True:
|
while True:
|
||||||
|
|
||||||
read_buffer = fptr.read(2)
|
read_buffer = fptr.read(2)
|
||||||
marker_id, = struct.unpack('>H', read_buffer)
|
try:
|
||||||
|
marker_id, = struct.unpack('>H', read_buffer)
|
||||||
|
except struct.error:
|
||||||
|
# Treat this as a warning.
|
||||||
|
msg = "Marker had length {0} instead of expected length of 2 "
|
||||||
|
msg += "bytes. Codestream parsing terminated."
|
||||||
|
warnings.warn(msg.format(len(read_buffer)))
|
||||||
|
break
|
||||||
|
|
||||||
|
if marker_id == 0xff90 and header_only:
|
||||||
|
# Start-of-tile (SOT) means that we are out of the main header
|
||||||
|
# and there is no need to go further.
|
||||||
|
break
|
||||||
|
|
||||||
try:
|
try:
|
||||||
segment = self._process_marker_segment(fptr, marker_id)
|
segment = self._process_marker_segment(fptr, marker_id)
|
||||||
|
|
@ -139,12 +140,6 @@ class Codestream(object):
|
||||||
|
|
||||||
fptr.seek(self._tile_offset[-1] + self._tile_length[-1])
|
fptr.seek(self._tile_offset[-1] + self._tile_length[-1])
|
||||||
|
|
||||||
if header_only:
|
|
||||||
# start-of-tile (SOT) means we are out of the main header.
|
|
||||||
# No need to go any further.
|
|
||||||
gen = takewhile(lambda s: s.marker_id != 'SOT', self.segment)
|
|
||||||
self.segment = list(gen)
|
|
||||||
|
|
||||||
|
|
||||||
def _process_marker_segment(self, fptr, marker_id):
|
def _process_marker_segment(self, fptr, marker_id):
|
||||||
"""Process and return a segment from the codestream.
|
"""Process and return a segment from the codestream.
|
||||||
|
|
@ -210,19 +205,12 @@ class Codestream(object):
|
||||||
# we encounter start-of-data marker segments.
|
# we encounter start-of-data marker segments.
|
||||||
|
|
||||||
segment = _parse_sot_segment(fptr)
|
segment = _parse_sot_segment(fptr)
|
||||||
if segment.offset not in self._tile_offset:
|
self._tile_offset.append(segment.offset)
|
||||||
self._tile_offset.append(segment.offset)
|
if segment.psot == 0:
|
||||||
if segment.psot == 0:
|
tile_part_length = self.offset + self.length - segment.offset - 2
|
||||||
tile_part_length = self.offset + self.length - segment.offset - 2
|
|
||||||
else:
|
|
||||||
tile_part_length = segment.psot
|
|
||||||
self._tile_length.append(tile_part_length)
|
|
||||||
else:
|
else:
|
||||||
msg = "Inconsistent start-of-tile (SOT) marker segment "
|
tile_part_length = segment.psot
|
||||||
msg += "encountered in tile with index {0}. "
|
self._tile_length.append(tile_part_length)
|
||||||
msg += "Codestream parsing terminated."
|
|
||||||
msg = msg.format(segment.isot)
|
|
||||||
raise InconsistentStartOfTileError(msg)
|
|
||||||
|
|
||||||
elif marker_id == 0xff93:
|
elif marker_id == 0xff93:
|
||||||
# start of data. Need to seek past the current tile part.
|
# start of data. Need to seek past the current tile part.
|
||||||
|
|
|
||||||
217
glymur/jp2box.py
217
glymur/jp2box.py
|
|
@ -520,6 +520,195 @@ class ChannelDefinitionBox(Jp2kBox):
|
||||||
return box
|
return box
|
||||||
|
|
||||||
|
|
||||||
|
class CodestreamHeaderBox(Jp2kBox):
|
||||||
|
"""Container for codestream header box information.
|
||||||
|
|
||||||
|
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.
|
||||||
|
box : list
|
||||||
|
List of boxes contained in this superbox.
|
||||||
|
"""
|
||||||
|
def __init__(self, length=0, offset=-1):
|
||||||
|
Jp2kBox.__init__(self, box_id='jpch', longname='Codestream Header')
|
||||||
|
self.length = length
|
||||||
|
self.offset = offset
|
||||||
|
self.box = []
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
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
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse(fptr, offset, length):
|
||||||
|
"""Parse codestream header box.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
fptr : file
|
||||||
|
Open file object.
|
||||||
|
offset : int
|
||||||
|
Start position of box in bytes.
|
||||||
|
length : int
|
||||||
|
Length of the box in bytes.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
AssociationBox instance
|
||||||
|
"""
|
||||||
|
box = CodestreamHeaderBox(length=length, offset=offset)
|
||||||
|
|
||||||
|
# The codestream header box is a superbox, so go ahead and parse its
|
||||||
|
# child boxes.
|
||||||
|
box.box = box.parse_superbox(fptr)
|
||||||
|
|
||||||
|
return box
|
||||||
|
|
||||||
|
|
||||||
|
class CompositingLayerHeaderBox(Jp2kBox):
|
||||||
|
"""Container for compositing layer header box information.
|
||||||
|
|
||||||
|
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.
|
||||||
|
box : list
|
||||||
|
List of boxes contained in this superbox.
|
||||||
|
"""
|
||||||
|
def __init__(self, length=0, offset=-1):
|
||||||
|
Jp2kBox.__init__(self, box_id='jplh',
|
||||||
|
longname='Compositing Layer Header')
|
||||||
|
self.length = length
|
||||||
|
self.offset = offset
|
||||||
|
self.box = []
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
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
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse(fptr, offset, length):
|
||||||
|
"""Parse compositing layer header box.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
fptr : file
|
||||||
|
Open file object.
|
||||||
|
offset : int
|
||||||
|
Start position of box in bytes.
|
||||||
|
length : int
|
||||||
|
Length of the box in bytes.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
AssociationBox instance
|
||||||
|
"""
|
||||||
|
box = CompositingLayerHeaderBox(length=length, offset=offset)
|
||||||
|
|
||||||
|
# This box is a superbox, so go ahead and parse its # child boxes.
|
||||||
|
box.box = box.parse_superbox(fptr)
|
||||||
|
|
||||||
|
return box
|
||||||
|
|
||||||
|
|
||||||
|
class JP2HeaderBox(Jp2kBox):
|
||||||
|
"""Container for JP2 header box information.
|
||||||
|
|
||||||
|
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.
|
||||||
|
box : list
|
||||||
|
List of boxes contained in this superbox.
|
||||||
|
"""
|
||||||
|
def __init__(self, length=0, offset=-1):
|
||||||
|
Jp2kBox.__init__(self, box_id='jp2h', longname='JP2 Header')
|
||||||
|
self.length = length
|
||||||
|
self.offset = offset
|
||||||
|
self.box = []
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
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(self, fptr):
|
||||||
|
"""Write a JP2 Header box to file.
|
||||||
|
"""
|
||||||
|
# Write the contained boxes, then come back and write the length.
|
||||||
|
orig_pos = fptr.tell()
|
||||||
|
fptr.write(struct.pack('>I', 0))
|
||||||
|
fptr.write('jp2h'.encode())
|
||||||
|
for box in self.box:
|
||||||
|
box.write(fptr)
|
||||||
|
|
||||||
|
end_pos = fptr.tell()
|
||||||
|
fptr.seek(orig_pos)
|
||||||
|
fptr.write(struct.pack('>I', end_pos - orig_pos))
|
||||||
|
fptr.seek(end_pos)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse(fptr, offset, length):
|
||||||
|
"""Parse JPEG 2000 header box.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
fptr : file
|
||||||
|
Open file object.
|
||||||
|
offset : int
|
||||||
|
Start position of box in bytes.
|
||||||
|
length : int
|
||||||
|
Length of the box in bytes.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
JP2HeaderBox instance
|
||||||
|
"""
|
||||||
|
box = JP2HeaderBox(length=length, offset=offset)
|
||||||
|
|
||||||
|
# The JP2 header box is a superbox, so go ahead and parse its child
|
||||||
|
# boxes.
|
||||||
|
box.box = box.parse_superbox(fptr)
|
||||||
|
|
||||||
|
return box
|
||||||
|
|
||||||
|
|
||||||
class ComponentMappingBox(Jp2kBox):
|
class ComponentMappingBox(Jp2kBox):
|
||||||
"""Container for channel identification information.
|
"""Container for channel identification information.
|
||||||
|
|
||||||
|
|
@ -1608,7 +1797,7 @@ class XMLBox(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.
|
||||||
xml : ElementTree.Element
|
xml : ElementTree object
|
||||||
XML section.
|
XML section.
|
||||||
"""
|
"""
|
||||||
def __init__(self, xml=None, filename=None, length=0, offset=-1):
|
def __init__(self, xml=None, filename=None, length=0, offset=-1):
|
||||||
|
|
@ -1636,10 +1825,7 @@ class XMLBox(Jp2kBox):
|
||||||
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
|
||||||
|
|
@ -1682,7 +1868,8 @@ class XMLBox(Jp2kBox):
|
||||||
text = text.rstrip('\0')
|
text = text.rstrip('\0')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
xml = ET.fromstring(text)
|
elt = ET.fromstring(text)
|
||||||
|
xml = ET.ElementTree(elt)
|
||||||
except ParseError as parse_error:
|
except ParseError as parse_error:
|
||||||
msg = 'A problem was encountered while parsing an XML box: "{0}"'
|
msg = 'A problem was encountered while parsing an XML box: "{0}"'
|
||||||
msg = msg.format(str(parse_error))
|
msg = msg.format(str(parse_error))
|
||||||
|
|
@ -1926,13 +2113,11 @@ class UUIDBox(Jp2kBox):
|
||||||
# XMP data. Parse as XML. Seems to be a difference between
|
# XMP data. Parse as XML. Seems to be a difference between
|
||||||
# ElementTree in version 2.7 and 3.3.
|
# ElementTree in version 2.7 and 3.3.
|
||||||
if sys.hexversion < 0x03000000:
|
if sys.hexversion < 0x03000000:
|
||||||
#parser = ET.XMLParser(encoding='utf-8')
|
elt = ET.fromstring(raw_data)
|
||||||
#import pdb; pdb.set_trace()
|
|
||||||
#self.data = ET.fromstringlist(raw_data, parser=parser)
|
|
||||||
self.data = ET.fromstring(raw_data)
|
|
||||||
else:
|
else:
|
||||||
text = raw_data.decode('utf-8')
|
text = raw_data.decode('utf-8')
|
||||||
self.data = ET.fromstring(text)
|
elt = ET.fromstring(text)
|
||||||
|
self.data = ET.ElementTree(elt)
|
||||||
elif the_uuid.bytes == b'JpgTiffExif->JP2':
|
elif the_uuid.bytes == b'JpgTiffExif->JP2':
|
||||||
exif_obj = Exif(raw_data)
|
exif_obj = Exif(raw_data)
|
||||||
ifds = OrderedDict()
|
ifds = OrderedDict()
|
||||||
|
|
@ -2513,11 +2698,13 @@ _BOX_WITH_ID = {
|
||||||
'cdef': ChannelDefinitionBox,
|
'cdef': ChannelDefinitionBox,
|
||||||
'cmap': ComponentMappingBox,
|
'cmap': ComponentMappingBox,
|
||||||
'colr': ColourSpecificationBox,
|
'colr': ColourSpecificationBox,
|
||||||
'jP ': JPEG2000SignatureBox,
|
|
||||||
'ftyp': FileTypeBox,
|
'ftyp': FileTypeBox,
|
||||||
'ihdr': ImageHeaderBox,
|
'ihdr': ImageHeaderBox,
|
||||||
'jp2h': JP2HeaderBox,
|
'jP ': JPEG2000SignatureBox,
|
||||||
|
'jpch': CodestreamHeaderBox,
|
||||||
|
'jplh': CompositingLayerHeaderBox,
|
||||||
'jp2c': ContiguousCodestreamBox,
|
'jp2c': ContiguousCodestreamBox,
|
||||||
|
'jp2h': JP2HeaderBox,
|
||||||
'lbl ': LabelBox,
|
'lbl ': LabelBox,
|
||||||
'pclr': PaletteBox,
|
'pclr': PaletteBox,
|
||||||
'res ': ResolutionBox,
|
'res ': ResolutionBox,
|
||||||
|
|
@ -2555,8 +2742,8 @@ def _pretty_print_xml(xml, level=0):
|
||||||
"""Pretty print XML data.
|
"""Pretty print XML data.
|
||||||
"""
|
"""
|
||||||
xml = copy.deepcopy(xml)
|
xml = copy.deepcopy(xml)
|
||||||
_indent(xml, level=level)
|
_indent(xml.getroot(), level=level)
|
||||||
xmltext = ET.tostring(xml).decode('utf-8')
|
xmltext = ET.tostring(xml.getroot()).decode('utf-8')
|
||||||
|
|
||||||
# Indent it a bit.
|
# Indent it a bit.
|
||||||
lst = [(' ' + x) for x in xmltext.split('\n')]
|
lst = [(' ' + x) for x in xmltext.split('\n')]
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ from .codestream import Codestream
|
||||||
from .core import SRGB
|
from .core import SRGB
|
||||||
from .core import GREYSCALE
|
from .core import GREYSCALE
|
||||||
from .core import PROGRESSION_ORDER
|
from .core import PROGRESSION_ORDER
|
||||||
|
from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE
|
||||||
from .jp2box import Jp2kBox
|
from .jp2box import Jp2kBox
|
||||||
from .jp2box import JPEG2000SignatureBox
|
from .jp2box import JPEG2000SignatureBox
|
||||||
from .jp2box import FileTypeBox
|
from .jp2box import FileTypeBox
|
||||||
|
|
@ -159,6 +160,24 @@ class Jp2k(Jp2kBox):
|
||||||
# boxes) here.
|
# boxes) here.
|
||||||
fptr.seek(0)
|
fptr.seek(0)
|
||||||
self.box = self.parse_superbox(fptr)
|
self.box = self.parse_superbox(fptr)
|
||||||
|
self._validate()
|
||||||
|
|
||||||
|
def _validate(self):
|
||||||
|
"""Validate the JPEG 2000 outermost superbox.
|
||||||
|
"""
|
||||||
|
# A jp2-branded file cannot contain an "any ICC profile
|
||||||
|
ftyp = self.box[1]
|
||||||
|
if ftyp.brand == 'jp2 ':
|
||||||
|
jp2h = [box for box in self.box if box.box_id == 'jp2h'][0]
|
||||||
|
colrs = [box for box in jp2h.box if box.box_id == 'colr']
|
||||||
|
for colr in colrs:
|
||||||
|
if colr.method not in (ENUMERATED_COLORSPACE,
|
||||||
|
RESTRICTED_ICC_PROFILE):
|
||||||
|
msg = "Color Specification box method must specify either "
|
||||||
|
msg += "an enumerated colorspace or a restricted ICC "
|
||||||
|
msg += "profile if the file type box brand is 'jp2 '."
|
||||||
|
warnings.warn(msg)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable-msg=W0221
|
# pylint: disable-msg=W0221
|
||||||
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,
|
||||||
|
|
|
||||||
|
|
@ -35,9 +35,6 @@ def glymurrc_fname():
|
||||||
fname = os.path.join(confdir, 'glymurrc')
|
fname = os.path.join(confdir, 'glymurrc')
|
||||||
if os.path.exists(fname):
|
if os.path.exists(fname):
|
||||||
return fname
|
return fname
|
||||||
else:
|
|
||||||
msg = "Configuration directory '{0}' does not exist.".format(fname)
|
|
||||||
warnings.warn(msg)
|
|
||||||
|
|
||||||
# didn't find a configuration file.
|
# didn't find a configuration file.
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
from .test_callbacks import TestCallbacks as callbacks
|
|
||||||
from .test_codestream import TestCodestream as codestream
|
|
||||||
from .test_config import TestSuite as config
|
|
||||||
from .test_jp2k import TestJp2k as jp2k
|
|
||||||
from .test_icc import TestICC as icc
|
|
||||||
from .test_printing import TestPrinting as printing
|
|
||||||
from .test_opj_suite import TestSuite as suite
|
|
||||||
from .test_opj_suite import TestSuiteDump as suitedump
|
|
||||||
from .test_opj_suite_write import TestSuiteWrite as suitew
|
|
||||||
from .test_opj_suite_neg import TestSuiteNegative as suiteneg
|
|
||||||
from .test_jp2box import TestJp2Boxes as box
|
|
||||||
|
|
@ -81,40 +81,5 @@ class TestSuite(unittest.TestCase):
|
||||||
with self.assertWarns(UserWarning) as cw:
|
with self.assertWarns(UserWarning) as cw:
|
||||||
imp.reload(glymur.lib.openjp2)
|
imp.reload(glymur.lib.openjp2)
|
||||||
|
|
||||||
def test_missing_config_file_via_environ(self):
|
|
||||||
# Verify that we error out properly if the configuration file
|
|
||||||
# specified via environment variable is not found.
|
|
||||||
with tempfile.TemporaryDirectory() as tdir:
|
|
||||||
with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
|
|
||||||
# Misconfigured new configuration file should
|
|
||||||
# be rejected.
|
|
||||||
with self.assertWarns(UserWarning) as cw:
|
|
||||||
imp.reload(glymur.lib.openjp2)
|
|
||||||
|
|
||||||
def test_home_dir_missing_config_dir(self):
|
|
||||||
# Verify no exception is raised if $HOME is missing .config directory.
|
|
||||||
with tempfile.TemporaryDirectory() as tdir:
|
|
||||||
with patch.dict('os.environ', {'HOME': tdir}):
|
|
||||||
# Misconfigured new configuration file should
|
|
||||||
# be rejected.
|
|
||||||
with self.assertWarns(UserWarning) as cw:
|
|
||||||
imp.reload(glymur.lib.openjp2)
|
|
||||||
|
|
||||||
def test_home_dir_missing_glymur_rc_dir(self):
|
|
||||||
# Should warn but not error if $HOME/.config but no glymurrc dir.
|
|
||||||
with tempfile.TemporaryDirectory() as tdir:
|
|
||||||
# We need the subdirectory to be specifically named as ".config"
|
|
||||||
# in order for this test to work. A specifically-named temporary
|
|
||||||
# directory does not seem to be possible, so try to symlink it.
|
|
||||||
# Supposedly the symlink gets cleaned up with tdir gets cleaned up.
|
|
||||||
with tempfile.TemporaryDirectory(suffix=".config", dir=tdir) \
|
|
||||||
as tdir_config:
|
|
||||||
os.symlink(tdir_config, os.path.join(tdir, '.config'))
|
|
||||||
with patch.dict('os.environ', {'HOME': tdir}):
|
|
||||||
# Misconfigured new configuration file should
|
|
||||||
# be rejected.
|
|
||||||
with self.assertWarns(UserWarning) as cw:
|
|
||||||
imp.reload(glymur.lib.openjp2)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
129
glymur/test/test_conformance.py
Normal file
129
glymur/test/test_conformance.py
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
"""
|
||||||
|
These tests deal with JPX/JP2/J2K images in the format-corpus repository.
|
||||||
|
"""
|
||||||
|
#pylint: disable-all
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if sys.hexversion < 0x02070000:
|
||||||
|
import unittest2 as unittest
|
||||||
|
else:
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from glymur import Jp2k
|
||||||
|
import glymur
|
||||||
|
|
||||||
|
try:
|
||||||
|
format_corpus_data_root = os.environ['FORMAT_CORPUS_DATA_ROOT']
|
||||||
|
except KeyError:
|
||||||
|
format_corpus_data_root = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
opj_data_root = os.environ['OPJ_DATA_ROOT']
|
||||||
|
except KeyError:
|
||||||
|
opj_data_root = None
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(format_corpus_data_root is None,
|
||||||
|
"FORMAT_CORPUS_DATA_ROOT environment variable not set")
|
||||||
|
@unittest.skipIf(sys.hexversion < 0x03020000,
|
||||||
|
"Requires features introduced in 3.2 (assertWarns)")
|
||||||
|
class TestSuiteFormatCorpus(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_balloon_trunc1(self):
|
||||||
|
# Has one byte shaved off of EOC marker.
|
||||||
|
jfile = os.path.join(format_corpus_data_root,
|
||||||
|
'jp2k-test/byteCorruption/balloon_trunc1.jp2')
|
||||||
|
j2k = Jp2k(jfile)
|
||||||
|
with self.assertWarns(UserWarning):
|
||||||
|
c = j2k.get_codestream(header_only=False)
|
||||||
|
|
||||||
|
# The last segment is truncated, so there should not be an EOC marker.
|
||||||
|
self.assertNotEqual(c.segment[-1].marker_id, 'EOC')
|
||||||
|
|
||||||
|
# The codestream is not as long as claimed.
|
||||||
|
with self.assertRaises(OSError):
|
||||||
|
j2k.read(rlevel=-1)
|
||||||
|
|
||||||
|
def test_balloon_trunc2(self):
|
||||||
|
# Shortened by 5000 bytes.
|
||||||
|
jfile = os.path.join(format_corpus_data_root,
|
||||||
|
'jp2k-test/byteCorruption/balloon_trunc2.jp2')
|
||||||
|
j2k = Jp2k(jfile)
|
||||||
|
with self.assertWarns(UserWarning):
|
||||||
|
c = j2k.get_codestream(header_only=False)
|
||||||
|
|
||||||
|
# The last segment is truncated, so there should not be an EOC marker.
|
||||||
|
self.assertNotEqual(c.segment[-1].marker_id, 'EOC')
|
||||||
|
|
||||||
|
# The codestream is not as long as claimed.
|
||||||
|
with self.assertRaises(OSError):
|
||||||
|
j2k.read(rlevel=-1)
|
||||||
|
|
||||||
|
def test_balloon_trunc3(self):
|
||||||
|
# Most of last tile is missing.
|
||||||
|
jfile = os.path.join(format_corpus_data_root,
|
||||||
|
'jp2k-test/byteCorruption/balloon_trunc3.jp2')
|
||||||
|
j2k = Jp2k(jfile)
|
||||||
|
with self.assertWarns(UserWarning):
|
||||||
|
c = j2k.get_codestream(header_only=False)
|
||||||
|
|
||||||
|
# The last segment is truncated, so there should not be an EOC marker.
|
||||||
|
self.assertNotEqual(c.segment[-1].marker_id, 'EOC')
|
||||||
|
|
||||||
|
# Should error out, it does not.
|
||||||
|
#with self.assertRaises(OSError):
|
||||||
|
# j2k.read(rlevel=-1)
|
||||||
|
|
||||||
|
def test_jp2_brand_vs_any_icc_profile(self):
|
||||||
|
# If 'jp2 ', then the method cannot be any icc profile.
|
||||||
|
jfile = os.path.join(format_corpus_data_root,
|
||||||
|
'jp2k-test', 'icc',
|
||||||
|
'balloon_eciRGBv2_ps_adobeplugin.jpf')
|
||||||
|
with self.assertWarns(UserWarning):
|
||||||
|
j2k = Jp2k(jfile)
|
||||||
|
|
||||||
|
def test_jp2_brand_vs_any_icc_profile_multiple_colr(self):
|
||||||
|
# Has colr box, one that conforms, one that does not.
|
||||||
|
|
||||||
|
# Wrong 'brand' field; contains two versions of ICC profile: one
|
||||||
|
# embedded using "Any ICC" method; other embedded using "Restricted
|
||||||
|
# ICC" method, with description ("Modified eciRGB v2") and profileClass
|
||||||
|
# ("Input Device") changed relative to original profile.
|
||||||
|
lst = [format_corpus_data_root, 'jp2k-test', 'icc',
|
||||||
|
'balloon_eciRGBv2_ps_adobeplugin_jp2compatible.jpf']
|
||||||
|
jfile = os.path.join(*lst)
|
||||||
|
with self.assertWarns(UserWarning):
|
||||||
|
j2k = Jp2k(jfile)
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(opj_data_root is None,
|
||||||
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
|
@unittest.skipIf(sys.hexversion < 0x03020000,
|
||||||
|
"Requires features introduced in 3.2 (assertWarns)")
|
||||||
|
class TestSuiteOpj(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_jp2_brand_vs_any_icc_profile(self):
|
||||||
|
# If 'jp2 ', then the method cannot be any icc profile.
|
||||||
|
filename = os.path.join(opj_data_root,
|
||||||
|
'input/nonregression/text_GBR.jp2')
|
||||||
|
with self.assertWarns(UserWarning):
|
||||||
|
j2k = Jp2k(filename)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
|
|
@ -19,6 +19,11 @@ from glymur.jp2box import *
|
||||||
from glymur.core import COLOR, OPACITY
|
from glymur.core import COLOR, OPACITY
|
||||||
from glymur.core import RED, GREEN, BLUE, GREY, WHOLE_IMAGE
|
from glymur.core import RED, GREEN, BLUE, GREY, WHOLE_IMAGE
|
||||||
|
|
||||||
|
try:
|
||||||
|
format_corpus_data_root = os.environ['FORMAT_CORPUS_DATA_ROOT']
|
||||||
|
except KeyError:
|
||||||
|
format_corpus_data_root = None
|
||||||
|
|
||||||
|
|
||||||
# Doc tests should be run as well.
|
# Doc tests should be run as well.
|
||||||
def load_tests(loader, tests, ignore):
|
def load_tests(loader, tests, ignore):
|
||||||
|
|
@ -365,7 +370,7 @@ class TestXML(unittest.TestCase):
|
||||||
j2k.wrap(tfile.name, boxes=boxes)
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
jp2 = Jp2k(tfile.name)
|
jp2 = Jp2k(tfile.name)
|
||||||
self.assertEqual(jp2.box[3].box_id, 'xml ')
|
self.assertEqual(jp2.box[3].box_id, 'xml ')
|
||||||
self.assertEqual(ET.tostring(jp2.box[3].xml),
|
self.assertEqual(ET.tostring(jp2.box[3].xml.getroot()),
|
||||||
b'<data>0</data>')
|
b'<data>0</data>')
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt",
|
@unittest.skipIf(os.name == "nt",
|
||||||
|
|
@ -467,7 +472,7 @@ class TestColourSpecificationBox(unittest.TestCase):
|
||||||
|
|
||||||
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
|
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
|
||||||
"Missing openjp2 library.")
|
"Missing openjp2 library.")
|
||||||
class TestJp2Boxes(unittest.TestCase):
|
class TestWrap(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.j2kfile = glymur.data.goodstuff()
|
self.j2kfile = glymur.data.goodstuff()
|
||||||
|
|
@ -476,39 +481,6 @@ class TestJp2Boxes(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
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.box_id, 'jp2c')
|
|
||||||
self.assertIsNone(b.main_header)
|
|
||||||
|
|
||||||
def verify_wrapped_raw(self, jp2file):
|
def verify_wrapped_raw(self, jp2file):
|
||||||
# Shared method by at least two tests.
|
# Shared method by at least two tests.
|
||||||
jp2 = Jp2k(jp2file)
|
jp2 = Jp2k(jp2file)
|
||||||
|
|
@ -673,5 +645,75 @@ class TestJp2Boxes(unittest.TestCase):
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
j2k.wrap(tfile.name, boxes=boxes)
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
|
||||||
|
class TestJp2Boxes(unittest.TestCase):
|
||||||
|
|
||||||
|
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.box_id, 'jp2c')
|
||||||
|
self.assertIsNone(b.main_header)
|
||||||
|
|
||||||
|
|
||||||
|
class TestJpxBoxes(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@unittest.skipIf(format_corpus_data_root is None,
|
||||||
|
"FORMAT_CORPUS_DATA_ROOT environment variable not set")
|
||||||
|
def test_codestream_header(self):
|
||||||
|
# Should recognize codestream header box.
|
||||||
|
jfile = os.path.join(format_corpus_data_root,
|
||||||
|
'jp2k-formats/balloon.jpf')
|
||||||
|
jpx = Jp2k(jfile)
|
||||||
|
|
||||||
|
# This superbox just happens to be empty.
|
||||||
|
self.assertEqual(jpx.box[4].box_id, 'jpch')
|
||||||
|
self.assertEqual(len(jpx.box[4].box), 0)
|
||||||
|
|
||||||
|
@unittest.skipIf(format_corpus_data_root is None,
|
||||||
|
"FORMAT_CORPUS_DATA_ROOT environment variable not set")
|
||||||
|
def test_compositing_layer_header(self):
|
||||||
|
# Should recognize compositing layer header box.
|
||||||
|
jfile = os.path.join(format_corpus_data_root,
|
||||||
|
'jp2k-formats/balloon.jpf')
|
||||||
|
jpx = Jp2k(jfile)
|
||||||
|
|
||||||
|
# This superbox just happens to be empty.
|
||||||
|
self.assertEqual(jpx.box[5].box_id, 'jplh')
|
||||||
|
self.assertEqual(len(jpx.box[5].box), 0)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -990,7 +990,11 @@ class TestSuite(unittest.TestCase):
|
||||||
def test_NR_DEC_text_GBR_jp2_29_decode(self):
|
def test_NR_DEC_text_GBR_jp2_29_decode(self):
|
||||||
jfile = os.path.join(data_root,
|
jfile = os.path.join(data_root,
|
||||||
'input/nonregression/text_GBR.jp2')
|
'input/nonregression/text_GBR.jp2')
|
||||||
data = Jp2k(jfile).read()
|
with warnings.catch_warnings():
|
||||||
|
# brand is 'jp2 ', but has any icc profile.
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
jp2 = Jp2k(jfile)
|
||||||
|
data = jp2.read()
|
||||||
self.assertTrue(True)
|
self.assertTrue(True)
|
||||||
|
|
||||||
def test_NR_DEC_pacs_ge_j2k_30_decode(self):
|
def test_NR_DEC_pacs_ge_j2k_30_decode(self):
|
||||||
|
|
@ -4021,7 +4025,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ')
|
self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ')
|
||||||
|
|
||||||
# XML box
|
# XML box
|
||||||
tags = [x.tag for x in jp2.box[2].xml]
|
tags = [x.tag for x in jp2.box[2].xml.getroot()]
|
||||||
self.assertEqual(tags,
|
self.assertEqual(tags,
|
||||||
['{http://www.jpeg.org/jpx/1.0/xml}'
|
['{http://www.jpeg.org/jpx/1.0/xml}'
|
||||||
+ 'GENERAL_CREATION_INFO'])
|
+ 'GENERAL_CREATION_INFO'])
|
||||||
|
|
@ -4046,7 +4050,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[3].box[1].colorspace, glymur.core.SRGB)
|
self.assertEqual(jp2.box[3].box[1].colorspace, glymur.core.SRGB)
|
||||||
|
|
||||||
# XML box
|
# XML box
|
||||||
tags = [x.tag for x in jp2.box[4].xml]
|
tags = [x.tag for x in jp2.box[4].xml.getroot()]
|
||||||
self.assertEqual(tags, ['{http://www.jpeg.org/jpx/1.0/xml}CAPTION',
|
self.assertEqual(tags, ['{http://www.jpeg.org/jpx/1.0/xml}CAPTION',
|
||||||
'{http://www.jpeg.org/jpx/1.0/xml}LOCATION',
|
'{http://www.jpeg.org/jpx/1.0/xml}LOCATION',
|
||||||
'{http://www.jpeg.org/jpx/1.0/xml}EVENT'])
|
'{http://www.jpeg.org/jpx/1.0/xml}EVENT'])
|
||||||
|
|
@ -4376,13 +4380,13 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertIsNone(jp2.box[2].box[1].colorspace)
|
self.assertIsNone(jp2.box[2].box[1].colorspace)
|
||||||
|
|
||||||
# XML box
|
# XML box
|
||||||
tags = [x.tag for x in jp2.box[3].xml]
|
tags = [x.tag for x in jp2.box[3].xml.getroot()]
|
||||||
self.assertEqual(tags,
|
self.assertEqual(tags,
|
||||||
['{http://www.jpeg.org/jpx/1.0/xml}'
|
['{http://www.jpeg.org/jpx/1.0/xml}'
|
||||||
+ 'GENERAL_CREATION_INFO'])
|
+ 'GENERAL_CREATION_INFO'])
|
||||||
|
|
||||||
# XML box
|
# XML box
|
||||||
tags = [x.tag for x in jp2.box[5].xml]
|
tags = [x.tag for x in jp2.box[5].xml.getroot()]
|
||||||
self.assertEqual(tags,
|
self.assertEqual(tags,
|
||||||
['{http://www.jpeg.org/jpx/1.0/xml}CAPTION',
|
['{http://www.jpeg.org/jpx/1.0/xml}CAPTION',
|
||||||
'{http://www.jpeg.org/jpx/1.0/xml}LOCATION',
|
'{http://www.jpeg.org/jpx/1.0/xml}LOCATION',
|
||||||
|
|
@ -6954,8 +6958,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(c.segment[3]._exponent, [4] + [5, 5, 6] * 5)
|
self.assertEqual(c.segment[3]._exponent, [4] + [5, 5, 6] * 5)
|
||||||
|
|
||||||
def test_NR_merged_dump(self):
|
def test_NR_merged_dump(self):
|
||||||
jfile = os.path.join(data_root,
|
jfile = os.path.join(data_root, 'input/nonregression/merged.jp2')
|
||||||
'input/nonregression/merged.jp2')
|
|
||||||
jp2 = Jp2k(jfile)
|
jp2 = Jp2k(jfile)
|
||||||
|
|
||||||
ids = [box.box_id for box in jp2.box]
|
ids = [box.box_id for box in jp2.box]
|
||||||
|
|
@ -7261,7 +7264,10 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
def test_NR_text_GBR_dump(self):
|
def test_NR_text_GBR_dump(self):
|
||||||
jfile = os.path.join(data_root,
|
jfile = os.path.join(data_root,
|
||||||
'input/nonregression/text_GBR.jp2')
|
'input/nonregression/text_GBR.jp2')
|
||||||
jp2 = Jp2k(jfile)
|
with warnings.catch_warnings():
|
||||||
|
# brand is 'jp2 ', but has any icc profile.
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
jp2 = Jp2k(jfile)
|
||||||
|
|
||||||
ids = [box.box_id for box in jp2.box]
|
ids = [box.box_id for box in jp2.box]
|
||||||
lst = ['jP ', 'ftyp', 'rreq', 'jp2h',
|
lst = ['jP ', 'ftyp', 'rreq', 'jp2h',
|
||||||
|
|
@ -7813,6 +7819,8 @@ class TestSuite15(unittest.TestCase):
|
||||||
data = jp2.read()
|
data = jp2.read()
|
||||||
self.assertTrue(True)
|
self.assertTrue(True)
|
||||||
|
|
||||||
|
@unittest.skipIf(int(glymur.lib.openjpeg.version().split('.')[1]) < 5,
|
||||||
|
"Segfaults openjpeg 1.4 and earlier.")
|
||||||
def test_NR_DEC_broken2_jp2_5_decode(self):
|
def test_NR_DEC_broken2_jp2_5_decode(self):
|
||||||
# Null pointer access
|
# Null pointer access
|
||||||
jfile = os.path.join(data_root, 'input/nonregression/broken2.jp2')
|
jfile = os.path.join(data_root, 'input/nonregression/broken2.jp2')
|
||||||
|
|
@ -7834,6 +7842,8 @@ class TestSuite15(unittest.TestCase):
|
||||||
with self.assertRaises(ValueError) as ce:
|
with self.assertRaises(ValueError) as ce:
|
||||||
d = j.read()
|
d = j.read()
|
||||||
|
|
||||||
|
@unittest.skipIf(int(glymur.lib.openjpeg.version().split('.')[1]) < 5,
|
||||||
|
"Segfaults openjpeg 1.4 and earlier.")
|
||||||
def test_NR_DEC_broken4_jp2_7_decode(self):
|
def test_NR_DEC_broken4_jp2_7_decode(self):
|
||||||
# Null pointer access
|
# Null pointer access
|
||||||
jfile = os.path.join(data_root, 'input/nonregression/broken4.jp2')
|
jfile = os.path.join(data_root, 'input/nonregression/broken4.jp2')
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import pkg_resources
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import warnings
|
||||||
|
|
||||||
if sys.hexversion < 0x02070000:
|
if sys.hexversion < 0x02070000:
|
||||||
import unittest2 as unittest
|
import unittest2 as unittest
|
||||||
|
|
@ -277,9 +278,12 @@ class TestPrinting(unittest.TestCase):
|
||||||
"OPJ_DATA_ROOT environment variable not set")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_icc_profile(self):
|
def test_icc_profile(self):
|
||||||
filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2')
|
filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2')
|
||||||
j = glymur.Jp2k(filename)
|
with warnings.catch_warnings():
|
||||||
|
# brand is 'jp2 ', but has any icc profile.
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
jp2 = Jp2k(filename)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
print(j.box[3].box[1])
|
print(jp2.box[3].box[1])
|
||||||
actual = fake_out.getvalue().strip()
|
actual = fake_out.getvalue().strip()
|
||||||
lin27 = ["Colour Specification Box (colr) @ (179, 1339)",
|
lin27 = ["Colour Specification Box (colr) @ (179, 1339)",
|
||||||
" Method: any ICC profile",
|
" Method: any ICC profile",
|
||||||
|
|
@ -902,10 +906,13 @@ class TestPrinting(unittest.TestCase):
|
||||||
# ICC profiles may be used in JP2, but the approximation field should
|
# ICC profiles may be used in JP2, but the approximation field should
|
||||||
# be zero unless we have jpx. This file does both.
|
# be zero unless we have jpx. This file does both.
|
||||||
filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2')
|
filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2')
|
||||||
j = glymur.Jp2k(filename)
|
with warnings.catch_warnings():
|
||||||
|
# brand is 'jp2 ', but has any icc profile.
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
jp2 = Jp2k(filename)
|
||||||
|
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
print(j.box[3].box[1])
|
print(jp2.box[3].box[1])
|
||||||
actual = fake_out.getvalue().strip()
|
actual = fake_out.getvalue().strip()
|
||||||
lines = ["Colour Specification Box (colr) @ (179, 1339)",
|
lines = ["Colour Specification Box (colr) @ (179, 1339)",
|
||||||
" Method: any ICC profile",
|
" Method: any ICC profile",
|
||||||
|
|
@ -942,10 +949,13 @@ class TestPrinting(unittest.TestCase):
|
||||||
def test_uuid(self):
|
def test_uuid(self):
|
||||||
# UUID box
|
# UUID box
|
||||||
filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2')
|
filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2')
|
||||||
j = glymur.Jp2k(filename)
|
with warnings.catch_warnings():
|
||||||
|
# brand is 'jp2 ', but has any icc profile.
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
jp2 = Jp2k(filename)
|
||||||
|
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
print(j.box[4])
|
print(jp2.box[4])
|
||||||
actual = fake_out.getvalue().strip()
|
actual = fake_out.getvalue().strip()
|
||||||
lines = ['UUID Box (uuid) @ (1544, 25)',
|
lines = ['UUID Box (uuid) @ (1544, 25)',
|
||||||
' UUID: 3a0d0218-0ae9-4115-b376-4bca41ce0e71',
|
' UUID: 3a0d0218-0ae9-4115-b376-4bca41ce0e71',
|
||||||
|
|
|
||||||
14
release.txt
14
release.txt
|
|
@ -6,30 +6,30 @@
|
||||||
| | | | | pass. |
|
| | | | | pass. |
|
||||||
+-----------+--------+--------+--------+--------------------------------------+
|
+-----------+--------+--------+--------+--------------------------------------+
|
||||||
| Mac | X | | | MacPorts with both OpenJPEG 1.5.1 |
|
| Mac | X | | | MacPorts with both OpenJPEG 1.5.1 |
|
||||||
| 10.6.8 | | | | and OpenJPEG svn. 353 of 449 tests |
|
| 10.6.8 | | | | and OpenJPEG svn. 352 of 450 tests |
|
||||||
| | | | | should pass. |
|
| | | | | should pass. |
|
||||||
+-----------+--------+--------+--------+--------------------------------------+
|
+-----------+--------+--------+--------+--------------------------------------+
|
||||||
| Mac | | X | | MacPorts with both OpenJPEG 1.5.1 |
|
| Mac | | X | | MacPorts with both OpenJPEG 1.5.1 |
|
||||||
| 10.6.8 | | | | and OpenJPEG svn. 376 of 454 tests |
|
| 10.6.8 | | | | and OpenJPEG svn. 377 of 455 tests |
|
||||||
| | | | | should pass. |
|
| | | | | should pass. |
|
||||||
+-----------+--------+--------+--------+--------------------------------------+
|
+-----------+--------+--------+--------+--------------------------------------+
|
||||||
| Mac | | | X | MacPorts with both OpenJPEG 1.5.1 |
|
| Mac | | | X | MacPorts with both OpenJPEG 1.5.1 |
|
||||||
| 10.6.8 | | | | and OpenJPEG svn. 401 of 454 |
|
| 10.6.8 | | | | and OpenJPEG svn. 402 of 455 |
|
||||||
| | | | | tests should pass. |
|
| | | | | tests should pass. |
|
||||||
+-----------+--------+--------+-----------------------------------------------+
|
+-----------+--------+--------+-----------------------------------------------+
|
||||||
| Fedora 19 | | | X | Ships with 1.5.1, openjp2 built too. |
|
| Fedora 19 | | | X | Ships with 1.5.1, openjp2 built too. |
|
||||||
| | | | | 401 of 454 tests should pass. |
|
| | | | | 402 of 455 tests should pass. |
|
||||||
+-----------+--------+--------+--------+--------------------------------------+
|
+-----------+--------+--------+--------+--------------------------------------+
|
||||||
| Fedora 18 | | | X | Ships with 1.5.1. 169 of 449 tests |
|
| Fedora 18 | | | X | Ships with 1.5.1. 169 of 449 tests |
|
||||||
| | | | | should pass. |
|
| | | | | should pass. |
|
||||||
+-----------+--------+--------+--------+--------------------------------------+
|
+-----------+--------+--------+--------+--------------------------------------+
|
||||||
| Fedora 17 | | X | | Ships with 1.4.0. 169 of 449 tests |
|
| Fedora 17 | | X | | Ships with 1.4.0. 166 of 450 tests |
|
||||||
| | | | | should pass. |
|
| | | | | should pass. |
|
||||||
+-----------+--------+--------+--------+--------------------------------------+
|
+-----------+--------+--------+--------+--------------------------------------+
|
||||||
| CentOS | X | | | Ships with 1.3.0. 167 of 449 tests |
|
| CentOS | X | | | Ships with 1.3.0. 164 of 450 tests |
|
||||||
| 6.3 | | | | should pass. |
|
| 6.3 | | | | should pass. |
|
||||||
+-----------+--------+--------+--------+--------------------------------------+
|
+-----------+--------+--------+--------+--------------------------------------+
|
||||||
| Raspberry | | X | | Ships with 1.3.0. 169 of 449 tests |
|
| Raspberry | | X | | Ships with 1.3.0. 166 of 450 tests |
|
||||||
| Pi | | | | should pass. |
|
| Pi | | | | should pass. |
|
||||||
| Debian 7 | | | | |
|
| Debian 7 | | | | |
|
||||||
+-----------+--------+--------+--------+--------------------------------------+
|
+-----------+--------+--------+--------+--------------------------------------+
|
||||||
|
|
|
||||||
2
setup.py
2
setup.py
|
|
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
kwargs = {'name': 'Glymur',
|
kwargs = {'name': 'Glymur',
|
||||||
'version': '0.2.3',
|
'version': '0.2.5',
|
||||||
'description': 'Tools for accessing JPEG2000 files',
|
'description': 'Tools for accessing JPEG2000 files',
|
||||||
'long_description': open('README.md').read(),
|
'long_description': open('README.md').read(),
|
||||||
'author': 'John Evans',
|
'author': 'John Evans',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue