More negative tests. Incompatible change to ChannelDefinitionBox. #175

This commit is contained in:
jevans 2014-02-22 21:30:52 -05:00
commit f8ced317db
5 changed files with 73 additions and 30 deletions

View file

@ -1,13 +1,14 @@
Feb 09, 2014 - Removed support for Python 2.6. Added write support for JP2 Feb 09, 2014 - Changed constructor for ChannelDefinition box. Removed support
UUID, DataEntryURL, Palette and Component Mapping boxes, JPX for Python 2.6. Added write support for JP2 UUID, DataEntryURL,
Association, NumberList and DataReference boxes. Added read Palette and Component Mapping boxes, JPX Association, NumberList
support for JPX free, number list, data reference, fragment and DataReference boxes. Added read support for JPX free,
table, and fragment list boxes. Improved JPX Reader Requirements box number list, data reference, fragment table, and fragment list
support. Added get_printoptions, set_printoptions functions. boxes. Improved JPX Reader Requirements box support. Added
Palette box now a 2D numpy array instead of a list of 1D arrays. get_printoptions, set_printoptions functions. Palette box now
JP2 super box constructors now take optional box list argument. a 2D numpy array instead of a list of 1D arrays. JP2 super box
Fixed bug where JPX files with more than one codestream but constructors now take optional box list argument. Fixed bug
advertising jp2 compatibility were not being read. 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 Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask
length is unsupported. length is unsupported.

View file

@ -11,6 +11,7 @@ ChangeLog
* added write support for JP2 UUID, dataEntryURL, palette, and component mapping boxes * 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/write support for JPX free, number list, and data reference boxes
* Added read support for JPX fragment list and fragment table 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 * 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) 0.5.0 (September 16, 2013)

View file

@ -359,7 +359,7 @@ channel, but we aren't doing that). ::
>>> from glymur.core import RED, GREEN, BLUE, WHOLE_IMAGE >>> from glymur.core import RED, GREEN, BLUE, WHOLE_IMAGE
>>> asoc = [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) >>> print(cdef)
Channel Definition Box (cdef) @ (0, 0) Channel Definition Box (cdef) @ (0, 0)
Channel 0 (color) ==> (1) Channel 0 (color) ==> (1)

View file

@ -544,23 +544,28 @@ class ChannelDefinitionBox(Jp2kBox):
association : list association : list
index of the associated color index of the associated color
""" """
def __init__(self, index=None, channel_type=None, association=None, def __init__(self, channel_type, association, index=None, **kwargs):
**kwargs):
Jp2kBox.__init__(self, box_id='cdef', longname='Channel Definition') 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: 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." msg = "Length of channel definition box inputs must be the same."
raise IOError(msg) raise IOError(msg)
# channel types must be one of 0, 1, 2, 65535 # 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 = "Channel types must be in the set of\n\n"
msg += " 0 - colour image data for associated color\n" msg += " 0 - colour image data for associated color\n"
msg += " 1 - opacity\n" msg += " 1 - opacity\n"
@ -568,10 +573,6 @@ class ChannelDefinitionBox(Jp2kBox):
msg += " 65535 - unspecified" msg += " 65535 - unspecified"
raise IOError(msg) raise IOError(msg)
self.index = tuple(index)
self.channel_type = tuple(channel_type)
self.association = tuple(association)
self.__dict__.update(**kwargs)
def __str__(self): def __str__(self):
msg = Jp2kBox.__str__(self) msg = Jp2kBox.__str__(self)
@ -597,6 +598,7 @@ class ChannelDefinitionBox(Jp2kBox):
def write(self, fptr): def write(self, fptr):
"""Write a channel definition box to file. """Write a channel definition box to file.
""" """
self._validate()
num_components = len(self.association) num_components = len(self.association)
fptr.write(struct.pack('>I', 8 + 2 + num_components * 6)) fptr.write(struct.pack('>I', 8 + 2 + num_components * 6))
fptr.write('cdef'.encode('utf-8')) fptr.write('cdef'.encode('utf-8'))
@ -634,9 +636,10 @@ class ChannelDefinitionBox(Jp2kBox):
channel_type = data[1:num_components * 6:3] channel_type = data[1:num_components * 6:3]
association = data[2:num_components * 6:3] association = data[2:num_components * 6:3]
box = ChannelDefinitionBox(index=index, channel_type=channel_type, box = ChannelDefinitionBox(index=tuple(index),
association=association, length=length, channel_type=tuple(channel_type),
offset=offset) association=tuple(association),
length=length, offset=offset)
return box return box
@ -795,7 +798,6 @@ class ComponentMappingBox(Jp2kBox):
if _printoptions['short'] == True: if _printoptions['short'] == True:
return msg return msg
for k in range(len(self.component_index)): for k in range(len(self.component_index)):
if self.mapping_type[k] == 1: if self.mapping_type[k] == 1:
msg += '\n Component {0} ==> palette column {1}' msg += '\n Component {0} ==> palette column {1}'
@ -1050,7 +1052,6 @@ class FileTypeBox(Jp2kBox):
self.brand = brand self.brand = brand
self.minor_version = minor_version self.minor_version = minor_version
if compatibility_list is None: if compatibility_list is None:
# see W0102, pylint
self.compatibility_list = ['jp2 '] self.compatibility_list = ['jp2 ']
else: else:
self.compatibility_list = compatibility_list self.compatibility_list = compatibility_list
@ -1718,6 +1719,15 @@ class PaletteBox(Jp2kBox):
self.signed = signed self.signed = signed
self.length = length self.length = length
self.offset = offset 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): def __repr__(self):
msg = "glymur.jp2box.PaletteBox({0}, bits_per_component={1}, " msg = "glymur.jp2box.PaletteBox({0}, bits_per_component={1}, "
@ -1737,6 +1747,7 @@ class PaletteBox(Jp2kBox):
def write(self, fptr): def write(self, fptr):
"""Write a Palette box to file. """Write a Palette box to file.
""" """
self._validate()
bytes_per_row = sum(self.bits_per_component) / 8 bytes_per_row = sum(self.bits_per_component) / 8
bytes_per_palette = bytes_per_row * self.palette.shape[0] bytes_per_palette = bytes_per_row * self.palette.shape[0]
box_length = 8 + 3 + self.palette.shape[1] + bytes_per_palette box_length = 8 + 3 + self.palette.shape[1] + bytes_per_palette

View file

@ -139,7 +139,7 @@ class TestChannelDefinition(unittest.TestCase):
def test_cdef_no_inputs(self): def test_cdef_no_inputs(self):
"""channel_type and association are required inputs.""" """channel_type and association are required inputs."""
with self.assertRaises(IOError): with self.assertRaises(TypeError):
glymur.jp2box.ChannelDefinitionBox() glymur.jp2box.ChannelDefinitionBox()
def test_rgb_with_index(self): def test_rgb_with_index(self):
@ -451,6 +451,36 @@ class TestColourSpecificationBox(unittest.TestCase):
colr.write(tfile) 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): class TestAppend(unittest.TestCase):
"""Tests for append method.""" """Tests for append method."""