diff --git a/CHANGES.txt b/CHANGES.txt index b1ab624..f80b04b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,7 @@ +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. diff --git a/README.md b/README.md index b8d38c0..d8ea732 100644 --- a/README.md +++ b/README.md @@ -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/ diff --git a/docs/source/conf.py b/docs/source/conf.py index 96eeba9..bd59b1b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -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.4' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index 14cf4d6..06c6b79 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -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 @@ -126,8 +126,11 @@ Windows ------- The only configuration I've tested is Python(xy), which uses Python 2.7. Python(xy) already comes with numpy, but you will have to install pip and then -contextlib2 and mock as well. Both 1.5.1 and the svn development versions of -openjpeg work. +contextlib2 and mock as well. Glymur seems to work with both 1.5.1 and the +svn development versions of openjpeg. + +Glymur has been tested **far less** extensively on Windows than on the other +platforms. ''''''' diff --git a/glymur/__init__.py b/glymur/__init__.py index fb37ef0..a69d28f 100644 --- a/glymur/__init__.py +++ b/glymur/__init__.py @@ -6,7 +6,7 @@ from .jp2k import Jp2k from .jp2dump import jp2dump from . import data -#from . import test +from . import test def runtests(): diff --git a/glymur/codestream.py b/glymur/codestream.py index ce11e9d..49d7acb 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -57,7 +57,12 @@ class Codestream(object): Attributes ---------- - segment : list of marker segments + segment : iterable + list of marker segments + offset : int + Offset of the codestream from start of the file in bytes. + length : int + Length of the codestream in bytes. Raises ------ @@ -70,17 +75,22 @@ class Codestream(object): 15444-1:2004 - Information technology -- JPEG 2000 image coding system: Core coding system """ - def __init__(self, fptr, header_only=True): + def __init__(self, fptr, length, header_only=True): """ Parameters ---------- fptr : file Open file object. + length : int + Length of the codestream in bytes. header_only : bool, optional If True, only marker segments in the main header are parsed. Supplying False may impose a large performance penalty. """ + self.offset = fptr.tell() + self.length = length + # Number of components. Must be kept track of for the processing of # many segments. self._csiz = -1 @@ -111,9 +121,9 @@ class Codestream(object): try: segment = self._process_marker_segment(fptr, marker_id) - except InconsistentStartOfTileError as isote: + except Exception as error: # Treat this as a warning. - msg = str(isote) + msg = str(error) warnings.warn(msg) break @@ -199,7 +209,11 @@ class Codestream(object): segment = _parse_sot_segment(fptr) if segment.offset not in self._tile_offset: self._tile_offset.append(segment.offset) - self._tile_length.append(segment.psot) + 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) else: msg = "Inconsistent start-of-tile (SOT) marker segment " msg += "encountered in tile with index {0}. " diff --git a/glymur/jp2box.py b/glymur/jp2box.py index b6f92f7..d205e1b 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -644,7 +644,7 @@ class ContiguousCodestreamBox(Jp2kBox): ------- ContiguousCodestreamBox instance """ - main_header = Codestream(fptr, header_only=True) + main_header = Codestream(fptr, length, header_only=True) box = ContiguousCodestreamBox(main_header, length=length, offset=offset) return box diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 23aed62..5baebac 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -1033,7 +1033,8 @@ class Jp2k(Jp2kBox): """ with open(self.filename, 'rb') as fptr: if self._codec_format == _opj2.CODEC_J2K: - codestream = Codestream(fptr, header_only=header_only) + codestream = Codestream(fptr, self.length, + header_only=header_only) else: box = [x for x in self.box if x.box_id == 'jp2c'] if len(box) != 1: @@ -1042,9 +1043,15 @@ class Jp2k(Jp2kBox): fptr.seek(box[0].offset) read_buffer = fptr.read(8) (box_length, _) = struct.unpack('>I4s', read_buffer) - if box_length == 1: + if box_length == 0: + # The length of the box is presumed to last until the end + # of the file. Compute the effective length of the box. + box_length = os.path.getsize(fptr.name) - fptr.tell() + 8 + elif box_length == 1: # Seek past the XL field. read_buffer = fptr.read(8) - codestream = Codestream(fptr, header_only=header_only) + box_length, = struct.unpack('>Q', read_buffer) + codestream = Codestream(fptr, box_length - 8, + header_only=header_only) return codestream diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py index 87d7df1..31c674c 100644 --- a/glymur/test/test_codestream.py +++ b/glymur/test/test_codestream.py @@ -28,8 +28,7 @@ except: class TestCodestream(unittest.TestCase): def setUp(self): - self.jp2file = pkg_resources.resource_filename(glymur.__name__, - "data/nemo.jp2") + self.jp2file = glymur.data.nemo() def tearDown(self): pass @@ -95,5 +94,16 @@ class TestCodestream(unittest.TestCase): self.assertEqual(c.segment[2].length, 3) self.assertEqual(c.segment[2]._data, b'\x00') + def test_psot_is_zero(self): + # Psot=0 in SOT is perfectly legal. Issue #78. + filename = os.path.join(data_root, + 'input/nonregression/123.j2c') + j = Jp2k(filename) + c = j.get_codestream(header_only=False) + + # The codestream is valid, so we should be able to get the entire + # codestream, so the last one is EOC. + self.assertEqual(c.segment[-1].marker_id, 'EOC') + if __name__ == "__main__": unittest.main() diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index ca233a2..cedd9cf 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -823,12 +823,16 @@ class TestSuite(unittest.TestCase): self.assertTrue(True) def test_NR_DEC_broken2_jp2_5_decode(self): - jfile = os.path.join(data_root, - 'input/nonregression/broken2.jp2') + # Null pointer access + jfile = os.path.join(data_root, 'input/nonregression/broken2.jp2') with self.assertRaises(IOError): - data = Jp2k(jfile).read() + with warnings.catch_warnings(): + # Library warning, invalid number of subbands. + warnings.simplefilter("ignore") + data = Jp2k(jfile).read() self.assertTrue(True) + @unittest.skipIf(sys.hexversion < 0x03020000, "Uses features introduced in 3.2.") def test_NR_DEC_broken3_jp2_6_decode(self): @@ -845,7 +849,10 @@ class TestSuite(unittest.TestCase): jfile = os.path.join(data_root, 'input/nonregression/broken4.jp2') with self.assertRaises(IOError): - data = Jp2k(jfile).read() + with warnings.catch_warnings(): + # Library warning, invalid number of subbands. + warnings.simplefilter("ignore") + data = Jp2k(jfile).read() self.assertTrue(True) @unittest.skip("fprintf stderr output in r2343.") @@ -6003,13 +6010,16 @@ class TestSuiteDump(unittest.TestCase): self.assertEqual(c.segment[6]._exponent, [8] + [9, 9, 10] * 5) + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2, 'assertWarns'.") def test_NR_broken2_jp2_dump(self): # Invalid marker ID on codestream. - jfile = os.path.join(data_root, - 'input/nonregression/broken2.jp2') - with self.assertRaises(IOError): + jfile = os.path.join(data_root, 'input/nonregression/broken2.jp2') + with self.assertWarns(UserWarning): jp2 = Jp2k(jfile) + self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') + @unittest.skipIf(sys.hexversion < 0x03020000, "Uses features introduced in 3.2.") def test_NR_broken3_jp2_dump(self): @@ -6137,12 +6147,16 @@ class TestSuiteDump(unittest.TestCase): self.assertEqual(c.segment[6]._exponent, [8] + [9, 9, 10] * 5) + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2, 'assertWarns'") def test_NR_broken4_jp2_dump(self): - jfile = os.path.join(data_root, - 'input/nonregression/broken4.jp2') - with self.assertRaises(IOError): + # Has an invalid marker in the main header + jfile = os.path.join(data_root, 'input/nonregression/broken4.jp2') + with self.assertWarns(UserWarning): jp2 = Jp2k(jfile) + self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') + def test_NR_file409752(self): jfile = os.path.join(data_root, 'input/nonregression/file409752.jp2') @@ -7799,18 +7813,22 @@ 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): - jfile = os.path.join(data_root, - 'input/nonregression/broken2.jp2') - with self.assertRaises(IOError): - data = Jp2k(jfile).read() + # Null pointer access + jfile = os.path.join(data_root, 'input/nonregression/broken2.jp2') + with self.assertRaises(ValueError): + with warnings.catch_warnings(): + # Library warning, invalid number of subbands. + warnings.simplefilter("ignore") + data = Jp2k(jfile).read() self.assertTrue(True) @unittest.skipIf(sys.hexversion < 0x03020000, "Uses features introduced in 3.2.") def test_NR_DEC_broken3_jp2_6_decode(self): - jfile = os.path.join(data_root, - 'input/nonregression/broken3.jp2') + jfile = os.path.join(data_root, 'input/nonregression/broken3.jp2') with self.assertWarns(UserWarning) as cw: # colr box has bad length. j = Jp2k(jfile) @@ -7818,11 +7836,16 @@ 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): - jfile = os.path.join(data_root, - 'input/nonregression/broken4.jp2') - with self.assertRaises(IOError): - data = Jp2k(jfile).read() + # Null pointer access + jfile = os.path.join(data_root, 'input/nonregression/broken4.jp2') + with self.assertRaises(ValueError): + with warnings.catch_warnings(): + # Library warning, invalid number of subbands. + warnings.simplefilter("ignore") + data = Jp2k(jfile).read() self.assertTrue(True) @unittest.skip("fprintf stderr output in r2343.") diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index 17962f7..48b8117 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -73,25 +73,16 @@ class TestSuiteNegative(unittest.TestCase): with self.assertRaises(RuntimeError): j.write(data, psnr=[30, 35, 40], cratios=[2, 3, 4]) - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") def test_NR_MarkerIsNotCompliant_j2k_dump(self): - # SOT marker gives bad offset. relpath = 'input/nonregression/MarkerIsNotCompliant.j2k' jfile = os.path.join(data_root, relpath) jp2k = Jp2k(jfile) - with self.assertWarns(UserWarning) as cw: - c = jp2k.get_codestream(header_only=False) - - # Verify that the last segment returned in the codestream is SOD, - # not EOC. Codestream parsing should stop when we try to jump to - # the end of SOT. - self.assertEqual(c.segment[-1].marker_id, 'SOD') + c = jp2k.get_codestream(header_only=False) @unittest.skipIf(sys.hexversion < 0x03020000, "Uses features introduced in 3.2.") def test_NR_illegalcolortransform_dump(self): - # SOT marker gives bad offset. + # EOC marker is bad relpath = 'input/nonregression/illegalcolortransform.j2k' jfile = os.path.join(data_root, relpath) jp2k = Jp2k(jfile) @@ -103,20 +94,11 @@ class TestSuiteNegative(unittest.TestCase): # the end of SOT. self.assertEqual(c.segment[-1].marker_id, 'SOD') - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") def test_NR_Cannotreaddatawithnosizeknown_j2k(self): - # SOT marker gives bad offset. relpath = 'input/nonregression/Cannotreaddatawithnosizeknown.j2k' jfile = os.path.join(data_root, relpath) jp2k = Jp2k(jfile) - with self.assertWarns(UserWarning) as cw: - c = jp2k.get_codestream(header_only=False) - - # Verify that the last segment returned in the codestream is SOD, - # not EOC. Codestream parsing should stop when we try to jump to - # the end of SOT. - self.assertEqual(c.segment[-1].marker_id, 'SOD') + c = jp2k.get_codestream(header_only=False) @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_code_block_dimensions(self): diff --git a/release.txt b/release.txt index d3ee018..f41ecb4 100644 --- a/release.txt +++ b/release.txt @@ -6,34 +6,38 @@ | | | | | 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 | | | | | +-----------+--------+--------+--------+--------------------------------------+ +Make release branch, qualify on all platforms. Pylint on entire package should exceed 0.95. pep8 should be pass cleanly. Coverage should exceed 95%. +Make release candidate, push to Pypi, qualify in virtual environment. +Merge release branch to master, tag, push to master, push to Pypi. + diff --git a/setup.py b/setup.py index 1f17ddc..95e778b 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages import sys kwargs = {'name': 'Glymur', - 'version': '0.2.3', + 'version': '0.2.4', 'description': 'Tools for accessing JPEG2000 files', 'long_description': open('README.md').read(), 'author': 'John Evans',