From f8ced317dbf3da38861e69ede3e6fe86f5d35604 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 22 Feb 2014 21:30:52 -0500 Subject: [PATCH] More negative tests. Incompatible change to ChannelDefinitionBox. #175 --- CHANGES.txt | 21 +++++++++-------- docs/source/changelog.rst | 1 + docs/source/how_do_i.rst | 2 +- glymur/jp2box.py | 47 +++++++++++++++++++++++--------------- glymur/test/test_jp2box.py | 32 +++++++++++++++++++++++++- 5 files changed, 73 insertions(+), 30 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 1febdfb..24d9616 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,13 +1,14 @@ -Feb 09, 2014 - Removed support for Python 2.6. Added write support for JP2 - UUID, DataEntryURL, Palette and Component Mapping boxes, JPX - Association, NumberList and DataReference boxes. Added read - support for JPX free, number list, data reference, fragment - table, and fragment list boxes. Improved JPX Reader Requirements box - support. Added get_printoptions, set_printoptions functions. - Palette box now a 2D numpy array instead of a list of 1D arrays. - JP2 super box constructors now take optional box list argument. - Fixed bug where JPX files with more than one codestream but - advertising jp2 compatibility were not being read. +Feb 09, 2014 - Changed constructor for ChannelDefinition box. Removed support + for Python 2.6. Added write support for JP2 UUID, DataEntryURL, + Palette and Component Mapping boxes, JPX Association, NumberList + and DataReference boxes. Added read support for JPX free, + number list, data reference, fragment table, and fragment list + boxes. Improved JPX Reader Requirements box support. Added + get_printoptions, set_printoptions functions. Palette box now + a 2D numpy array instead of a list of 1D arrays. JP2 super box + constructors now take optional box list argument. Fixed bug + where JPX files with more than one codestream but advertising + jp2 compatibility were not being read. Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask length is unsupported. diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index f7e5921..ad4e40b 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -11,6 +11,7 @@ ChangeLog * added write support for JP2 UUID, dataEntryURL, palette, and component mapping boxes * added read/write support for JPX free, number list, and data reference boxes * Added read support for JPX fragment list and fragment table boxes + * incompatible change to channel definition box constructor, channel_type and association are no longer keyword arguments * incompatible change to palette box constructor, it now takes a 2D numpy array instead of a list of 1D arrays 0.5.0 (September 16, 2013) diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index a1fe089..fa8f098 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -359,7 +359,7 @@ channel, but we aren't doing that). :: >>> from glymur.core import RED, GREEN, BLUE, WHOLE_IMAGE >>> asoc = [RED, GREEN, BLUE, WHOLE_IMAGE] - >>> cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=ctype, association=asoc) + >>> cdef = glymur.jp2box.ChannelDefinitionBox(ctype, asoc) >>> print(cdef) Channel Definition Box (cdef) @ (0, 0) Channel 0 (color) ==> (1) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index f3fce20..f82151a 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -544,23 +544,28 @@ class ChannelDefinitionBox(Jp2kBox): association : list index of the associated color """ - def __init__(self, index=None, channel_type=None, association=None, - **kwargs): + def __init__(self, channel_type, association, index=None, **kwargs): Jp2kBox.__init__(self, box_id='cdef', longname='Channel Definition') - # channel type and association must be specified. - if channel_type is None or association is None: - raise IOError("channel_type and association must be specified.") - if index is None: - index = list(range(len(channel_type))) + self.index = tuple(range(len(channel_type))) + else: + self.index = tuple(index) - if len(index) != len(channel_type) or len(index) != len(association): + self.channel_type = tuple(channel_type) + self.association = tuple(association) + self.__dict__.update(**kwargs) + self._validate() + + def _validate(self): + """Verify that the box obeys the specifications.""" + # channel type and association must be specified. + if not (len(self.index) == len(self.channel_type) == len(self.association)): msg = "Length of channel definition box inputs must be the same." raise IOError(msg) # channel types must be one of 0, 1, 2, 65535 - if any(x not in [0, 1, 2, 65535] for x in channel_type): + if any(x not in [0, 1, 2, 65535] for x in self.channel_type): msg = "Channel types must be in the set of\n\n" msg += " 0 - colour image data for associated color\n" msg += " 1 - opacity\n" @@ -568,10 +573,6 @@ class ChannelDefinitionBox(Jp2kBox): msg += " 65535 - unspecified" raise IOError(msg) - self.index = tuple(index) - self.channel_type = tuple(channel_type) - self.association = tuple(association) - self.__dict__.update(**kwargs) def __str__(self): msg = Jp2kBox.__str__(self) @@ -597,6 +598,7 @@ class ChannelDefinitionBox(Jp2kBox): def write(self, fptr): """Write a channel definition box to file. """ + self._validate() num_components = len(self.association) fptr.write(struct.pack('>I', 8 + 2 + num_components * 6)) fptr.write('cdef'.encode('utf-8')) @@ -634,9 +636,10 @@ class ChannelDefinitionBox(Jp2kBox): channel_type = data[1:num_components * 6:3] association = data[2:num_components * 6:3] - box = ChannelDefinitionBox(index=index, channel_type=channel_type, - association=association, length=length, - offset=offset) + box = ChannelDefinitionBox(index=tuple(index), + channel_type=tuple(channel_type), + association=tuple(association), + length=length, offset=offset) return box @@ -795,7 +798,6 @@ class ComponentMappingBox(Jp2kBox): if _printoptions['short'] == True: return msg - for k in range(len(self.component_index)): if self.mapping_type[k] == 1: msg += '\n Component {0} ==> palette column {1}' @@ -1050,7 +1052,6 @@ class FileTypeBox(Jp2kBox): self.brand = brand self.minor_version = minor_version if compatibility_list is None: - # see W0102, pylint self.compatibility_list = ['jp2 '] else: self.compatibility_list = compatibility_list @@ -1718,6 +1719,15 @@ class PaletteBox(Jp2kBox): self.signed = signed self.length = length self.offset = offset + self._validate() + + def _validate(self): + """Verify that the box obeys the specifications.""" + if ((len(self.bits_per_component) != len(self.signed)) or + (len(self.signed) != self.palette.shape[1])): + msg = "The length of the 'bits_per_component' and the 'signed' " + msg += "members must equal the number of columns of the palette." + raise IOError(msg) def __repr__(self): msg = "glymur.jp2box.PaletteBox({0}, bits_per_component={1}, " @@ -1737,6 +1747,7 @@ class PaletteBox(Jp2kBox): def write(self, fptr): """Write a Palette box to file. """ + self._validate() bytes_per_row = sum(self.bits_per_component) / 8 bytes_per_palette = bytes_per_row * self.palette.shape[0] box_length = 8 + 3 + self.palette.shape[1] + bytes_per_palette diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index b6f0524..fb15e26 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -139,7 +139,7 @@ class TestChannelDefinition(unittest.TestCase): def test_cdef_no_inputs(self): """channel_type and association are required inputs.""" - with self.assertRaises(IOError): + with self.assertRaises(TypeError): glymur.jp2box.ChannelDefinitionBox() def test_rgb_with_index(self): @@ -451,6 +451,36 @@ class TestColourSpecificationBox(unittest.TestCase): colr.write(tfile) +@unittest.skipIf(os.name == "nt", + "Problems using NamedTemporaryFile on windows.") +class TestPaletteBox(unittest.TestCase): + """Test suite for pclr box instantiation.""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_mismatched_bitdepth_signed(self): + """bitdepth and signed arguments must have equal length""" + palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint8) + bps = (8, 8, 8) + signed = (False, False) + with self.assertRaises(IOError): + pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps, + signed=signed) + + def test_mismatched_signed_palette(self): + """bitdepth and signed arguments must have equal length""" + palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint8) + bps = (8, 8, 8, 8) + signed = (False, False, False, False) + with self.assertRaises(IOError): + pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps, + signed=signed) + + class TestAppend(unittest.TestCase): """Tests for append method."""