Merge branch 'issue204' into devel

This commit is contained in:
John Evans 2014-03-19 16:53:54 -04:00
commit 8a448600d2
3 changed files with 66 additions and 44 deletions

View file

@ -316,9 +316,6 @@ class ColourSpecificationBox(Jp2kBox):
self.method = method self.method = method
self.precedence = precedence self.precedence = precedence
if approximation not in (0, 1, 2, 3, 4):
warnings.warn("Invalid approximation: {0}".format(approximation))
self.approximation = approximation self.approximation = approximation
self.colorspace = colorspace self.colorspace = colorspace
@ -326,14 +323,19 @@ class ColourSpecificationBox(Jp2kBox):
self.length = length self.length = length
self.offset = offset self.offset = offset
self._validate() self._validate(writing=False)
def _validate(self): def _validate(self, writing=False):
"""Verify that the box obeys the specifications.""" """Verify that the box obeys the specifications."""
if self.colorspace is not None and self.icc_profile is not None: if self.colorspace is not None and self.icc_profile is not None:
raise IOError("colorspace and icc_profile cannot both be set.") msg = "Colorspace and icc_profile cannot both be set."
self._dispatch_validation_error(msg, writing=writing)
if self.method not in (1, 2, 3, 4): if self.method not in (1, 2, 3, 4):
raise IOError("Invalid method.") msg = "Invalid method.".format(self.method)
self._dispatch_validation_error(msg, writing=writing)
if self.approximation not in (0, 1, 2, 3, 4):
msg = "Invalid approximation: {0}".format(self.approximation)
self._dispatch_validation_error(msg, writing=writing)
def _write_validate(self): def _write_validate(self):
"""In addition to constructor validation steps, run validation steps """In addition to constructor validation steps, run validation steps
@ -341,15 +343,15 @@ class ColourSpecificationBox(Jp2kBox):
if self.colorspace is None: if self.colorspace is None:
msg = "Writing Colour Specification boxes without enumerated " msg = "Writing Colour Specification boxes without enumerated "
msg += "colorspaces is not supported at this time." msg += "colorspaces is not supported at this time."
raise IOError(msg) self._dispatch_validation_error(msg, writing=True)
if self.icc_profile is None: if self.icc_profile is None:
if self.colorspace not in [SRGB, GREYSCALE, YCC]: if self.colorspace not in [SRGB, GREYSCALE, YCC]:
msg = "Colorspace should correspond to one of SRGB, GREYSCALE, " msg = "Colorspace should correspond to one of SRGB, GREYSCALE, "
msg += "or YCC." msg += "or YCC."
raise IOError(msg) self._dispatch_validation_error(msg, writing=True)
self._validate() self._validate(writing=True)
def __repr__(self): def __repr__(self):
@ -607,15 +609,15 @@ class ChannelDefinitionBox(Jp2kBox):
self.channel_type = tuple(channel_type) self.channel_type = tuple(channel_type)
self.association = tuple(association) self.association = tuple(association)
self.__dict__.update(**kwargs) self.__dict__.update(**kwargs)
self._validate() self._validate(writing=False)
def _validate(self): def _validate(self, writing=False):
"""Verify that the box obeys the specifications.""" """Verify that the box obeys the specifications."""
# channel type and association must be specified. # channel type and association must be specified.
if not ((len(self.index) == len(self.channel_type)) and if not ((len(self.index) == len(self.channel_type)) and
(len(self.channel_type) == len(self.association))): (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) self._dispatch_validation_error(msg, writing=writing)
# 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 self.channel_type): if any(x not in [0, 1, 2, 65535] for x in self.channel_type):
@ -624,7 +626,7 @@ class ChannelDefinitionBox(Jp2kBox):
msg += " 1 - opacity\n" msg += " 1 - opacity\n"
msg += " 2 - premultiplied opacity\n" msg += " 2 - premultiplied opacity\n"
msg += " 65535 - unspecified" msg += " 65535 - unspecified"
raise IOError(msg) self._dispatch_validation_error(msg, writing=writing)
def __str__(self): def __str__(self):
@ -651,7 +653,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() self._validate(writing=True)
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'))
@ -993,23 +995,23 @@ class DataReferenceBox(Jp2kBox):
self.DR = data_entry_url_boxes self.DR = data_entry_url_boxes
self.length = length self.length = length
self.offset = offset self.offset = offset
self._validate() self._validate(writing=False)
def _validate(self): def _validate(self, writing=False):
"""Verify that the box obeys the specifications.""" """Verify that the box obeys the specifications."""
for box in self.DR: for box in self.DR:
if box.box_id != 'url ': if box.box_id != 'url ':
msg = 'All child boxes of a data reference box must be data ' msg = 'All child boxes of a data reference box must be data '
msg += 'entry URL boxes.' msg += 'entry URL boxes.'
raise IOError(msg) self._dispatch_validation_error(msg, writing=writing)
def _write_validate(self): def _write_validate(self):
"""Verify that the box obeys the specifications for writing. """Verify that the box obeys the specifications for writing.
""" """
if len(self.DR) == 0: if len(self.DR) == 0:
msg = "A data reference box cannot be empty when written to a file." msg = "A data reference box cannot be empty when written to a file."
raise IOError(msg) self._dispatch_validation_error(msg, writing=True)
self._validate() self._validate(writing=True)
def write(self, fptr): def write(self, fptr):
"""Write a Data Reference box to file. """Write a Data Reference box to file.
@ -1227,18 +1229,21 @@ class FragmentListBox(Jp2kBox):
self.data_reference = data_reference self.data_reference = data_reference
self.length = length self.length = length
self.offset = offset self.offset = offset
self._validate(writing=False)
def _validate(self): def _validate(self, writing=False):
"""Validate internal correctness.""" """Validate internal correctness."""
if (((len(self.fragment_offset) != len(self.fragment_length)) or if (((len(self.fragment_offset) != len(self.fragment_length)) or
(len(self.fragment_length) != len(self.data_reference)))): (len(self.fragment_length) != len(self.data_reference)))):
msg = "The lengths of the fragment offsets, fragment lengths, and " msg = "The lengths of the fragment offsets, fragment lengths, and "
msg += "data reference items must be the same." msg += "data reference items must be the same."
raise IOError(msg) self._dispatch_validation_error(msg, writing=writing)
if any([x <= 0 for x in self.fragment_offset]): if any([x <= 0 for x in self.fragment_offset]):
raise IOError("Fragment offsets must all be positive.") msg = "Fragment offsets must all be positive."
self._dispatch_validation_error(msg, writing=writing)
if any([x <= 0 for x in self.fragment_length]): if any([x <= 0 for x in self.fragment_length]):
raise IOError("Fragment lengths must all be positive.") msg = "Fragment lengths must all be positive."
self._dispatch_validation_error(msg, writing=writing)
def __repr__(self): def __repr__(self):
msg = "glymur.jp2box.FragmentListBox()" msg = "glymur.jp2box.FragmentListBox()"
@ -1262,7 +1267,7 @@ class FragmentListBox(Jp2kBox):
def write(self, fptr): def write(self, fptr):
"""Write a fragment list box to file. """Write a fragment list box to file.
""" """
self._validate() self._validate(writing=True)
num_items = len(self.fragment_offset) num_items = len(self.fragment_offset)
length = 8 + 2 + num_items * 14 length = 8 + 2 + num_items * 14
fptr.write(struct.pack('>I', length)) fptr.write(struct.pack('>I', length))
@ -1357,18 +1362,18 @@ class FragmentTableBox(Jp2kBox):
return box return box
def _validate(self): def _validate(self, writing=False):
"""Self-validate the box before writing.""" """Self-validate the box before writing."""
box_ids = [box.box_id for box in self.box] box_ids = [box.box_id for box in self.box]
if len(box_ids) != 1 or box_ids[0] != 'flst': if len(box_ids) != 1 or box_ids[0] != 'flst':
msg = "Fragment table boxes must have a single fragment list " msg = "Fragment table boxes must have a single fragment list "
msg += "box as a child box." msg += "box as a child box."
raise IOError(msg) self._dispatch_validation_error(msg, writing=writing)
def write(self, fptr): def write(self, fptr):
"""Write a fragment table box to file. """Write a fragment table box to file.
""" """
self._validate() self._validate(writing=True)
self._write_superbox(fptr) self._write_superbox(fptr)
@ -1779,15 +1784,15 @@ class PaletteBox(Jp2kBox):
self.signed = signed self.signed = signed
self.length = length self.length = length
self.offset = offset self.offset = offset
self._validate() self._validate(writing=False)
def _validate(self): def _validate(self, writing=False):
"""Verify that the box obeys the specifications.""" """Verify that the box obeys the specifications."""
if ((len(self.bits_per_component) != len(self.signed)) or if ((len(self.bits_per_component) != len(self.signed)) or
(len(self.signed) != self.palette.shape[1])): (len(self.signed) != self.palette.shape[1])):
msg = "The length of the 'bits_per_component' and the 'signed' " msg = "The length of the 'bits_per_component' and the 'signed' "
msg += "members must equal the number of columns of the palette." msg += "members must equal the number of columns of the palette."
raise IOError(msg) self._dispatch_validation_error(msg, writing=writing)
def __repr__(self): def __repr__(self):
msg = "glymur.jp2box.PaletteBox({0}, bits_per_component={1}, " msg = "glymur.jp2box.PaletteBox({0}, bits_per_component={1}, "
@ -1807,7 +1812,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() self._validate(writing=True)
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

@ -340,6 +340,7 @@ class TestChannelDefinition(unittest.TestCase):
with self.assertRaises((IOError, OSError)): with self.assertRaises((IOError, OSError)):
j2k.wrap(tfile.name, boxes=boxes) j2k.wrap(tfile.name, boxes=boxes)
@unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.")
def test_bad_type(self): def test_bad_type(self):
"""Channel types are limited to 0, 1, 2, 65535 """Channel types are limited to 0, 1, 2, 65535
Should reject if not all of index, channel_type, association the Should reject if not all of index, channel_type, association the
@ -347,17 +348,18 @@ class TestChannelDefinition(unittest.TestCase):
""" """
channel_type = (COLOR, COLOR, 3) channel_type = (COLOR, COLOR, 3)
association = (RED, GREEN, BLUE) association = (RED, GREEN, BLUE)
with self.assertRaises(IOError): with self.assertWarns(UserWarning):
glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type, glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
association=association) association=association)
@unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.")
def test_wrong_lengths(self): def test_wrong_lengths(self):
"""Should reject if not all of index, channel_type, association the """Should reject if not all of index, channel_type, association the
same length. same length.
""" """
channel_type = (COLOR, COLOR) channel_type = (COLOR, COLOR)
association = (RED, GREEN, BLUE) association = (RED, GREEN, BLUE)
with self.assertRaises(IOError): with self.assertWarns(UserWarning):
glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type, glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
association=association) association=association)
@ -443,19 +445,21 @@ class TestColourSpecificationBox(unittest.TestCase):
self.assertEqual(colr.colorspace, glymur.core.SRGB) self.assertEqual(colr.colorspace, glymur.core.SRGB)
self.assertIsNone(colr.icc_profile) self.assertIsNone(colr.icc_profile)
@unittest.skipIf(sys.hexversion < 0x03030000, "Requires 3.3+")
def test_colr_with_cspace_and_icc(self): def test_colr_with_cspace_and_icc(self):
"""Colour specification boxes can't have both.""" """Colour specification boxes can't have both."""
with self.assertRaises((OSError, IOError)): with self.assertWarns(UserWarning):
colorspace = glymur.core.SRGB colorspace = glymur.core.SRGB
rawb = b'\x01\x02\x03\x04' rawb = b'\x01\x02\x03\x04'
glymur.jp2box.ColourSpecificationBox(colorspace=colorspace, glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
icc_profile=rawb) icc_profile=rawb)
@unittest.skipIf(sys.hexversion < 0x03030000, "Requires 3.3+")
def test_colr_with_bad_method(self): def test_colr_with_bad_method(self):
"""colr must have a valid method field""" """colr must have a valid method field"""
colorspace = glymur.core.SRGB colorspace = glymur.core.SRGB
method = -1 method = -1
with self.assertRaises(IOError): with self.assertWarns(UserWarning):
glymur.jp2box.ColourSpecificationBox(colorspace=colorspace, glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
method=method) method=method)
@ -490,21 +494,23 @@ class TestPaletteBox(unittest.TestCase):
def tearDown(self): def tearDown(self):
pass pass
@unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.")
def test_mismatched_bitdepth_signed(self): def test_mismatched_bitdepth_signed(self):
"""bitdepth and signed arguments must have equal length""" """bitdepth and signed arguments must have equal length"""
palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint8) palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint8)
bps = (8, 8, 8) bps = (8, 8, 8)
signed = (False, False) signed = (False, False)
with self.assertRaises(IOError): with self.assertWarns(UserWarning):
pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps, pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps,
signed=signed) signed=signed)
@unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.")
def test_mismatched_signed_palette(self): def test_mismatched_signed_palette(self):
"""bitdepth and signed arguments must have equal length""" """bitdepth and signed arguments must have equal length"""
palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint8) palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint8)
bps = (8, 8, 8, 8) bps = (8, 8, 8, 8)
signed = (False, False, False, False) signed = (False, False, False, False)
with self.assertRaises(IOError): with self.assertWarns(UserWarning):
pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps, pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps,
signed=signed) signed=signed)

View file

@ -8,6 +8,8 @@ import struct
import sys import sys
import tempfile import tempfile
import unittest import unittest
import warnings
import lxml.etree as ET import lxml.etree as ET
import glymur import glymur
@ -207,13 +209,14 @@ class TestJPXWrap(unittest.TestCase):
with self.assertRaises(IOError): with self.assertRaises(IOError):
jp2.wrap(tfile.name, boxes=boxes) jp2.wrap(tfile.name, boxes=boxes)
@unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.")
def test_deurl_child_of_dtbl(self): def test_deurl_child_of_dtbl(self):
"""Data reference boxes can only contain data entry url boxes.""" """Data reference boxes can only contain data entry url boxes."""
jp2 = Jp2k(self.jp2file) jp2 = Jp2k(self.jp2file)
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
ftyp = glymur.jp2box.FileTypeBox() ftyp = glymur.jp2box.FileTypeBox()
with self.assertRaises(IOError): with self.assertWarns(UserWarning):
dref = glymur.jp2box.DataReferenceBox([ftyp]) dref = glymur.jp2box.DataReferenceBox([ftyp])
# Try to get around it by appending the ftyp box after creation. # Try to get around it by appending the ftyp box after creation.
@ -337,6 +340,8 @@ class TestJPX(unittest.TestCase):
offset = [89] offset = [89]
length = [1132288] length = [1132288]
reference = [0, 0] reference = [0, 0]
with warnings.catch_warnings():
warnings.simplefilter("ignore")
flst = glymur.jp2box.FragmentListBox(offset, length, reference) flst = glymur.jp2box.FragmentListBox(offset, length, reference)
with self.assertRaises(IOError): with self.assertRaises(IOError):
with tempfile.TemporaryFile() as tfile: with tempfile.TemporaryFile() as tfile:
@ -347,8 +352,10 @@ class TestJPX(unittest.TestCase):
offset = [0] offset = [0]
length = [1132288] length = [1132288]
reference = [0] reference = [0]
with warnings.catch_warnings():
warnings.simplefilter("ignore")
flst = glymur.jp2box.FragmentListBox(offset, length, reference) flst = glymur.jp2box.FragmentListBox(offset, length, reference)
with self.assertRaises(IOError): with self.assertRaises((IOError, OSError)):
with tempfile.TemporaryFile() as tfile: with tempfile.TemporaryFile() as tfile:
flst.write(tfile) flst.write(tfile)
@ -357,6 +364,8 @@ class TestJPX(unittest.TestCase):
offset = [89] offset = [89]
length = [0] length = [0]
reference = [0] reference = [0]
with warnings.catch_warnings():
warnings.simplefilter("ignore")
flst = glymur.jp2box.FragmentListBox(offset, length, reference) flst = glymur.jp2box.FragmentListBox(offset, length, reference)
with self.assertRaises(IOError): with self.assertRaises(IOError):
with tempfile.TemporaryFile() as tfile: with tempfile.TemporaryFile() as tfile:
@ -364,6 +373,8 @@ class TestJPX(unittest.TestCase):
def test_ftbl_boxes_empty(self): def test_ftbl_boxes_empty(self):
"""A fragment table box must have at least one child box.""" """A fragment table box must have at least one child box."""
with warnings.catch_warnings():
warnings.simplefilter("ignore")
ftbl = glymur.jp2box.FragmentTableBox() ftbl = glymur.jp2box.FragmentTableBox()
with self.assertRaises(IOError): with self.assertRaises(IOError):
with tempfile.TemporaryFile() as tfile: with tempfile.TemporaryFile() as tfile: