Merge branch 'release-0.4.1'
This commit is contained in:
commit
8b0c8a0198
16 changed files with 903 additions and 73 deletions
17
.travis.yml
Normal file
17
.travis.yml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.3"
|
||||
|
||||
before_install:
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq libopenjpeg2
|
||||
- sudo apt-get install -qq python-numpy
|
||||
- sudo apt-get install -qq python3-numpy
|
||||
|
||||
# command to install dependencies
|
||||
install:
|
||||
- pip install . --use-mirrors
|
||||
# command to run tests
|
||||
script:
|
||||
- "python -m unittest discover"
|
||||
|
|
@ -1,3 +1,10 @@
|
|||
Aug 21, 2013 - v0.4.1 Fixed segfault with openjpeg 1.x when rlevel=-1
|
||||
|
||||
Aug 18, 2013 - v0.4.0 Added append method.
|
||||
|
||||
Aug 15, 2013 - v0.3.2 Fixed test bug where missing Pillow package caused test
|
||||
failures.
|
||||
|
||||
Aug 14, 2013 - v0.3.1 Exposed mantissa, exponent, and guard_bits fields in QCC
|
||||
and QCD segments. Exposed layers and code_block_size in COD segment.
|
||||
Exposed precinct_size in COC segment.
|
||||
|
|
|
|||
|
|
@ -76,9 +76,9 @@ copyright = u'2013, John Evans'
|
|||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.3'
|
||||
version = '0.4'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.3.1'
|
||||
release = '0.4.1'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
|
|
|||
|
|
@ -137,13 +137,14 @@ In addition, you must install contextlib2 and Pillow via pip. ::
|
|||
|
||||
Windows
|
||||
-------
|
||||
32-bit WinPython 2.7.5 seems to work with OpenJPEG 1.X, 2.0, and the
|
||||
development version, but still requires contextlib2 and mock to be
|
||||
32-bit WinPython 2.7.5 seemed to work with OpenJPEG 1.X, 2.0, and the
|
||||
development version, but still required contextlib2 and mock to be
|
||||
installed via pip. WinPython 3.3.2, however, seems to have trouble
|
||||
with OpenJPEG 2.0, so I would suggest using the development version
|
||||
there (I'm unwilling to spend ANY more time trying to figure out what
|
||||
the problem is there).
|
||||
|
||||
At the moment I do not have access to a win32 machine, and
|
||||
64-bit windows is completely untested.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ How do I...?
|
|||
------------
|
||||
|
||||
|
||||
Read the lowest resolution thumbnail?
|
||||
read the lowest resolution thumbnail?
|
||||
=====================================
|
||||
Printing the Jp2k object should reveal the number of resolutions (look in the
|
||||
COD segment section), but you can take a shortcut by supplying -1 as the
|
||||
|
|
@ -14,7 +14,7 @@ resolution level. ::
|
|||
>>> j = glymur.Jp2k(file)
|
||||
>>> thumbnail = j.read(rlevel=-1)
|
||||
|
||||
Display metadata?
|
||||
display metadata?
|
||||
=================
|
||||
There are two ways. From the unix command line, the script *jp2dump* is
|
||||
available. ::
|
||||
|
|
@ -34,8 +34,40 @@ codestream box, only the main header is printed. It is possible to print
|
|||
|
||||
>>> print(j.get_codestream())
|
||||
|
||||
Add XML Metadata?
|
||||
add XML metadata?
|
||||
=================
|
||||
You can append any number of XML boxes to a JP2 file (not to a raw codestream).
|
||||
Consider the following XML file `data.xml` : ::
|
||||
|
||||
<?xml version="1.0"?>
|
||||
<info>
|
||||
<locality>
|
||||
<city>Boston</city>
|
||||
<snowfall>24.9 inches</snowfall>
|
||||
</locality>
|
||||
<locality>
|
||||
<city>Portland</city>
|
||||
<snowfall>31.9 inches</snowfall>
|
||||
</locality>
|
||||
<locality>
|
||||
<city>New York City</city>
|
||||
<snowfall>11.4 inches</snowfall>
|
||||
</locality>
|
||||
</info>
|
||||
|
||||
The **append** method can add an XML box as shown below::
|
||||
|
||||
>>> import shutil
|
||||
>>> import glymur
|
||||
>>> shutil.copyfile(glymur.data.nemo(), 'myfile.jp2')
|
||||
>>> from xml.etree import cElementTree as ET
|
||||
>>> jp2 = glymur.Jp2k('myfile.jp2')
|
||||
>>> xmlbox = glymur.jp2box.XMLBox(filename='data.xml')
|
||||
>>> jp2.append(xmlbox)
|
||||
>>> print(jp2)
|
||||
|
||||
add metadata in a more general fashion?
|
||||
=======================================
|
||||
An existing raw codestream (or JP2 file) can be wrapped (re-wrapped) in a
|
||||
user-defined set of JP2 boxes. To get just a minimal JP2 jacket on the
|
||||
codestream provided by `goodstuff.j2k` (a file consisting of a raw codestream),
|
||||
|
|
@ -75,8 +107,8 @@ two additional boxes (image header and color specification) contained in the
|
|||
JP2 header superbox.
|
||||
|
||||
XML boxes are not in the minimal set of box requirements for the JP2 format, so
|
||||
in order to add an XML box into the mix, we'll need to specify all of the
|
||||
boxes. If you already have a JP2 jacket in place, you can just reuse it,
|
||||
in order to add an XML box into the mix before the codestream box, we'll need to
|
||||
re-specify all of the boxes. If you already have a JP2 jacket in place, you can just reuse that,
|
||||
though. Take the following example content in an XML file `favorites.xml` : ::
|
||||
|
||||
<?xml version="1.0"?>
|
||||
|
|
@ -84,10 +116,11 @@ though. Take the following example content in an XML file `favorites.xml` : ::
|
|||
<category>Light Ale</category>
|
||||
</favorite_things>
|
||||
|
||||
and add it after the JP2 header box, but before the codestream box ::
|
||||
In order to add the XML after the JP2 header box, but before the codestream box,
|
||||
the following will work. ::
|
||||
|
||||
>>> boxes = jp2.box # The box attribute is the list of JP2 boxes
|
||||
>>> xmlbox = glymur.jp2box.XMLBox(file='favorites.xml')
|
||||
>>> xmlbox = glymur.jp2box.XMLBox(filename='favorites.xml')
|
||||
>>> boxes.insert(3, xmlbox)
|
||||
>>> jp2_xml = jp2.wrap("newfile_with_xml.jp2", boxes=boxes)
|
||||
>>> print(jp2_xml)
|
||||
|
|
@ -119,7 +152,12 @@ and add it after the JP2 header box, but before the codestream box ::
|
|||
. (truncated)
|
||||
.
|
||||
|
||||
Create an image with an alpha layer?
|
||||
As to the question of which method you should use, **append** or **wrap**,
|
||||
to add metadata, you should keep in mind that **wrap** produces a new JP2 file,
|
||||
while **append** modifies an existing file and is currently limited to XML
|
||||
boxes.
|
||||
|
||||
create an image with an alpha layer?
|
||||
====================================
|
||||
|
||||
OpenJPEG can create JP2 files with more than 3 components (requires
|
||||
|
|
@ -181,7 +219,7 @@ Here's how the Preview application on the mac shows the RGBA image.
|
|||
.. image:: goodstuff_alpha.png
|
||||
|
||||
|
||||
Work with XMP UUIDs?
|
||||
work with XMP UUIDs?
|
||||
====================
|
||||
The example JP2 file shipped with glymur has an XMP UUID. ::
|
||||
|
||||
|
|
|
|||
|
|
@ -1308,7 +1308,7 @@ class QCCsegment(Segment):
|
|||
self.offset = offset
|
||||
|
||||
self.mantissa, self.exponent = parse_quantization(self.spqcc,
|
||||
self.sqcc)
|
||||
self.sqcc)
|
||||
self.guard_bits = (self.sqcc & 0xe0) >> 5
|
||||
|
||||
def __str__(self):
|
||||
|
|
|
|||
|
|
@ -432,6 +432,42 @@ class Jp2k(Jp2kBox):
|
|||
# Refresh the metadata.
|
||||
self.parse()
|
||||
|
||||
def append(self, box):
|
||||
"""Append a JP2 box to the file in-place.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
box : Jp2Box
|
||||
Instance of a JP2 box. Currently only XML boxes are allowed.
|
||||
"""
|
||||
if self._codec_format == opj2.CODEC_J2K:
|
||||
msg = "Only JP2 files can currently have boxes appended to them."
|
||||
raise IOError(msg)
|
||||
|
||||
if box.box_id != 'xml ':
|
||||
raise IOError("Only XML boxes can currently be appended.")
|
||||
|
||||
# Check the last box. If the length field is zero, then rewrite
|
||||
# the length field to reflect the true length of the box.
|
||||
with open(self.filename, 'rb') as ifile:
|
||||
offset = self.box[-1].offset
|
||||
ifile.seek(offset)
|
||||
read_buffer = ifile.read(4)
|
||||
box_length, = struct.unpack('>I', read_buffer)
|
||||
if box_length == 0:
|
||||
# Reopen the file in write mode and rewrite the length field.
|
||||
true_box_length = os.path.getsize(ifile.name) - offset
|
||||
with open(self.filename, 'r+b') as ofile:
|
||||
ofile.seek(offset)
|
||||
write_buffer = struct.pack('>I', true_box_length)
|
||||
ofile.write(write_buffer)
|
||||
|
||||
# Can now safely append the box.
|
||||
with open(self.filename, 'ab') as ofile:
|
||||
box.write(ofile)
|
||||
|
||||
self.parse()
|
||||
|
||||
def wrap(self, filename, boxes=None):
|
||||
"""Write the codestream back out to file, wrapped in new JP2 jacket.
|
||||
|
||||
|
|
@ -600,6 +636,19 @@ class Jp2k(Jp2kBox):
|
|||
"""
|
||||
self._subsampling_sanity_check()
|
||||
|
||||
if rlevel != 0:
|
||||
# Must check the specified rlevel against the maximum.
|
||||
# OpenJPEG 1.3 will segfault if rlevel is too high.
|
||||
codestream = self.get_codestream()
|
||||
max_rlevel = codestream.segment[2].spcod[4]
|
||||
if rlevel == -1:
|
||||
# -1 is shorthand for the largest rlevel
|
||||
rlevel = max_rlevel
|
||||
if rlevel < -1 or rlevel > max_rlevel:
|
||||
msg = "rlevel must be in the range [-1, {0}] for this image."
|
||||
msg = msg.format(max_rlevel)
|
||||
raise IOError(msg)
|
||||
|
||||
with ExitStack() as stack:
|
||||
# Set decoding parameters.
|
||||
dparameters = opj.DecompressionParametersType()
|
||||
|
|
@ -1133,6 +1182,7 @@ def _unpack_colorspace(colorspace, img_array, cparams):
|
|||
|
||||
return colorspace
|
||||
|
||||
|
||||
def _populate_comptparms(img_array, cparams):
|
||||
"""Instantiate and populate comptparms structure.
|
||||
|
||||
|
|
@ -1171,6 +1221,7 @@ def _populate_comptparms(img_array, cparams):
|
|||
|
||||
return comptparms
|
||||
|
||||
|
||||
def _populate_image_struct(cparams, image, imgdata):
|
||||
"""Populates image struct needed for compression.
|
||||
|
||||
|
|
@ -1203,6 +1254,7 @@ def _populate_image_struct(cparams, image, imgdata):
|
|||
|
||||
return image
|
||||
|
||||
|
||||
def _validate_compression_params(img_array, cparams):
|
||||
"""Check that the compression parameters are valid.
|
||||
|
||||
|
|
@ -1313,4 +1365,3 @@ class LibraryNotFoundError(IOError):
|
|||
"""
|
||||
def __init__(self, msg):
|
||||
IOError.__init__(self, msg)
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,12 @@ NO_READ_BACKEND_MSG += "order to run the tests in this suite."
|
|||
|
||||
try:
|
||||
from matplotlib.pyplot import imread
|
||||
|
||||
# The whole point of trying to import PIL is to determine if it's there
|
||||
# or not. We won't use it directly.
|
||||
# pylint: disable=F0401,W0611
|
||||
import PIL
|
||||
|
||||
NO_READ_BACKEND = False
|
||||
except ImportError:
|
||||
NO_READ_BACKEND = True
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ class TestCallbacks15(unittest.TestCase):
|
|||
|
||||
def test_info_callbacks_on_read(self):
|
||||
"""Verify stdout when reading.
|
||||
|
||||
|
||||
Verify that we get the expected stdio output when our internal info
|
||||
callback handler is enabled.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ Test suite for codestream parsing.
|
|||
# unittest doesn't work well with R0904.
|
||||
# pylint: disable=R0904
|
||||
|
||||
# tempfile.TemporaryDirectory, unittest.assertWarns introduced in 3.2
|
||||
# tempfile.TemporaryDirectory, unittest.assertWarns introduced in 3.2
|
||||
# pylint: disable=E1101
|
||||
|
||||
# unittest2 is python2.6 only (pylint/python-2.7)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ OPENJP2 may be present in some form or other.
|
|||
# unittest doesn't work well with R0904.
|
||||
# pylint: disable=R0904
|
||||
|
||||
# tempfile.TemporaryDirectory, unittest.assertWarns introduced in 3.2
|
||||
# tempfile.TemporaryDirectory, unittest.assertWarns introduced in 3.2
|
||||
# pylint: disable=E1101
|
||||
|
||||
# unittest.mock only in Python 3.3 (python2.7/pylint import issue)
|
||||
|
|
@ -90,5 +90,48 @@ class TestSuite(unittest.TestCase):
|
|||
with self.assertWarns(UserWarning):
|
||||
imp.reload(glymur.lib.openjp2)
|
||||
|
||||
|
||||
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None and
|
||||
glymur.lib.openjpeg.OPENJPEG is None,
|
||||
"Missing openjp2 library.")
|
||||
class TestConfig(unittest.TestCase):
|
||||
"""Test suite for reading without proper library in place."""
|
||||
|
||||
def setUp(self):
|
||||
self.jp2file = glymur.data.nemo()
|
||||
self.j2kfile = glymur.data.goodstuff()
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_read_without_library(self):
|
||||
"""Don't have either openjp2 or openjpeg libraries? Must error out.
|
||||
"""
|
||||
with patch('glymur.lib.openjp2.OPENJP2', new=None):
|
||||
with patch('glymur.lib.openjpeg.OPENJPEG', new=None):
|
||||
with self.assertRaises(glymur.jp2k.LibraryNotFoundError):
|
||||
glymur.Jp2k(self.jp2file).read()
|
||||
|
||||
def test_read_bands_without_library(self):
|
||||
"""Don't have openjp2 library? Must error out.
|
||||
"""
|
||||
with patch('glymur.lib.openjp2.OPENJP2', new=None):
|
||||
with patch('glymur.lib.openjpeg.OPENJPEG', new=None):
|
||||
with self.assertRaises(glymur.jp2k.LibraryNotFoundError):
|
||||
glymur.Jp2k(self.jp2file).read_bands()
|
||||
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
def test_write_without_library(self):
|
||||
"""Don't have openjp2 library? Must error out.
|
||||
"""
|
||||
data = glymur.Jp2k(self.j2kfile).read()
|
||||
with patch('glymur.lib.openjp2.OPENJP2', new=None):
|
||||
with patch('glymur.lib.openjpeg.OPENJPEG', new=None):
|
||||
with self.assertRaises(glymur.jp2k.LibraryNotFoundError):
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
ofile = Jp2k(tfile.name, 'wb')
|
||||
ofile.write(data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
|
|
@ -18,8 +18,11 @@ Test suite specifically targeting JP2 box layout.
|
|||
|
||||
import doctest
|
||||
import os
|
||||
import shutil
|
||||
import struct
|
||||
import sys
|
||||
import tempfile
|
||||
import uuid
|
||||
import xml.etree.cElementTree as ET
|
||||
|
||||
if sys.hexversion < 0x02070000:
|
||||
|
|
@ -502,6 +505,100 @@ class TestColourSpecificationBox(unittest.TestCase):
|
|||
approximation=approx)
|
||||
|
||||
|
||||
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
|
||||
"Missing openjp2 library.")
|
||||
class TestAppend(unittest.TestCase):
|
||||
"""Tests for append method."""
|
||||
|
||||
def setUp(self):
|
||||
self.j2kfile = glymur.data.goodstuff()
|
||||
self.jp2file = glymur.data.nemo()
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_append_xml(self):
|
||||
"""Should be able to append an XML box."""
|
||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||
shutil.copyfile(self.jp2file, tfile.name)
|
||||
|
||||
jp2 = Jp2k(tfile.name)
|
||||
the_xml = ET.fromstring('<?xml version="1.0"?><data>0</data>')
|
||||
xmlbox = glymur.jp2box.XMLBox(xml=the_xml)
|
||||
jp2.append(xmlbox)
|
||||
|
||||
# The sequence of box IDs should be the same as before, but with an
|
||||
# xml box at the end.
|
||||
box_ids = [box.box_id for box in jp2.box]
|
||||
expected = ['jP ', 'ftyp', 'jp2h', 'uuid', 'uuid', 'jp2c', 'xml ']
|
||||
self.assertEqual(box_ids, expected)
|
||||
self.assertEqual(ET.tostring(jp2.box[-1].xml.getroot()),
|
||||
b'<data>0</data>')
|
||||
|
||||
def test_only_jp2_allowed_to_append(self):
|
||||
"""Only JP2 files are allowed to be appended."""
|
||||
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
||||
shutil.copyfile(self.j2kfile, tfile.name)
|
||||
|
||||
jp2 = Jp2k(tfile.name)
|
||||
|
||||
# Make a UUID box.
|
||||
uuid_instance = uuid.UUID('00000000-0000-0000-0000-000000000000')
|
||||
data = b'0123456789'
|
||||
uuidbox = glymur.jp2box.UUIDBox(uuid_instance, data)
|
||||
with self.assertRaises(IOError):
|
||||
jp2.append(uuidbox)
|
||||
|
||||
def test_length_field_is_zero(self):
|
||||
"""L=0 (length field in box header) is handled.
|
||||
|
||||
L=0 implies that the containing box is the last box. If this is not
|
||||
handled properly, the appended box is never seen.
|
||||
"""
|
||||
baseline_jp2 = Jp2k(self.jp2file)
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
with open(self.jp2file, 'rb') as ifile:
|
||||
# Everything up until the jp2c box.
|
||||
offset = baseline_jp2.box[-1].offset
|
||||
tfile.write(ifile.read(offset))
|
||||
|
||||
# Write the L, T fields of the jp2c box such that L == 0
|
||||
write_buffer = struct.pack('>I4s', int(0), b'jp2c')
|
||||
tfile.write(write_buffer)
|
||||
|
||||
# Write out the rest of the codestream.
|
||||
ifile.seek(offset+8)
|
||||
tfile.write(ifile.read())
|
||||
tfile.flush()
|
||||
|
||||
jp2 = Jp2k(tfile.name)
|
||||
the_xml = ET.fromstring('<?xml version="1.0"?><data>0</data>')
|
||||
xmlbox = glymur.jp2box.XMLBox(xml=the_xml)
|
||||
jp2.append(xmlbox)
|
||||
|
||||
# The sequence of box IDs should be the same as before, but with an
|
||||
# xml box at the end.
|
||||
box_ids = [box.box_id for box in jp2.box]
|
||||
expected = ['jP ', 'ftyp', 'jp2h', 'uuid', 'uuid', 'jp2c', 'xml ']
|
||||
self.assertEqual(box_ids, expected)
|
||||
self.assertEqual(ET.tostring(jp2.box[-1].xml.getroot()),
|
||||
b'<data>0</data>')
|
||||
|
||||
def test_only_xml_allowed_to_append(self):
|
||||
"""Only XML boxes are allowed to be appended."""
|
||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||
shutil.copyfile(self.jp2file, tfile.name)
|
||||
|
||||
jp2 = Jp2k(tfile.name)
|
||||
|
||||
# Make a UUID box.
|
||||
uuid_instance = uuid.UUID('00000000-0000-0000-0000-000000000000')
|
||||
data = b'0123456789'
|
||||
uuidbox = glymur.jp2box.UUIDBox(uuid_instance, data)
|
||||
with self.assertRaises(IOError):
|
||||
jp2.append(uuidbox)
|
||||
|
||||
|
||||
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
|
||||
"Missing openjp2 library.")
|
||||
class TestWrap(unittest.TestCase):
|
||||
|
|
@ -703,7 +800,7 @@ class TestJp2Boxes(unittest.TestCase):
|
|||
def test_default_ihdr(self):
|
||||
"""Should be able to instantiate an image header box."""
|
||||
ihdr = glymur.jp2box.ImageHeaderBox(height=512, width=256,
|
||||
num_components=3)
|
||||
num_components=3)
|
||||
self.assertEqual(ihdr.height, 512)
|
||||
self.assertEqual(ihdr.width, 256)
|
||||
self.assertEqual(ihdr.num_components, 3)
|
||||
|
|
@ -715,7 +812,7 @@ class TestJp2Boxes(unittest.TestCase):
|
|||
"""Should be able to set jp2h boxes."""
|
||||
box = JP2HeaderBox()
|
||||
box.box = [ImageHeaderBox(height=512, width=256),
|
||||
ColourSpecificationBox(colorspace=glymur.core.GREYSCALE)]
|
||||
ColourSpecificationBox(colorspace=glymur.core.GREYSCALE)]
|
||||
self.assertTrue(True)
|
||||
|
||||
def test_default_ccodestreambox(self):
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import glymur
|
|||
from glymur import Jp2k
|
||||
|
||||
from .fixtures import OPENJP2_IS_V2_OFFICIAL
|
||||
from .fixtures import OPENJPEG_VERSION
|
||||
|
||||
try:
|
||||
DATA_ROOT = os.environ['OPJ_DATA_ROOT']
|
||||
|
|
@ -65,48 +66,6 @@ def load_tests(loader, tests, ignore):
|
|||
return tests
|
||||
|
||||
|
||||
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None and
|
||||
glymur.lib.openjpeg.OPENJPEG is None,
|
||||
"Missing openjp2 library.")
|
||||
class TestConfig(unittest.TestCase):
|
||||
"""Test suite for reading without proper library in place."""
|
||||
|
||||
def setUp(self):
|
||||
self.jp2file = glymur.data.nemo()
|
||||
self.j2kfile = glymur.data.goodstuff()
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_read_without_library(self):
|
||||
"""Don't have either openjp2 or openjpeg libraries? Must error out.
|
||||
"""
|
||||
with patch('glymur.lib.openjp2.OPENJP2', new=None):
|
||||
with patch('glymur.lib.openjpeg.OPENJPEG', new=None):
|
||||
with self.assertRaises(glymur.jp2k.LibraryNotFoundError):
|
||||
glymur.Jp2k(self.jp2file).read()
|
||||
|
||||
def test_read_bands_without_library(self):
|
||||
"""Don't have openjp2 library? Must error out.
|
||||
"""
|
||||
with patch('glymur.lib.openjp2.OPENJP2', new=None):
|
||||
with patch('glymur.lib.openjpeg.OPENJPEG', new=None):
|
||||
with self.assertRaises(glymur.jp2k.LibraryNotFoundError):
|
||||
glymur.Jp2k(self.jp2file).read_bands()
|
||||
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
def test_write_without_library(self):
|
||||
"""Don't have openjp2 library? Must error out.
|
||||
"""
|
||||
data = glymur.Jp2k(self.j2kfile).read()
|
||||
with patch('glymur.lib.openjp2.OPENJP2', new=None):
|
||||
with patch('glymur.lib.openjpeg.OPENJPEG', new=None):
|
||||
with self.assertRaises(glymur.jp2k.LibraryNotFoundError):
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
ofile = Jp2k(tfile.name, 'wb')
|
||||
ofile.write(data)
|
||||
|
||||
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
|
||||
"Missing openjp2 library.")
|
||||
|
|
@ -333,13 +292,10 @@ class TestJp2k(unittest.TestCase):
|
|||
self.assertEqual(jp2k.box[2].box[1].colorspace, glymur.core.SRGB)
|
||||
self.assertIsNone(jp2k.box[2].box[1].icc_profile)
|
||||
|
||||
@unittest.skipIf(DATA_ROOT is None,
|
||||
"OPJ_DATA_ROOT environment variable not set")
|
||||
def test_j2k_box(self):
|
||||
"""A J2K/J2C file must not have any boxes."""
|
||||
# Verify that a J2K file has no boxes.
|
||||
filename = os.path.join(DATA_ROOT, 'input/conformance/p0_01.j2k')
|
||||
jp2k = Jp2k(filename)
|
||||
jp2k = Jp2k(self.j2kfile)
|
||||
self.assertEqual(len(jp2k.box), 0)
|
||||
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
|
|
@ -412,6 +368,18 @@ class TestJp2k(unittest.TestCase):
|
|||
self.assertEqual(new_jp2.box[j].length,
|
||||
baseline_jp2.box[j].length)
|
||||
|
||||
def test_basic_jp2(self):
|
||||
"""Just a very basic test that reading a JP2 file does not error out.
|
||||
"""
|
||||
j2k = Jp2k(self.jp2file)
|
||||
j2k.read(rlevel=1)
|
||||
|
||||
def test_basic_j2k(self):
|
||||
"""Just a very basic test that reading a J2K file does not error out.
|
||||
"""
|
||||
j2k = Jp2k(self.j2kfile)
|
||||
j2k.read()
|
||||
|
||||
@unittest.skipIf(DATA_ROOT is None,
|
||||
"OPJ_DATA_ROOT environment variable not set")
|
||||
def test_read_differing_subsamples(self):
|
||||
|
|
@ -425,14 +393,11 @@ class TestJp2k(unittest.TestCase):
|
|||
with self.assertRaises(RuntimeError):
|
||||
j.read()
|
||||
|
||||
@unittest.skipIf(DATA_ROOT is None,
|
||||
"OPJ_DATA_ROOT environment variable not set")
|
||||
def test_empty_box_with_j2k(self):
|
||||
"""Verify that the list of boxes in a J2C/J2K file is present, but
|
||||
empty.
|
||||
"""
|
||||
filename = os.path.join(DATA_ROOT, 'input/conformance/p0_05.j2k')
|
||||
j = Jp2k(filename)
|
||||
j = Jp2k(self.j2kfile)
|
||||
self.assertEqual(j.box, [])
|
||||
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
|
|
@ -820,6 +785,14 @@ class TestJp2k15(unittest.TestCase):
|
|||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_rlevel_max(self):
|
||||
"""Verify that rlevel=-1 gets us the lowest resolution image"""
|
||||
j = Jp2k(self.j2kfile)
|
||||
thumbnail2 = j.read(rlevel=5)
|
||||
thumbnail1 = j.read(rlevel=-1)
|
||||
np.testing.assert_array_equal(thumbnail1, thumbnail2)
|
||||
self.assertEqual(thumbnail1.shape, (25, 15, 3))
|
||||
|
||||
def test_area(self):
|
||||
"""Area option not allowed for 1.5.1.
|
||||
"""
|
||||
|
|
@ -841,6 +814,215 @@ class TestJp2k15(unittest.TestCase):
|
|||
with self.assertRaises(TypeError):
|
||||
j2k.read(layer=1)
|
||||
|
||||
def test_rlevel_too_high(self):
|
||||
"""Should error out appropriately if reduce level too high"""
|
||||
j = Jp2k(self.jp2file)
|
||||
with self.assertRaises(IOError):
|
||||
j.read(rlevel=6)
|
||||
|
||||
def test_not_jpeg2000(self):
|
||||
"""Should error out appropriately if not given a JPEG 2000 file."""
|
||||
filename = pkg_resources.resource_filename(glymur.__name__, "jp2k.py")
|
||||
with self.assertRaises(IOError):
|
||||
Jp2k(filename)
|
||||
|
||||
def test_file_not_present(self):
|
||||
"""Should error out if reading from a file that does not exist"""
|
||||
# Verify that we error out appropriately if not given an existing file
|
||||
# at all.
|
||||
with self.assertRaises(OSError):
|
||||
filename = 'this file does not actually exist on the file system.'
|
||||
Jp2k(filename)
|
||||
|
||||
@unittest.skip("Writing requires openjp2 library at the moment.")
|
||||
@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.skip("Writing requires openjp2 library at the moment.")
|
||||
@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.skip("Writing requires openjp2 library at the moment.")
|
||||
@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.skip("Writing requires openjp2 library at the moment.")
|
||||
@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):
|
||||
"""Verify the boxes of a JP2 file. Basic jp2 test."""
|
||||
jp2k = Jp2k(self.jp2file)
|
||||
|
||||
# top-level boxes
|
||||
self.assertEqual(len(jp2k.box), 6)
|
||||
|
||||
self.assertEqual(jp2k.box[0].box_id, 'jP ')
|
||||
self.assertEqual(jp2k.box[0].offset, 0)
|
||||
self.assertEqual(jp2k.box[0].length, 12)
|
||||
self.assertEqual(jp2k.box[0].longname, 'JPEG 2000 Signature')
|
||||
|
||||
self.assertEqual(jp2k.box[1].box_id, 'ftyp')
|
||||
self.assertEqual(jp2k.box[1].offset, 12)
|
||||
self.assertEqual(jp2k.box[1].length, 20)
|
||||
self.assertEqual(jp2k.box[1].longname, 'File Type')
|
||||
|
||||
self.assertEqual(jp2k.box[2].box_id, 'jp2h')
|
||||
self.assertEqual(jp2k.box[2].offset, 32)
|
||||
self.assertEqual(jp2k.box[2].length, 45)
|
||||
self.assertEqual(jp2k.box[2].longname, 'JP2 Header')
|
||||
|
||||
self.assertEqual(jp2k.box[3].box_id, 'uuid')
|
||||
self.assertEqual(jp2k.box[3].offset, 77)
|
||||
self.assertEqual(jp2k.box[3].length, 638)
|
||||
|
||||
self.assertEqual(jp2k.box[4].box_id, 'uuid')
|
||||
self.assertEqual(jp2k.box[4].offset, 715)
|
||||
self.assertEqual(jp2k.box[4].length, 2412)
|
||||
|
||||
self.assertEqual(jp2k.box[5].box_id, 'jp2c')
|
||||
self.assertEqual(jp2k.box[5].offset, 3127)
|
||||
self.assertEqual(jp2k.box[5].length, 1132296)
|
||||
|
||||
# jp2h super box
|
||||
self.assertEqual(len(jp2k.box[2].box), 2)
|
||||
|
||||
self.assertEqual(jp2k.box[2].box[0].box_id, 'ihdr')
|
||||
self.assertEqual(jp2k.box[2].box[0].offset, 40)
|
||||
self.assertEqual(jp2k.box[2].box[0].length, 22)
|
||||
self.assertEqual(jp2k.box[2].box[0].longname, 'Image Header')
|
||||
self.assertEqual(jp2k.box[2].box[0].height, 1456)
|
||||
self.assertEqual(jp2k.box[2].box[0].width, 2592)
|
||||
self.assertEqual(jp2k.box[2].box[0].num_components, 3)
|
||||
self.assertEqual(jp2k.box[2].box[0].bits_per_component, 8)
|
||||
self.assertEqual(jp2k.box[2].box[0].signed, False)
|
||||
self.assertEqual(jp2k.box[2].box[0].compression, 7)
|
||||
self.assertEqual(jp2k.box[2].box[0].colorspace_unknown, False)
|
||||
self.assertEqual(jp2k.box[2].box[0].ip_provided, False)
|
||||
|
||||
self.assertEqual(jp2k.box[2].box[1].box_id, 'colr')
|
||||
self.assertEqual(jp2k.box[2].box[1].offset, 62)
|
||||
self.assertEqual(jp2k.box[2].box[1].length, 15)
|
||||
self.assertEqual(jp2k.box[2].box[1].longname, 'Colour Specification')
|
||||
self.assertEqual(jp2k.box[2].box[1].precedence, 0)
|
||||
self.assertEqual(jp2k.box[2].box[1].approximation, 0)
|
||||
self.assertEqual(jp2k.box[2].box[1].colorspace, glymur.core.SRGB)
|
||||
self.assertIsNone(jp2k.box[2].box[1].icc_profile)
|
||||
|
||||
def test_j2k_box(self):
|
||||
"""A J2K/J2C file must not have any boxes."""
|
||||
# Verify that a J2K file has no boxes.
|
||||
jp2k = Jp2k(self.j2kfile)
|
||||
self.assertEqual(len(jp2k.box), 0)
|
||||
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
def test_64bit_xl_field(self):
|
||||
"""XL field should be supported"""
|
||||
# Verify that boxes with the XL field are properly read.
|
||||
# Don't have such a file on hand, so we create one. Copy our example
|
||||
# file, but making the codestream have a 64-bit XL field.
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
with open(self.jp2file, 'rb') as ifile:
|
||||
# Everything up until the jp2c box.
|
||||
write_buffer = ifile.read(3127)
|
||||
tfile.write(write_buffer)
|
||||
|
||||
# The L field must be 1 in order to signal the presence of the
|
||||
# XL field. The actual length of the jp2c box increased by 8
|
||||
# (8 bytes for the XL field).
|
||||
length = 1
|
||||
typ = b'jp2c'
|
||||
xlen = 1133427 + 8
|
||||
write_buffer = struct.pack('>I4sQ', int(length), typ, xlen)
|
||||
tfile.write(write_buffer)
|
||||
|
||||
# Get the rest of the input file (minus the 8 bytes for L and
|
||||
# T.
|
||||
ifile.seek(8, 1)
|
||||
write_buffer = ifile.read()
|
||||
tfile.write(write_buffer)
|
||||
tfile.flush()
|
||||
|
||||
jp2k = Jp2k(tfile.name)
|
||||
|
||||
self.assertEqual(jp2k.box[5].box_id, 'jp2c')
|
||||
self.assertEqual(jp2k.box[5].offset, 3127)
|
||||
self.assertEqual(jp2k.box[5].length, 1133427 + 8)
|
||||
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
def test_length_field_is_zero(self):
|
||||
"""L=0 (length field in box header) is allowed"""
|
||||
# Verify that boxes with the L field as zero are correctly read.
|
||||
# This should only happen in the last box of a JPEG 2000 file.
|
||||
# Our example image has its last box at byte 588458.
|
||||
baseline_jp2 = Jp2k(self.jp2file)
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
with open(self.jp2file, 'rb') as ifile:
|
||||
# Everything up until the jp2c box.
|
||||
write_buffer = ifile.read(588458)
|
||||
tfile.write(write_buffer)
|
||||
|
||||
length = 0
|
||||
typ = b'uuid'
|
||||
write_buffer = struct.pack('>I4s', int(length), typ)
|
||||
tfile.write(write_buffer)
|
||||
|
||||
# Get the rest of the input file (minus the 8 bytes for L and
|
||||
# T.
|
||||
ifile.seek(8, 1)
|
||||
write_buffer = ifile.read()
|
||||
tfile.write(write_buffer)
|
||||
tfile.flush()
|
||||
|
||||
new_jp2 = Jp2k(tfile.name)
|
||||
|
||||
# The top level boxes in each file should match.
|
||||
for j in range(len(baseline_jp2.box)):
|
||||
self.assertEqual(new_jp2.box[j].box_id,
|
||||
baseline_jp2.box[j].box_id)
|
||||
self.assertEqual(new_jp2.box[j].offset,
|
||||
baseline_jp2.box[j].offset)
|
||||
self.assertEqual(new_jp2.box[j].length,
|
||||
baseline_jp2.box[j].length)
|
||||
|
||||
def test_basic_jp2(self):
|
||||
"""This test is only useful when openjp2 is not available
|
||||
and OPJ_DATA_ROOT is not set. We need at least one
|
||||
|
|
@ -857,6 +1039,393 @@ class TestJp2k15(unittest.TestCase):
|
|||
j2k = Jp2k(self.j2kfile)
|
||||
j2k.read()
|
||||
|
||||
@unittest.skipIf(DATA_ROOT is None,
|
||||
"OPJ_DATA_ROOT environment variable not set")
|
||||
def test_read_differing_subsamples(self):
|
||||
"""should error out with read used on differently subsampled images"""
|
||||
# Verify that we error out appropriately if we use the read method
|
||||
# on an image with differing subsamples
|
||||
#
|
||||
# Issue 86.
|
||||
filename = os.path.join(DATA_ROOT, 'input/conformance/p0_05.j2k')
|
||||
j = Jp2k(filename)
|
||||
with self.assertRaises(RuntimeError):
|
||||
j.read()
|
||||
|
||||
def test_empty_box_with_j2k(self):
|
||||
"""Verify that the list of boxes in a J2C/J2K file is present, but
|
||||
empty.
|
||||
"""
|
||||
j = Jp2k(self.j2kfile)
|
||||
self.assertEqual(j.box, [])
|
||||
|
||||
@unittest.skip("Writing requires openjp2 library at the moment.")
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
def test_cblkh_different_than_width(self):
|
||||
"""Verify that we can set a code block size where height does not equal
|
||||
width.
|
||||
"""
|
||||
data = np.zeros((128, 128), dtype=np.uint8)
|
||||
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
|
||||
j = Jp2k(tfile.name, 'wb')
|
||||
|
||||
# The code block dimensions are given as rows x columns.
|
||||
j.write(data, cbsize=(16, 32))
|
||||
|
||||
codestream = j.get_codestream()
|
||||
|
||||
# Code block size is reported as XY in the codestream.
|
||||
self.assertEqual(tuple(codestream.segment[2].spcod[5:7]), (3, 2))
|
||||
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
def test_too_many_dimensions(self):
|
||||
"""OpenJP2 only allows 2D or 3D images."""
|
||||
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
|
||||
j = Jp2k(tfile.name, 'wb')
|
||||
with self.assertRaises(IOError):
|
||||
data = np.zeros((128, 128, 2, 2), dtype=np.uint8)
|
||||
j.write(data)
|
||||
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
def test_unrecognized_jp2_clrspace(self):
|
||||
"""We only allow RGB and GRAYSCALE. Should error out with others"""
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
j = Jp2k(tfile.name, 'wb')
|
||||
with self.assertRaises(IOError):
|
||||
data = np.zeros((128, 128, 3), dtype=np.uint8)
|
||||
j.write(data, colorspace='cmyk')
|
||||
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
def test_2d_rgb(self):
|
||||
"""RGB must have at least 3 components."""
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
j = Jp2k(tfile.name, 'wb')
|
||||
with self.assertRaises(IOError):
|
||||
data = np.zeros((128, 128, 2), dtype=np.uint8)
|
||||
j.write(data, colorspace='rgb')
|
||||
|
||||
@unittest.skip("Writing requires openjp2 library at the moment.")
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
def test_colorspace_with_j2k(self):
|
||||
"""Specifying a colorspace with J2K does not make sense"""
|
||||
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
|
||||
j = Jp2k(tfile.name, 'wb')
|
||||
with self.assertRaises(IOError):
|
||||
data = np.zeros((128, 128, 3), dtype=np.uint8)
|
||||
j.write(data, colorspace='rgb')
|
||||
|
||||
@unittest.skip("Writing requires openjp2 library at the moment.")
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
def test_specify_rgb(self):
|
||||
"""specify RGB explicitly"""
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
j = Jp2k(tfile.name, 'wb')
|
||||
data = np.zeros((128, 128, 3), dtype=np.uint8)
|
||||
j.write(data, colorspace='rgb')
|
||||
self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB)
|
||||
|
||||
@unittest.skip("Writing requires openjp2 library at the moment.")
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
def test_specify_gray(self):
|
||||
"""test gray explicitly specified (that's GRAY, not GREY)"""
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
j = Jp2k(tfile.name, 'wb')
|
||||
data = np.zeros((128, 128), dtype=np.uint8)
|
||||
j.write(data, colorspace='gray')
|
||||
self.assertEqual(j.box[2].box[1].colorspace,
|
||||
glymur.core.GREYSCALE)
|
||||
|
||||
@unittest.skip("Writing requires openjp2 library at the moment.")
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
def test_specify_grey(self):
|
||||
"""test grey explicitly specified"""
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
j = Jp2k(tfile.name, 'wb')
|
||||
data = np.zeros((128, 128), dtype=np.uint8)
|
||||
j.write(data, colorspace='grey')
|
||||
self.assertEqual(j.box[2].box[1].colorspace,
|
||||
glymur.core.GREYSCALE)
|
||||
|
||||
@unittest.skip("Writing requires openjp2 library at the moment.")
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
def test_grey_with_extra_component(self):
|
||||
"""version 2.0 cannot write gray + extra"""
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
j = Jp2k(tfile.name, 'wb')
|
||||
data = np.zeros((128, 128, 2), dtype=np.uint8)
|
||||
j.write(data)
|
||||
self.assertEqual(j.box[2].box[0].height, 128)
|
||||
self.assertEqual(j.box[2].box[0].width, 128)
|
||||
self.assertEqual(j.box[2].box[0].num_components, 2)
|
||||
self.assertEqual(j.box[2].box[1].colorspace,
|
||||
glymur.core.GREYSCALE)
|
||||
|
||||
@unittest.skip("Writing requires openjp2 library at the moment.")
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
def test_grey_with_two_extra_comps(self):
|
||||
"""should be able to write gray + two extra components"""
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
j = Jp2k(tfile.name, 'wb')
|
||||
data = np.zeros((128, 128, 3), dtype=np.uint8)
|
||||
j.write(data, colorspace='gray')
|
||||
self.assertEqual(j.box[2].box[0].height, 128)
|
||||
self.assertEqual(j.box[2].box[0].width, 128)
|
||||
self.assertEqual(j.box[2].box[0].num_components, 3)
|
||||
self.assertEqual(j.box[2].box[1].colorspace,
|
||||
glymur.core.GREYSCALE)
|
||||
|
||||
@unittest.skip("Writing requires openjp2 library at the moment.")
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
def test_rgb_with_extra_component(self):
|
||||
"""v2.0+ should be able to write extra components"""
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
j = Jp2k(tfile.name, 'wb')
|
||||
data = np.zeros((128, 128, 4), dtype=np.uint8)
|
||||
j.write(data)
|
||||
self.assertEqual(j.box[2].box[0].height, 128)
|
||||
self.assertEqual(j.box[2].box[0].width, 128)
|
||||
self.assertEqual(j.box[2].box[0].num_components, 4)
|
||||
self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB)
|
||||
|
||||
@unittest.skip("Writing requires openjp2 library at the moment.")
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
def test_extra_components_on_v2(self):
|
||||
"""must error out in 1.x with extra components."""
|
||||
# Extra components seems to require 2.0+. Verify that we error out.
|
||||
with self.assertRaises(IOError):
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
j = Jp2k(tfile.name, 'wb')
|
||||
data = np.zeros((128, 128, 4), dtype=np.uint8)
|
||||
j.write(data)
|
||||
|
||||
@unittest.skip("Writing requires openjp2 library at the moment.")
|
||||
def test_specify_ycc(self):
|
||||
"""Should reject YCC"""
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
j = Jp2k(tfile.name, 'wb')
|
||||
with self.assertRaises(IOError):
|
||||
data = np.zeros((128, 128, 3), dtype=np.uint8)
|
||||
j.write(data, colorspace='ycc')
|
||||
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
def test_uinf_ulst_url_boxes(self):
|
||||
"""Verify that we can read UINF, ULST, and URL boxes"""
|
||||
# Verify that we can read UINF, ULST, and URL boxes. I don't have
|
||||
# easy access to such a file, and there's no such file in the
|
||||
# openjpeg repository, so I'll fake one.
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
with open(self.jp2file, 'rb') as ifile:
|
||||
# Everything up until the jp2c box.
|
||||
write_buffer = ifile.read(77)
|
||||
tfile.write(write_buffer)
|
||||
|
||||
# Write the UINF superbox
|
||||
# Length = 50, id is uinf.
|
||||
write_buffer = struct.pack('>I4s', int(50), b'uinf')
|
||||
tfile.write(write_buffer)
|
||||
|
||||
# Write the ULST box.
|
||||
# Length is 26, 1 UUID, hard code that UUID as zeros.
|
||||
write_buffer = struct.pack('>I4sHIIII', int(26), b'ulst',
|
||||
int(1), int(0), int(0), int(0),
|
||||
int(0))
|
||||
tfile.write(write_buffer)
|
||||
|
||||
# Write the URL box.
|
||||
# Length is 16, version is one byte, flag is 3 bytes, url
|
||||
# is the rest.
|
||||
write_buffer = struct.pack('>I4sBBBB',
|
||||
int(16), b'url ',
|
||||
int(0), int(0), int(0), int(0))
|
||||
tfile.write(write_buffer)
|
||||
write_buffer = struct.pack('>ssss', b'a', b'b', b'c', b'd')
|
||||
tfile.write(write_buffer)
|
||||
|
||||
# Get the rest of the input file.
|
||||
write_buffer = ifile.read()
|
||||
tfile.write(write_buffer)
|
||||
tfile.flush()
|
||||
|
||||
jp2k = Jp2k(tfile.name)
|
||||
|
||||
self.assertEqual(jp2k.box[3].box_id, 'uinf')
|
||||
self.assertEqual(jp2k.box[3].offset, 77)
|
||||
self.assertEqual(jp2k.box[3].length, 50)
|
||||
|
||||
self.assertEqual(jp2k.box[3].box[0].box_id, 'ulst')
|
||||
self.assertEqual(jp2k.box[3].box[0].offset, 85)
|
||||
self.assertEqual(jp2k.box[3].box[0].length, 26)
|
||||
ulst = []
|
||||
ulst.append(uuid.UUID('00000000-0000-0000-0000-000000000000'))
|
||||
self.assertEqual(jp2k.box[3].box[0].ulst, ulst)
|
||||
|
||||
self.assertEqual(jp2k.box[3].box[1].box_id, 'url ')
|
||||
self.assertEqual(jp2k.box[3].box[1].offset, 111)
|
||||
self.assertEqual(jp2k.box[3].box[1].length, 16)
|
||||
self.assertEqual(jp2k.box[3].box[1].version, 0)
|
||||
self.assertEqual(jp2k.box[3].box[1].flag, (0, 0, 0))
|
||||
self.assertEqual(jp2k.box[3].box[1].url, 'abcd')
|
||||
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
def test_xml_with_trailing_nulls(self):
|
||||
"""ElementTree doesn't like trailing null chars after valid XML text"""
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
with open(self.jp2file, 'rb') as ifile:
|
||||
# Everything up until the jp2c box.
|
||||
write_buffer = ifile.read(77)
|
||||
tfile.write(write_buffer)
|
||||
|
||||
# Write the xml box
|
||||
# Length = 36, id is 'xml '.
|
||||
write_buffer = struct.pack('>I4s', int(36), b'xml ')
|
||||
tfile.write(write_buffer)
|
||||
|
||||
write_buffer = '<test>this is a test</test>' + chr(0)
|
||||
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()
|
||||
|
||||
jp2k = Jp2k(tfile.name)
|
||||
|
||||
self.assertEqual(jp2k.box[3].box_id, 'xml ')
|
||||
self.assertEqual(jp2k.box[3].offset, 77)
|
||||
self.assertEqual(jp2k.box[3].length, 36)
|
||||
self.assertEqual(ET.tostring(jp2k.box[3].xml.getroot()),
|
||||
b'<test>this is a test</test>')
|
||||
|
||||
@unittest.skip("Writing requires openjp2 library at the moment.")
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
def test_asoc_label_box(self):
|
||||
"""Test asoc and label box"""
|
||||
# Construct a fake file with an asoc and a label box, as
|
||||
# OpenJPEG doesn't have such a file.
|
||||
data = Jp2k(self.jp2file).read(rlevel=1)
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
j = Jp2k(tfile.name, 'wb')
|
||||
j.write(data)
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2:
|
||||
|
||||
# Offset of the codestream is where we start.
|
||||
read_buffer = tfile.read(77)
|
||||
tfile2.write(read_buffer)
|
||||
|
||||
# read the rest of the file, it's the codestream.
|
||||
codestream = tfile.read()
|
||||
|
||||
# Write the asoc superbox.
|
||||
# Length = 36, id is 'asoc'.
|
||||
write_buffer = struct.pack('>I4s', int(56), b'asoc')
|
||||
tfile2.write(write_buffer)
|
||||
|
||||
# Write the contained label box
|
||||
write_buffer = struct.pack('>I4s', int(13), b'lbl ')
|
||||
tfile2.write(write_buffer)
|
||||
tfile2.write('label'.encode())
|
||||
|
||||
# Write the xml box
|
||||
# Length = 36, id is 'xml '.
|
||||
write_buffer = struct.pack('>I4s', int(35), b'xml ')
|
||||
tfile2.write(write_buffer)
|
||||
|
||||
write_buffer = '<test>this is a test</test>'
|
||||
write_buffer = write_buffer.encode()
|
||||
tfile2.write(write_buffer)
|
||||
|
||||
# Now append the codestream.
|
||||
tfile2.write(codestream)
|
||||
tfile2.flush()
|
||||
|
||||
jasoc = Jp2k(tfile2.name)
|
||||
self.assertEqual(jasoc.box[3].box_id, 'asoc')
|
||||
self.assertEqual(jasoc.box[3].box[0].box_id, 'lbl ')
|
||||
self.assertEqual(jasoc.box[3].box[0].label, 'label')
|
||||
self.assertEqual(jasoc.box[3].box[1].box_id, 'xml ')
|
||||
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
@unittest.skipIf(re.match('1\.[345]\.\d', OPENJPEG_VERSION) is not None,
|
||||
"Segfault on official v1.x series.")
|
||||
def test_openjpeg_library_message(self):
|
||||
"""Verify the error message produced by the openjpeg library"""
|
||||
# This will confirm that the error callback mechanism is working.
|
||||
with open(self.jp2file, 'rb') as fptr:
|
||||
data = fptr.read()
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
# Codestream starts at byte 3127. SIZ marker at 3137.
|
||||
# COD marker at 3186. Subsampling at 3180.
|
||||
tfile.write(data[0:3179])
|
||||
|
||||
# Make the DY bytes of the SIZ segment zero. That means that
|
||||
# a subsampling factor is zero, which is illegal.
|
||||
tfile.write(b'\x00')
|
||||
tfile.write(data[3180:3182])
|
||||
tfile.write(b'\x00')
|
||||
tfile.write(data[3184:3186])
|
||||
tfile.write(b'\x00')
|
||||
|
||||
tfile.write(data[3186:])
|
||||
tfile.flush()
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
j = Jp2k(tfile.name)
|
||||
regexp = re.compile(r'''OpenJPEG\slibrary\serror:\s+
|
||||
Invalid\svalues\sfor\scomp\s=\s0\s+
|
||||
:\sdx=1\sdy=0''', re.VERBOSE)
|
||||
if sys.hexversion < 0x03020000:
|
||||
with self.assertRaisesRegexp((IOError, OSError), regexp):
|
||||
j.read(rlevel=1)
|
||||
else:
|
||||
with self.assertRaisesRegex((IOError, OSError), regexp):
|
||||
j.read(rlevel=1)
|
||||
|
||||
def test_xmp_attribute(self):
|
||||
"""Verify the XMP packet in the shipping example file can be read."""
|
||||
j = Jp2k(self.jp2file)
|
||||
xmp = j.box[4].data
|
||||
ns0 = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}'
|
||||
ns1 = '{http://ns.adobe.com/xap/1.0/}'
|
||||
name = '{0}RDF/{0}Description'.format(ns0)
|
||||
elt = xmp.find(name)
|
||||
attr_value = elt.attrib['{0}CreatorTool'.format(ns1)]
|
||||
self.assertEqual(attr_value, 'glymur')
|
||||
|
||||
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||
def test_unrecognized_exif_tag(self):
|
||||
"""An unrecognized exif tag should be handled gracefully."""
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
shutil.copyfile(self.jp2file, tfile.name)
|
||||
|
||||
# The Exif UUID starts at byte 77. There are 8 bytes for the L and
|
||||
# T fields, then 16 bytes for the UUID identifier, then 6 exif
|
||||
# header bytes, then 8 bytes for the TIFF header, then 2 bytes
|
||||
# the the Image IFD number of tags, where we finally find the first
|
||||
# tag, "Make" (271). We'll corrupt it by changing it into 171,
|
||||
# which does not correspond to any known Exif Image tag.
|
||||
with open(tfile.name, 'r+b') as fptr:
|
||||
fptr.seek(117)
|
||||
write_buffer = struct.pack('<H', int(171))
|
||||
fptr.write(write_buffer)
|
||||
|
||||
# Verify that a warning is issued, but only on python3.
|
||||
# On python2, just suppress the warning.
|
||||
if sys.hexversion < 0x03030000:
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
j = Jp2k(tfile.name)
|
||||
else:
|
||||
with self.assertWarns(UserWarning):
|
||||
j = Jp2k(tfile.name)
|
||||
|
||||
exif = j.box[3].data
|
||||
# Were the tag == 271, 'Make' would be in the keys instead.
|
||||
self.assertTrue(171 in exif['Image'].keys())
|
||||
self.assertFalse('Make' in exif['Image'].keys())
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ suite.
|
|||
# unittest fools pylint with "too many public methods"
|
||||
# pylint: disable=R0904
|
||||
|
||||
# Some tests use numpy test infrastructure, which means the tests never
|
||||
# Some tests use numpy test infrastructure, which means the tests never
|
||||
# reference "self", so pylint claims it should be a function. No, no, no.
|
||||
# pylint: disable=R0201
|
||||
|
||||
|
|
|
|||
|
|
@ -441,7 +441,8 @@ class TestSuiteWrite(unittest.TestCase):
|
|||
self.assertEqual(len(codestream.segment[2].spcod), 9)
|
||||
|
||||
# 18 SOP segments.
|
||||
nsops = [x.nsop for x in codestream.segment if x.marker_id == 'SOP']
|
||||
nsops = [x.nsop for x in codestream.segment
|
||||
if x.marker_id == 'SOP']
|
||||
self.assertEqual(nsops, list(range(18)))
|
||||
|
||||
def test_NR_ENC_Bretagne2_ppm_7_encode(self):
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||
import sys
|
||||
|
||||
kwargs = {'name': 'Glymur',
|
||||
'version': '0.3.1',
|
||||
'version': '0.4.1',
|
||||
'description': 'Tools for accessing JPEG2000 files',
|
||||
'long_description': open('README.md').read(),
|
||||
'author': 'John Evans',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue