Merge branch 'issue86' into devel

This commit is contained in:
John Evans 2013-07-24 19:03:17 -04:00
commit ce46cfe710
19 changed files with 513 additions and 164 deletions

View file

@ -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
change to ChannelDefinitionBox constructor. Added RGBA example.

View file

@ -1,9 +1,9 @@
glymur: a Python interface for JPEG 2000
=========================================
**glymur** contains a Python interface to the OpenJPEG library
which allows linux and mac users to read and write JPEG 2000 files.
**glymur** works on Python 2.6, 2.7 and 3.3. Python 3.3 is strongly
**glymur** contains a Python interface to the OpenJPEG library which
allows one to read and write JPEG 2000 files. **glymur** works on
Python 2.6, 2.7 and 3.3. Python 3.3 is strongly
recommended.
Please read the docs, https://glymur.readthedocs.org/en/latest/

View file

@ -78,7 +78,7 @@ copyright = u'2013, John Evans'
# The short X.Y version.
version = '0.1'
# 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
# for a list of supported languages.

View file

@ -115,7 +115,7 @@ following RPMs installed.
* numpy
* 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
$ pip-python install Pillow --user
@ -137,8 +137,17 @@ platforms.
Testing
'''''''
If you wish to run the tests (strongly recommended :-), you can either run them
from within python as follows ... ::
There are two environment variables you may wish to set before running the
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
>>> glymur.runtests()

View file

@ -185,9 +185,8 @@ Work with XMP UUIDs?
====================
The example JP2 file shipped with glymur has an XMP UUID. ::
>>> from glymur import Jp2k
>>> file = glymur.data.nemo()
>>> j = Jp2k(file)
>>> import glymur
>>> j = glymur.Jp2k(glymur.data.nemo())
>>> print(j.box[4])
UUID Box (uuid) @ (715, 2412)
UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP)
@ -198,7 +197,7 @@ The example JP2 file shipped with glymur has an XMP UUID. ::
</rdf:RDF>
</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
**CreatorTool** attribute value, the following would work::

View file

@ -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
* investigate using CFFI or cython instead of ctypes to wrap openjp2
* investigate swapping out ElementTree for LXML
* eventually expose the openjp2 API
* investigate JPIP

View file

@ -6,7 +6,6 @@ from .jp2k import Jp2k
from .jp2dump import jp2dump
from . import data
from . import test
def runtests():

View file

@ -5,7 +5,6 @@ codestreams.
"""
# pylint: disable=C0302,R0902,R0903,R0913
from itertools import takewhile
import math
import struct
import sys
@ -43,16 +42,6 @@ for _marker in range(0xff90, 0xff94):
_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):
"""Container for codestream information.
@ -113,7 +102,19 @@ class Codestream(object):
while True:
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:
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])
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):
"""Process and return a segment from the codestream.
@ -210,19 +205,12 @@ class Codestream(object):
# we encounter start-of-data marker segments.
segment = _parse_sot_segment(fptr)
if segment.offset not in self._tile_offset:
self._tile_offset.append(segment.offset)
if segment.psot == 0:
tile_part_length = self.offset + self.length - segment.offset - 2
else:
tile_part_length = segment.psot
self._tile_length.append(tile_part_length)
self._tile_offset.append(segment.offset)
if segment.psot == 0:
tile_part_length = self.offset + self.length - segment.offset - 2
else:
msg = "Inconsistent start-of-tile (SOT) marker segment "
msg += "encountered in tile with index {0}. "
msg += "Codestream parsing terminated."
msg = msg.format(segment.isot)
raise InconsistentStartOfTileError(msg)
tile_part_length = segment.psot
self._tile_length.append(tile_part_length)
elif marker_id == 0xff93:
# start of data. Need to seek past the current tile part.

View file

@ -520,6 +520,195 @@ class ChannelDefinitionBox(Jp2kBox):
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):
"""Container for channel identification information.
@ -1608,7 +1797,7 @@ class XMLBox(Jp2kBox):
offset of the box from the start of the file.
longname : str
more verbose description of the box.
xml : ElementTree.Element
xml : ElementTree object
XML section.
"""
def __init__(self, xml=None, filename=None, length=0, offset=-1):
@ -1636,10 +1825,7 @@ class XMLBox(Jp2kBox):
msg = Jp2kBox.__str__(self)
xml = self.xml
if self.xml is not None:
try:
msg += _pretty_print_xml(self.xml)
except TypeError:
msg += _pretty_print_xml(self.xml.getroot())
msg += _pretty_print_xml(self.xml)
else:
msg += '\n {0}'.format(xml)
return msg
@ -1682,7 +1868,8 @@ class XMLBox(Jp2kBox):
text = text.rstrip('\0')
try:
xml = ET.fromstring(text)
elt = ET.fromstring(text)
xml = ET.ElementTree(elt)
except ParseError as parse_error:
msg = 'A problem was encountered while parsing an XML box: "{0}"'
msg = msg.format(str(parse_error))
@ -1926,13 +2113,11 @@ class UUIDBox(Jp2kBox):
# XMP data. Parse as XML. Seems to be a difference between
# ElementTree in version 2.7 and 3.3.
if sys.hexversion < 0x03000000:
#parser = ET.XMLParser(encoding='utf-8')
#import pdb; pdb.set_trace()
#self.data = ET.fromstringlist(raw_data, parser=parser)
self.data = ET.fromstring(raw_data)
elt = ET.fromstring(raw_data)
else:
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':
exif_obj = Exif(raw_data)
ifds = OrderedDict()
@ -2513,11 +2698,13 @@ _BOX_WITH_ID = {
'cdef': ChannelDefinitionBox,
'cmap': ComponentMappingBox,
'colr': ColourSpecificationBox,
'jP ': JPEG2000SignatureBox,
'ftyp': FileTypeBox,
'ihdr': ImageHeaderBox,
'jp2h': JP2HeaderBox,
'jP ': JPEG2000SignatureBox,
'jpch': CodestreamHeaderBox,
'jplh': CompositingLayerHeaderBox,
'jp2c': ContiguousCodestreamBox,
'jp2h': JP2HeaderBox,
'lbl ': LabelBox,
'pclr': PaletteBox,
'res ': ResolutionBox,
@ -2555,8 +2742,8 @@ def _pretty_print_xml(xml, level=0):
"""Pretty print XML data.
"""
xml = copy.deepcopy(xml)
_indent(xml, level=level)
xmltext = ET.tostring(xml).decode('utf-8')
_indent(xml.getroot(), level=level)
xmltext = ET.tostring(xml.getroot()).decode('utf-8')
# Indent it a bit.
lst = [(' ' + x) for x in xmltext.split('\n')]

View file

@ -22,6 +22,7 @@ from .codestream import Codestream
from .core import SRGB
from .core import GREYSCALE
from .core import PROGRESSION_ORDER
from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE
from .jp2box import Jp2kBox
from .jp2box import JPEG2000SignatureBox
from .jp2box import FileTypeBox
@ -159,6 +160,24 @@ class Jp2k(Jp2kBox):
# boxes) here.
fptr.seek(0)
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
def write(self, img_array, cratios=None, eph=False, psnr=None, numres=None,

View file

@ -35,9 +35,6 @@ def glymurrc_fname():
fname = os.path.join(confdir, 'glymurrc')
if os.path.exists(fname):
return fname
else:
msg = "Configuration directory '{0}' does not exist.".format(fname)
warnings.warn(msg)
# didn't find a configuration file.
return None

View file

@ -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

View file

@ -81,40 +81,5 @@ class TestSuite(unittest.TestCase):
with self.assertWarns(UserWarning) as cw:
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__":
unittest.main()

View 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()

View file

@ -19,6 +19,11 @@ from glymur.jp2box import *
from glymur.core import COLOR, OPACITY
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.
def load_tests(loader, tests, ignore):
@ -365,7 +370,7 @@ class TestXML(unittest.TestCase):
j2k.wrap(tfile.name, boxes=boxes)
jp2 = Jp2k(tfile.name)
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>')
@unittest.skipIf(os.name == "nt",
@ -467,7 +472,7 @@ class TestColourSpecificationBox(unittest.TestCase):
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
"Missing openjp2 library.")
class TestJp2Boxes(unittest.TestCase):
class TestWrap(unittest.TestCase):
def setUp(self):
self.j2kfile = glymur.data.goodstuff()
@ -476,39 +481,6 @@ class TestJp2Boxes(unittest.TestCase):
def tearDown(self):
pass
def test_default_JPEG2000SignatureBox(self):
# Should be able to instantiate a JPEG2000SignatureBox
b = glymur.jp2box.JPEG2000SignatureBox()
self.assertEqual(b.signature, (13, 10, 135, 10))
def test_default_FileTypeBox(self):
# Should be able to instantiate a FileTypeBox
b = glymur.jp2box.FileTypeBox()
self.assertEqual(b.brand, 'jp2 ')
self.assertEqual(b.minor_version, 0)
self.assertEqual(b.compatibility_list, ['jp2 '])
def test_default_ImageHeaderBox(self):
# Should be able to instantiate an image header box.
b = glymur.jp2box.ImageHeaderBox(height=512, width=256,
num_components=3)
self.assertEqual(b.height, 512)
self.assertEqual(b.width, 256)
self.assertEqual(b.num_components, 3)
self.assertEqual(b.bits_per_component, 8)
self.assertFalse(b.signed)
self.assertFalse(b.colorspace_unknown)
def test_default_JP2HeaderBox(self):
b1 = JP2HeaderBox()
b1.box = [ImageHeaderBox(height=512, width=256),
ColourSpecificationBox(colorspace=glymur.core.GREYSCALE)]
def test_default_ContiguousCodestreamBox(self):
b = ContiguousCodestreamBox()
self.assertEqual(b.box_id, 'jp2c')
self.assertIsNone(b.main_header)
def verify_wrapped_raw(self, jp2file):
# Shared method by at least two tests.
jp2 = Jp2k(jp2file)
@ -673,5 +645,75 @@ class TestJp2Boxes(unittest.TestCase):
with self.assertRaises(IOError):
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__":
unittest.main()

View file

@ -990,7 +990,11 @@ class TestSuite(unittest.TestCase):
def test_NR_DEC_text_GBR_jp2_29_decode(self):
jfile = os.path.join(data_root,
'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)
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 ')
# 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,
['{http://www.jpeg.org/jpx/1.0/xml}'
+ 'GENERAL_CREATION_INFO'])
@ -4046,7 +4050,7 @@ class TestSuiteDump(unittest.TestCase):
self.assertEqual(jp2.box[3].box[1].colorspace, glymur.core.SRGB)
# 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',
'{http://www.jpeg.org/jpx/1.0/xml}LOCATION',
'{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)
# 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,
['{http://www.jpeg.org/jpx/1.0/xml}'
+ 'GENERAL_CREATION_INFO'])
# 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,
['{http://www.jpeg.org/jpx/1.0/xml}CAPTION',
'{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)
def test_NR_merged_dump(self):
jfile = os.path.join(data_root,
'input/nonregression/merged.jp2')
jfile = os.path.join(data_root, 'input/nonregression/merged.jp2')
jp2 = Jp2k(jfile)
ids = [box.box_id for box in jp2.box]
@ -7261,7 +7264,10 @@ class TestSuiteDump(unittest.TestCase):
def test_NR_text_GBR_dump(self):
jfile = os.path.join(data_root,
'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]
lst = ['jP ', 'ftyp', 'rreq', 'jp2h',
@ -7813,6 +7819,8 @@ class TestSuite15(unittest.TestCase):
data = jp2.read()
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):
# Null pointer access
jfile = os.path.join(data_root, 'input/nonregression/broken2.jp2')
@ -7834,6 +7842,8 @@ class TestSuite15(unittest.TestCase):
with self.assertRaises(ValueError) as ce:
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):
# Null pointer access
jfile = os.path.join(data_root, 'input/nonregression/broken4.jp2')

View file

@ -4,6 +4,7 @@ import pkg_resources
import struct
import sys
import tempfile
import warnings
if sys.hexversion < 0x02070000:
import unittest2 as unittest
@ -277,9 +278,12 @@ class TestPrinting(unittest.TestCase):
"OPJ_DATA_ROOT environment variable not set")
def test_icc_profile(self):
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:
print(j.box[3].box[1])
print(jp2.box[3].box[1])
actual = fake_out.getvalue().strip()
lin27 = ["Colour Specification Box (colr) @ (179, 1339)",
" Method: any ICC profile",
@ -902,10 +906,13 @@ class TestPrinting(unittest.TestCase):
# ICC profiles may be used in JP2, but the approximation field should
# be zero unless we have jpx. This file does both.
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:
print(j.box[3].box[1])
print(jp2.box[3].box[1])
actual = fake_out.getvalue().strip()
lines = ["Colour Specification Box (colr) @ (179, 1339)",
" Method: any ICC profile",
@ -942,10 +949,13 @@ class TestPrinting(unittest.TestCase):
def test_uuid(self):
# UUID box
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:
print(j.box[4])
print(jp2.box[4])
actual = fake_out.getvalue().strip()
lines = ['UUID Box (uuid) @ (1544, 25)',
' UUID: 3a0d0218-0ae9-4115-b376-4bca41ce0e71',

View file

@ -6,30 +6,30 @@
| | | | | pass. |
+-----------+--------+--------+--------+--------------------------------------+
| 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. |
+-----------+--------+--------+--------+--------------------------------------+
| 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. |
+-----------+--------+--------+--------+--------------------------------------+
| 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. |
+-----------+--------+--------+-----------------------------------------------+
| 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 |
| | | | | 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. |
+-----------+--------+--------+--------+--------------------------------------+
| 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. |
+-----------+--------+--------+--------+--------------------------------------+
| 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. |
| Debian 7 | | | | |
+-----------+--------+--------+--------+--------------------------------------+

View file

@ -2,7 +2,7 @@ from setuptools import setup, find_packages
import sys
kwargs = {'name': 'Glymur',
'version': '0.2.3',
'version': '0.2.5',
'description': 'Tools for accessing JPEG2000 files',
'long_description': open('README.md').read(),
'author': 'John Evans',