Don't error out when progression order is invalid. #186
Instead of a regular dictionary for the display of progression order, use a subclass of defaultdict instead. The defaultdict's __missing__ method is overridden to supply a custom error message that used the offending key. Everybody wins!!!
This commit is contained in:
parent
2708e85be1
commit
e15a8a6dba
4 changed files with 92 additions and 27 deletions
|
|
@ -17,6 +17,7 @@ codestreams.
|
||||||
# the base Segment class.
|
# the base Segment class.
|
||||||
# pylint: disable=R0903
|
# pylint: disable=R0903
|
||||||
|
|
||||||
|
import collections
|
||||||
import math
|
import math
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -30,12 +31,26 @@ from .core import WAVELET_XFORM_5X3_REVERSIBLE
|
||||||
from .core import _CAPABILITIES_DISPLAY
|
from .core import _CAPABILITIES_DISPLAY
|
||||||
from .lib import openjp2 as opj2
|
from .lib import openjp2 as opj2
|
||||||
|
|
||||||
_PROGRESSION_ORDER_DISPLAY = {
|
class _keydefaultdict(collections.defaultdict):
|
||||||
LRCP: 'LRCP',
|
"""Unlisted keys help form their own error message.
|
||||||
|
|
||||||
|
Normally defaultdict uses a factory function with no input arguments, but
|
||||||
|
that's not quite the behavior we want.
|
||||||
|
"""
|
||||||
|
def __missing__(self, key):
|
||||||
|
if self.default_factory is None:
|
||||||
|
raise KeyError(key)
|
||||||
|
else:
|
||||||
|
ret = self[key] = self.default_factory(key)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
_factory = lambda x: '{0} (invalid)'.format(x)
|
||||||
|
_PROGRESSION_ORDER_DISPLAY = _keydefaultdict(_factory,
|
||||||
|
{ LRCP: 'LRCP',
|
||||||
RLCP: 'RLCP',
|
RLCP: 'RLCP',
|
||||||
RPCL: 'RPCL',
|
RPCL: 'RPCL',
|
||||||
PCRL: 'PCRL',
|
PCRL: 'PCRL',
|
||||||
CPRL: 'CPRL'}
|
CPRL: 'CPRL'})
|
||||||
|
|
||||||
_WAVELET_TRANSFORM_DISPLAY = {
|
_WAVELET_TRANSFORM_DISPLAY = {
|
||||||
WAVELET_XFORM_9X7_IRREVERSIBLE: '9-7 irreversible',
|
WAVELET_XFORM_9X7_IRREVERSIBLE: '9-7 irreversible',
|
||||||
|
|
@ -371,6 +386,9 @@ class Codestream(object):
|
||||||
numbytes = offset + 2 + length - fptr.tell()
|
numbytes = offset + 2 + length - fptr.tell()
|
||||||
spcod = fptr.read(numbytes)
|
spcod = fptr.read(numbytes)
|
||||||
spcod = np.frombuffer(spcod, dtype=np.uint8)
|
spcod = np.frombuffer(spcod, dtype=np.uint8)
|
||||||
|
if spcod[0] not in [LRCP, RLCP, RPCL, PCRL, CPRL]:
|
||||||
|
msg = "Invalid progression order in COD segment: {0}."
|
||||||
|
warnings.warn(msg.format(spcod[0]))
|
||||||
|
|
||||||
sop = (scod & 2) > 0
|
sop = (scod & 2) > 0
|
||||||
eph = (scod & 4) > 0
|
eph = (scod & 4) > 0
|
||||||
|
|
|
||||||
|
|
@ -587,3 +587,26 @@ issue_183_colr = """Colour Specification Box (colr) @ (62, 12)
|
||||||
Method: restricted ICC profile
|
Method: restricted ICC profile
|
||||||
Precedence: 0
|
Precedence: 0
|
||||||
ICC Profile: None"""
|
ICC Profile: None"""
|
||||||
|
|
||||||
|
|
||||||
|
# Progression order is invalid.
|
||||||
|
issue_186_progression_order = """COD marker segment @ (174, 12)
|
||||||
|
Coding style:
|
||||||
|
Entropy coder, without partitions
|
||||||
|
SOP marker segments: False
|
||||||
|
EPH marker segments: False
|
||||||
|
Coding style parameters:
|
||||||
|
Progression order: 33 (invalid)
|
||||||
|
Number of layers: 1
|
||||||
|
Multiple component transformation usage: reversible
|
||||||
|
Number of resolutions: 6
|
||||||
|
Code block height, width: (32 x 32)
|
||||||
|
Wavelet transform: 9-7 irreversible
|
||||||
|
Precinct size: default, 2^15 x 2^15
|
||||||
|
Code block context:
|
||||||
|
Selective arithmetic coding bypass: False
|
||||||
|
Reset context probabilities on coding pass boundaries: False
|
||||||
|
Termination on each coding pass: False
|
||||||
|
Vertically stripe causal context: False
|
||||||
|
Predictable termination: False
|
||||||
|
Segmentation symbols: False"""
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,39 @@ class TestCodestream(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def test_siz_segment_ssiz_unsigned(self):
|
||||||
|
"""ssiz attribute to be removed in future release"""
|
||||||
|
j = Jp2k(self.jp2file)
|
||||||
|
codestream = j.get_codestream()
|
||||||
|
|
||||||
|
# The ssiz attribute was simply a tuple of raw bytes.
|
||||||
|
# The first 7 bits are interpreted as the bitdepth, the MSB determines
|
||||||
|
# whether or not it is signed.
|
||||||
|
self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7))
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
||||||
"OPJ_DATA_ROOT environment variable not set")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
|
class TestCodestreamOpjData(unittest.TestCase):
|
||||||
|
"""Test suite for unusual codestream cases. Uses OPJ_DATA_ROOT"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.jp2file = glymur.data.nemo()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_invalid_progression_order(self):
|
||||||
|
"""Should still be able to parse even if prog order is invalid."""
|
||||||
|
jfile = opj_data_file('input/nonregression/2977.pdf.asan.67.2198.jp2')
|
||||||
|
if sys.hexversion < 0x03000000:
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
Jp2k(jfile)
|
||||||
|
else:
|
||||||
|
with self.assertWarns(UserWarning):
|
||||||
|
Jp2k(jfile)
|
||||||
|
|
||||||
def test_tile_height_is_zero(self):
|
def test_tile_height_is_zero(self):
|
||||||
"""Zero tile height should not cause an exception."""
|
"""Zero tile height should not cause an exception."""
|
||||||
filename = opj_data_file('input/nonregression/2539.pdf.SIGFPE.706.1712.jp2')
|
filename = opj_data_file('input/nonregression/2539.pdf.SIGFPE.706.1712.jp2')
|
||||||
|
|
@ -43,8 +74,6 @@ class TestCodestream(unittest.TestCase):
|
||||||
Jp2k(filename)
|
Jp2k(filename)
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
|
||||||
"OPJ_DATA_ROOT environment variable not set")
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
def test_reserved_marker_segment(self):
|
def test_reserved_marker_segment(self):
|
||||||
"""Reserved marker segments are ok."""
|
"""Reserved marker segments are ok."""
|
||||||
|
|
@ -76,8 +105,6 @@ class TestCodestream(unittest.TestCase):
|
||||||
self.assertEqual(codestream.segment[2].length, 3)
|
self.assertEqual(codestream.segment[2].length, 3)
|
||||||
self.assertEqual(codestream.segment[2].data, b'\x00')
|
self.assertEqual(codestream.segment[2].data, b'\x00')
|
||||||
|
|
||||||
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
|
||||||
"OPJ_DATA_ROOT environment variable not set")
|
|
||||||
@unittest.skipIf(sys.hexversion < 0x03020000,
|
@unittest.skipIf(sys.hexversion < 0x03020000,
|
||||||
"Uses features introduced in 3.2.")
|
"Uses features introduced in 3.2.")
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
|
|
@ -109,8 +136,6 @@ class TestCodestream(unittest.TestCase):
|
||||||
self.assertEqual(codestream.segment[2].length, 3)
|
self.assertEqual(codestream.segment[2].length, 3)
|
||||||
self.assertEqual(codestream.segment[2].data, b'\x00')
|
self.assertEqual(codestream.segment[2].data, b'\x00')
|
||||||
|
|
||||||
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
|
||||||
"OPJ_DATA_ROOT environment variable not set")
|
|
||||||
def test_psot_is_zero(self):
|
def test_psot_is_zero(self):
|
||||||
"""Psot=0 in SOT is perfectly legal. Issue #78."""
|
"""Psot=0 in SOT is perfectly legal. Issue #78."""
|
||||||
filename = os.path.join(OPJ_DATA_ROOT,
|
filename = os.path.join(OPJ_DATA_ROOT,
|
||||||
|
|
@ -123,19 +148,6 @@ class TestCodestream(unittest.TestCase):
|
||||||
self.assertEqual(codestream.segment[-1].marker_id, 'EOC')
|
self.assertEqual(codestream.segment[-1].marker_id, 'EOC')
|
||||||
|
|
||||||
|
|
||||||
def test_siz_segment_ssiz_unsigned(self):
|
|
||||||
"""ssiz attribute to be removed in future release"""
|
|
||||||
j = Jp2k(self.jp2file)
|
|
||||||
codestream = j.get_codestream()
|
|
||||||
|
|
||||||
# The ssiz attribute was simply a tuple of raw bytes.
|
|
||||||
# The first 7 bits are interpreted as the bitdepth, the MSB determines
|
|
||||||
# whether or not it is signed.
|
|
||||||
self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7))
|
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
|
||||||
"OPJ_DATA_ROOT environment variable not set")
|
|
||||||
def test_siz_segment_ssiz_signed(self):
|
def test_siz_segment_ssiz_signed(self):
|
||||||
"""ssiz attribute to be removed in future release"""
|
"""ssiz attribute to be removed in future release"""
|
||||||
filename = os.path.join(OPJ_DATA_ROOT, 'input/conformance/p0_03.j2k')
|
filename = os.path.join(OPJ_DATA_ROOT, 'input/conformance/p0_03.j2k')
|
||||||
|
|
|
||||||
|
|
@ -645,6 +645,18 @@ class TestPrintingOpjDataRoot(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def test_invalid_progression_order(self):
|
||||||
|
"""Should still be able to print even if prog order is invalid."""
|
||||||
|
jfile = opj_data_file('input/nonregression/2977.pdf.asan.67.2198.jp2')
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
jp2 = Jp2k(jfile)
|
||||||
|
codestream = jp2.get_codestream()
|
||||||
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
print(codestream.segment[2])
|
||||||
|
actual = fake_out.getvalue().strip()
|
||||||
|
self.assertEqual(actual, fixtures.issue_186_progression_order)
|
||||||
|
|
||||||
def test_crg(self):
|
def test_crg(self):
|
||||||
"""verify printing of CRG segment"""
|
"""verify printing of CRG segment"""
|
||||||
filename = opj_data_file('input/conformance/p0_03.j2k')
|
filename = opj_data_file('input/conformance/p0_03.j2k')
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue