Merge branch 'devel'
This commit is contained in:
commit
f2e67007a3
13 changed files with 213 additions and 121 deletions
11
CHANGES.txt
11
CHANGES.txt
|
|
@ -1,7 +1,8 @@
|
||||||
May 28, 2013 - Added jp2 boxes to rst docs, XMLBox.indent method made private.
|
May 30, 2013 - v0.1.2. Added XMP UUID read support. Added jp2 boxes to rst
|
||||||
|
docs, XMLBox.indent method made into a private module method. Precinct
|
||||||
|
sizes restricted to be multiples of two.
|
||||||
|
|
||||||
May 27, 2013 - v0.1.1, Changed write example to not rely on matplotlib. Fixed
|
May 27, 2013 - v0.1.1. Changed write example to not rely on matplotlib. Fixed
|
||||||
readthedocs.org setup to build documentation automatically. Can import
|
readthedocs.org setup to build documentation automatically. Can import
|
||||||
glymur without libopenjp2 actually being present.
|
glymur without libopenjp2 actually being present. Changed write example
|
||||||
|
to not rely on matplotlib.
|
||||||
May 27, 2013 - Changed write example to not rely on matplotlib.
|
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,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.1.1'
|
release = '0.1.2'
|
||||||
|
|
||||||
# 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.
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ which allows linux and mac users to read and write JPEG 2000 files. For more
|
||||||
information about OpenJPEG, please consult http://www.openjpeg.org. glymur
|
information about OpenJPEG, please consult http://www.openjpeg.org. glymur
|
||||||
should be considered to be alpha-quality software.
|
should be considered to be alpha-quality software.
|
||||||
|
|
||||||
glymur tries to support reading (including all metadata) and writing of
|
glymur tries to support reading and writing of JP2 and J2C files. Writing
|
||||||
JP2 and J2C files. Writing J2C/JP2 files is currently limited to images that
|
J2C/JP2 files is currently limited to images that can fit in memory, however.
|
||||||
can fit in memory, however.
|
Of particular focus is metadata retrieval, such as XMP packets.
|
||||||
|
|
||||||
There is some very limited support for reading JPX
|
There is some very limited support for reading JPX
|
||||||
metadata. For instance, **asoc** and **labl** boxes are recognized, so GMLJP2
|
metadata. For instance, **asoc** and **labl** boxes are recognized, so GMLJP2
|
||||||
|
|
|
||||||
|
|
@ -16,24 +16,15 @@ def _glymurrc_fname():
|
||||||
if os.path.exists(fname):
|
if os.path.exists(fname):
|
||||||
return fname
|
return fname
|
||||||
|
|
||||||
# environ var GLYMURCONFIGDIR
|
# Either GLYMURCONFIGDIR/glymurrc or $HOME/.glymur/glymurrc
|
||||||
if 'GLYMURCONFIGDIR' in os.environ:
|
|
||||||
path = os.environ['GLYMURCONFIGDIR']
|
|
||||||
if os.path.exists(path):
|
|
||||||
fname = os.path.join(path, 'glymurrc')
|
|
||||||
if os.path.exists(fname):
|
|
||||||
return fname
|
|
||||||
else:
|
|
||||||
msg = "glymurrc file hinted at by GLYMURCONFIGDIR does not "
|
|
||||||
msg += "exist."
|
|
||||||
warnings.warn(msg, UserWarning)
|
|
||||||
|
|
||||||
# HOME/.glymur/glymurrc
|
|
||||||
confdir = _get_configdir()
|
confdir = _get_configdir()
|
||||||
if confdir is not None:
|
if confdir is not None:
|
||||||
fname = os.path.join(_get_configdir(), 'glymurrc')
|
fname = os.path.join(confdir, 'glymurrc')
|
||||||
if os.path.exists(fname):
|
if os.path.exists(fname):
|
||||||
return fname
|
return fname
|
||||||
|
else:
|
||||||
|
msg = "Configuration file '{0}' does not exist.".format(confdir)
|
||||||
|
warnings.warn(msg, UserWarning)
|
||||||
|
|
||||||
# didn't find a configuration file.
|
# didn't find a configuration file.
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -472,8 +472,6 @@ class Codestream:
|
||||||
SPcod = f.read(n)
|
SPcod = f.read(n)
|
||||||
kwargs['SPcod'] = np.frombuffer(SPcod, dtype=np.uint8)
|
kwargs['SPcod'] = np.frombuffer(SPcod, dtype=np.uint8)
|
||||||
|
|
||||||
# 0:
|
|
||||||
# 1: layers
|
|
||||||
params = struct.unpack('>BHBBBBBB', SPcod[0:9])
|
params = struct.unpack('>BHBBBBBB', SPcod[0:9])
|
||||||
kwargs['_layers'] = params[1]
|
kwargs['_layers'] = params[1]
|
||||||
kwargs['_numresolutions'] = params[3]
|
kwargs['_numresolutions'] = params[3]
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1288,37 +1288,11 @@ class XMLBox(Jp2kBox):
|
||||||
Jp2kBox.__init__(self, id='', longname='XML')
|
Jp2kBox.__init__(self, id='', longname='XML')
|
||||||
self.__dict__.update(**kwargs)
|
self.__dict__.update(**kwargs)
|
||||||
|
|
||||||
def _indent(self, elem, level=0):
|
|
||||||
"""recipe for pretty printing XML. Please see
|
|
||||||
|
|
||||||
http://effbot.org/zone/element-lib.htm#prettyprint
|
|
||||||
"""
|
|
||||||
i = "\n" + level * " "
|
|
||||||
if len(elem):
|
|
||||||
if not elem.text or not elem.text.strip():
|
|
||||||
elem.text = i + " "
|
|
||||||
if not elem.tail or not elem.tail.strip():
|
|
||||||
elem.tail = i
|
|
||||||
for elem in elem:
|
|
||||||
self._indent(elem, level + 1)
|
|
||||||
if not elem.tail or not elem.tail.strip():
|
|
||||||
elem.tail = i
|
|
||||||
else:
|
|
||||||
if level and (not elem.tail or not elem.tail.strip()):
|
|
||||||
elem.tail = i
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = Jp2kBox.__str__(self)
|
msg = Jp2kBox.__str__(self)
|
||||||
xml = self.xml
|
xml = self.xml
|
||||||
if self.xml is not None:
|
if self.xml is not None:
|
||||||
xml = copy.deepcopy(self.xml)
|
msg += _pretty_print_xml(self.xml)
|
||||||
self._indent(xml)
|
|
||||||
xmltext = ET.tostring(xml).decode('utf-8')
|
|
||||||
|
|
||||||
# Indent it a bit.
|
|
||||||
lst = [(' ' + x) for x in xmltext.split('\n')]
|
|
||||||
xml = '\n'.join(lst)
|
|
||||||
msg += '\n{0}'.format(xml)
|
|
||||||
else:
|
else:
|
||||||
msg += '\n {0}'.format(xml)
|
msg += '\n {0}'.format(xml)
|
||||||
return msg
|
return msg
|
||||||
|
|
@ -1581,17 +1555,30 @@ 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
|
data : bytes or ElementTree.Element
|
||||||
Vendor-specific UUID data.
|
Vendor-specific UUID data. XMP UUIDs are interpreted as standard XML.
|
||||||
"""
|
"""
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
Jp2kBox.__init__(self, id='', longname='UUID')
|
Jp2kBox.__init__(self, id='', longname='UUID')
|
||||||
self.__dict__.update(**kwargs)
|
self.__dict__.update(**kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = Jp2kBox.__str__(self)
|
msg = '{0}\n'
|
||||||
msg += '\n UUID: {0}'.format(self.uuid)
|
msg += ' UUID: {1}{2}\n'
|
||||||
msg += '\n UUID Data: {0} bytes'.format(len(self.data))
|
msg += ' UUID Data: {3}'
|
||||||
|
|
||||||
|
if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'):
|
||||||
|
uuid_type = ' (XMP)'
|
||||||
|
uuid_data = _pretty_print_xml(self.data)
|
||||||
|
else:
|
||||||
|
uuid_type = ''
|
||||||
|
uuid_data = '{0} bytes'.format(len(self.data))
|
||||||
|
|
||||||
|
msg = msg.format(Jp2kBox.__str__(self),
|
||||||
|
self.uuid,
|
||||||
|
uuid_type,
|
||||||
|
uuid_data)
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -1622,7 +1609,17 @@ class UUIDBox(Jp2kBox):
|
||||||
|
|
||||||
n = offset + length - f.tell()
|
n = offset + length - f.tell()
|
||||||
buffer = f.read(n)
|
buffer = f.read(n)
|
||||||
kwargs['data'] = buffer
|
if kwargs['uuid'] == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'):
|
||||||
|
# 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')
|
||||||
|
kwargs['data'] = ET.fromstringlist(buffer, parser=parser)
|
||||||
|
else:
|
||||||
|
text = buffer.decode('utf-8')
|
||||||
|
kwargs['data'] = ET.fromstring(text)
|
||||||
|
else:
|
||||||
|
kwargs['data'] = buffer
|
||||||
box = UUIDBox(**kwargs)
|
box = UUIDBox(**kwargs)
|
||||||
return box
|
return box
|
||||||
|
|
||||||
|
|
@ -1649,3 +1646,34 @@ _box_with_id = {
|
||||||
'url ': DataEntryURLBox,
|
'url ': DataEntryURLBox,
|
||||||
'uuid': UUIDBox,
|
'uuid': UUIDBox,
|
||||||
'xml ': XMLBox}
|
'xml ': XMLBox}
|
||||||
|
|
||||||
|
def _indent(elem, level=0):
|
||||||
|
"""Recipe for pretty printing XML. Please see
|
||||||
|
|
||||||
|
http://effbot.org/zone/element-lib.htm#prettyprint
|
||||||
|
"""
|
||||||
|
i = "\n" + level * " "
|
||||||
|
if len(elem):
|
||||||
|
if not elem.text or not elem.text.strip():
|
||||||
|
elem.text = i + " "
|
||||||
|
if not elem.tail or not elem.tail.strip():
|
||||||
|
elem.tail = i
|
||||||
|
for elem in elem:
|
||||||
|
_indent(elem, level + 1)
|
||||||
|
if not elem.tail or not elem.tail.strip():
|
||||||
|
elem.tail = i
|
||||||
|
else:
|
||||||
|
if level and (not elem.tail or not elem.tail.strip()):
|
||||||
|
elem.tail = i
|
||||||
|
|
||||||
|
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 it a bit.
|
||||||
|
lst = [(' ' + x) for x in xmltext.split('\n')]
|
||||||
|
xml = '\n'.join(lst)
|
||||||
|
return '\n{0}'.format(xml)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ if sys.hexversion >= 0x03030000:
|
||||||
else:
|
else:
|
||||||
from contextlib2 import ExitStack
|
from contextlib2 import ExitStack
|
||||||
import ctypes
|
import ctypes
|
||||||
|
import math
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
import warnings
|
import warnings
|
||||||
|
|
@ -152,7 +153,7 @@ class Jp2k(Jp2kBox):
|
||||||
data : array
|
data : array
|
||||||
Image data to be written to file.
|
Image data to be written to file.
|
||||||
callbacks : bool, optional
|
callbacks : bool, optional
|
||||||
If true, enable default info handler such that INFO messages
|
If true, enable default info handler such that INFO messages
|
||||||
produced by the OpenJPEG library are output to the console. By
|
produced by the OpenJPEG library are output to the console. By
|
||||||
default, OpenJPEG warning and error messages are captured by
|
default, OpenJPEG warning and error messages are captured by
|
||||||
Python's own warning and error mechanisms.
|
Python's own warning and error mechanisms.
|
||||||
|
|
@ -181,7 +182,8 @@ class Jp2k(Jp2kBox):
|
||||||
psnr : list, optional
|
psnr : list, optional
|
||||||
Different PSNR for successive layers.
|
Different PSNR for successive layers.
|
||||||
psizes : list, optional
|
psizes : list, optional
|
||||||
List of precinct sizes.
|
List of precinct sizes. Each precinct size tuple is defined in
|
||||||
|
(height x width).
|
||||||
sop : bool, optional
|
sop : bool, optional
|
||||||
If true, write SOP marker before each packet.
|
If true, write SOP marker before each packet.
|
||||||
subsam : tuple, optional
|
subsam : tuple, optional
|
||||||
|
|
@ -266,9 +268,15 @@ class Jp2k(Jp2kBox):
|
||||||
cparams.cp_fixed_quality = 1
|
cparams.cp_fixed_quality = 1
|
||||||
|
|
||||||
if psizes is not None:
|
if psizes is not None:
|
||||||
for j, precinct in enumerate(psizes):
|
for j, (prch, prcw) in enumerate(psizes):
|
||||||
cparams.prcw_init[j] = precinct[0]
|
if ((math.log(prch, 2) != math.floor(math.log(prch, 2)) or
|
||||||
cparams.prch_init[j] = precinct[1]
|
math.log(prcw, 2) != math.floor(math.log(prcw, 2)))):
|
||||||
|
msg = "Bad precinct size ({0}, {1}), "
|
||||||
|
msg += "must be multiple of 2."
|
||||||
|
raise IOError(msg.format(prch, prcw))
|
||||||
|
|
||||||
|
cparams.prcw_init[j] = prcw
|
||||||
|
cparams.prch_init[j] = prch
|
||||||
cparams.csty |= 0x01
|
cparams.csty |= 0x01
|
||||||
cparams.res_spec = len(psizes)
|
cparams.res_spec = len(psizes)
|
||||||
|
|
||||||
|
|
@ -634,7 +642,7 @@ class Jp2k(Jp2kBox):
|
||||||
>>> jp = glymur.Jp2k(jfile)
|
>>> jp = glymur.Jp2k(jfile)
|
||||||
>>> codestream = jp.get_codestream()
|
>>> codestream = jp.get_codestream()
|
||||||
>>> print(codestream.segment[1])
|
>>> print(codestream.segment[1])
|
||||||
SIZ marker segment @ (87, 47)
|
SIZ marker segment @ (3137, 47)
|
||||||
Profile: 2
|
Profile: 2
|
||||||
Reference Grid Height, Width: (1456 x 2592)
|
Reference Grid Height, Width: (1456 x 2592)
|
||||||
Vertical, Horizontal Reference Grid Offset: (0 x 0)
|
Vertical, Horizontal Reference Grid Offset: (0 x 0)
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ class TestCallbacks(unittest.TestCase):
|
||||||
d = j.read(reduce=3, verbose=True, area=(0, 0, 512, 1024))
|
d = j.read(reduce=3, verbose=True, area=(0, 0, 512, 1024))
|
||||||
actual = sys.stdout.getvalue().strip()
|
actual = sys.stdout.getvalue().strip()
|
||||||
|
|
||||||
lines = ['[INFO] Start to read j2k main header (85).',
|
lines = ['[INFO] Start to read j2k main header (3135).',
|
||||||
'[INFO] Main header has been correctly decoded.',
|
'[INFO] Main header has been correctly decoded.',
|
||||||
'[INFO] Setting decoding area to 0,0,1024,512',
|
'[INFO] Setting decoding area to 0,0,1024,512',
|
||||||
'[INFO] Header of tile 0 / 17 has been read.',
|
'[INFO] Header of tile 0 / 17 has been read.',
|
||||||
|
|
|
||||||
|
|
@ -50,19 +50,13 @@ def chdir(dirname=None):
|
||||||
|
|
||||||
class TestJp2k(unittest.TestCase):
|
class TestJp2k(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
self.jp2file = pkg_resources.resource_filename(glymur.__name__,
|
def setUpClass(cls):
|
||||||
"data/nemo.jp2")
|
jp2file = pkg_resources.resource_filename(glymur.__name__,
|
||||||
|
"data/nemo.jp2")
|
||||||
def tearDown(self):
|
with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile:
|
||||||
pass
|
cls._bad_xml_file = tfile.name
|
||||||
|
with open(jp2file, 'rb') as ifile:
|
||||||
@unittest.skipIf(sys.hexversion < 0x03020000,
|
|
||||||
"Uses features introduced in 3.2.")
|
|
||||||
def test_invalid_xml_box(self):
|
|
||||||
# Should be able to recover from xml box with bad xml.
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
with open(self.jp2file, 'rb') as ifile:
|
|
||||||
# Everything up until the jp2c box.
|
# Everything up until the jp2c box.
|
||||||
buffer = ifile.read(77)
|
buffer = ifile.read(77)
|
||||||
tfile.write(buffer)
|
tfile.write(buffer)
|
||||||
|
|
@ -81,13 +75,35 @@ class TestJp2k(unittest.TestCase):
|
||||||
tfile.write(buffer)
|
tfile.write(buffer)
|
||||||
tfile.flush()
|
tfile.flush()
|
||||||
|
|
||||||
with self.assertWarns(UserWarning) as cw:
|
@classmethod
|
||||||
jp2k = Jp2k(tfile.name)
|
def tearDownClass(cls):
|
||||||
|
os.unlink(cls._bad_xml_file)
|
||||||
|
|
||||||
self.assertEqual(jp2k.box[3].id, 'xml ')
|
def setUp(self):
|
||||||
self.assertEqual(jp2k.box[3].offset, 77)
|
self.jp2file = pkg_resources.resource_filename(glymur.__name__,
|
||||||
self.assertEqual(jp2k.box[3].length, 28)
|
"data/nemo.jp2")
|
||||||
self.assertIsNone(jp2k.box[3].xml)
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.hexversion < 0x03020000,
|
||||||
|
"Uses features introduced in 3.2.")
|
||||||
|
def test_invalid_xml_box_warning(self):
|
||||||
|
# Should be able to recover from xml box with bad xml.
|
||||||
|
# Just verify that a warning is issued on 3.2+
|
||||||
|
with self.assertWarns(UserWarning) as cw:
|
||||||
|
jp2k = Jp2k(self._bad_xml_file)
|
||||||
|
|
||||||
|
def test_invalid_xml_box(self):
|
||||||
|
# Should be able to recover from xml box with bad xml.
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
jp2k = Jp2k(self._bad_xml_file)
|
||||||
|
|
||||||
|
self.assertEqual(jp2k.box[3].id, 'xml ')
|
||||||
|
self.assertEqual(jp2k.box[3].offset, 77)
|
||||||
|
self.assertEqual(jp2k.box[3].length, 28)
|
||||||
|
self.assertIsNone(jp2k.box[3].xml)
|
||||||
|
|
||||||
def test_bad_area_parameter(self):
|
def test_bad_area_parameter(self):
|
||||||
# Verify that we error out appropriately if given a bad area parameter.
|
# Verify that we error out appropriately if given a bad area parameter.
|
||||||
|
|
@ -139,7 +155,7 @@ class TestJp2k(unittest.TestCase):
|
||||||
jp2k = Jp2k(self.jp2file)
|
jp2k = Jp2k(self.jp2file)
|
||||||
|
|
||||||
# top-level boxes
|
# top-level boxes
|
||||||
self.assertEqual(len(jp2k.box), 4)
|
self.assertEqual(len(jp2k.box), 6)
|
||||||
|
|
||||||
self.assertEqual(jp2k.box[0].id, 'jP ')
|
self.assertEqual(jp2k.box[0].id, 'jP ')
|
||||||
self.assertEqual(jp2k.box[0].offset, 0)
|
self.assertEqual(jp2k.box[0].offset, 0)
|
||||||
|
|
@ -156,9 +172,17 @@ class TestJp2k(unittest.TestCase):
|
||||||
self.assertEqual(jp2k.box[2].length, 45)
|
self.assertEqual(jp2k.box[2].length, 45)
|
||||||
self.assertEqual(jp2k.box[2].longname, 'JP2 Header')
|
self.assertEqual(jp2k.box[2].longname, 'JP2 Header')
|
||||||
|
|
||||||
self.assertEqual(jp2k.box[3].id, 'jp2c')
|
self.assertEqual(jp2k.box[3].id, 'uuid')
|
||||||
self.assertEqual(jp2k.box[3].offset, 77)
|
self.assertEqual(jp2k.box[3].offset, 77)
|
||||||
self.assertEqual(jp2k.box[3].length, 1133427)
|
self.assertEqual(jp2k.box[3].length, 638)
|
||||||
|
|
||||||
|
self.assertEqual(jp2k.box[4].id, 'uuid')
|
||||||
|
self.assertEqual(jp2k.box[4].offset, 715)
|
||||||
|
self.assertEqual(jp2k.box[4].length, 2412)
|
||||||
|
|
||||||
|
self.assertEqual(jp2k.box[5].id, 'jp2c')
|
||||||
|
self.assertEqual(jp2k.box[5].offset, 3127)
|
||||||
|
self.assertEqual(jp2k.box[5].length, 1133427)
|
||||||
|
|
||||||
# jp2h super box
|
# jp2h super box
|
||||||
self.assertEqual(len(jp2k.box[2].box), 2)
|
self.assertEqual(len(jp2k.box[2].box), 2)
|
||||||
|
|
@ -200,7 +224,7 @@ class TestJp2k(unittest.TestCase):
|
||||||
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:
|
||||||
# Everything up until the jp2c box.
|
# Everything up until the jp2c box.
|
||||||
buffer = ifile.read(77)
|
buffer = ifile.read(3127)
|
||||||
tfile.write(buffer)
|
tfile.write(buffer)
|
||||||
|
|
||||||
# The L field must be 1 in order to signal the presence of the
|
# The L field must be 1 in order to signal the presence of the
|
||||||
|
|
@ -221,9 +245,9 @@ class TestJp2k(unittest.TestCase):
|
||||||
|
|
||||||
jp2k = Jp2k(tfile.name)
|
jp2k = Jp2k(tfile.name)
|
||||||
|
|
||||||
self.assertEqual(jp2k.box[3].id, 'jp2c')
|
self.assertEqual(jp2k.box[5].id, 'jp2c')
|
||||||
self.assertEqual(jp2k.box[3].offset, 77)
|
self.assertEqual(jp2k.box[5].offset, 3127)
|
||||||
self.assertEqual(jp2k.box[3].length, 1133427 + 8)
|
self.assertEqual(jp2k.box[5].length, 1133427 + 8)
|
||||||
|
|
||||||
def test_L_is_zero(self):
|
def test_L_is_zero(self):
|
||||||
# Verify that boxes with the L field as zero are correctly read.
|
# Verify that boxes with the L field as zero are correctly read.
|
||||||
|
|
@ -513,10 +537,9 @@ class TestJp2k(unittest.TestCase):
|
||||||
|
|
||||||
# Now append the codestream.
|
# Now append the codestream.
|
||||||
tfile2.write(codestream)
|
tfile2.write(codestream)
|
||||||
|
tfile2.flush()
|
||||||
|
|
||||||
with warnings.catch_warnings():
|
jasoc = Jp2k(tfile2.name)
|
||||||
warnings.simplefilter("ignore")
|
|
||||||
jasoc = Jp2k(tfile2.name)
|
|
||||||
self.assertEqual(jasoc.box[3].id, 'asoc')
|
self.assertEqual(jasoc.box[3].id, 'asoc')
|
||||||
self.assertEqual(jasoc.box[3].box[0].id, 'lbl ')
|
self.assertEqual(jasoc.box[3].box[0].id, 'lbl ')
|
||||||
self.assertEqual(jasoc.box[3].box[0].label, 'label')
|
self.assertEqual(jasoc.box[3].box[0].label, 'label')
|
||||||
|
|
@ -528,17 +551,19 @@ class TestJp2k(unittest.TestCase):
|
||||||
with open(self.jp2file, 'rb') as fp:
|
with open(self.jp2file, 'rb') as fp:
|
||||||
data = fp.read()
|
data = fp.read()
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
tfile.write(data[0:129])
|
# Codestream starts at byte 3127. SIZ marker at 3137.
|
||||||
|
# COD marker at 3186. Subsampling at 3180.
|
||||||
|
tfile.write(data[0:3179])
|
||||||
|
|
||||||
# Make the DY bytes of the SIZ segment zero. That means that
|
# Make the DY bytes of the SIZ segment zero. That means that
|
||||||
# a subsampling factor is zero, which is illegal.
|
# a subsampling factor is zero, which is illegal.
|
||||||
tfile.write(b'\x00')
|
tfile.write(b'\x00')
|
||||||
tfile.write(data[130:132])
|
tfile.write(data[3180:3182])
|
||||||
tfile.write(b'\x00')
|
tfile.write(b'\x00')
|
||||||
tfile.write(data[134:136])
|
tfile.write(data[3184:3186])
|
||||||
tfile.write(b'\x00')
|
tfile.write(b'\x00')
|
||||||
|
|
||||||
tfile.write(data[136:])
|
tfile.write(data[3186:])
|
||||||
tfile.flush()
|
tfile.flush()
|
||||||
with warnings.catch_warnings():
|
with warnings.catch_warnings():
|
||||||
warnings.simplefilter("ignore")
|
warnings.simplefilter("ignore")
|
||||||
|
|
@ -597,6 +622,17 @@ 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_config_dir(self):
|
||||||
|
# Verify no exception is raised if $HOME is missing .glymur 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)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import unittest
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import pkg_resources
|
||||||
|
|
||||||
from ..lib import openjp2 as opj2
|
from ..lib import openjp2 as opj2
|
||||||
|
|
||||||
|
|
@ -62,7 +63,8 @@ def read_image(infile):
|
||||||
class TestSuiteNegative(unittest.TestCase):
|
class TestSuiteNegative(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
pass
|
self.jp2file = pkg_resources.resource_filename(glymur.__name__,
|
||||||
|
"data/nemo.jp2")
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
@ -150,5 +152,14 @@ class TestSuiteNegative(unittest.TestCase):
|
||||||
with self.assertWarns(UserWarning) as cw:
|
with self.assertWarns(UserWarning) as cw:
|
||||||
j = Jp2k(infile)
|
j = Jp2k(infile)
|
||||||
|
|
||||||
|
def test_precinct_size_not_multiple_of_two(self):
|
||||||
|
# Seems like precinct size should be a multiple of two.
|
||||||
|
ifile = Jp2k(self.jp2file)
|
||||||
|
data = ifile.read(reduce=3)
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
ofile = Jp2k(tfile.name, 'wb')
|
||||||
|
with self.assertRaises(IOError) as ce:
|
||||||
|
ofile.write(data, psizes=[(13, 13)])
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,26 @@ class TestPrinting(unittest.TestCase):
|
||||||
' Method: enumerated colorspace',
|
' Method: enumerated colorspace',
|
||||||
' Precedence: 0',
|
' Precedence: 0',
|
||||||
' Colorspace: sRGB',
|
' Colorspace: sRGB',
|
||||||
'Contiguous Codestream Box (jp2c) @ (77, 1133427)',
|
'UUID Box (uuid) @ (77, 638)',
|
||||||
|
' UUID: 4a706754-6966-6645-7869-662d3e4a5032',
|
||||||
|
' UUID Data: 614 bytes',
|
||||||
|
'UUID Box (uuid) @ (715, 2412)',
|
||||||
|
' UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP)',
|
||||||
|
' UUID Data: ',
|
||||||
|
' <ns0:xmpmeta xmlns:ns0="adobe:ns:meta/" '
|
||||||
|
+ 'xmlns:ns2="http://ns.adobe.com/xap/1.0/" '
|
||||||
|
+ 'xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" '
|
||||||
|
+ 'ns0:xmptk="XMP Core 4.4.0-Exiv2">',
|
||||||
|
' <rdf:RDF>',
|
||||||
|
' <rdf:Description ns2:CreatorTool="glymur" '
|
||||||
|
+ 'rdf:about="" />',
|
||||||
|
' </rdf:RDF>',
|
||||||
|
' </ns0:xmpmeta>',
|
||||||
|
' ',
|
||||||
|
'Contiguous Codestream Box (jp2c) @ (3127, 1133427)',
|
||||||
' Main header:',
|
' Main header:',
|
||||||
' SOC marker segment @ (85, 0)',
|
' SOC marker segment @ (3135, 0)',
|
||||||
' SIZ marker segment @ (87, 47)',
|
' SIZ marker segment @ (3137, 47)',
|
||||||
' Profile: 2',
|
' Profile: 2',
|
||||||
' Reference Grid Height, Width: (1456 x 2592)',
|
' Reference Grid Height, Width: (1456 x 2592)',
|
||||||
' Vertical, Horizontal Reference Grid Offset: '
|
' Vertical, Horizontal Reference Grid Offset: '
|
||||||
|
|
@ -62,7 +78,7 @@ class TestPrinting(unittest.TestCase):
|
||||||
' Signed: (False, False, False)',
|
' Signed: (False, False, False)',
|
||||||
' Vertical, Horizontal Subsampling: '
|
' Vertical, Horizontal Subsampling: '
|
||||||
+ '((1, 1), (1, 1), (1, 1))',
|
+ '((1, 1), (1, 1), (1, 1))',
|
||||||
' COD marker segment @ (136, 12)',
|
' COD marker segment @ (3186, 12)',
|
||||||
' Coding style:',
|
' Coding style:',
|
||||||
' Entropy coder, without partitions',
|
' Entropy coder, without partitions',
|
||||||
' SOP marker segments: False',
|
' SOP marker segments: False',
|
||||||
|
|
@ -86,7 +102,7 @@ class TestPrinting(unittest.TestCase):
|
||||||
+ 'False',
|
+ 'False',
|
||||||
' Predictable termination: False',
|
' Predictable termination: False',
|
||||||
' Segmentation symbols: False',
|
' Segmentation symbols: False',
|
||||||
' QCD marker segment @ (150, 19)',
|
' QCD marker segment @ (3200, 19)',
|
||||||
' Quantization style: no quantization, '
|
' Quantization style: no quantization, '
|
||||||
+ '2 guard bits',
|
+ '2 guard bits',
|
||||||
' Step size: [(0, 8), (0, 9), (0, 9), '
|
' Step size: [(0, 8), (0, 9), (0, 9), '
|
||||||
|
|
@ -98,10 +114,13 @@ class TestPrinting(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
# Restore stdout.
|
# Restore stdout.
|
||||||
sys.stdout = self.stdout
|
sys.stdout = self.stdout
|
||||||
|
#import pdb; pdb.set_trace()
|
||||||
|
|
||||||
def test_jp2dump(self):
|
def test_jp2dump(self):
|
||||||
glymur.jp2dump(self.jp2file)
|
glymur.jp2dump(self.jp2file)
|
||||||
actual = sys.stdout.getvalue().strip()
|
actual = sys.stdout.getvalue().strip()
|
||||||
|
self.actual = actual
|
||||||
|
self.expected = self.expectedNemo
|
||||||
self.assertEqual(actual, self.expectedNemo)
|
self.assertEqual(actual, self.expectedNemo)
|
||||||
|
|
||||||
def test_COC_segment(self):
|
def test_COC_segment(self):
|
||||||
|
|
@ -110,7 +129,7 @@ class TestPrinting(unittest.TestCase):
|
||||||
print(codestream.segment[5])
|
print(codestream.segment[5])
|
||||||
actual = sys.stdout.getvalue().strip()
|
actual = sys.stdout.getvalue().strip()
|
||||||
|
|
||||||
lines = ['COC marker segment @ (183, 9)',
|
lines = ['COC marker segment @ (3233, 9)',
|
||||||
' Associated component: 1',
|
' Associated component: 1',
|
||||||
' Coding style for this component: '
|
' Coding style for this component: '
|
||||||
+ 'Entropy coder, PARTITION = 0',
|
+ 'Entropy coder, PARTITION = 0',
|
||||||
|
|
@ -136,7 +155,7 @@ class TestPrinting(unittest.TestCase):
|
||||||
print(codestream.segment[2])
|
print(codestream.segment[2])
|
||||||
actual = sys.stdout.getvalue().strip()
|
actual = sys.stdout.getvalue().strip()
|
||||||
|
|
||||||
lines = ['COD marker segment @ (136, 12)',
|
lines = ['COD marker segment @ (3186, 12)',
|
||||||
' Coding style:',
|
' Coding style:',
|
||||||
' Entropy coder, without partitions',
|
' Entropy coder, without partitions',
|
||||||
' SOP marker segments: False',
|
' SOP marker segments: False',
|
||||||
|
|
@ -226,7 +245,7 @@ class TestPrinting(unittest.TestCase):
|
||||||
print(codestream.segment[-1])
|
print(codestream.segment[-1])
|
||||||
actual = sys.stdout.getvalue().strip()
|
actual = sys.stdout.getvalue().strip()
|
||||||
|
|
||||||
lines = ['EOC marker segment @ (1133502, 0)']
|
lines = ['EOC marker segment @ (1136552, 0)']
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
|
@ -313,7 +332,7 @@ class TestPrinting(unittest.TestCase):
|
||||||
print(codestream.segment[6])
|
print(codestream.segment[6])
|
||||||
actual = sys.stdout.getvalue().strip()
|
actual = sys.stdout.getvalue().strip()
|
||||||
|
|
||||||
lines = ['QCC marker segment @ (194, 20)',
|
lines = ['QCC marker segment @ (3244, 20)',
|
||||||
' Associated Component: 1',
|
' Associated Component: 1',
|
||||||
' Quantization style: no quantization, 2 guard bits',
|
' Quantization style: no quantization, 2 guard bits',
|
||||||
' Step size: [(0, 8), (0, 9), (0, 9), (0, 10), (0, 9), '
|
' Step size: [(0, 8), (0, 9), (0, 9), (0, 10), (0, 9), '
|
||||||
|
|
@ -329,7 +348,7 @@ class TestPrinting(unittest.TestCase):
|
||||||
print(codestream.segment[3])
|
print(codestream.segment[3])
|
||||||
actual = sys.stdout.getvalue().strip()
|
actual = sys.stdout.getvalue().strip()
|
||||||
|
|
||||||
lines = ['QCD marker segment @ (150, 19)',
|
lines = ['QCD marker segment @ (3200, 19)',
|
||||||
' Quantization style: no quantization, 2 guard bits',
|
' Quantization style: no quantization, 2 guard bits',
|
||||||
' Step size: [(0, 8), (0, 9), (0, 9), (0, 10), (0, 9), '
|
' Step size: [(0, 8), (0, 9), (0, 9), (0, 10), (0, 9), '
|
||||||
+ '(0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), '
|
+ '(0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), '
|
||||||
|
|
@ -344,7 +363,7 @@ class TestPrinting(unittest.TestCase):
|
||||||
print(codestream.segment[1])
|
print(codestream.segment[1])
|
||||||
actual = sys.stdout.getvalue().strip()
|
actual = sys.stdout.getvalue().strip()
|
||||||
|
|
||||||
lines = ['SIZ marker segment @ (87, 47)',
|
lines = ['SIZ marker segment @ (3137, 47)',
|
||||||
' Profile: 2',
|
' Profile: 2',
|
||||||
' Reference Grid Height, Width: (1456 x 2592)',
|
' Reference Grid Height, Width: (1456 x 2592)',
|
||||||
' Vertical, Horizontal Reference Grid Offset: (0 x 0)',
|
' Vertical, Horizontal Reference Grid Offset: (0 x 0)',
|
||||||
|
|
@ -364,7 +383,7 @@ class TestPrinting(unittest.TestCase):
|
||||||
print(codestream.segment[0])
|
print(codestream.segment[0])
|
||||||
actual = sys.stdout.getvalue().strip()
|
actual = sys.stdout.getvalue().strip()
|
||||||
|
|
||||||
lines = ['SOC marker segment @ (85, 0)']
|
lines = ['SOC marker segment @ (3135, 0)']
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
|
@ -374,7 +393,7 @@ class TestPrinting(unittest.TestCase):
|
||||||
print(codestream.segment[9])
|
print(codestream.segment[9])
|
||||||
actual = sys.stdout.getvalue().strip()
|
actual = sys.stdout.getvalue().strip()
|
||||||
|
|
||||||
lines = ['SOD marker segment @ (249, 0)']
|
lines = ['SOD marker segment @ (3299, 0)']
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
|
@ -384,7 +403,7 @@ class TestPrinting(unittest.TestCase):
|
||||||
print(codestream.segment[4])
|
print(codestream.segment[4])
|
||||||
actual = sys.stdout.getvalue().strip()
|
actual = sys.stdout.getvalue().strip()
|
||||||
|
|
||||||
lines = ['SOT marker segment @ (171, 10)',
|
lines = ['SOT marker segment @ (3221, 10)',
|
||||||
' Tile part index: 0',
|
' Tile part index: 0',
|
||||||
' Tile part length: 78629',
|
' Tile part length: 78629',
|
||||||
' Tile part instance: 0',
|
' Tile part instance: 0',
|
||||||
|
|
@ -422,8 +441,8 @@ class TestPrinting(unittest.TestCase):
|
||||||
actual = sys.stdout.getvalue().strip()
|
actual = sys.stdout.getvalue().strip()
|
||||||
|
|
||||||
lst = ['Codestream:',
|
lst = ['Codestream:',
|
||||||
' SOC marker segment @ (85, 0)',
|
' SOC marker segment @ (3135, 0)',
|
||||||
' SIZ marker segment @ (87, 47)',
|
' SIZ marker segment @ (3137, 47)',
|
||||||
' Profile: 2',
|
' Profile: 2',
|
||||||
' Reference Grid Height, Width: (1456 x 2592)',
|
' Reference Grid Height, Width: (1456 x 2592)',
|
||||||
' Vertical, Horizontal Reference Grid Offset: (0 x 0)',
|
' Vertical, Horizontal Reference Grid Offset: (0 x 0)',
|
||||||
|
|
@ -433,7 +452,7 @@ class TestPrinting(unittest.TestCase):
|
||||||
' Signed: (False, False, False)',
|
' Signed: (False, False, False)',
|
||||||
' Vertical, Horizontal Subsampling: '
|
' Vertical, Horizontal Subsampling: '
|
||||||
+ '((1, 1), (1, 1), (1, 1))',
|
+ '((1, 1), (1, 1), (1, 1))',
|
||||||
' COD marker segment @ (136, 12)',
|
' COD marker segment @ (3186, 12)',
|
||||||
' Coding style:',
|
' Coding style:',
|
||||||
' Entropy coder, without partitions',
|
' Entropy coder, without partitions',
|
||||||
' SOP marker segments: False',
|
' SOP marker segments: False',
|
||||||
|
|
@ -455,7 +474,7 @@ class TestPrinting(unittest.TestCase):
|
||||||
' Vertically stripe causal context: False',
|
' Vertically stripe causal context: False',
|
||||||
' Predictable termination: False',
|
' Predictable termination: False',
|
||||||
' Segmentation symbols: False',
|
' Segmentation symbols: False',
|
||||||
' QCD marker segment @ (150, 19)',
|
' QCD marker segment @ (3200, 19)',
|
||||||
' Quantization style: no quantization, '
|
' Quantization style: no quantization, '
|
||||||
+ '2 guard bits',
|
+ '2 guard bits',
|
||||||
' Step size: [(0, 8), (0, 9), (0, 9), '
|
' Step size: [(0, 8), (0, 9), (0, 9), '
|
||||||
|
|
|
||||||
8
setup.py
8
setup.py
|
|
@ -1,9 +1,9 @@
|
||||||
from distutils.core import setup
|
from distutils.core import setup
|
||||||
|
|
||||||
kwargs = {'name': 'glymur',
|
kwargs = {'name': 'Glymur',
|
||||||
'version': '0.1.1',
|
'version': '0.1.2',
|
||||||
'description': 'Tools for manipulating JPEG2000 files',
|
'description': 'Tools for accessing JPEG2000 files',
|
||||||
'long_description': open('README').read(),
|
'long_description': open('README.md').read(),
|
||||||
'author': 'John Evans',
|
'author': 'John Evans',
|
||||||
'author_email': 'johnevans938 at gmail dot com',
|
'author_email': 'johnevans938 at gmail dot com',
|
||||||
'url': 'https://github.com/quintusdias/glymur',
|
'url': 'https://github.com/quintusdias/glymur',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue