This commit is contained in:
jevans 2014-11-19 23:09:12 -05:00
commit 3f7c08739c
9 changed files with 117 additions and 185 deletions

View file

@ -25,16 +25,14 @@ features such as quality layers.
... write images?
=================
So long as the image data can fit entirely into memory, array-style slicing may
also be used to write JPEG 2000 files.
It's pretty simple, just supply the image data as the 2nd argument to the Jp2k
constructor.
>>> import glymur, numpy as np
>>> jp2 = glymur.Jp2k('zeros.jp2', mode='wb')
>>> jp2[:] = np.zeros((640, 480), dtype=np.uint8)
>>> data = np.zeros((640, 480), dtype=np.uint8)
>>> jp2 = glymur.Jp2k('zeros.jp2', data=data)
The :py:meth:`write` method exposes many more options for other JPEG 2000
features. You should have OpenJPEG version 1.5 or more recent before writing
JPEG 2000 images.
You should have OpenJPEG version 1.5 or more recent before writing JPEG 2000 images.
... display metadata?
=====================
@ -354,15 +352,14 @@ image isn't square. ::
>>> import numpy as np
>>> import glymur
>>> from glymur import Jp2k
>>> rgb = Jp2k(glymur.data.goodstuff()).read()
>>> rgb = Jp2k(glymur.data.goodstuff())[:]
>>> lx, ly = rgb.shape[0:2]
>>> X, Y = np.ogrid[0:lx, 0:ly]
>>> mask = ly**2*(X - lx / 2) ** 2 + lx**2*(Y - ly / 2) ** 2 > (lx * ly / 2)**2
>>> alpha = 255 * np.ones((lx, ly, 1), dtype=np.uint8)
>>> alpha[mask] = 0
>>> rgba = np.concatenate((rgb, alpha), axis=2)
>>> jp2 = Jp2k('tmp.jp2', 'wb')
>>> jp2[:] = rgba
>>> jp2 = Jp2k('tmp.jp2', data=rgba)
Next we need to specify what types of channels we have.
The first three channels are color channels, but we identify the fourth as
@ -465,8 +462,7 @@ http://photojournal.jpl.nasa.gov/tiff/PIA17145.tif info JPEG 2000::
>>> import skimage.io
>>> image = skimage.io.imread('PIA17145.tif')
>>> from glymur import Jp2k
>>> jp2 = Jp2k('PIA17145.jp2', 'wb')
>>> jp2[:] = image
>>> jp2 = Jp2k('PIA17145.jp2', data=image)
Next you can extract the XMP metadata.

View file

@ -1,12 +1,3 @@
-------------------------------------
Platforms Tested (0.7.0 release only)
-------------------------------------
* Linux Mint 17 / Python 3.4.0 and 2.7.6 / OpenJPEG 2.1.0 and 1.3.0
* MacOS 10.6.8 / MacPorts Python 3.4.1, 3.3.5,and 2.7.8 / OpenJPEG 2.1.0
* CentOS 6.5 / Anaconda Python 3.4.1 / OpenJPEG 1.3.0
* Fedora 20 i386 / Python 2.7.5 and 3.3.2 / OpenJPEG 1.5.1
* Windows 7 32bit / Anaconda Python 2.7.6 and 3.4.1 / OpenJPEG 2.1.0
------------
Known Issues
------------

View file

@ -2,10 +2,26 @@
Changes in glymur 0.7
=====================
Changes in 0.7.3
=================
* added read support back for metadata only when the OpenJPEG library is
not installed
Changes in 0.7.2
=================
* added ellipsis support in array-style slicing
Changes in 0.7.1
=================
* fixed release notes regarding Python 3.4
Changes in 0.7.0
=================
* implemented :py:meth:`__getitem__`, :py:meth:`__setitem__` support
* added back windows support
* box_id and longname are class attributes now instead of instance
attributes (see issue 248)
attributes

View file

@ -0,0 +1,10 @@
=====================
Changes in glymur 0.8
=====================
Changes in 0.8.0
=================
* Simplified writing images by moving data and options into the
constructor. This is backwards-incompatible with 0.7.x.
* Deprecated :py:meth:`read` method in favor of array-style slicing.

View file

@ -8,6 +8,7 @@ These document the changes between minor (or major) versions of glymur.
.. toctree::
0.5
0.6
0.8
0.7
0.6
0.5

View file

@ -82,10 +82,63 @@ class Jp2k(Jp2kBox):
def __init__(self, filename, data=None, shape=None, **kwargs):
"""
Only the filename parameter is required in order to read a JPEG 2000
file.
Parameters
----------
filename : str or file
The path to JPEG 2000 file.
the path to JPEG 2000 file
image_data : ndarray, optional
image data to be written
shape : tuple
size of image data, only required when image_data is not provided
cbsize : tuple, optional
code block size (DY, DX)
cinema2k : int, optional
frames per second, either 24 or 48
cinema4k : bool, optional
set to True to specify Cinema4K mode, defaults to false
colorspace : str, optional
either 'rgb' or 'gray'
cratios : iterable
compression ratios for successive layers
eph : bool, optional
if true, write SOP marker after each header packet
grid_offset : tuple, optional
offset (DY, DX) of the origin of the image in the reference grid
irreversible : bool, optional
if true, use the irreversible DWT 9-7 transform
mct : bool, optional
specifies usage of the multi component transform, if not
specified, defaults to True if the colorspace is RGB
modesw : int, optional
mode switch
1 = BYPASS(LAZY)
2 = RESET
4 = RESTART(TERMALL)
8 = VSC
16 = ERTERM(SEGTERM)
32 = SEGMARK(SEGSYM)
numres : int, optional
number of resolutions
prog : str, optional
progression order, one of "LRCP" "RLCP", "RPCL", "PCRL", "CPRL"
psnr : iterable, optional
different PSNR for successive layers
psizes : list, optional
list of precinct sizes, each precinct size tuple is defined in
(height x width)
sop : bool, optional
if true, write SOP marker before each packet
subsam : tuple, optional
subsampling factors (dy, dx)
tilesize : tuple, optional
numeric tuple specifying tile size in terms of (numrows, numcols),
not (X, Y)
verbose : bool, optional
print informational messages produced by the OpenJPEG library
"""
Jp2kBox.__init__(self)
self.filename = filename
@ -93,8 +146,11 @@ class Jp2k(Jp2kBox):
self.box = []
self._codec_format = None
self._colorspace = None
self._shape = None
self._layer = 0
if data is not None:
self._shape = data.shape
else:
self._shape = shape
self._ignore_pclr_cmap_cdef = False
self._verbose = False
@ -102,7 +158,7 @@ class Jp2k(Jp2kBox):
# Parse the file for JP2/JPX contents only if we are reading it.
if data is None and shape is None:
self.parse()
else:
elif data is not None:
self._write(data, **kwargs)
@property
@ -427,134 +483,11 @@ class Jp2k(Jp2kBox):
This method can only be used to create JPEG 2000 images that can fit
in memory.
Parameters
----------
img_array : ndarray
Image data to be written to file.
cbsize : tuple, optional
Code block size (DY, DX).
cinema2k : int, optional
frames per second, either 24 or 48
cinema4k : bool, optional
Set to True to specify Cinema4K mode, defaults to false.
colorspace : str, optional
Either 'rgb' or 'gray'.
cratios : iterable
Compression ratios for successive layers.
eph : bool, optional
If true, write SOP marker after each header packet.
grid_offset : tuple, optional
Offset (DY, DX) of the origin of the image in the reference grid.
irreversible : bool, optional
If true, use the irreversible DWT 9-7 transform.
mct : bool, optional
Specifies usage of the multi component transform. If not
specified, defaults to True if the colorspace is RGB.
modesw : int, optional
Mode switch.
1 = BYPASS(LAZY)
2 = RESET
4 = RESTART(TERMALL)
8 = VSC
16 = ERTERM(SEGTERM)
32 = SEGMARK(SEGSYM)
numres : int, optional
Number of resolutions.
prog : str, optional
Progression order, one of "LRCP" "RLCP", "RPCL", "PCRL", "CPRL".
psnr : iterable, optional
Different PSNR for successive layers.
psizes : list, optional
List of precinct sizes. Each precinct size tuple is defined in
(height x width).
sop : bool, optional
If true, write SOP marker before each packet.
subsam : tuple, optional
Subsampling factors (dy, dx).
tilesize : tuple, optional
Numeric tuple specifying tile size in terms of (numrows, numcols),
not (X, Y).
verbose : bool, optional
Print informational messages produced by the OpenJPEG library.
"""
if re.match("1.[0-4]", version.openjpeg_version) is not None:
raise RuntimeError("You must have at least version 1.5 of OpenJPEG "
"in order to write images.")
self._shape = img_array.shape
self._determine_colorspace(**kwargs)
self._populate_cparams(img_array, **kwargs)
if opj2.OPENJP2 is not None:
self._write_openjp2(img_array, verbose=verbose)
else:
self._write_openjpeg(img_array, verbose=verbose)
def write(self, img_array, verbose=False, **kwargs):
"""Write image data to a JP2/JPX/J2k file. Intended usage of the
various parameters follows that of OpenJPEG's opj_compress utility.
This method can only be used to create JPEG 2000 images that can fit
in memory.
Parameters
----------
img_array : ndarray
Image data to be written to file.
cbsize : tuple, optional
Code block size (DY, DX).
cinema2k : int, optional
frames per second, either 24 or 48
cinema4k : bool, optional
Set to True to specify Cinema4K mode, defaults to false.
colorspace : str, optional
Either 'rgb' or 'gray'.
cratios : iterable
Compression ratios for successive layers.
eph : bool, optional
If true, write SOP marker after each header packet.
grid_offset : tuple, optional
Offset (DY, DX) of the origin of the image in the reference grid.
irreversible : bool, optional
If true, use the irreversible DWT 9-7 transform.
mct : bool, optional
Specifies usage of the multi component transform. If not
specified, defaults to True if the colorspace is RGB.
modesw : int, optional
Mode switch.
1 = BYPASS(LAZY)
2 = RESET
4 = RESTART(TERMALL)
8 = VSC
16 = ERTERM(SEGTERM)
32 = SEGMARK(SEGSYM)
numres : int, optional
Number of resolutions.
prog : str, optional
Progression order, one of "LRCP" "RLCP", "RPCL", "PCRL", "CPRL".
psnr : iterable, optional
Different PSNR for successive layers.
psizes : list, optional
List of precinct sizes. Each precinct size tuple is defined in
(height x width).
sop : bool, optional
If true, write SOP marker before each packet.
subsam : tuple, optional
Subsampling factors (dy, dx).
tilesize : tuple, optional
Numeric tuple specifying tile size in terms of (numrows, numcols),
not (X, Y).
verbose : bool, optional
Print informational messages produced by the OpenJPEG library.
"""
if re.match("1.[0-4]", version.openjpeg_version) is not None:
raise RuntimeError("You must have at least version 1.5 of OpenJPEG "
"in order to write images.")
self._shape = img_array.shape
self._determine_colorspace(**kwargs)
self._populate_cparams(img_array, **kwargs)
@ -964,7 +897,7 @@ class Jp2k(Jp2kBox):
# Case of jp2[:] = data, i.e. write the entire image.
#
# Should have a slice object where start = stop = step = None
self.write(data)
self._write(data)
else:
msg = "Partial write operations are currently not allowed."
raise TypeError(msg)
@ -987,7 +920,7 @@ class Jp2k(Jp2kBox):
if pargs is Ellipsis:
# Case of jp2[...]
return self.read()
return self._read()
if isinstance(pargs, slice):
if pargs.start is None and pargs.stop is None and pargs.step is None:

View file

@ -81,13 +81,12 @@ class SliceProtocolBase(unittest.TestCase):
@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG)
class TestSliceProtocolBaseWrite(SliceProtocolBase):
@unittest.skip('requires shape implementation')
def test_write_ellipsis(self):
expected = self.j2k_data
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
j[...] = self.j2k_data
j = Jp2k(tfile.name, shape=expected.shape)
j[...] = expected
actual = j[:]
np.testing.assert_array_equal(actual, expected)
@ -101,10 +100,9 @@ class TestSliceProtocolBaseWrite(SliceProtocolBase):
np.testing.assert_array_equal(actual, expected)
@unittest.skip('requires shape implementation')
def test_cannot_write_with_non_default_single_slice(self):
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
j = Jp2k(tfile.name, shape=self.j2k_data.shape)
with self.assertRaises(TypeError):
j[slice(None, 0)] = self.j2k_data
with self.assertRaises(TypeError):
@ -114,38 +112,33 @@ class TestSliceProtocolBaseWrite(SliceProtocolBase):
with self.assertRaises(TypeError):
j[slice(0, 640)] = self.j2k_data
@unittest.skip('requires shape implementation')
def test_cannot_write_a_row(self):
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
j = Jp2k(tfile.name, shape=self.j2k_data.shape)
with self.assertRaises(TypeError):
j[5] = self.j2k_data
@unittest.skip('requires shape implementation')
def test_cannot_write_a_pixel(self):
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
j = Jp2k(tfile.name, shape=self.j2k_data.shape)
with self.assertRaises(TypeError):
j[25, 35] = self.j2k_data[25, 35]
@unittest.skip('requires shape implementation')
def test_cannot_write_a_column(self):
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
j = Jp2k(tfile.name, shape=self.j2k_data.shape)
with self.assertRaises(TypeError):
j[:, 25, :] = self.j2k_data[:, :25, :]
@unittest.skip('requires shape implementation')
def test_cannot_write_a_band(self):
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
j = Jp2k(tfile.name, shape=self.j2k_data.shape)
with self.assertRaises(TypeError):
j[:, :, 0] = self.j2k_data[:, :, 0]
@unittest.skip('requires shape implementation')
def test_cannot_write_a_subarray(self):
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
j = Jp2k(tfile.name, shape=self.j2k_data.shape)
with self.assertRaises(TypeError):
j[:25, :45, :] = self.j2k_data[:25, :25, :]
@ -884,13 +877,13 @@ class TestJp2k_2_0(unittest.TestCase):
j = Jp2k(self.jp2file)
with self.assertRaises(IOError):
# Start corner must be >= 0
j.read(area=(-1, -1, 1, 1))
j[-1:1, -1:1]
with self.assertRaises(IOError):
# End corner must be > 0
j.read(area=(10, 10, 0, 0))
j[10:0, 10:0]
with self.assertRaises(IOError):
# End corner must be >= start corner
j.read(area=(10, 10, 8, 8))
j[10:8, 10:8]
@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG)
def test_unrecognized_jp2_clrspace(self):
@ -1043,7 +1036,6 @@ class TestParsing(unittest.TestCase):
with self.assertWarnsRegex(UserWarning, 'Invalid profile'):
jp2 = Jp2k(filename)
#@unittest.skip('trouble is a brewing...')
def test_main_header(self):
"""Verify that the main header is not loaded when parsing turned off."""
# The hidden _main_header attribute should show up after accessing it.

View file

@ -2847,8 +2847,7 @@ class TestSuiteWarns(MetadataBase):
relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2'
jfile = opj_data_file(relpath)
with self.assertWarns(UserWarning):
j = Jp2k(jfile)
d = j.read()
d = Jp2k(jfile)[:]
self.assertTrue(True)
def test_NR_broken4_jp2_dump(self):

View file

@ -123,10 +123,9 @@ class WriteCinemaWarns(CinemaBase):
infile = opj_data_file(relfile)
data = skimage.io.imread(infile)
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
regex = 'OpenJPEG library warning:.*'
with self.assertWarnsRegex(UserWarning, re.compile(regex)):
j.write(data, cinema4k=True)
j = Jp2k(tfile.name, data=data, cinema4k=True)
codestream = j.get_codestream()
self.check_cinema4k_codestream(codestream, (4096, 2160))
@ -136,9 +135,8 @@ class WriteCinemaWarns(CinemaBase):
infile = opj_data_file(relfile)
data = skimage.io.imread(infile)
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
with self.assertWarnsRegex(UserWarning, 'OpenJPEG library warning'):
j.write(data, cinema2k=48)
j = Jp2k(tfile.name, data=data, cinema2k=48)
codestream = j.get_codestream()
self.check_cinema2k_codestream(codestream, (2048, 857))
@ -148,9 +146,8 @@ class WriteCinemaWarns(CinemaBase):
infile = opj_data_file(relfile)
data = skimage.io.imread(infile)
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
with self.assertWarnsRegex(UserWarning, 'OpenJPEG library warning'):
j.write(data, cinema2k=48)
j = Jp2k(tfile.name, data=data, cinema2k=48)
codestream = j.get_codestream()
self.check_cinema2k_codestream(codestream, (2048, 1080))
@ -160,9 +157,8 @@ class WriteCinemaWarns(CinemaBase):
infile = opj_data_file(relfile)
data = skimage.io.imread(infile)
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
with self.assertWarnsRegex(UserWarning, 'OpenJPEG library warning'):
j.write(data, cinema2k=24)
j = Jp2k(tfile.name, data=data, cinema2k=24)
codestream = j.get_codestream()
self.check_cinema2k_codestream(codestream, (2048, 1080))
@ -172,11 +168,10 @@ class WriteCinemaWarns(CinemaBase):
infile = opj_data_file(relfile)
data = skimage.io.imread(infile)
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
with self.assertWarnsRegex(UserWarning, 'OpenJPEG library warning'):
# OpenJPEG library warning: The desired maximum codestream
# size has limited at least one of the desired quality layers
j.write(data, cinema2k=24)
j = Jp2k(tfile.name, data=data, cinema2k=24)
codestream = j.get_codestream()
self.check_cinema2k_codestream(codestream, (2048, 857))
@ -186,12 +181,11 @@ class WriteCinemaWarns(CinemaBase):
infile = opj_data_file(relfile)
data = skimage.io.imread(infile)
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
regex = 'OpenJPEG library warning'
with self.assertWarnsRegex(UserWarning, regex):
# OpenJPEG library warning: The desired maximum codestream
# size has limited at least one of the desired quality layers
j.write(data, cinema2k=48)
j = Jp2k(tfile.name, data=data, cinema2k=48)
codestream = j.get_codestream()
self.check_cinema2k_codestream(codestream, (1998, 1080))