Releasing 0.5.12.

Fix a few documentation warts.
Added changelog into RST documentation.
Restored glymur.lib.openjp2.*_v3 functions removed in 0.5.11
This commit is contained in:
John Evans 2014-05-15 06:58:32 -04:00
commit f37da173e3
11 changed files with 578 additions and 26 deletions

View file

@ -1,3 +1,5 @@
May 18, 2014 - v0.5.12 Restored _v3 functions removed in 0.5.11
May 09, 2014 - v0.5.11 Added support for Python 3.4, OpenJPEG 2.0.1, and
OpenJPEG 2.1.0.

View file

@ -78,7 +78,7 @@ copyright = u'2013, John Evans'
# The short X.Y version.
version = '0.5'
# The full version, including alpha/beta/rc tags.
release = '0.5.11'
release = '0.5.12'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View file

@ -1,20 +1,21 @@
----------------------------------
Advanced Installation Instructions
----------------------------------
Most users won't need to read this! You've been warned...
''''''''''''''''''''''
Glymur Configuration
''''''''''''''''''''''
The default glymur installation process relies upon OpenJPEG being
properly installed on your system as a shared library. You need
at least version 1.5 in order to read and write JPEG 2000 files.
properly installed on your system as a shared library. If you have OpenJPEG
installed through your system's package manager on linux or if you use MacPorts
on the mac, you are probably already set to go. But if you have OpenJPEG
installed into a non-standard place or if you use windows, then read on.
Glymur uses ctypes to access the openjp2/openjpeg libraries, and
because ctypes accesses libraries in a platform-dependent manner,
it is recommended that if you compile and install OpenJPEG into a
non-standard location, you should create a configuration file to
non-standard location, you should then create a configuration file to
help Glymur properly find the openjpeg or openjp2 libraries (linux
users or macports users don't need to bother with this if you are
using OpenJPEG as provided by your package manager). The configuration

View file

@ -68,10 +68,11 @@ The **append** method can add an XML box as shown below::
... 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),
you can use the **wrap** method with no box argument: ::
An existing raw codestream or JP2 file can be wrapped (re-wrapped in the case
of JP2) 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 just a raw codestream), you can use the **wrap** method
with no box argument: ::
>>> import glymur
>>> jfile = glymur.data.goodstuff()
@ -106,10 +107,12 @@ layer (the signature, file type, JP2 header, and contiguous codestream), with
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 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 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 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"?>
<favorite_things>
@ -219,8 +222,8 @@ 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. ::
>>> import glymur

View file

@ -15,8 +15,10 @@ Contents:
introduction
detailed_installation
how_do_i
roadmap
api
roadmap
whatsnew/index
------------------
Indices and tables

View file

@ -3,7 +3,7 @@ Glymur: a Python interface for JPEG 2000
----------------------------------------
**Glymur** is an interface to the OpenJPEG library
which allows one to read and write JPEG 2000 files from within Python.
which allows one to read and write JPEG 2000 files from Python.
Glymur supports both reading and writing of JPEG 2000 images, but writing
JPEG 2000 images is currently limited to images that can fit in memory
@ -18,7 +18,7 @@ Glymur works on Python 2.6, 2.7, 3.3, and 3.4.
OpenJPEG Installation
=====================
Glymur will read JPEG 2000 images with versions 1.3, 1.4, 1.5, 2.0, and 2.1 of
OpenJPEG. Writing images is only supported with the 1.5 or better, however,
OpenJPEG. Writing images is only supported with OpenJPEG 1.5 or better, however,
and version 2.1 is strongly recommended. For more information about OpenJPEG,
please consult http://www.openjpeg.org.

View file

@ -0,0 +1,50 @@
=====================
Changes in glymur 0.5
=====================
Changes in 0.5.12
=================
* Minor documentation fixes for grammar and style.
* The functions removed in 0.5.11 due to API changes in OpenJPEG 2.1.0 were
restored for backwards compatibility. They are deprecated, though, and will
be removed in 0.6.0.
* ``glymur.lib.openjp2.stream_create_default_file_stream_v3``
* ``glymur.lib.openjp2.opj.stream_destroy_v3``
Changes in 0.5.11
=================
* Added support for Python 3.4.
* OpenJPEG 1.5.2 and 2.0.1 are officially supported.
* OpenJPEG 2.1.0 is officially supported, but the ABI changes introduced by
OpenJPEG 2.1.0 required corresponding changes to glymur's ctypes interface.
The functions
* ``glymur.lib.openjp2.stream_create_default_file_stream_v3``
* ``glymur.lib.openjp2.opj.stream_destroy_v3``
functions were renamed to
* ``glymur.lib.openjp2.stream_create_default_file_stream``
* ``glymur.lib.openjp2.opj.stream_destroy``
in order to follow OpenJPEG's upstream changes. Unless you were using the
svn version of OpenJPEG, you should not be affected by this.
Changes in 0.5.10
=================
* Fixed bad warning issued when an unsupported reader requirement box mask
length was encountered.
Changes in 0.5.9
================
* Fixed bad library load on linux as a result of botched 0.5.8 release.
This release was primarily aimed at supporting SunPy.

View file

@ -0,0 +1,11 @@
.. _whatsnew:
**********************
"What's new" documents
**********************
These document the changes between minor (or major) versions of glymur.
.. toctree::
0.5

View file

@ -1293,7 +1293,7 @@ def start_compress(codec, image, stream):
def _stream_create_default_file_stream_2p0(fptr, isa_read_stream):
"""Wraps openjp2 library function opj_stream_create_default_vile_stream.
"""Wraps openjp2 library function opj_stream_create_default_file_stream.
Sets the stream to be a file stream.
@ -1318,7 +1318,7 @@ def _stream_create_default_file_stream_2p0(fptr, isa_read_stream):
def _stream_create_default_file_stream_2p1(fname, isa_read_stream):
"""Wraps openjp2 library function opj_stream_create_default_vile_stream.
"""Wraps openjp2 library function opj_stream_create_default_file_stream.
Sets the stream to be a file stream.
@ -1343,11 +1343,6 @@ def _stream_create_default_file_stream_2p1(fname, isa_read_stream):
read_stream)
return stream
if re.match(r'''2.0''', version()):
stream_create_default_file_stream = _stream_create_default_file_stream_2p0
else:
stream_create_default_file_stream = _stream_create_default_file_stream_2p1
def stream_destroy(stream):
"""Wraps openjp2 library function opj_stream_destroy.
@ -1362,6 +1357,51 @@ def stream_destroy(stream):
OPENJP2.opj_stream_destroy.restype = ctypes.c_void_p
OPENJP2.opj_stream_destroy(stream)
if re.match(r'''2.0''', version()):
# We must have the 2.0.x
stream_create_default_file_stream = _stream_create_default_file_stream_2p0
else:
# We must have version 2.1.
stream_create_default_file_stream = _stream_create_default_file_stream_2p1
# The _v3 functions existed only in the SVN version of OpenJPEG, before 2.1.0
# was released
stream_create_default_file_stream_v3 = _stream_create_default_file_stream_2p1
stream_create_default_file_stream_v3.__doc__ = r"""
Wraps openjp2 library function opj_stream_create_default_file_stream_v3.
Sets the stream to be a file stream.
This function is deprecated and will be removed in the 0.6.0 version of
glymur. Please use stream_create_default_file_stream instead.
Parameters
----------
fname : str
Specifies a file.
isa_read_stream: bool
True (read) or False (write)
Returns
-------
stream : stream_t
An OpenJPEG file stream.
"""
stream_destroy_v3 = stream_destroy
stream_destroy_v3.__doc__ = r"""
Wraps openjp2 library function opj_stream_destroy.
Destroys the stream created by create_stream.
This function is deprecated and wil be removed in the 0.6.0 version of
glymur. Please use stream_destroy instead.
Parameters
----------
stream : STREAM_TYPE_P
The file stream.
"""
def write_tile(codec, tile_index, data, data_size, stream):
"""Wraps openjp2 library function opj_write_tile.

View file

@ -0,0 +1,443 @@
"""
Tests for functions that wrap SVN version of libopenjp2 (before release of
2.1.0)
"""
# R0904: Seems like pylint is fooled in this situation
# W0142: using kwargs is ok in this context
# pylint: disable=R0904,W0142
# unittest2 is python-2.6 only (pylint/python-2.7)
# pylint: disable=F0401
import os
import re
import sys
import tempfile
if sys.hexversion < 0x02070000:
import unittest2 as unittest
else:
import unittest
import numpy as np
import glymur
from glymur.lib import openjp2
if re.match("2.0", glymur.version.openjpeg_version):
OPENJP2_IS_V2_OFFICIAL = True
else:
OPENJP2_IS_V2_OFFICIAL = False
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
@unittest.skipIf(openjp2.OPENJP2 is None,
"Missing openjp2 library.")
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL, "API followed here specific to V2.1")
class TestOpenJP2(unittest.TestCase):
"""Test openjp2 library functionality.
Some tests correspond to those in the openjpeg test suite.
"""
def test_default_encoder_parameters(self):
"""Ensure that the encoder structure is clean upon init."""
cparams = openjp2.set_default_encoder_parameters()
self.assertEqual(cparams.res_spec, 0)
self.assertEqual(cparams.cblockw_init, 64)
self.assertEqual(cparams.cblockh_init, 64)
self.assertEqual(cparams.numresolution, 6)
self.assertEqual(cparams.subsampling_dx, 1)
self.assertEqual(cparams.subsampling_dy, 1)
self.assertEqual(cparams.mode, 0)
self.assertEqual(cparams.prog_order, glymur.core.LRCP)
self.assertEqual(cparams.roi_shift, 0)
self.assertEqual(cparams.cp_tx0, 0)
self.assertEqual(cparams.cp_ty0, 0)
self.assertEqual(cparams.irreversible, 0)
def test_default_decoder_parameters(self):
"""Tests that the structure is clean upon initialization"""
dparams = openjp2.set_default_decoder_parameters()
self.assertEqual(dparams.DA_x0, 0)
self.assertEqual(dparams.DA_y0, 0)
self.assertEqual(dparams.DA_x1, 0)
self.assertEqual(dparams.DA_y1, 0)
def tile_macro(self, codec, stream, imagep, tidx):
"""called only by j2k_random_tile_access"""
openjp2.get_decoded_tile(codec, stream, imagep, tidx)
for j in range(imagep.contents.numcomps):
self.assertIsNotNone(imagep.contents.comps[j].data)
def j2k_random_tile_access(self, filename, codec_format=None):
"""fixture called by the test_rtaX methods"""
dparam = openjp2.set_default_decoder_parameters()
infile = filename.encode()
nelts = openjp2.PATH_LEN - len(infile)
infile += b'0' * nelts
dparam.infile = infile
dparam.decod_format = codec_format
codec = openjp2.create_decompress(codec_format)
openjp2.set_info_handler(codec, None)
openjp2.set_warning_handler(codec, None)
openjp2.set_error_handler(codec, None)
stream = openjp2.stream_create_default_file_stream_v3(filename, True)
openjp2.setup_decoder(codec, dparam)
image = openjp2.read_header(stream, codec)
cstr_info = openjp2.get_cstr_info(codec)
tile_ul = 0
tile_ur = cstr_info.contents.tw - 1
tile_lr = cstr_info.contents.tw * cstr_info.contents.th - 1
tile_ll = tile_lr - cstr_info.contents.tw
self.tile_macro(codec, stream, image, tile_ul)
self.tile_macro(codec, stream, image, tile_ur)
self.tile_macro(codec, stream, image, tile_lr)
self.tile_macro(codec, stream, image, tile_ll)
openjp2.destroy_cstr_info(cstr_info)
openjp2.end_decompress(codec, stream)
openjp2.destroy_codec(codec)
openjp2.stream_destroy_v3(stream)
openjp2.image_destroy(image)
def test_tte0(self):
"""Runs test designated tte0 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
ttx0_setup(tfile.name)
self.assertTrue(True)
def test_ttd0(self):
"""Runs test designated ttd0 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
# Produce the tte0 output file for ttd0 input.
ttx0_setup(tfile.name)
kwargs = {'x0': 0,
'y0': 0,
'x1': 1000,
'y1': 1000,
'filename': tfile.name,
'codec_format': openjp2.CODEC_J2K}
tile_decoder(**kwargs)
self.assertTrue(True)
def xtx1_setup(self, filename):
"""Runs tests tte1, rta1."""
kwargs = {'filename': filename,
'codec': openjp2.CODEC_J2K,
'comp_prec': 8,
'irreversible': 1,
'num_comps': 3,
'image_height': 256,
'image_width': 256,
'tile_height': 128,
'tile_width': 128}
tile_encoder(**kwargs)
self.assertTrue(True)
def test_tte1(self):
"""Runs test designated tte1 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
self.xtx1_setup(tfile.name)
def test_ttd1(self):
"""Runs test designated ttd1 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
# Produce the tte0 output file for ttd0 input.
self.xtx1_setup(tfile.name)
kwargs = {'x0': 0,
'y0': 0,
'x1': 128,
'y1': 128,
'filename': tfile.name,
'codec_format': openjp2.CODEC_J2K}
tile_decoder(**kwargs)
self.assertTrue(True)
def test_rta1(self):
"""Runs test designated rta1 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
self.xtx1_setup(tfile.name)
codec_format = openjp2.CODEC_J2K
self.j2k_random_tile_access(tfile.name, codec_format)
self.assertTrue(True)
def test_tte2(self):
"""Runs test designated tte2 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
xtx2_setup(tfile.name)
self.assertTrue(True)
def test_ttd2(self):
"""Runs test designated ttd2 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
# Produce the tte0 output file for ttd0 input.
xtx2_setup(tfile.name)
kwargs = {'x0': 0,
'y0': 0,
'x1': 128,
'y1': 128,
'filename': tfile.name,
'codec_format': openjp2.CODEC_JP2}
tile_decoder(**kwargs)
self.assertTrue(True)
def test_rta2(self):
"""Runs test designated rta2 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
xtx2_setup(tfile.name)
codec_format = openjp2.CODEC_JP2
self.j2k_random_tile_access(tfile.name, codec_format)
def test_tte3(self):
"""Runs test designated tte3 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
xtx3_setup(tfile.name)
self.assertTrue(True)
def test_rta3(self):
"""Runs test designated rta3 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
xtx3_setup(tfile.name)
codec_format = openjp2.CODEC_J2K
self.j2k_random_tile_access(tfile.name, codec_format)
self.assertTrue(True)
def test_tte4(self):
"""Runs test designated tte4 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
xtx4_setup(tfile.name)
self.assertTrue(True)
def test_rta4(self):
"""Runs test designated rta4 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
xtx4_setup(tfile.name)
codec_format = openjp2.CODEC_J2K
self.j2k_random_tile_access(tfile.name, codec_format)
def test_tte5(self):
"""Runs test designated tte5 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
xtx5_setup(tfile.name)
self.assertTrue(True)
def test_rta5(self):
"""Runs test designated rta5 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
xtx5_setup(tfile.name)
codec_format = openjp2.CODEC_J2K
self.j2k_random_tile_access(tfile.name, codec_format)
#def tile_encoder(num_comps=None, tile_width=None, tile_height=None,
# filename=None, codec=None, comp_prec=None,
# image_width=None, image_height=None,
# irreversible=None):
def tile_encoder(**kwargs):
"""Fixture used by many tests."""
num_tiles = ((kwargs['image_width'] / kwargs['tile_width']) *
(kwargs['image_height'] / kwargs['tile_height']))
tile_size = ((kwargs['tile_width'] * kwargs['tile_height']) *
(kwargs['num_comps'] * kwargs['comp_prec'] / 8))
data = np.random.random((kwargs['tile_height'],
kwargs['tile_width'],
kwargs['num_comps']))
data = (data * 255).astype(np.uint8)
l_param = openjp2.set_default_encoder_parameters()
l_param.tcp_numlayers = 1
l_param.cp_fixed_quality = 1
l_param.tcp_distoratio[0] = 20
# position of the tile grid aligned with the image
l_param.cp_tx0 = 0
l_param.cp_ty0 = 0
# tile size, we are using tile based encoding
l_param.tile_size_on = 1
l_param.cp_tdx = kwargs['tile_width']
l_param.cp_tdy = kwargs['tile_height']
# use irreversible encoding
l_param.irreversible = kwargs['irreversible']
l_param.numresolution = 6
l_param.prog_order = glymur.core.LRCP
l_params = (openjp2.ImageComptParmType * kwargs['num_comps'])()
for j in range(kwargs['num_comps']):
l_params[j].dx = 1
l_params[j].dy = 1
l_params[j].h = kwargs['image_height']
l_params[j].w = kwargs['image_width']
l_params[j].sgnd = 0
l_params[j].prec = kwargs['comp_prec']
l_params[j].x0 = 0
l_params[j].y0 = 0
codec = openjp2.create_compress(kwargs['codec'])
openjp2.set_info_handler(codec, None)
openjp2.set_warning_handler(codec, None)
openjp2.set_error_handler(codec, None)
cspace = openjp2.CLRSPC_SRGB
l_image = openjp2.image_tile_create(l_params, cspace)
l_image.contents.x0 = 0
l_image.contents.y0 = 0
l_image.contents.x1 = kwargs['image_width']
l_image.contents.y1 = kwargs['image_height']
l_image.contents.color_space = openjp2.CLRSPC_SRGB
openjp2.setup_encoder(codec, l_param, l_image)
stream = openjp2.stream_create_default_file_stream_v3(kwargs['filename'],
False)
openjp2.start_compress(codec, l_image, stream)
for j in np.arange(num_tiles):
openjp2.write_tile(codec, j, data, tile_size, stream)
openjp2.end_compress(codec, stream)
openjp2.stream_destroy_v3(stream)
openjp2.destroy_codec(codec)
openjp2.image_destroy(l_image)
def tile_decoder(**kwargs):
"""Fixture called with various configurations by many tests.
Reads a tile. That's all it does.
"""
stream = openjp2.stream_create_default_file_stream_v3(kwargs['filename'],
True)
dparam = openjp2.set_default_decoder_parameters()
dparam.decod_format = kwargs['codec_format']
# Do not use layer decoding limitation.
dparam.cp_layer = 0
# do not use resolution reductions.
dparam.cp_reduce = 0
codec = openjp2.create_decompress(kwargs['codec_format'])
openjp2.set_info_handler(codec, None)
openjp2.set_warning_handler(codec, None)
openjp2.set_error_handler(codec, None)
openjp2.setup_decoder(codec, dparam)
image = openjp2.read_header(stream, codec)
openjp2.set_decode_area(codec, image,
kwargs['x0'], kwargs['y0'],
kwargs['x1'], kwargs['y1'])
data = np.zeros((1150, 2048, 3), dtype=np.uint8)
while True:
rargs = openjp2.read_tile_header(codec, stream)
tidx = rargs[0]
size = rargs[1]
go_on = rargs[-1]
if not go_on:
break
openjp2.decode_tile_data(codec, tidx, data, size, stream)
openjp2.end_decompress(codec, stream)
openjp2.destroy_codec(codec)
openjp2.stream_destroy_v3(stream)
openjp2.image_destroy(image)
def ttx0_setup(filename):
"""Runs tests tte0, tte0."""
kwargs = {'filename': filename,
'codec': openjp2.CODEC_J2K,
'comp_prec': 8,
'irreversible': 1,
'num_comps': 3,
'image_height': 200,
'image_width': 200,
'tile_height': 100,
'tile_width': 100}
tile_encoder(**kwargs)
def xtx2_setup(filename):
"""Runs tests rta2, tte2, ttd2."""
kwargs = {'filename': filename,
'codec': openjp2.CODEC_JP2,
'comp_prec': 8,
'irreversible': 1,
'num_comps': 3,
'image_height': 256,
'image_width': 256,
'tile_height': 128,
'tile_width': 128}
tile_encoder(**kwargs)
def xtx3_setup(filename):
"""Runs tests tte3, rta3."""
kwargs = {'filename': filename,
'codec': openjp2.CODEC_J2K,
'comp_prec': 8,
'irreversible': 1,
'num_comps': 1,
'image_height': 256,
'image_width': 256,
'tile_height': 128,
'tile_width': 128}
tile_encoder(**kwargs)
def xtx4_setup(filename):
"""Runs tests rta4, tte4."""
kwargs = {'filename': filename,
'codec': openjp2.CODEC_J2K,
'comp_prec': 8,
'irreversible': 0,
'num_comps': 1,
'image_height': 256,
'image_width': 256,
'tile_height': 128,
'tile_width': 128}
tile_encoder(**kwargs)
def xtx5_setup(filename):
"""Runs tests rta5, tte5."""
kwargs = {'filename': filename,
'codec': openjp2.CODEC_J2K,
'comp_prec': 8,
'irreversible': 0,
'num_comps': 1,
'image_height': 512,
'image_width': 512,
'tile_height': 256,
'tile_width': 256}
tile_encoder(**kwargs)
if __name__ == "__main__":
unittest.main()

View file

@ -15,7 +15,7 @@ from .lib import openjp2 as opj2
# Do not change the format of this next line! Doing so risks breaking
# setup.py
version = "0.5.11"
version = "0.5.12"
_sv = LooseVersion(version)
version_tuple = _sv.version