From 2ab86918986c277f37872c1166bb479d3d05251a Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 19 Mar 2014 11:22:44 -0400 Subject: [PATCH] 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. --- glymur/jp2box.py | 21 +++++++++++++++++---- glymur/test/test_icc.py | 6 +++++- glymur/test/test_jp2box.py | 9 +++++++-- glymur/test/test_jp2k.py | 12 +++++++++++- glymur/test/test_opj_suite.py | 10 ++++++++-- glymur/test/test_printing.py | 5 ++++- 6 files changed, 52 insertions(+), 11 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 7a8eb74..f3f0776 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -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()) diff --git a/glymur/test/test_icc.py b/glymur/test/test_icc.py index 0ef166b..c49055e 100644 --- a/glymur/test/test_icc.py +++ b/glymur/test/test_icc.py @@ -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) diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 9ed6292..5563085 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -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) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 3eab4e9..10363ab 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -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) diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 069884a..2a49b5d 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -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 ', diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index a6f1862..7a17e4e 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -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()