Merge branch 'issue77' into devel
This commit is contained in:
commit
6a65bdb0a4
11 changed files with 205 additions and 18 deletions
|
|
@ -1,3 +1,6 @@
|
||||||
|
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
|
Jul 21, 2013 - v0.2.4 Fixed markdown bug for Fedora 17 information, fixed
|
||||||
out-of-date windows information (issue79). Fixed incorrect
|
out-of-date windows information (issue79). Fixed incorrect
|
||||||
interpretation of Psot parameter (issue78).
|
interpretation of Psot parameter (issue78).
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ copyright = u'2013, John Evans'
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '0.1'
|
version = '0.1'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '0.2.4'
|
release = '0.2.5'
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
|
|
||||||
|
|
@ -137,8 +137,17 @@ platforms.
|
||||||
Testing
|
Testing
|
||||||
'''''''
|
'''''''
|
||||||
|
|
||||||
If you wish to run the tests (strongly recommended :-), you can either run them
|
There are two environment variables you may wish to set before running the
|
||||||
from within python as follows ... ::
|
tests.
|
||||||
|
|
||||||
|
* **OPJ_DATA_ROOT** - points to directory for OpenJPEG test data
|
||||||
|
* **FORMAT_CORPUS_ROOT** - points to directory for format-corpus repository (see https://github.com/openplanets/format-corpus)
|
||||||
|
|
||||||
|
Setting these two environment variables is not required, as any tests using
|
||||||
|
either of them will be skipped.
|
||||||
|
|
||||||
|
In order to run the tests, you can either run them from within
|
||||||
|
python as follows ... ::
|
||||||
|
|
||||||
>>> import glymur
|
>>> import glymur
|
||||||
>>> glymur.runtests()
|
>>> glymur.runtests()
|
||||||
|
|
|
||||||
|
|
@ -185,9 +185,8 @@ Work with XMP UUIDs?
|
||||||
====================
|
====================
|
||||||
The example JP2 file shipped with glymur has an XMP UUID. ::
|
The example JP2 file shipped with glymur has an XMP UUID. ::
|
||||||
|
|
||||||
>>> from glymur import Jp2k
|
>>> import glymur
|
||||||
>>> file = glymur.data.nemo()
|
>>> j = glymur.Jp2k(glymur.data.nemo())
|
||||||
>>> j = Jp2k(file)
|
|
||||||
>>> print(j.box[4])
|
>>> print(j.box[4])
|
||||||
UUID Box (uuid) @ (715, 2412)
|
UUID Box (uuid) @ (715, 2412)
|
||||||
UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP)
|
UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP)
|
||||||
|
|
@ -198,7 +197,7 @@ The example JP2 file shipped with glymur has an XMP UUID. ::
|
||||||
</rdf:RDF>
|
</rdf:RDF>
|
||||||
</ns0:xmpmeta>
|
</ns0:xmpmeta>
|
||||||
|
|
||||||
Since the UUID data in this case is returned as an ElementTree Element, one can
|
Since the UUID data in this case is returned as an ElementTree instance, one can
|
||||||
use ElementTree to access the data. For example, to extract the
|
use ElementTree to access the data. For example, to extract the
|
||||||
**CreatorTool** attribute value, the following would work::
|
**CreatorTool** attribute value, the following would work::
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from .jp2k import Jp2k
|
||||||
from .jp2dump import jp2dump
|
from .jp2dump import jp2dump
|
||||||
|
|
||||||
from . import data
|
from . import data
|
||||||
from . import test
|
#from . import test
|
||||||
|
|
||||||
|
|
||||||
def runtests():
|
def runtests():
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,14 @@ class Codestream(object):
|
||||||
while True:
|
while True:
|
||||||
|
|
||||||
read_buffer = fptr.read(2)
|
read_buffer = fptr.read(2)
|
||||||
marker_id, = struct.unpack('>H', read_buffer)
|
try:
|
||||||
|
marker_id, = struct.unpack('>H', read_buffer)
|
||||||
|
except struct.error:
|
||||||
|
# Treat this as a warning.
|
||||||
|
msg = "Marker had length {0} instead of expected length of 2 "
|
||||||
|
msg += "bytes. Codestream parsing terminated."
|
||||||
|
warnings.warn(msg.format(len(read_buffer)))
|
||||||
|
break
|
||||||
|
|
||||||
if marker_id == 0xff90 and header_only:
|
if marker_id == 0xff90 and header_only:
|
||||||
# Start-of-tile (SOT) means that we are out of the main header
|
# Start-of-tile (SOT) means that we are out of the main header
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ from .codestream import Codestream
|
||||||
from .core import SRGB
|
from .core import SRGB
|
||||||
from .core import GREYSCALE
|
from .core import GREYSCALE
|
||||||
from .core import PROGRESSION_ORDER
|
from .core import PROGRESSION_ORDER
|
||||||
|
from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE
|
||||||
from .jp2box import Jp2kBox
|
from .jp2box import Jp2kBox
|
||||||
from .jp2box import JPEG2000SignatureBox
|
from .jp2box import JPEG2000SignatureBox
|
||||||
from .jp2box import FileTypeBox
|
from .jp2box import FileTypeBox
|
||||||
|
|
@ -159,6 +160,24 @@ class Jp2k(Jp2kBox):
|
||||||
# boxes) here.
|
# boxes) here.
|
||||||
fptr.seek(0)
|
fptr.seek(0)
|
||||||
self.box = self.parse_superbox(fptr)
|
self.box = self.parse_superbox(fptr)
|
||||||
|
self._validate()
|
||||||
|
|
||||||
|
def _validate(self):
|
||||||
|
"""Validate the JPEG 2000 outermost superbox.
|
||||||
|
"""
|
||||||
|
# A jp2-branded file cannot contain an "any ICC profile
|
||||||
|
ftyp = self.box[1]
|
||||||
|
if ftyp.brand == 'jp2 ':
|
||||||
|
jp2h = [box for box in self.box if box.box_id == 'jp2h'][0]
|
||||||
|
colrs = [box for box in jp2h.box if box.box_id == 'colr']
|
||||||
|
for colr in colrs:
|
||||||
|
if colr.method not in (ENUMERATED_COLORSPACE,
|
||||||
|
RESTRICTED_ICC_PROFILE):
|
||||||
|
msg = "Color Specification box method must specify either "
|
||||||
|
msg += "an enumerated colorspace or a restricted ICC "
|
||||||
|
msg += "profile if the file type box brand is 'jp2 '."
|
||||||
|
warnings.warn(msg)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable-msg=W0221
|
# pylint: disable-msg=W0221
|
||||||
def write(self, img_array, cratios=None, eph=False, psnr=None, numres=None,
|
def write(self, img_array, cratios=None, eph=False, psnr=None, numres=None,
|
||||||
|
|
|
||||||
133
glymur/test/test_conformance.py
Normal file
133
glymur/test/test_conformance.py
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
except:
|
||||||
|
raise
|
||||||
|
|
||||||
|
try:
|
||||||
|
opj_data_root = os.environ['OPJ_DATA_ROOT']
|
||||||
|
except KeyError:
|
||||||
|
opj_data_root = None
|
||||||
|
except:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
@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()
|
||||||
|
|
@ -990,7 +990,11 @@ class TestSuite(unittest.TestCase):
|
||||||
def test_NR_DEC_text_GBR_jp2_29_decode(self):
|
def test_NR_DEC_text_GBR_jp2_29_decode(self):
|
||||||
jfile = os.path.join(data_root,
|
jfile = os.path.join(data_root,
|
||||||
'input/nonregression/text_GBR.jp2')
|
'input/nonregression/text_GBR.jp2')
|
||||||
data = Jp2k(jfile).read()
|
with warnings.catch_warnings():
|
||||||
|
# brand is 'jp2 ', but has any icc profile.
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
jp2 = Jp2k(jfile)
|
||||||
|
data = jp2.read()
|
||||||
self.assertTrue(True)
|
self.assertTrue(True)
|
||||||
|
|
||||||
def test_NR_DEC_pacs_ge_j2k_30_decode(self):
|
def test_NR_DEC_pacs_ge_j2k_30_decode(self):
|
||||||
|
|
@ -7260,7 +7264,10 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
def test_NR_text_GBR_dump(self):
|
def test_NR_text_GBR_dump(self):
|
||||||
jfile = os.path.join(data_root,
|
jfile = os.path.join(data_root,
|
||||||
'input/nonregression/text_GBR.jp2')
|
'input/nonregression/text_GBR.jp2')
|
||||||
jp2 = Jp2k(jfile)
|
with warnings.catch_warnings():
|
||||||
|
# brand is 'jp2 ', but has any icc profile.
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
jp2 = Jp2k(jfile)
|
||||||
|
|
||||||
ids = [box.box_id for box in jp2.box]
|
ids = [box.box_id for box in jp2.box]
|
||||||
lst = ['jP ', 'ftyp', 'rreq', 'jp2h',
|
lst = ['jP ', 'ftyp', 'rreq', 'jp2h',
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import pkg_resources
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import warnings
|
||||||
|
|
||||||
if sys.hexversion < 0x02070000:
|
if sys.hexversion < 0x02070000:
|
||||||
import unittest2 as unittest
|
import unittest2 as unittest
|
||||||
|
|
@ -277,9 +278,12 @@ class TestPrinting(unittest.TestCase):
|
||||||
"OPJ_DATA_ROOT environment variable not set")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_icc_profile(self):
|
def test_icc_profile(self):
|
||||||
filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2')
|
filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2')
|
||||||
j = glymur.Jp2k(filename)
|
with warnings.catch_warnings():
|
||||||
|
# brand is 'jp2 ', but has any icc profile.
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
jp2 = Jp2k(filename)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
print(j.box[3].box[1])
|
print(jp2.box[3].box[1])
|
||||||
actual = fake_out.getvalue().strip()
|
actual = fake_out.getvalue().strip()
|
||||||
lin27 = ["Colour Specification Box (colr) @ (179, 1339)",
|
lin27 = ["Colour Specification Box (colr) @ (179, 1339)",
|
||||||
" Method: any ICC profile",
|
" Method: any ICC profile",
|
||||||
|
|
@ -902,10 +906,13 @@ class TestPrinting(unittest.TestCase):
|
||||||
# ICC profiles may be used in JP2, but the approximation field should
|
# ICC profiles may be used in JP2, but the approximation field should
|
||||||
# be zero unless we have jpx. This file does both.
|
# be zero unless we have jpx. This file does both.
|
||||||
filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2')
|
filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2')
|
||||||
j = glymur.Jp2k(filename)
|
with warnings.catch_warnings():
|
||||||
|
# brand is 'jp2 ', but has any icc profile.
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
jp2 = Jp2k(filename)
|
||||||
|
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
print(j.box[3].box[1])
|
print(jp2.box[3].box[1])
|
||||||
actual = fake_out.getvalue().strip()
|
actual = fake_out.getvalue().strip()
|
||||||
lines = ["Colour Specification Box (colr) @ (179, 1339)",
|
lines = ["Colour Specification Box (colr) @ (179, 1339)",
|
||||||
" Method: any ICC profile",
|
" Method: any ICC profile",
|
||||||
|
|
@ -942,10 +949,13 @@ class TestPrinting(unittest.TestCase):
|
||||||
def test_uuid(self):
|
def test_uuid(self):
|
||||||
# UUID box
|
# UUID box
|
||||||
filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2')
|
filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2')
|
||||||
j = glymur.Jp2k(filename)
|
with warnings.catch_warnings():
|
||||||
|
# brand is 'jp2 ', but has any icc profile.
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
jp2 = Jp2k(filename)
|
||||||
|
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
print(j.box[4])
|
print(jp2.box[4])
|
||||||
actual = fake_out.getvalue().strip()
|
actual = fake_out.getvalue().strip()
|
||||||
lines = ['UUID Box (uuid) @ (1544, 25)',
|
lines = ['UUID Box (uuid) @ (1544, 25)',
|
||||||
' UUID: 3a0d0218-0ae9-4115-b376-4bca41ce0e71',
|
' UUID: 3a0d0218-0ae9-4115-b376-4bca41ce0e71',
|
||||||
|
|
|
||||||
2
setup.py
2
setup.py
|
|
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
kwargs = {'name': 'Glymur',
|
kwargs = {'name': 'Glymur',
|
||||||
'version': '0.2.4',
|
'version': '0.2.5',
|
||||||
'description': 'Tools for accessing JPEG2000 files',
|
'description': 'Tools for accessing JPEG2000 files',
|
||||||
'long_description': open('README.md').read(),
|
'long_description': open('README.md').read(),
|
||||||
'author': 'John Evans',
|
'author': 'John Evans',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue