From 3f7c08739c7c28a381e8fa4a217a0f3b60eb9c0d Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 19 Nov 2014 23:09:12 -0500 Subject: [PATCH] progress --- docs/source/how_do_i.rst | 20 ++- docs/source/roadmap.rst | 9 -- docs/source/whatsnew/0.7.rst | 18 ++- docs/source/whatsnew/0.8.rst | 10 ++ docs/source/whatsnew/index.rst | 5 +- glymur/jp2k.py | 189 +++++++++------------------- glymur/test/test_jp2k.py | 30 ++--- glymur/test/test_opj_suite_dump.py | 3 +- glymur/test/test_opj_suite_write.py | 18 +-- 9 files changed, 117 insertions(+), 185 deletions(-) create mode 100644 docs/source/whatsnew/0.8.rst diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index 5d11d67..65647fa 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -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. diff --git a/docs/source/roadmap.rst b/docs/source/roadmap.rst index 503fbc4..f0d7017 100644 --- a/docs/source/roadmap.rst +++ b/docs/source/roadmap.rst @@ -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 ------------ diff --git a/docs/source/whatsnew/0.7.rst b/docs/source/whatsnew/0.7.rst index 0a21d9b..3ed7715 100644 --- a/docs/source/whatsnew/0.7.rst +++ b/docs/source/whatsnew/0.7.rst @@ -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 diff --git a/docs/source/whatsnew/0.8.rst b/docs/source/whatsnew/0.8.rst new file mode 100644 index 0000000..f36b593 --- /dev/null +++ b/docs/source/whatsnew/0.8.rst @@ -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. diff --git a/docs/source/whatsnew/index.rst b/docs/source/whatsnew/index.rst index 2d69949..4569abd 100644 --- a/docs/source/whatsnew/index.rst +++ b/docs/source/whatsnew/index.rst @@ -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 diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 2126d89..d7e04bb 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -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: diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 257688f..8eaf82a 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -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. diff --git a/glymur/test/test_opj_suite_dump.py b/glymur/test/test_opj_suite_dump.py index 8201c9f..ee1838b 100644 --- a/glymur/test/test_opj_suite_dump.py +++ b/glymur/test/test_opj_suite_dump.py @@ -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): diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 9d8f5b7..ecb7a7d 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -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))