Merge branch 'issue48' into devel

This commit is contained in:
jevans 2013-06-16 20:04:38 -04:00
commit d9c4462c83
13 changed files with 1075 additions and 983 deletions

View file

@ -1,4 +1,7 @@
Jun 11, 2013 - v0.1.8 Added reduce=-1 option to get lowest resolution Jun 15, 2013 - v0.1.9 Reading ICC profile headers as ordered dicts. Exif
dictionaries changed to ordered dicts.
Jun 14, 2013 - v0.1.8 Added reduce=-1 option to get lowest resolution
thumbnail. thumbnail.
Jun 07, 2013 - v0.1.7 Changed Exif dictionary names from ['Exif', 'Photo', Jun 07, 2013 - v0.1.7 Changed Exif dictionary names from ['Exif', 'Photo',

View file

@ -172,8 +172,8 @@ because ctypes access libraries in a platform-dependent manner, it is
recommended that you create a configuration file to help glymur properly find recommended that you create a configuration file to help glymur properly find
the openjp2 library. You may create the configuration file as follows:: the openjp2 library. You may create the configuration file as follows::
$ mkdir ~/.glymur $ mkdir -p ~/.config/glymur
$ cd ~/.glymur $ cd ~/.config/glymur
$ cat > glymurrc << EOF $ cat > glymurrc << EOF
> [library] > [library]
> openjp2: /opt/openjp2-svn/lib/libopenjp2.so > openjp2: /opt/openjp2-svn/lib/libopenjp2.so

View file

@ -7,8 +7,8 @@ def _glymurrc_fname():
Search order: Search order:
1) current working directory 1) current working directory
2) environ var GLYMURCONFIGDIR 2) environ var XDG_CONFIG_HOME
3) HOME/.glymur/glymurrc 3) $HOME/.config/glymur/glymurrc
""" """
# Current directory. # Current directory.
@ -62,15 +62,15 @@ def _config():
def _get_configdir(): def _get_configdir():
"""Return string representing the configuration directory. """Return string representing the configuration directory.
Default is HOME/.glymur. You can override this with the GLYMURCONFIGDIR Default is $HOME/.config/glymur. You can override this with the
environment variable. XDG_CONFIG_HOME environment variable.
""" """
if 'GLYMURCONFIGDIR' in os.environ: if 'XDG_CONFIG_HOME' in os.environ:
return os.environ['GLYMURCONFIGDIR'] return os.path.join(os.environ['XDG_CONFIG_HOME'], 'glymur')
if 'HOME' in os.environ: if 'HOME' in os.environ:
return os.path.join(os.environ['HOME'], '.glymur') return os.path.join(os.environ['HOME'], '.config', 'glymur')
import warnings import warnings
import sys import sys

View file

@ -11,6 +11,7 @@ References
Extensions Extensions
""" """
import collections
import copy import copy
import datetime import datetime
import math import math
@ -154,9 +155,9 @@ class ColourSpecificationBox(Jp2kBox):
colorspace : int or None colorspace : int or None
Enumerated colorspace, corresponds to one of 'sRGB', 'greyscale', or Enumerated colorspace, corresponds to one of 'sRGB', 'greyscale', or
'YCC'. If not None, then icc_profile must be None. 'YCC'. If not None, then icc_profile must be None.
icc_profile : _ICCProfile or None icc_profile : dict
ICC profile header according to ICC profile specification. If not ICC profile header according to ICC profile specification. If
None, then color_space must be None. colorspace is not None, then icc_profile must be empty.
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
Jp2kBox.__init__(self, id='', longname='Colour Specification') Jp2kBox.__init__(self, id='', longname='Colour Specification')
@ -175,7 +176,15 @@ class ColourSpecificationBox(Jp2kBox):
x = _colorspace_map_display[self.colorspace] x = _colorspace_map_display[self.colorspace]
msg += '\n Colorspace: {0}'.format(x) msg += '\n Colorspace: {0}'.format(x)
else: else:
msg += '\n ICC Profile: {0}'.format(self.icc_profile.__str__()) # 2.7 has trouble pretty-printing ordered dicts so we just have
# to print as a regular dict in this case.
if sys.hexversion < 0x03000000:
icc_profile = dict(self.icc_profile)
else:
icc_profile = self.icc_profile
x = pprint.pformat(icc_profile)
lines = [' ' * 8 + y for y in x.split('\n')]
msg += '\n ICC Profile:\n{0}'.format('\n'.join(lines))
return msg return msg
@ -228,7 +237,7 @@ class ColourSpecificationBox(Jp2kBox):
kwargs['icc_profile'] = None kwargs['icc_profile'] = None
else: else:
icc_profile = _ICCProfile(f.read(n)) icc_profile = _ICCProfile(f.read(n))
kwargs['icc_profile'] = icc_profile kwargs['icc_profile'] = icc_profile.header
box = ColourSpecificationBox(**kwargs) box = ColourSpecificationBox(**kwargs)
return box return box
@ -278,110 +287,71 @@ class _ICCProfile:
def __init__(self, buffer): def __init__(self, buffer):
self._raw_buffer = buffer self._raw_buffer = buffer
header = collections.OrderedDict()
self.size, = struct.unpack('>I', self._raw_buffer[0:4]) data = struct.unpack('>IIBB', self._raw_buffer[0:10])
self.preferred_cmm_type, = struct.unpack('>I', self._raw_buffer[4:8]) header['Size'] = data[0]
header['Preferred CMM Type'] = data[1]
major = data[2]
minor = (data[3] & 0xf0) >> 4
bugfix = (data[3] & 0x0f)
header['Version'] = '{0}.{1}.{2}'.format(major, minor, bugfix)
data = struct.unpack('>BB', self._raw_buffer[8:10]) header['Device Class'] = self.profile_class[self._raw_buffer[12:16]]
major = data[0] header['Color Space'] = self.colour_space_dict[self._raw_buffer[16:20]]
minor = (data[1] & 0xf0) >> 4 data = self.colour_space_dict[self._raw_buffer[20:24]]
bugfix = (data[1] & 0x0f) header['Connection Space'] = data
self.version = '{0}.{1}.{2}'.format(major, minor, bugfix)
self.device_class = self.profile_class[self._raw_buffer[12:16]]
self.colour_space = self.colour_space_dict[self._raw_buffer[16:20]]
self.connection_space = self.colour_space_dict[self._raw_buffer[20:24]]
data = struct.unpack('>HHHHHH', self._raw_buffer[24:36]) data = struct.unpack('>HHHHHH', self._raw_buffer[24:36])
self.datetime = datetime.datetime(*data) header['Datetime'] = datetime.datetime(*data)
self.file_signature = buffer[36:40].decode('utf-8') header['File Signature'] = buffer[36:40].decode('utf-8')
if buffer[40:44] == b'\x00\x00\x00\x00': if buffer[40:44] == b'\x00\x00\x00\x00':
self.platform = 'unrecognized' header['Platform'] = 'unrecognized'
else: else:
self.platform = buffer[40:44].decode('utf-8') header['Platform'] = buffer[40:44].decode('utf-8')
self.flags, = struct.unpack('>I', buffer[44:48]) x, = struct.unpack('>I', buffer[44:48])
y = 'embedded, ' if x & 0x01 else 'not embedded, '
y += 'cannot ' if x & 0x02 else 'can '
y += 'be used independently'
header['Flags'] = y
self.device_manufacturer = buffer[48:52].decode('utf-8') header['Device Manufacturer'] = buffer[48:52].decode('utf-8')
if buffer[52:56] == b'\x00\x00\x00\x00': if buffer[52:56] == b'\x00\x00\x00\x00':
self.device_model = '' device_model = ''
else: else:
self.device_model = buffer[52:56].decode('utf-8') device_model = buffer[52:56].decode('utf-8')
self.device_attributes, = struct.unpack('>Q', buffer[56:64]) header['Device Model'] = device_model
self.rendering_intent, = struct.unpack('>I', buffer[64:68])
x, = struct.unpack('>Q', buffer[56:64])
y = 'transparency, ' if x & 0x01 else 'reflective, '
y += 'matte, ' if x & 0x02 else 'glossy, '
y += 'negative ' if x & 0x04 else 'positive '
y += 'media polarity, '
y += 'black and white media' if x & 0x08 else 'color media'
header['Device Attributes'] = y
x, = struct.unpack('>I', buffer[64:68])
try:
header['Rendering Intent'] = self.rendering_intent_dict[x]
except KeyError:
header['Rendering Intent'] = 'unknown'
data = struct.unpack('>iii', buffer[68:80]) data = struct.unpack('>iii', buffer[68:80])
self.illuminant = np.array(data, dtype=np.float64) / 65536 header['Illuminant'] = np.array(data, dtype=np.float64) / 65536
if buffer[80:84] == b'\x00\x00\x00\x00': if buffer[80:84] == b'\x00\x00\x00\x00':
self.creator = 'unrecognized' creator = 'unrecognized'
else: else:
self.creator = buffer[80:84].decode('utf-8') creator = buffer[80:84].decode('utf-8')
header['Creator'] = creator
self.profile_id = buffer[84:100] if header['Version'][0] == '4':
self.reserved = buffer[100:127] header['Profile Id'] = buffer[84:100]
def __str__(self): # Final 27 bytes are reserved.
msg = "\n Size: {0}"
msg += "\n Preferred CMM type: {1:x}"
msg += "\n Version: {2}"
msg += "\n Device class signature: {3}"
msg += "\n Color space: {4}"
msg += "\n Connection space: {5}"
msg += "\n Creation time: {6}"
msg += "\n File signature: {7}"
msg += "\n Platform: {8}"
msg += "\n Flags: {9}"
msg += "\n Device manufacturer: {10}"
msg += "\n Device model: {11}"
msg += "\n Device attributes: {12}"
msg += "\n Rendering intent: {13}"
msg += "\n Illuminant: {14}"
msg += "\n Creator signature: {15}"
if self.flags & 0x01: self.header = header
flag_string = 'embedded, '
else:
flag_string = 'not embedded, '
if self.flags & 0x02:
flag_string += 'cannot be used independently'
else:
flag_string += 'can be used independently'
if self.device_attributes & 0x01:
attr_string = 'transparency, '
else:
attr_string = 'reflective, '
if self.device_attributes & 0x02:
attr_string += 'matte, '
else:
attr_string += 'glossy, '
if self.device_attributes & 0x04:
attr_string += 'negative media polarity, '
else:
attr_string += 'positive media polarity, '
if self.device_attributes & 0x08:
attr_string += 'black and white media'
else:
attr_string += 'color media'
msg = msg.format(self.size,
self.preferred_cmm_type,
self.version,
self.device_class,
self.colour_space,
self.connection_space,
self.datetime,
self.file_signature,
self.platform,
flag_string,
self.device_manufacturer,
self.device_model,
attr_string,
self.rendering_intent_dict[self.rendering_intent],
self.illuminant,
self.creator)
return(msg)
class ComponentDefinitionBox(Jp2kBox): class ComponentDefinitionBox(Jp2kBox):
@ -1713,8 +1683,8 @@ class UUIDBox(Jp2kBox):
more verbose description of the box. more verbose description of the box.
uuid : uuid.UUID uuid : uuid.UUID
16-byte UUID 16-byte UUID
data : bytes or dictionary or ElementTree.Element data : bytes or dict or ElementTree.Element
Vendor-specific UUID data. Exif UUIDs are interpreted as dictionaries. Vendor-specific data. Exif UUIDs are interpreted as dictionaries.
XMP UUIDs are interpreted as standard XML. XMP UUIDs are interpreted as standard XML.
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -1731,7 +1701,10 @@ class UUIDBox(Jp2kBox):
uuid_data = _pretty_print_xml(self.data) uuid_data = _pretty_print_xml(self.data)
elif self.uuid.bytes == b'JpgTiffExif->JP2': elif self.uuid.bytes == b'JpgTiffExif->JP2':
uuid_type = ' (Exif)' uuid_type = ' (Exif)'
uuid_data = '\n' + pprint.pformat(self.data) # 2.7 has trouble pretty-printing ordered dicts, so print them
# as regular dicts. Not ideal, but at least it's good on 3.3+.
x = self.data if sys.hexversion >= 0x03000000 else dict(self.data)
uuid_data = '\n' + pprint.pformat(x)
else: else:
uuid_type = '' uuid_type = ''
uuid_data = '{0} bytes'.format(len(self.data)) uuid_data = '{0} bytes'.format(len(self.data))
@ -1782,7 +1755,7 @@ class UUIDBox(Jp2kBox):
kwargs['data'] = ET.fromstring(text) kwargs['data'] = ET.fromstring(text)
elif kwargs['uuid'].bytes == b'JpgTiffExif->JP2': elif kwargs['uuid'].bytes == b'JpgTiffExif->JP2':
e = Exif(buffer) e = Exif(buffer)
d = {} d = collections.OrderedDict()
d['Image'] = e.exif_image d['Image'] = e.exif_image
d['Photo'] = e.exif_photo d['Photo'] = e.exif_photo
d['GPSInfo'] = e.exif_gpsinfo d['GPSInfo'] = e.exif_gpsinfo
@ -1877,7 +1850,7 @@ class _Ifd:
def __init__(self, endian, buffer, offset): def __init__(self, endian, buffer, offset):
self.endian = endian self.endian = endian
self.buffer = buffer self.buffer = buffer
self.processed_ifd = {} self.processed_ifd = collections.OrderedDict()
self.num_tags, = struct.unpack(endian + 'H', self.num_tags, = struct.unpack(endian + 'H',
buffer[offset:offset + 2]) buffer[offset:offset + 2])
@ -1885,7 +1858,7 @@ class _Ifd:
fmt = self.endian + 'HHII' * self.num_tags fmt = self.endian + 'HHII' * self.num_tags
ifd_buffer = buffer[offset + 2:offset + 2 + self.num_tags * 12] ifd_buffer = buffer[offset + 2:offset + 2 + self.num_tags * 12]
data = struct.unpack(fmt, ifd_buffer) data = struct.unpack(fmt, ifd_buffer)
self.raw_ifd = {} self.raw_ifd = collections.OrderedDict()
for j, tag in enumerate(data[0::4]): for j, tag in enumerate(data[0::4]):
# The offset to the tag offset/payload is the offset to the IFD # The offset to the tag offset/payload is the offset to the IFD
# plus 2 bytes for the number of tags plus 12 bytes for each # plus 2 bytes for the number of tags plus 12 bytes for each

View file

@ -1 +1 @@
from .test_openjp2 import TestOpenJP2 as openjp2 #from .test_openjp2 import TestOpenJP2 as openjp2

View file

@ -12,12 +12,14 @@ import numpy as np
import glymur import glymur
# Doc tests should be run as well.
def load_tests(loader, tests, ignore): def load_tests(loader, tests, ignore):
tests.addTests(doctest.DocTestSuite('glymur.lib.openjp2')) if glymur.lib.openjp2._OPENJP2 is not None:
tests.addTests(doctest.DocTestSuite('glymur.lib.openjp2'))
return tests return tests
@unittest.skipIf(glymur.lib.openjp2._OPENJP2 is None,
"Missing openjp2 library.")
class TestOpenJP2(unittest.TestCase): class TestOpenJP2(unittest.TestCase):
def setUp(self): def setUp(self):

View file

@ -12,14 +12,9 @@ else:
import glymur import glymur
try:
data_root = os.environ['OPJ_DATA_ROOT']
except KeyError:
data_root = None
except:
raise
@unittest.skipIf(glymur.lib.openjp2._OPENJP2 is None,
"Missing openjp2 library.")
class TestCallbacks(unittest.TestCase): class TestCallbacks(unittest.TestCase):
def setUp(self): def setUp(self):

View file

@ -35,32 +35,31 @@ class TestICC(unittest.TestCase):
filename = os.path.join(data_root, 'input/conformance/file5.jp2') filename = os.path.join(data_root, 'input/conformance/file5.jp2')
j = Jp2k(filename) j = Jp2k(filename)
profile = j.box[3].box[1].icc_profile profile = j.box[3].box[1].icc_profile
self.assertEqual(profile.size, 546) self.assertEqual(profile['Size'], 546)
self.assertEqual(profile.preferred_cmm_type, 0) self.assertEqual(profile['Preferred CMM Type'], 0)
self.assertEqual(profile.version, '2.2.0') self.assertEqual(profile['Version'], '2.2.0')
self.assertEqual(profile.device_class, 'input device profile') self.assertEqual(profile['Device Class'], 'input device profile')
self.assertEqual(profile.colour_space, 'RGB') self.assertEqual(profile['Color Space'], 'RGB')
self.assertEqual(profile.datetime, self.assertEqual(profile['Datetime'],
datetime.datetime(2001, 8, 30, 13, 32, 37)) datetime.datetime(2001, 8, 30, 13, 32, 37))
self.assertEqual(profile.file_signature, 'acsp') self.assertEqual(profile['File Signature'], 'acsp')
self.assertEqual(profile.platform, 'unrecognized') self.assertEqual(profile['Platform'], 'unrecognized')
self.assertTrue(profile.flags & 0x01) # embedded self.assertEqual(profile['Flags'],
self.assertFalse(profile.flags & 0x02) # use anywhere 'embedded, can be used independently')
self.assertEqual(profile.device_manufacturer, 'KODA') self.assertEqual(profile['Device Manufacturer'], 'KODA')
self.assertEqual(profile.device_model, 'ROMM') self.assertEqual(profile['Device Model'], 'ROMM')
self.assertFalse(profile.device_attributes & 0x01) # reflective self.assertEqual(profile['Device Attributes'],
self.assertFalse(profile.device_attributes & 0x02) # glossy 'reflective, glossy, positive media polarity, '
self.assertFalse(profile.device_attributes & 0x04) # positive + 'color media')
self.assertFalse(profile.device_attributes & 0x08) # colour self.assertEqual(profile['Rendering Intent'], 'perceptual')
self.assertEqual(profile.rendering_intent & 0x00ff, 0) # perceptual
np.testing.assert_almost_equal(profile.illuminant, np.testing.assert_almost_equal(profile['Illuminant'],
(0.964203, 1.000000, 0.824905), (0.964203, 1.000000, 0.824905),
decimal=6) decimal=6)
self.assertEqual(profile.creator, 'JPEG') self.assertEqual(profile['Creator'], 'JPEG')
@unittest.skipIf(sys.hexversion < 0x03020000, @unittest.skipIf(sys.hexversion < 0x03020000,
"Uses features introduced in 3.2.") "Uses features introduced in 3.2.")
@ -68,7 +67,7 @@ class TestICC(unittest.TestCase):
jfile = os.path.join(data_root, jfile = os.path.join(data_root,
'input/nonregression/orb-blue10-lin-jp2.jp2') 'input/nonregression/orb-blue10-lin-jp2.jp2')
with self.assertWarns(UserWarning) as cw: with self.assertWarns(UserWarning) as cw:
data = Jp2k(jfile).read() j = Jp2k(jfile)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -34,21 +34,13 @@ except:
# 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):
tests.addTests(doctest.DocTestSuite('glymur.jp2k')) if glymur.lib.openjp2._OPENJP2 is not None:
tests.addTests(doctest.DocTestSuite('glymur.jp2k'))
return tests return tests
@contextlib.contextmanager @unittest.skipIf(glymur.lib.openjp2._OPENJP2 is None,
def chdir(dirname=None): "Missing openjp2 library.")
curdir = os.getcwd()
try:
if dirname is not None:
os.chdir(dirname)
yield
finally:
os.chdir(curdir)
class TestJp2k(unittest.TestCase): class TestJp2k(unittest.TestCase):
@classmethod @classmethod
@ -607,13 +599,15 @@ class TestJp2k(unittest.TestCase):
def test_config_file_via_environ(self): def test_config_file_via_environ(self):
"""Verify that we can read a configuration file set via environ var.""" """Verify that we can read a configuration file set via environ var."""
with tempfile.TemporaryDirectory() as tdir: with tempfile.TemporaryDirectory() as tdir:
filename = os.path.join(tdir, 'glymurrc') configdir = os.path.join(tdir, 'glymur')
os.mkdir(configdir)
filename = os.path.join(configdir, 'glymurrc')
with open(filename, 'wb') as tfile: with open(filename, 'wb') as tfile:
tfile.write('[library]\n'.encode()) tfile.write('[library]\n'.encode())
line = 'openjp2: {0}\n'.format(glymur._OPENJP2._name) line = 'openjp2: {0}\n'.format(glymur._OPENJP2._name)
tfile.write(line.encode()) tfile.write(line.encode())
tfile.flush() tfile.flush()
with patch.dict('os.environ', {'GLYMURCONFIGDIR': tdir}): with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
imp.reload(glymur) imp.reload(glymur)
j = Jp2k(self.jp2file) j = Jp2k(self.jp2file)
@ -622,13 +616,15 @@ class TestJp2k(unittest.TestCase):
def test_config_file_via_environ_is_wrong(self): def test_config_file_via_environ_is_wrong(self):
# A non-existant library location should be rejected. # A non-existant library location should be rejected.
with tempfile.TemporaryDirectory() as tdir: with tempfile.TemporaryDirectory() as tdir:
fname = os.path.join(tdir, 'glymurrc') configdir = os.path.join(tdir, 'glymur')
os.mkdir(configdir)
fname = os.path.join(configdir, 'glymurrc')
with open(fname, 'w') as fp: with open(fname, 'w') as fp:
with tempfile.NamedTemporaryFile(suffix='.dylib') as tfile: with tempfile.NamedTemporaryFile(suffix='.dylib') as tfile:
fp.write('[library]\n') fp.write('[library]\n')
fp.write('openjp2: {0}.not.there\n'.format(tfile.name)) fp.write('openjp2: {0}.not.there\n'.format(tfile.name))
fp.flush() fp.flush()
with patch.dict('os.environ', {'GLYMURCONFIGDIR': tdir}): with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
# Misconfigured new configuration file should # Misconfigured new configuration file should
# be rejected. # be rejected.
with self.assertWarns(UserWarning) as cw: with self.assertWarns(UserWarning) as cw:
@ -640,7 +636,7 @@ class TestJp2k(unittest.TestCase):
# Verify that we error out properly if the configuration file # Verify that we error out properly if the configuration file
# specified via environment variable is not found. # specified via environment variable is not found.
with tempfile.TemporaryDirectory() as tdir: with tempfile.TemporaryDirectory() as tdir:
with patch.dict('os.environ', {'GLYMURCONFIGDIR': tdir}): with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
# Misconfigured new configuration file should # Misconfigured new configuration file should
# be rejected. # be rejected.
with self.assertWarns(UserWarning) as cw: with self.assertWarns(UserWarning) as cw:
@ -649,7 +645,7 @@ class TestJp2k(unittest.TestCase):
@unittest.skipIf(sys.hexversion < 0x03020000, @unittest.skipIf(sys.hexversion < 0x03020000,
"Uses features introduced in 3.2.") "Uses features introduced in 3.2.")
def test_home_dir_missing_config_dir(self): def test_home_dir_missing_config_dir(self):
# Verify no exception is raised if $HOME is missing .glymur directory. # Verify no exception is raised if $HOME is missing .config directory.
with tempfile.TemporaryDirectory() as tdir: with tempfile.TemporaryDirectory() as tdir:
with patch.dict('os.environ', {'HOME': tdir}): with patch.dict('os.environ', {'HOME': tdir}):
# Misconfigured new configuration file should # Misconfigured new configuration file should
@ -657,6 +653,24 @@ class TestJp2k(unittest.TestCase):
with self.assertWarns(UserWarning) as cw: with self.assertWarns(UserWarning) as cw:
imp.reload(glymur) imp.reload(glymur)
@unittest.skipIf(sys.hexversion < 0x03020000,
"Uses features introduced in 3.2.")
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)
def test_xmp_attribute(self): def test_xmp_attribute(self):
# Verify that we can read the XMP packet in our shipping example file. # Verify that we can read the XMP packet in our shipping example file.
j = Jp2k(self.jp2file) j = Jp2k(self.jp2file)

File diff suppressed because it is too large Load diff

View file

@ -57,6 +57,8 @@ def read_image(infile):
return data return data
@unittest.skipIf(glymur.lib.openjp2._OPENJP2 is None,
"Missing openjp2 library.")
@unittest.skipIf(no_read_backend, no_read_backend_msg) @unittest.skipIf(no_read_backend, no_read_backend_msg)
@unittest.skipIf(data_root is None, @unittest.skipIf(data_root is None,
"OPJ_DATA_ROOT environment variable not set") "OPJ_DATA_ROOT environment variable not set")

View file

@ -57,6 +57,8 @@ def read_image(infile):
return data return data
@unittest.skipIf(glymur.lib.openjp2._OPENJP2 is None,
"Missing openjp2 library.")
@unittest.skipIf(no_read_backend, no_read_backend_msg) @unittest.skipIf(no_read_backend, no_read_backend_msg)
@unittest.skipIf(data_root is None, @unittest.skipIf(data_root is None,
"OPJ_DATA_ROOT environment variable not set") "OPJ_DATA_ROOT environment variable not set")

View file

@ -21,7 +21,11 @@ except:
raise raise
class TestPrinting(unittest.TestCase): @unittest.skipIf(glymur.lib.openjp2._OPENJP2 is None,
"Missing openjp2 library.")
class TestPrintingNeedsLib(unittest.TestCase):
"""These tests require the library, mostly in order to just setup the test.
"""
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
@ -40,11 +44,11 @@ class TestPrinting(unittest.TestCase):
os.unlink(cls._plain_nemo_file) os.unlink(cls._plain_nemo_file)
def setUp(self): def setUp(self):
self.jp2file = pkg_resources.resource_filename(glymur.__name__,
"data/nemo.jp2")
# Save sys.stdout. # Save sys.stdout.
self.stdout = sys.stdout self.stdout = sys.stdout
sys.stdout = StringIO() sys.stdout = StringIO()
self.jp2file = pkg_resources.resource_filename(glymur.__name__,
"data/nemo.jp2")
# Save the output of dumping nemo.jp2 for more than one test. # Save the output of dumping nemo.jp2 for more than one test.
lines = ['JPEG 2000 Signature Box (jP ) @ (0, 12)', lines = ['JPEG 2000 Signature Box (jP ) @ (0, 12)',
@ -115,6 +119,57 @@ class TestPrinting(unittest.TestCase):
# Restore stdout. # Restore stdout.
sys.stdout = self.stdout sys.stdout = self.stdout
def test_asoc_label_box(self):
# Construct a fake file with an asoc and a label box, as
# OpenJPEG doesn't have such a file.
data = glymur.Jp2k(self.jp2file).read(reduce=3)
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = glymur.Jp2k(tfile.name, 'wb')
j.write(data)
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2:
# Offset of the codestream is where we start.
buffer = tfile.read(77)
tfile2.write(buffer)
# read the rest of the file, it's the codestream.
codestream = tfile.read()
# Write the asoc superbox.
# Length = 36, id is 'asoc'.
buffer = struct.pack('>I4s', int(56), b'asoc')
tfile2.write(buffer)
# Write the contained label box
buffer = struct.pack('>I4s', int(13), b'lbl ')
tfile2.write(buffer)
tfile2.write('label'.encode())
# Write the xml box
# Length = 36, id is 'xml '.
buffer = struct.pack('>I4s', int(35), b'xml ')
tfile2.write(buffer)
buffer = '<test>this is a test</test>'
buffer = buffer.encode()
tfile2.write(buffer)
# Now append the codestream.
tfile2.write(codestream)
tfile2.flush()
jasoc = glymur.Jp2k(tfile2.name)
print(jasoc.box[3])
actual = sys.stdout.getvalue().strip()
lines = ['Association Box (asoc) @ (77, 56)',
' Label Box (lbl ) @ (85, 13)',
' Label: label',
' XML Box (xml ) @ (98, 35)',
' <test>this is a test</test>']
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
def test_jp2dump(self): def test_jp2dump(self):
glymur.jp2dump(self._plain_nemo_file) glymur.jp2dump(self._plain_nemo_file)
actual = sys.stdout.getvalue().strip() actual = sys.stdout.getvalue().strip()
@ -126,6 +181,32 @@ class TestPrinting(unittest.TestCase):
self.assertEqual(actual, self.expectedPlain) self.assertEqual(actual, self.expectedPlain)
def test_entire_file(self):
j = glymur.Jp2k(self._plain_nemo_file)
print(j)
actual = sys.stdout.getvalue().strip()
# Get rid of the filename line, as it is not set in stone.
lst = actual.split('\n')
lst = lst[1:]
actual = '\n'.join(lst)
self.assertEqual(actual, self.expectedPlain)
class TestPrinting(unittest.TestCase):
def setUp(self):
# Save sys.stdout.
self.stdout = sys.stdout
sys.stdout = StringIO()
self.jp2file = pkg_resources.resource_filename(glymur.__name__,
"data/nemo.jp2")
def tearDown(self):
# Restore stdout.
sys.stdout = self.stdout
def test_COC_segment(self): def test_COC_segment(self):
j = glymur.Jp2k(self.jp2file) j = glymur.Jp2k(self.jp2file)
codestream = j.get_codestream(header_only=False) codestream = j.get_codestream(header_only=False)
@ -186,6 +267,70 @@ class TestPrinting(unittest.TestCase):
self.expected = expected self.expected = expected
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
@unittest.skipIf(data_root is None,
"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)
print(j.box[3].box[1])
actual = sys.stdout.getvalue().strip()
lin27 = ["Colour Specification Box (colr) @ (179, 1339)",
" Method: any ICC profile",
" Precedence: 2",
" Approximation: accurately represents correct "
+ "colorspace definition",
" ICC Profile:",
" {'Color Space': 'RGB',",
" 'Connection Space': 'XYZ',",
" 'Creator': u'appl',",
" 'Datetime': "
+ "datetime.datetime(2009, 2, 25, 11, 26, 11),",
" 'Device Attributes': 'reflective, glossy, "
+ "positive media polarity, color media',",
" 'Device Class': 'display device profile',",
" 'Device Manufacturer': u'appl',",
" 'Device Model': '',",
" 'File Signature': u'acsp',",
" 'Flags': "
+ "'not embedded, can be used independently',",
" 'Illuminant': "
+ "array([ 0.96420288, 1. , 0.8249054 ]),",
" 'Platform': u'APPL',",
" 'Preferred CMM Type': 1634758764,",
" 'Rendering Intent': 'perceptual',",
" 'Size': 1328,",
" 'Version': '2.2.0'}"]
lin33 = ["Colour Specification Box (colr) @ (179, 1339)",
" Method: any ICC profile",
" Precedence: 2",
" Approximation: accurately represents correct "
+ "colorspace definition",
" ICC Profile:",
" {'Size': 1328,",
" 'Preferred CMM Type': 1634758764,",
" 'Version': '2.2.0',",
" 'Device Class': 'display device profile',",
" 'Color Space': 'RGB',",
" 'Connection Space': 'XYZ',",
" 'Datetime': "
+ "datetime.datetime(2009, 2, 25, 11, 26, 11),",
" 'File Signature': 'acsp',",
" 'Platform': 'APPL',",
" 'Flags': 'not embedded, can be used "
+ "independently',",
" 'Device Manufacturer': 'appl',",
" 'Device Model': '',",
" 'Device Attributes': 'reflective, glossy, "
+ "positive media polarity, color media',",
" 'Rendering Intent': 'perceptual',",
" 'Illuminant': "
+ "array([ 0.96420288, 1. , 0.8249054 ]),",
" 'Creator': 'appl'}"]
lines = lin27 if sys.hexversion < 0x03000000 else lin33
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@unittest.skipIf(data_root is None, @unittest.skipIf(data_root is None,
"OPJ_DATA_ROOT environment variable not set") "OPJ_DATA_ROOT environment variable not set")
def test_CRG(self): def test_CRG(self):
@ -452,18 +597,6 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lst) expected = '\n'.join(lst)
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
def test_entire_file(self):
j = glymur.Jp2k(self._plain_nemo_file)
print(j)
actual = sys.stdout.getvalue().strip()
# Get rid of the filename line, as it is not set in stone.
lst = actual.split('\n')
lst = lst[1:]
actual = '\n'.join(lst)
self.assertEqual(actual, self.expectedPlain)
def test_codestream(self): def test_codestream(self):
j = glymur.Jp2k(self.jp2file) j = glymur.Jp2k(self.jp2file)
print(j.get_codestream()) print(j.get_codestream())
@ -651,57 +784,6 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines) expected = '\n'.join(lines)
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
def test_asoc_label_box(self):
# Construct a fake file with an asoc and a label box, as
# OpenJPEG doesn't have such a file.
data = glymur.Jp2k(self.jp2file).read(reduce=3)
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = glymur.Jp2k(tfile.name, 'wb')
j.write(data)
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2:
# Offset of the codestream is where we start.
buffer = tfile.read(77)
tfile2.write(buffer)
# read the rest of the file, it's the codestream.
codestream = tfile.read()
# Write the asoc superbox.
# Length = 36, id is 'asoc'.
buffer = struct.pack('>I4s', int(56), b'asoc')
tfile2.write(buffer)
# Write the contained label box
buffer = struct.pack('>I4s', int(13), b'lbl ')
tfile2.write(buffer)
tfile2.write('label'.encode())
# Write the xml box
# Length = 36, id is 'xml '.
buffer = struct.pack('>I4s', int(35), b'xml ')
tfile2.write(buffer)
buffer = '<test>this is a test</test>'
buffer = buffer.encode()
tfile2.write(buffer)
# Now append the codestream.
tfile2.write(codestream)
tfile2.flush()
jasoc = glymur.Jp2k(tfile2.name)
print(jasoc.box[3])
actual = sys.stdout.getvalue().strip()
lines = ['Association Box (asoc) @ (77, 56)',
' Label Box (lbl ) @ (85, 13)',
' Label: label',
' XML Box (xml ) @ (98, 35)',
' <test>this is a test</test>']
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
def test_less_common_boxes(self): def test_less_common_boxes(self):
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
with open(self.jp2file, 'rb') as ifile: with open(self.jp2file, 'rb') as ifile:
@ -775,6 +857,8 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines) expected = '\n'.join(lines)
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
@unittest.skipIf(sys.hexversion < 0x03000000,
"Ordered dicts not printing well in 2.7")
@unittest.skipIf(data_root is None, @unittest.skipIf(data_root is None,
"OPJ_DATA_ROOT environment variable not set") "OPJ_DATA_ROOT environment variable not set")
def test_jpx_approximation_with_icc_profile(self): def test_jpx_approximation_with_icc_profile(self):
@ -785,29 +869,32 @@ class TestPrinting(unittest.TestCase):
print(j.box[3].box[1]) print(j.box[3].box[1])
actual = sys.stdout.getvalue().strip() actual = sys.stdout.getvalue().strip()
lines = ['Colour Specification Box (colr) @ (179, 1339)', lines = ["Colour Specification Box (colr) @ (179, 1339)",
' Method: any ICC profile', " Method: any ICC profile",
' Precedence: 2', " Precedence: 2",
' Approximation: accurately represents ' " Approximation: accurately represents "
+ 'correct colorspace definition', + "correct colorspace definition",
' ICC Profile: ', " ICC Profile:",
' Size: 1328', " {'Size': 1328,",
' Preferred CMM type: 6170706c', " 'Preferred CMM Type': 1634758764,",
' Version: 2.2.0', " 'Version': '2.2.0',",
' Device class signature: display device profile', " 'Device Class': 'display device profile',",
' Color space: RGB', " 'Color Space': 'RGB',",
' Connection space: XYZ', " 'Connection Space': 'XYZ',",
' Creation time: 2009-02-25 11:26:11', " 'Datetime': "
' File signature: acsp', + "datetime.datetime(2009, 2, 25, 11, 26, 11),",
' Platform: APPL', " 'File Signature': 'acsp',",
' Flags: not embedded, can be used independently', " 'Platform': 'APPL',",
' Device manufacturer: appl', " 'Flags': 'not embedded, "
' Device model: ', + "can be used independently',",
' Device attributes: ' " 'Device Manufacturer': 'appl',",
+ 'reflective, glossy, positive media polarity, color media', " 'Device Model': '',",
' Rendering intent: perceptual', " 'Device Attributes': 'reflective, glossy, "
' Illuminant: [ 0.96420288 1. 0.8249054 ]', + "positive media polarity, color media',",
' Creator signature: appl'] " 'Rendering Intent': 'perceptual',",
" 'Illuminant': array([ 0.96420288, 1. ,"
+ " 0.8249054 ]),",
" 'Creator': 'appl'}"]
expected = '\n'.join(lines) expected = '\n'.join(lines)
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
@ -828,6 +915,8 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines) expected = '\n'.join(lines)
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
@unittest.skipIf(sys.hexversion < 0x03000000,
"Ordered dicts not printing well in 2.7")
def test_exif_uuid(self): def test_exif_uuid(self):
j = glymur.Jp2k(self.jp2file) j = glymur.Jp2k(self.jp2file)
@ -837,13 +926,33 @@ class TestPrinting(unittest.TestCase):
lines = ["UUID Box (uuid) @ (77, 638)", lines = ["UUID Box (uuid) @ (77, 638)",
" UUID: 4a706754-6966-6645-7869-662d3e4a5032 (Exif)", " UUID: 4a706754-6966-6645-7869-662d3e4a5032 (Exif)",
" UUID Data: ", " UUID Data: ",
"{'GPSInfo': {'GPSAltitude': 0.0,", "{'Image': {'Make': 'HTC',",
" 'GPSAltitudeRef': 0,", " 'Model': 'HTC Glacier',",
" 'GPSDateStamp': '2013:02:09',", " 'XResolution': 72.0,",
" 'GPSLatitude': [42.0, 20.0, 33.61],", " 'YResolution': 72.0,",
" 'ResolutionUnit': 2,",
" 'YCbCrPositioning': 1,",
" 'ExifTag': 138,",
" 'GPSTag': 354},",
" 'Photo': {'ISOSpeedRatings': 76,",
" 'ExifVersion': (48, 50, 50, 48),",
" 'DateTimeOriginal': '2013:02:09 14:47:53',",
" 'DateTimeDigitized': '2013:02:09 14:47:53',",
" 'ComponentsConfiguration': (1, 2, 3, 0),",
" 'FocalLength': 3.53,",
" 'FlashpixVersion': (48, 49, 48, 48),",
" 'ColorSpace': 1,",
" 'PixelXDimension': 2528,",
" 'PixelYDimension': 1424,",
" 'InteroperabilityTag': 324},",
" 'GPSInfo': {'GPSVersionID': (2, 2, 0),",
" 'GPSLatitudeRef': 'N',", " 'GPSLatitudeRef': 'N',",
" 'GPSLongitude': [71.0, 5.0, 17.32],", " 'GPSLatitude': [42.0, 20.0, 33.61],",
" 'GPSLongitudeRef': 'W',", " 'GPSLongitudeRef': 'W',",
" 'GPSLongitude': [71.0, 5.0, 17.32],",
" 'GPSAltitudeRef': 0,",
" 'GPSAltitude': 0.0,",
" 'GPSTimeStamp': [19.0, 47.0, 53.0],",
" 'GPSMapDatum': 'WGS-84',", " 'GPSMapDatum': 'WGS-84',",
" 'GPSProcessingMethod': (65,", " 'GPSProcessingMethod': (65,",
" 83,", " 83,",
@ -860,28 +969,8 @@ class TestPrinting(unittest.TestCase):
" 79,", " 79,",
" 82,", " 82,",
" 75),", " 75),",
" 'GPSTimeStamp': [19.0, 47.0, 53.0],", " 'GPSDateStamp': '2013:02:09'},",
" 'GPSVersionID': (2, 2, 0)},", " 'Iop': None}"]
" 'Image': {'ExifTag': 138,",
" 'GPSTag': 354,",
" 'Make': 'HTC',",
" 'Model': 'HTC Glacier',",
" 'ResolutionUnit': 2,",
" 'XResolution': 72.0,",
" 'YCbCrPositioning': 1,",
" 'YResolution': 72.0},",
" 'Iop': None,",
" 'Photo': {'ColorSpace': 1,",
" 'ComponentsConfiguration': (1, 2, 3, 0),",
" 'DateTimeDigitized': '2013:02:09 14:47:53',",
" 'DateTimeOriginal': '2013:02:09 14:47:53',",
" 'ExifVersion': (48, 50, 50, 48),",
" 'FlashpixVersion': (48, 49, 48, 48),",
" 'FocalLength': 3.53,",
" 'ISOSpeedRatings': 76,",
" 'InteroperabilityTag': 324,",
" 'PixelXDimension': 2528,",
" 'PixelYDimension': 1424}}"]
expected = '\n'.join(lines) expected = '\n'.join(lines)