Merge branch 'issue118' into devel
This commit is contained in:
commit
a2685c359c
5 changed files with 552 additions and 223 deletions
|
|
@ -1849,11 +1849,29 @@ class XMLBox(Jp2kBox):
|
||||||
"""
|
"""
|
||||||
num_bytes = offset + length - fptr.tell()
|
num_bytes = offset + length - fptr.tell()
|
||||||
read_buffer = fptr.read(num_bytes)
|
read_buffer = fptr.read(num_bytes)
|
||||||
text = read_buffer.decode('utf-8')
|
try:
|
||||||
|
text = read_buffer.decode('utf-8')
|
||||||
|
except UnicodeDecodeError as ude:
|
||||||
|
# Possibly bad string of bytes to begin with.
|
||||||
|
# Try to search for <?xml and go from there.
|
||||||
|
decl_start = read_buffer.find(b'<?xml')
|
||||||
|
if decl_start > -1:
|
||||||
|
text = read_buffer[decl_start:].decode('utf-8')
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Let the user know that the XML box was problematic.
|
||||||
|
msg = 'A UnicodeDecodeError was encountered parsing an XML box at '
|
||||||
|
msg += 'byte position {0} ({1}), but the XML was still recovered.'
|
||||||
|
msg = msg.format(offset, ude.reason)
|
||||||
|
warnings.warn(msg, UserWarning)
|
||||||
|
|
||||||
|
|
||||||
# Strip out any trailing nulls, as they can foul up XML parsing.
|
# Strip out any trailing nulls, as they can foul up XML parsing.
|
||||||
text = text.rstrip(chr(0))
|
text = text.rstrip(chr(0))
|
||||||
|
|
||||||
|
# Scan for the start of the xml declaration.
|
||||||
|
|
||||||
try:
|
try:
|
||||||
elt = ET.fromstring(text)
|
elt = ET.fromstring(text)
|
||||||
xml = ET.ElementTree(elt)
|
xml = ET.ElementTree(elt)
|
||||||
|
|
|
||||||
|
|
@ -320,113 +320,6 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
association=association)
|
association=association)
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
|
||||||
class TestXML(unittest.TestCase):
|
|
||||||
"""Test suite for XML boxes."""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.jp2file = glymur.data.nemo()
|
|
||||||
self.j2kfile = glymur.data.goodstuff()
|
|
||||||
|
|
||||||
raw_xml = b"""<?xml version="1.0"?>
|
|
||||||
<data>
|
|
||||||
<country name="Liechtenstein">
|
|
||||||
<rank>1</rank>
|
|
||||||
<year>2008</year>
|
|
||||||
<gdppc>141100</gdppc>
|
|
||||||
<neighbor name="Austria" direction="E"/>
|
|
||||||
<neighbor name="Switzerland" direction="W"/>
|
|
||||||
</country>
|
|
||||||
<country name="Singapore">
|
|
||||||
<rank>4</rank>
|
|
||||||
<year>2011</year>
|
|
||||||
<gdppc>59900</gdppc>
|
|
||||||
<neighbor name="Malaysia" direction="N"/>
|
|
||||||
</country>
|
|
||||||
<country name="Panama">
|
|
||||||
<rank>68</rank>
|
|
||||||
<year>2011</year>
|
|
||||||
<gdppc>13600</gdppc>
|
|
||||||
<neighbor name="Costa Rica" direction="W"/>
|
|
||||||
<neighbor name="Colombia" direction="E"/>
|
|
||||||
</country>
|
|
||||||
</data>"""
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".xml", delete=False) as tfile:
|
|
||||||
tfile.write(raw_xml)
|
|
||||||
tfile.flush()
|
|
||||||
self.xmlfile = tfile.name
|
|
||||||
|
|
||||||
j2k = Jp2k(self.j2kfile)
|
|
||||||
codestream = j2k.get_codestream()
|
|
||||||
height = codestream.segment[1].ysiz
|
|
||||||
width = codestream.segment[1].xsiz
|
|
||||||
num_components = len(codestream.segment[1].xrsiz)
|
|
||||||
|
|
||||||
self.jp2b = JPEG2000SignatureBox()
|
|
||||||
self.ftyp = FileTypeBox()
|
|
||||||
self.jp2h = JP2HeaderBox()
|
|
||||||
self.jp2c = ContiguousCodestreamBox()
|
|
||||||
self.ihdr = ImageHeaderBox(height=height, width=width,
|
|
||||||
num_components=num_components)
|
|
||||||
self.colr = ColourSpecificationBox(colorspace=glymur.core.SRGB)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
os.unlink(self.xmlfile)
|
|
||||||
|
|
||||||
def test_negative_file_and_xml(self):
|
|
||||||
"""The XML should come from only one source."""
|
|
||||||
xml_object = ET.parse(self.xmlfile)
|
|
||||||
with self.assertRaises((IOError, OSError)):
|
|
||||||
glymur.jp2box.XMLBox(filename=self.xmlfile, xml=xml_object)
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt",
|
|
||||||
"Problems using NamedTemporaryFile on windows.")
|
|
||||||
def test_basic_xml(self):
|
|
||||||
"""Should be able to write a basic XMLBox"""
|
|
||||||
j2k = Jp2k(self.j2kfile)
|
|
||||||
|
|
||||||
self.jp2h.box = [self.ihdr, self.colr]
|
|
||||||
|
|
||||||
the_xml = ET.fromstring('<?xml version="1.0"?><data>0</data>')
|
|
||||||
xmlb = glymur.jp2box.XMLBox(xml=the_xml)
|
|
||||||
self.assertEqual(ET.tostring(xmlb.xml),
|
|
||||||
b'<data>0</data>')
|
|
||||||
|
|
||||||
boxes = [self.jp2b, self.ftyp, self.jp2h, xmlb, self.jp2c]
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
|
||||||
j2k.wrap(tfile.name, boxes=boxes)
|
|
||||||
jp2 = Jp2k(tfile.name)
|
|
||||||
self.assertEqual(jp2.box[3].box_id, 'xml ')
|
|
||||||
self.assertEqual(ET.tostring(jp2.box[3].xml.getroot()),
|
|
||||||
b'<data>0</data>')
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt",
|
|
||||||
"Problems using NamedTemporaryFile on windows.")
|
|
||||||
def test_xml_from_file(self):
|
|
||||||
"""Must be able to create an XML box from an XML file."""
|
|
||||||
j2k = Jp2k(self.j2kfile)
|
|
||||||
|
|
||||||
self.jp2h.box = [self.ihdr, self.colr]
|
|
||||||
|
|
||||||
xmlb = glymur.jp2box.XMLBox(filename=self.xmlfile)
|
|
||||||
boxes = [self.jp2b, self.ftyp, self.jp2h, xmlb, self.jp2c]
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
|
||||||
j2k.wrap(tfile.name, boxes=boxes)
|
|
||||||
jp2 = Jp2k(tfile.name)
|
|
||||||
|
|
||||||
output_boxes = [box.box_id for box in jp2.box]
|
|
||||||
self.assertEqual(output_boxes, ['jP ', 'ftyp', 'jp2h', 'xml ',
|
|
||||||
'jp2c'])
|
|
||||||
|
|
||||||
elts = jp2.box[3].xml.findall('country')
|
|
||||||
self.assertEqual(len(elts), 3)
|
|
||||||
|
|
||||||
neighbor = elts[1].find('neighbor')
|
|
||||||
self.assertEqual(neighbor.attrib['name'], 'Malaysia')
|
|
||||||
self.assertEqual(neighbor.attrib['direction'], 'N')
|
|
||||||
|
|
||||||
|
|
||||||
class TestColourSpecificationBox(unittest.TestCase):
|
class TestColourSpecificationBox(unittest.TestCase):
|
||||||
"""Test suite for colr box instantiation."""
|
"""Test suite for colr box instantiation."""
|
||||||
|
|
||||||
|
|
@ -856,5 +749,264 @@ class TestJpxBoxes(unittest.TestCase):
|
||||||
self.assertEqual(len(jpx.box[5].box), 0)
|
self.assertEqual(len(jpx.box[5].box), 0)
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
|
class TestChannelDefinition(unittest.TestCase):
|
||||||
|
"""Test suite for channel definition boxes."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
"""Need a one_plane plane image for greyscale testing."""
|
||||||
|
j2k = Jp2k(glymur.data.goodstuff())
|
||||||
|
data = j2k.read()
|
||||||
|
# Write the first component back out to file.
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".j2k", delete=False) as tfile:
|
||||||
|
grey_j2k = Jp2k(tfile.name, 'wb')
|
||||||
|
grey_j2k.write(data[:, :, 0])
|
||||||
|
cls.one_plane = tfile.name
|
||||||
|
# Write the first two components back out to file.
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".j2k", delete=False) as tfile:
|
||||||
|
grey_j2k = Jp2k(tfile.name, 'wb')
|
||||||
|
grey_j2k.write(data[:, :, 0:1])
|
||||||
|
cls.two_planes = tfile.name
|
||||||
|
# Write four components back out to file.
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".j2k", delete=False) as tfile:
|
||||||
|
rgba_jp2 = Jp2k(tfile.name, 'wb')
|
||||||
|
shape = (data.shape[0], data.shape[1], 1)
|
||||||
|
alpha = np.zeros((shape), dtype=data.dtype)
|
||||||
|
data4 = np.concatenate((data, alpha), axis=2)
|
||||||
|
rgba_jp2.write(data4)
|
||||||
|
cls.four_planes = tfile.name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
os.unlink(cls.one_plane)
|
||||||
|
os.unlink(cls.two_planes)
|
||||||
|
os.unlink(cls.four_planes)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.jp2file = glymur.data.nemo()
|
||||||
|
self.j2kfile = glymur.data.goodstuff()
|
||||||
|
|
||||||
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
codestream = j2k.get_codestream()
|
||||||
|
height = codestream.segment[1].ysiz
|
||||||
|
width = codestream.segment[1].xsiz
|
||||||
|
num_components = len(codestream.segment[1].xrsiz)
|
||||||
|
|
||||||
|
self.jp2b = JPEG2000SignatureBox()
|
||||||
|
self.ftyp = FileTypeBox()
|
||||||
|
self.jp2h = JP2HeaderBox()
|
||||||
|
self.jp2c = ContiguousCodestreamBox()
|
||||||
|
self.ihdr = ImageHeaderBox(height=height, width=width,
|
||||||
|
num_components=num_components)
|
||||||
|
self.colr_rgb = ColourSpecificationBox(colorspace=glymur.core.SRGB)
|
||||||
|
self.colr_gr = ColourSpecificationBox(colorspace=glymur.core.GREYSCALE)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_cdef_no_inputs(self):
|
||||||
|
"""channel_type and association are required inputs."""
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
glymur.jp2box.ChannelDefinitionBox()
|
||||||
|
|
||||||
|
def test_rgb_with_index(self):
|
||||||
|
"""Just regular RGB."""
|
||||||
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
channel_type = [COLOR, COLOR, COLOR]
|
||||||
|
association = [RED, GREEN, BLUE]
|
||||||
|
cdef = glymur.jp2box.ChannelDefinitionBox(index=[0, 1, 2],
|
||||||
|
channel_type=channel_type,
|
||||||
|
association=association)
|
||||||
|
boxes = [self.ihdr, self.colr_rgb, cdef]
|
||||||
|
self.jp2h.box = boxes
|
||||||
|
boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
jp2 = Jp2k(tfile.name)
|
||||||
|
jp2h = jp2.box[2]
|
||||||
|
boxes = [box.box_id for box in jp2h.box]
|
||||||
|
self.assertEqual(boxes, ['ihdr', 'colr', 'cdef'])
|
||||||
|
self.assertEqual(jp2h.box[2].index, (0, 1, 2))
|
||||||
|
self.assertEqual(jp2h.box[2].channel_type,
|
||||||
|
(COLOR, COLOR, COLOR))
|
||||||
|
self.assertEqual(jp2h.box[2].association,
|
||||||
|
(RED, GREEN, BLUE))
|
||||||
|
|
||||||
|
def test_rgb(self):
|
||||||
|
"""Just regular RGB, but don't supply the optional index."""
|
||||||
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
channel_type = [COLOR, COLOR, COLOR]
|
||||||
|
association = [RED, GREEN, BLUE]
|
||||||
|
cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
|
association=association)
|
||||||
|
boxes = [self.ihdr, self.colr_rgb, cdef]
|
||||||
|
self.jp2h.box = boxes
|
||||||
|
boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
jp2 = Jp2k(tfile.name)
|
||||||
|
jp2h = jp2.box[2]
|
||||||
|
boxes = [box.box_id for box in jp2h.box]
|
||||||
|
self.assertEqual(boxes, ['ihdr', 'colr', 'cdef'])
|
||||||
|
self.assertEqual(jp2h.box[2].index, (0, 1, 2))
|
||||||
|
self.assertEqual(jp2h.box[2].channel_type,
|
||||||
|
(COLOR, COLOR, COLOR))
|
||||||
|
self.assertEqual(jp2h.box[2].association,
|
||||||
|
(RED, GREEN, BLUE))
|
||||||
|
|
||||||
|
def test_rgba(self):
|
||||||
|
"""Just regular RGBA."""
|
||||||
|
j2k = Jp2k(self.four_planes)
|
||||||
|
channel_type = (COLOR, COLOR, COLOR, OPACITY)
|
||||||
|
association = (RED, GREEN, BLUE, WHOLE_IMAGE)
|
||||||
|
cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
|
association=association)
|
||||||
|
boxes = [self.ihdr, self.colr_rgb, cdef]
|
||||||
|
self.jp2h.box = boxes
|
||||||
|
boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
jp2 = Jp2k(tfile.name)
|
||||||
|
jp2h = jp2.box[2]
|
||||||
|
boxes = [box.box_id for box in jp2h.box]
|
||||||
|
self.assertEqual(boxes, ['ihdr', 'colr', 'cdef'])
|
||||||
|
self.assertEqual(jp2h.box[2].index, (0, 1, 2, 3))
|
||||||
|
self.assertEqual(jp2h.box[2].channel_type, channel_type)
|
||||||
|
self.assertEqual(jp2h.box[2].association, association)
|
||||||
|
|
||||||
|
def test_bad_rgba(self):
|
||||||
|
"""R, G, and B must be specified."""
|
||||||
|
j2k = Jp2k(self.four_planes)
|
||||||
|
channel_type = (COLOR, COLOR, OPACITY, OPACITY)
|
||||||
|
association = (RED, GREEN, BLUE, WHOLE_IMAGE)
|
||||||
|
cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
|
association=association)
|
||||||
|
boxes = [self.ihdr, self.colr_rgb, cdef]
|
||||||
|
self.jp2h.box = boxes
|
||||||
|
boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
def test_grey(self):
|
||||||
|
"""Just regular greyscale."""
|
||||||
|
j2k = Jp2k(self.one_plane)
|
||||||
|
channel_type = (COLOR,)
|
||||||
|
association = (GREY,)
|
||||||
|
cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
|
association=association)
|
||||||
|
boxes = [self.ihdr, self.colr_gr, cdef]
|
||||||
|
self.jp2h.box = boxes
|
||||||
|
boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
jp2 = Jp2k(tfile.name)
|
||||||
|
jp2h = jp2.box[2]
|
||||||
|
boxes = [box.box_id for box in jp2h.box]
|
||||||
|
self.assertEqual(boxes, ['ihdr', 'colr', 'cdef'])
|
||||||
|
self.assertEqual(jp2h.box[2].index, (0,))
|
||||||
|
self.assertEqual(jp2h.box[2].channel_type, channel_type)
|
||||||
|
self.assertEqual(jp2h.box[2].association, association)
|
||||||
|
|
||||||
|
def test_grey_alpha(self):
|
||||||
|
"""Just regular greyscale plus alpha."""
|
||||||
|
j2k = Jp2k(self.two_planes)
|
||||||
|
channel_type = (COLOR, OPACITY)
|
||||||
|
association = (GREY, WHOLE_IMAGE)
|
||||||
|
cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
|
association=association)
|
||||||
|
boxes = [self.ihdr, self.colr_gr, cdef]
|
||||||
|
self.jp2h.box = boxes
|
||||||
|
boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
jp2 = Jp2k(tfile.name)
|
||||||
|
jp2h = jp2.box[2]
|
||||||
|
boxes = [box.box_id for box in jp2h.box]
|
||||||
|
self.assertEqual(boxes, ['ihdr', 'colr', 'cdef'])
|
||||||
|
self.assertEqual(jp2h.box[2].index, (0, 1))
|
||||||
|
self.assertEqual(jp2h.box[2].channel_type, channel_type)
|
||||||
|
self.assertEqual(jp2h.box[2].association, association)
|
||||||
|
|
||||||
|
def test_bad_grey_alpha(self):
|
||||||
|
"""A greyscale image with alpha layer must specify a color channel"""
|
||||||
|
j2k = Jp2k(self.two_planes)
|
||||||
|
|
||||||
|
channel_type = (OPACITY, OPACITY)
|
||||||
|
association = (GREY, WHOLE_IMAGE)
|
||||||
|
|
||||||
|
# This cdef box
|
||||||
|
cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
|
association=association)
|
||||||
|
boxes = [self.ihdr, self.colr_gr, cdef]
|
||||||
|
self.jp2h.box = boxes
|
||||||
|
boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
|
with self.assertRaises((OSError, IOError)):
|
||||||
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
def test_only_one_cdef_in_jp2h(self):
|
||||||
|
"""There can only be one channel definition box in the jp2 header."""
|
||||||
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
|
||||||
|
channel_type = (COLOR, COLOR, COLOR)
|
||||||
|
association = (RED, GREEN, BLUE)
|
||||||
|
cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
|
association=association)
|
||||||
|
|
||||||
|
boxes = [self.ihdr, cdef, self.colr_rgb, cdef]
|
||||||
|
self.jp2h.box = boxes
|
||||||
|
|
||||||
|
boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
def test_not_in_jp2h(self):
|
||||||
|
"""need cdef in jp2h"""
|
||||||
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
boxes = [self.ihdr, self.colr_rgb]
|
||||||
|
self.jp2h.box = boxes
|
||||||
|
|
||||||
|
channel_type = (COLOR, COLOR, COLOR)
|
||||||
|
association = (RED, GREEN, BLUE)
|
||||||
|
cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
|
association=association)
|
||||||
|
|
||||||
|
boxes = [self.jp2b, self.ftyp, self.jp2h, cdef, self.jp2c]
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
def test_bad_type(self):
|
||||||
|
"""Channel types are limited to 0, 1, 2, 65535
|
||||||
|
Should reject if not all of index, channel_type, association the
|
||||||
|
same length.
|
||||||
|
"""
|
||||||
|
channel_type = (COLOR, COLOR, 3)
|
||||||
|
association = (RED, GREEN, BLUE)
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
|
association=association)
|
||||||
|
|
||||||
|
def test_wrong_lengths(self):
|
||||||
|
"""Should reject if not all of index, channel_type, association the
|
||||||
|
same length.
|
||||||
|
"""
|
||||||
|
channel_type = (COLOR, COLOR)
|
||||||
|
association = (RED, GREEN, BLUE)
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
|
association=association)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
266
glymur/test/test_jp2box_xml.py
Normal file
266
glymur/test/test_jp2box_xml.py
Normal file
|
|
@ -0,0 +1,266 @@
|
||||||
|
"""
|
||||||
|
Test suite specifically targeting JP2 box layout.
|
||||||
|
"""
|
||||||
|
# E1103: return value from read may be list or np array
|
||||||
|
# pylint: disable=E1103
|
||||||
|
|
||||||
|
# F0401: unittest2 is needed on python-2.6 (pylint on 2.7)
|
||||||
|
# pylint: disable=F0401
|
||||||
|
|
||||||
|
# R0902: More than 7 instance attributes are just fine for testing.
|
||||||
|
# pylint: disable=R0902
|
||||||
|
|
||||||
|
# R0904: Seems like pylint is fooled in this situation
|
||||||
|
# pylint: disable=R0904
|
||||||
|
|
||||||
|
# W0613: load_tests doesn't need to use ignore or loader arguments.
|
||||||
|
# pylint: disable=W0613
|
||||||
|
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import warnings
|
||||||
|
import xml.etree.cElementTree as ET
|
||||||
|
|
||||||
|
if sys.hexversion < 0x02070000:
|
||||||
|
import unittest2 as unittest
|
||||||
|
else:
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import glymur
|
||||||
|
from glymur import Jp2k
|
||||||
|
from glymur.jp2box import ColourSpecificationBox, ContiguousCodestreamBox
|
||||||
|
from glymur.jp2box import FileTypeBox, ImageHeaderBox, JP2HeaderBox
|
||||||
|
from glymur.jp2box import JPEG2000SignatureBox
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
|
class TestXML(unittest.TestCase):
|
||||||
|
"""Test suite for XML boxes."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.jp2file = glymur.data.nemo()
|
||||||
|
self.j2kfile = glymur.data.goodstuff()
|
||||||
|
|
||||||
|
raw_xml = b"""<?xml version="1.0"?>
|
||||||
|
<data>
|
||||||
|
<country name="Liechtenstein">
|
||||||
|
<rank>1</rank>
|
||||||
|
<year>2008</year>
|
||||||
|
<gdppc>141100</gdppc>
|
||||||
|
<neighbor name="Austria" direction="E"/>
|
||||||
|
<neighbor name="Switzerland" direction="W"/>
|
||||||
|
</country>
|
||||||
|
<country name="Singapore">
|
||||||
|
<rank>4</rank>
|
||||||
|
<year>2011</year>
|
||||||
|
<gdppc>59900</gdppc>
|
||||||
|
<neighbor name="Malaysia" direction="N"/>
|
||||||
|
</country>
|
||||||
|
<country name="Panama">
|
||||||
|
<rank>68</rank>
|
||||||
|
<year>2011</year>
|
||||||
|
<gdppc>13600</gdppc>
|
||||||
|
<neighbor name="Costa Rica" direction="W"/>
|
||||||
|
<neighbor name="Colombia" direction="E"/>
|
||||||
|
</country>
|
||||||
|
</data>"""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".xml", delete=False) as tfile:
|
||||||
|
tfile.write(raw_xml)
|
||||||
|
tfile.flush()
|
||||||
|
self.xmlfile = tfile.name
|
||||||
|
|
||||||
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
codestream = j2k.get_codestream()
|
||||||
|
height = codestream.segment[1].ysiz
|
||||||
|
width = codestream.segment[1].xsiz
|
||||||
|
num_components = len(codestream.segment[1].xrsiz)
|
||||||
|
|
||||||
|
self.jp2b = JPEG2000SignatureBox()
|
||||||
|
self.ftyp = FileTypeBox()
|
||||||
|
self.jp2h = JP2HeaderBox()
|
||||||
|
self.jp2c = ContiguousCodestreamBox()
|
||||||
|
self.ihdr = ImageHeaderBox(height=height, width=width,
|
||||||
|
num_components=num_components)
|
||||||
|
self.colr = ColourSpecificationBox(colorspace=glymur.core.SRGB)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
os.unlink(self.xmlfile)
|
||||||
|
|
||||||
|
def test_negative_file_and_xml(self):
|
||||||
|
"""The XML should come from only one source."""
|
||||||
|
xml_object = ET.parse(self.xmlfile)
|
||||||
|
with self.assertRaises((IOError, OSError)):
|
||||||
|
glymur.jp2box.XMLBox(filename=self.xmlfile, xml=xml_object)
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt",
|
||||||
|
"Problems using NamedTemporaryFile on windows.")
|
||||||
|
def test_basic_xml(self):
|
||||||
|
"""Should be able to write a basic XMLBox"""
|
||||||
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
|
||||||
|
self.jp2h.box = [self.ihdr, self.colr]
|
||||||
|
|
||||||
|
the_xml = ET.fromstring('<?xml version="1.0"?><data>0</data>')
|
||||||
|
xmlb = glymur.jp2box.XMLBox(xml=the_xml)
|
||||||
|
self.assertEqual(ET.tostring(xmlb.xml),
|
||||||
|
b'<data>0</data>')
|
||||||
|
|
||||||
|
boxes = [self.jp2b, self.ftyp, self.jp2h, xmlb, self.jp2c]
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
jp2 = Jp2k(tfile.name)
|
||||||
|
self.assertEqual(jp2.box[3].box_id, 'xml ')
|
||||||
|
self.assertEqual(ET.tostring(jp2.box[3].xml.getroot()),
|
||||||
|
b'<data>0</data>')
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt",
|
||||||
|
"Problems using NamedTemporaryFile on windows.")
|
||||||
|
def test_xml_from_file(self):
|
||||||
|
"""Must be able to create an XML box from an XML file."""
|
||||||
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
|
||||||
|
self.jp2h.box = [self.ihdr, self.colr]
|
||||||
|
|
||||||
|
xmlb = glymur.jp2box.XMLBox(filename=self.xmlfile)
|
||||||
|
boxes = [self.jp2b, self.ftyp, self.jp2h, xmlb, self.jp2c]
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
jp2 = Jp2k(tfile.name)
|
||||||
|
|
||||||
|
output_boxes = [box.box_id for box in jp2.box]
|
||||||
|
self.assertEqual(output_boxes, ['jP ', 'ftyp', 'jp2h', 'xml ',
|
||||||
|
'jp2c'])
|
||||||
|
|
||||||
|
elts = jp2.box[3].xml.findall('country')
|
||||||
|
self.assertEqual(len(elts), 3)
|
||||||
|
|
||||||
|
neighbor = elts[1].find('neighbor')
|
||||||
|
self.assertEqual(neighbor.attrib['name'], 'Malaysia')
|
||||||
|
self.assertEqual(neighbor.attrib['direction'], 'N')
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
|
class TestJp2kBadXmlFile(unittest.TestCase):
|
||||||
|
"""Test suite for bad XML box situations"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
"""Setup a JP2 file with a bad XML box. We only need to do this once
|
||||||
|
per class rather than once per test.
|
||||||
|
"""
|
||||||
|
jp2file = glymur.data.nemo()
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile:
|
||||||
|
cls._bad_xml_file = tfile.name
|
||||||
|
with open(jp2file, 'rb') as ifile:
|
||||||
|
# Everything up until the UUID box.
|
||||||
|
write_buffer = ifile.read(77)
|
||||||
|
tfile.write(write_buffer)
|
||||||
|
|
||||||
|
# Write the xml box with bad xml
|
||||||
|
# Length = 28, id is 'xml '.
|
||||||
|
write_buffer = struct.pack('>I4s', int(28), b'xml ')
|
||||||
|
tfile.write(write_buffer)
|
||||||
|
|
||||||
|
write_buffer = '<test>this is a test'
|
||||||
|
write_buffer = write_buffer.encode()
|
||||||
|
tfile.write(write_buffer)
|
||||||
|
|
||||||
|
# Get the rest of the input file.
|
||||||
|
write_buffer = ifile.read()
|
||||||
|
tfile.write(write_buffer)
|
||||||
|
tfile.flush()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
os.unlink(cls._bad_xml_file)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.jp2file = glymur.data.nemo()
|
||||||
|
self.j2kfile = glymur.data.goodstuff()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.hexversion < 0x03020000,
|
||||||
|
"Uses features introduced in 3.2.")
|
||||||
|
def test_invalid_xml_box_warning(self):
|
||||||
|
"""Should warn in case of bad XML"""
|
||||||
|
with self.assertWarns(UserWarning):
|
||||||
|
Jp2k(self._bad_xml_file)
|
||||||
|
|
||||||
|
def test_invalid_xml_box(self):
|
||||||
|
"""Should be able to recover info from xml box with bad xml."""
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
jp2k = Jp2k(self._bad_xml_file)
|
||||||
|
|
||||||
|
self.assertEqual(jp2k.box[3].box_id, 'xml ')
|
||||||
|
self.assertEqual(jp2k.box[3].offset, 77)
|
||||||
|
self.assertEqual(jp2k.box[3].length, 28)
|
||||||
|
self.assertIsNone(jp2k.box[3].xml)
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
|
class TestBadButRecoverableXmlFile(unittest.TestCase):
|
||||||
|
"""Test suite for XML box that is bad, but we can still recover the XML."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
"""Setup a JP2 file with bad bytes preceding the XML. We only need
|
||||||
|
to do this once per class rather than once per test.
|
||||||
|
"""
|
||||||
|
jp2file = glymur.data.nemo()
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile:
|
||||||
|
cls._bad_xml_file = tfile.name
|
||||||
|
with open(jp2file, 'rb') as ifile:
|
||||||
|
# Everything up until the UUID box.
|
||||||
|
write_buffer = ifile.read(77)
|
||||||
|
tfile.write(write_buffer)
|
||||||
|
|
||||||
|
# Write the xml box with bad xml
|
||||||
|
# Length = 64, id is 'xml '.
|
||||||
|
write_buffer = struct.pack('>I4s', int(64), b'xml ')
|
||||||
|
tfile.write(write_buffer)
|
||||||
|
|
||||||
|
# Write out 8 bad bytes.
|
||||||
|
write_buffer = b'\x00\x00\x07\x90xml '
|
||||||
|
tfile.write(write_buffer)
|
||||||
|
|
||||||
|
# Write out 48 good bytes constituting the XML payload.
|
||||||
|
write_buffer = b'<?xml version="1.0"?>'
|
||||||
|
tfile.write(write_buffer)
|
||||||
|
write_buffer = b'<test>this is a test</test>'
|
||||||
|
tfile.write(write_buffer)
|
||||||
|
|
||||||
|
# Get the rest of the input file.
|
||||||
|
write_buffer = ifile.read()
|
||||||
|
tfile.write(write_buffer)
|
||||||
|
tfile.flush()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
os.unlink(cls._bad_xml_file)
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.hexversion < 0x03020000,
|
||||||
|
"Uses features introduced in 3.2.")
|
||||||
|
def test_bad_xml_box_warning(self):
|
||||||
|
"""Should warn in case of bad XML"""
|
||||||
|
with self.assertWarns(UserWarning):
|
||||||
|
Jp2k(self._bad_xml_file)
|
||||||
|
|
||||||
|
def test_recover_from_bad_xml(self):
|
||||||
|
"""Should be able to recover info from xml box with bad xml."""
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
jp2 = Jp2k(self._bad_xml_file)
|
||||||
|
|
||||||
|
self.assertEqual(jp2.box[3].box_id, 'xml ')
|
||||||
|
self.assertEqual(jp2.box[3].offset, 77)
|
||||||
|
self.assertEqual(jp2.box[3].length, 64)
|
||||||
|
self.assertEqual(ET.tostring(jp2.box[3].xml.getroot()),
|
||||||
|
b'<test>this is a test</test>')
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -55,71 +55,8 @@ def load_tests(loader, tests, ignore):
|
||||||
return tests
|
return tests
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None and
|
||||||
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
|
not OPENJP2_IS_V2_OFFICIAL,
|
||||||
"Missing openjp2 library.")
|
|
||||||
class TestJp2kBadXmlFile(unittest.TestCase):
|
|
||||||
"""Test suite for bad XML box situations"""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpClass(cls):
|
|
||||||
"""Setup a JP2 file with a bad XML box. We only need to do this once
|
|
||||||
per class rather than once per test.
|
|
||||||
"""
|
|
||||||
jp2file = pkg_resources.resource_filename(glymur.__name__,
|
|
||||||
"data/nemo.jp2")
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile:
|
|
||||||
cls._bad_xml_file = tfile.name
|
|
||||||
with open(jp2file, 'rb') as ifile:
|
|
||||||
# Everything up until the jp2c box.
|
|
||||||
write_buffer = ifile.read(77)
|
|
||||||
tfile.write(write_buffer)
|
|
||||||
|
|
||||||
# Write the xml box with bad xml
|
|
||||||
# Length = 28, id is 'xml '.
|
|
||||||
write_buffer = struct.pack('>I4s', int(28), b'xml ')
|
|
||||||
tfile.write(write_buffer)
|
|
||||||
|
|
||||||
write_buffer = '<test>this is a test'
|
|
||||||
write_buffer = write_buffer.encode()
|
|
||||||
tfile.write(write_buffer)
|
|
||||||
|
|
||||||
# Get the rest of the input file.
|
|
||||||
write_buffer = ifile.read()
|
|
||||||
tfile.write(write_buffer)
|
|
||||||
tfile.flush()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def tearDownClass(cls):
|
|
||||||
os.unlink(cls._bad_xml_file)
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.jp2file = glymur.data.nemo()
|
|
||||||
self.j2kfile = glymur.data.goodstuff()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@unittest.skipIf(sys.hexversion < 0x03020000,
|
|
||||||
"Uses features introduced in 3.2.")
|
|
||||||
def test_invalid_xml_box_warning(self):
|
|
||||||
"""Should warn in case of bad XML"""
|
|
||||||
with self.assertWarns(UserWarning):
|
|
||||||
Jp2k(self._bad_xml_file)
|
|
||||||
|
|
||||||
def test_invalid_xml_box(self):
|
|
||||||
"""Should be able to recover info from xml box with bad xml."""
|
|
||||||
with warnings.catch_warnings():
|
|
||||||
warnings.simplefilter("ignore")
|
|
||||||
jp2k = Jp2k(self._bad_xml_file)
|
|
||||||
|
|
||||||
self.assertEqual(jp2k.box[3].box_id, 'xml ')
|
|
||||||
self.assertEqual(jp2k.box[3].offset, 77)
|
|
||||||
self.assertEqual(jp2k.box[3].length, 28)
|
|
||||||
self.assertIsNone(jp2k.box[3].xml)
|
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None and not OPENJP2_IS_V2_OFFICIAL,
|
|
||||||
"Missing openjp2 library version 2.0+.")
|
"Missing openjp2 library version 2.0+.")
|
||||||
class TestJp2k_2_1(unittest.TestCase):
|
class TestJp2k_2_1(unittest.TestCase):
|
||||||
"""Test suite for version 2.0+ of openjpeg software"""
|
"""Test suite for version 2.0+ of openjpeg software"""
|
||||||
|
|
@ -312,56 +249,6 @@ class TestJp2k_1_x(unittest.TestCase):
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
j.read(rlevel=6)
|
j.read(rlevel=6)
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_write_with_jp2_in_caps(self):
|
|
||||||
"""should be able to write with JP2 suffix."""
|
|
||||||
j2k = Jp2k(self.j2kfile)
|
|
||||||
expdata = j2k.read()
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.JP2') as tfile:
|
|
||||||
ofile = Jp2k(tfile.name, 'wb')
|
|
||||||
ofile.write(expdata)
|
|
||||||
actdata = ofile.read()
|
|
||||||
np.testing.assert_array_equal(actdata, expdata)
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_write_srgb_without_mct(self):
|
|
||||||
"""should be able to write RGB without specifying mct"""
|
|
||||||
j2k = Jp2k(self.j2kfile)
|
|
||||||
expdata = j2k.read()
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
ofile = Jp2k(tfile.name, 'wb')
|
|
||||||
ofile.write(expdata, mct=False)
|
|
||||||
actdata = ofile.read()
|
|
||||||
np.testing.assert_array_equal(actdata, expdata)
|
|
||||||
|
|
||||||
codestream = ofile.get_codestream()
|
|
||||||
self.assertEqual(codestream.segment[2].spcod[3], 0) # no mct
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_write_grayscale_with_mct(self):
|
|
||||||
"""MCT usage makes no sense for grayscale images."""
|
|
||||||
j2k = Jp2k(self.j2kfile)
|
|
||||||
expdata = j2k.read()
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
ofile = Jp2k(tfile.name, 'wb')
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
ofile.write(expdata[:, :, 0], mct=True)
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
|
||||||
def test_write_cprl(self):
|
|
||||||
"""Must be able to write a CPRL progression order file"""
|
|
||||||
# Issue 17
|
|
||||||
j = Jp2k(self.jp2file)
|
|
||||||
expdata = j.read(rlevel=1)
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
ofile = Jp2k(tfile.name, 'wb')
|
|
||||||
ofile.write(expdata, prog='CPRL')
|
|
||||||
actdata = ofile.read()
|
|
||||||
np.testing.assert_array_equal(actdata, expdata)
|
|
||||||
|
|
||||||
codestream = ofile.get_codestream()
|
|
||||||
self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL)
|
|
||||||
|
|
||||||
def test_jp2_boxes(self):
|
def test_jp2_boxes(self):
|
||||||
"""Verify the boxes of a JP2 file. Basic jp2 test."""
|
"""Verify the boxes of a JP2 file. Basic jp2 test."""
|
||||||
jp2k = Jp2k(self.jp2file)
|
jp2k = Jp2k(self.jp2file)
|
||||||
|
|
|
||||||
|
|
@ -6724,6 +6724,8 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
[8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10])
|
[8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10])
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
||||||
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1,
|
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1,
|
||||||
"Feature not supported in glymur until openjpeg 2.0")
|
"Feature not supported in glymur until openjpeg 2.0")
|
||||||
class TestSuite_bands(unittest.TestCase):
|
class TestSuite_bands(unittest.TestCase):
|
||||||
|
|
@ -6853,6 +6855,8 @@ class TestSuite_bands(unittest.TestCase):
|
||||||
self.assertTrue(True)
|
self.assertTrue(True)
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
||||||
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1,
|
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1,
|
||||||
"Tests not passing until 2.0")
|
"Tests not passing until 2.0")
|
||||||
class TestSuite2point0(unittest.TestCase):
|
class TestSuite2point0(unittest.TestCase):
|
||||||
|
|
@ -6915,6 +6919,8 @@ class TestSuite2point0(unittest.TestCase):
|
||||||
self.assertTrue(True)
|
self.assertTrue(True)
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
||||||
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL,
|
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL,
|
||||||
"Test not in done in v2.0.0 official")
|
"Test not in done in v2.0.0 official")
|
||||||
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1,
|
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue