Added warning about incorrect ftyp brand. #194

While at it, added new infrastructure for either erroring or just
warning when an conformance issue is encountered.  If parsing a file, we
warn.  If about to write a file, we error out.
This commit is contained in:
John Evans 2014-03-19 11:22:44 -04:00
commit 2ab8691898
6 changed files with 52 additions and 11 deletions

View file

@ -85,6 +85,17 @@ class Jp2kBox(object):
msg += " @ ({0}, {1})".format(self.offset, self.length)
return msg
def _dispatch_validation_error(self, msg, writing=False):
"""Issue either a warning or an error depending on circumstance.
If writing to file, then error out, as we do not wish to create bad
JP2 files. If reading, then we should be more lenient and just warn.
"""
if writing:
raise IOError(msg)
else:
warnings.warn(msg)
def write(self, _):
"""Must be implemented in a subclass.
"""
@ -1102,6 +1113,7 @@ class FileTypeBox(Jp2kBox):
self.compatibility_list = compatibility_list
self.length = length
self.offset = offset
self._validate(writing=False)
def __repr__(self):
msg = "glymur.jp2box.FileTypeBox(brand='{0}', minor_version={1}, "
@ -1123,22 +1135,23 @@ class FileTypeBox(Jp2kBox):
return msg
def _validate(self):
def _validate(self, writing=False):
"""Validate the box before writing to file."""
if self.brand not in ['jp2 ', 'jpx ']:
msg = "The file type brand must be either 'jp2 ' or 'jpx '."
raise IOError(msg)
self._dispatch_validation_error(msg, writing=writing)
valid_cls = ['jp2 ', 'jpx ', 'jpxb']
for item in self.compatibility_list:
if item not in valid_cls:
msg = "The file type compatibility list item '{0}' is not "
msg += "valid: valid entries are {1}"
raise IOError(msg.format(item, valid_cls))
msg = msg.format(item, valid_cls)
self._dispatch_validation_error(msg, writing=writing)
def write(self, fptr):
"""Write a File Type box to file.
"""
self._validate()
self._validate(writing=True)
length = 16 + 4*len(self.compatibility_list)
fptr.write(struct.pack('>I', length))
fptr.write('ftyp'.encode())

View file

@ -9,6 +9,7 @@ import datetime
import os
import sys
import unittest
import warnings
import numpy as np
@ -30,7 +31,10 @@ class TestICC(unittest.TestCase):
def test_file5(self):
"""basic ICC profile"""
filename = opj_data_file('input/conformance/file5.jp2')
j = Jp2k(filename)
with warnings.catch_warnings():
# The file has a bad compatibility list entry. Not important here.
warnings.simplefilter("ignore")
j = Jp2k(filename)
profile = j.box[2].box[1].icc_profile
self.assertEqual(profile['Size'], 546)
self.assertEqual(profile['Preferred CMM Type'], 0)

View file

@ -373,14 +373,19 @@ class TestFileTypeBox(unittest.TestCase):
def test_brand_unknown(self):
"""A ftyp box brand must be 'jp2 ' or 'jpx '."""
ftyp = glymur.jp2box.FileTypeBox(brand='jp3')
with warnings.catch_warnings():
warnings.simplefilter("ignore")
ftyp = glymur.jp2box.FileTypeBox(brand='jp3')
with self.assertRaises(IOError):
with tempfile.TemporaryFile() as tfile:
ftyp.write(tfile)
def test_cl_entry_unknown(self):
"""A ftyp box cl list can only contain 'jp2 ', 'jpx ', or 'jpxb'."""
ftyp = glymur.jp2box.FileTypeBox(compatibility_list=['jp3'])
with warnings.catch_warnings():
# Bad compatibility list item.
warnings.simplefilter("ignore")
ftyp = glymur.jp2box.FileTypeBox(compatibility_list=['jp3'])
with self.assertRaises(IOError):
with tempfile.TemporaryFile() as tfile:
ftyp.write(tfile)

View file

@ -758,6 +758,13 @@ class TestJp2k_2_1(unittest.TestCase):
class TestJp2kOpjDataRoot(unittest.TestCase):
"""These tests should be run by just about all configuration."""
@unittest.skipIf(sys.hexversion < 0x03000000, "Test requires Python 3.3+")
def test_invalid_approximation(self):
"""Should warn in case of bad ftyp brand."""
filename = opj_data_file('input/nonregression/edf_c2_1000290.jp2')
with self.assertWarns(UserWarning):
jp2 = Jp2k(filename)
@unittest.skipIf(sys.hexversion < 0x03000000, "Test requires Python 3.3+")
def test_invalid_approximation(self):
"""Should warn in case of invalid approximation."""
@ -817,7 +824,10 @@ class TestJp2kOpjDataRoot(unittest.TestCase):
# This file has the components physically reversed. The cmap box
# tells the decoder how to order them, but this flag prevents that.
filename = opj_data_file('input/conformance/file2.jp2')
j = Jp2k(filename)
with warnings.catch_warnings():
# The file has a bad compatibility list entry. Not important here.
warnings.simplefilter("ignore")
j = Jp2k(filename)
ycbcr = j.read()
crcby = j.read(ignore_pclr_cmap_cdef=True)

View file

@ -327,7 +327,10 @@ class TestSuite(unittest.TestCase):
def test_ETS_JP2_file1(self):
jfile = opj_data_file('input/conformance/file1.jp2')
jp2k = Jp2k(jfile)
with warnings.catch_warnings():
# Bad compatibility list item.
warnings.simplefilter("ignore")
jp2k = Jp2k(jfile)
jpdata = jp2k.read()
self.assertEqual(jpdata.shape, (512, 768, 3))
@ -3114,7 +3117,10 @@ class TestSuiteDump(unittest.TestCase):
def test_NR_file1_dump(self):
jfile = opj_data_file('input/conformance/file1.jp2')
jp2 = Jp2k(jfile)
with warnings.catch_warnings():
# Bad compatibility list item.
warnings.simplefilter("ignore")
jp2 = Jp2k(jfile)
ids = [box.box_id for box in jp2.box]
self.assertEqual(ids, ['jP ', 'ftyp', 'xml ', 'jp2h', 'xml ',

View file

@ -863,7 +863,10 @@ class TestPrintingOpjDataRoot(unittest.TestCase):
def test_channel_definition(self):
"""verify printing of cdef box"""
filename = opj_data_file('input/conformance/file2.jp2')
j = glymur.Jp2k(filename)
with warnings.catch_warnings():
# Bad compatibility list item.
warnings.simplefilter("ignore")
j = glymur.Jp2k(filename)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.box[2].box[2])
actual = fake_out.getvalue().strip()