diff --git a/glymur/core.py b/glymur/core.py index 32b6818..05af436 100644 --- a/glymur/core.py +++ b/glymur/core.py @@ -186,4 +186,3 @@ _reader_requirements_display = { + 'requirements in M.9.2.3', 73: 'YPbPr(1125/60) enumerated colourspace', 74: 'YPbPr(1250/50) enumerated colourspace'} - diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 96ead49..4219218 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -18,6 +18,7 @@ import numpy as np from .codestream import Codestream from .core import * from .jp2box import * +from .lib import openjpeg as opj from .lib import openjp2 as opj2 _cspace_map = {'rgb': opj2._CLRSPC_SRGB, @@ -544,16 +545,17 @@ class Jp2k(Jp2kBox): jp2 = Jp2k(filename) return jp2 - def read(self, reduce=0, layer=0, area=None, tile=None, verbose=False): + def read(self, **kwargs): """Read a JPEG 2000 image. Parameters ---------- - layer : int, optional - Number of quality layer to decode. reduce : int, optional Factor by which to reduce output resolution. Use -1 to get the - lowest resolution thumbnail. + lowest resolution thumbnail. This is the only keyword option + available to use when only OpenJPEG version 1.5.1 is present. + layer : int, optional + Number of quality layer to decode. area : tuple, optional Specifies decoding image area, (first_row, first_col, last_row, last_col) @@ -587,6 +589,62 @@ class Jp2k(Jp2kBox): >>> thumbnail.shape (46, 81, 3) """ + if opj2._OPENJP2 is not None: + img_array = self._read_openjp2(**kwargs) + else: + img_array = self._read_openjpeg(**kwargs) + return img_array + + def _read_openjpeg(self, reduce=0): + """Read a JPEG 2000 image using libopenjpeg. + + Parameters + ---------- + reduce : int, optional + Factor by which to reduce output resolution. Use -1 to get the + lowest resolution thumbnail. + + Returns + ------- + img_array : ndarray + The image data. + + Raises + ------ + IOError + If the image has differing subsample factors. + """ + raise NotImplementedError("not there yet") + + def _read_openjp2(self, reduce=0, layer=0, area=None, tile=None, + verbose=False): + """Read a JPEG 2000 image using libopenjp2. + + Parameters + ---------- + layer : int, optional + Number of quality layer to decode. + reduce : int, optional + Factor by which to reduce output resolution. Use -1 to get the + lowest resolution thumbnail. + area : tuple, optional + Specifies decoding image area, + (first_row, first_col, last_row, last_col) + tile : int, optional + Number of tile to decode. + verbose : bool, optional + Print informational messages produced by the OpenJPEG library. + + Returns + ------- + img_array : ndarray + The image data. + + Raises + ------ + IOError + If the image has differing subsample factors. + """ # Check for differing subsample factors. codestream = self.get_codestream(header_only=True) dxs = np.array(codestream.segment[1].XRsiz) diff --git a/glymur/lib/__init__.py b/glymur/lib/__init__.py index ca7460c..8484e19 100644 --- a/glymur/lib/__init__.py +++ b/glymur/lib/__init__.py @@ -1,3 +1,4 @@ """This package organizes individual libraries employed by glymur.""" from . import openjp2 -from . import test +from . import openjpeg +#from . import test diff --git a/glymur/lib/openjpeg.py b/glymur/lib/openjpeg.py new file mode 100644 index 0000000..b4b6ced --- /dev/null +++ b/glymur/lib/openjpeg.py @@ -0,0 +1,58 @@ +"""Wraps library calls to openjpeg. +""" + +import ctypes +from ctypes.util import find_library +import platform +import os + +if os.name == "nt": + _OPENJPEG = ctypes.windll.LoadLibrary('openjpeg') +else: + if platform.system() == 'Darwin': + _OPENJPEG = ctypes.CDLL('/opt/local/lib/libopenjpeg.dylib') +if _OPENJPEG is None: + # Make one last attempt. Does the system know where it is? + _OPENJPEG = find_library('openjpeg') + +OPJ_PATH_LEN = 4096 # maximum allowed size for filenames + + +class dparameters_t(ctypes.Structure): + # cp_reduce: the number of highest resolution levels to be discarded + _fields_ = [("cp_reduce", ctypes.c_int), + # cp_layer: the maximum number of quality layers to decode + ("cp_layer", ctypes.c_int), + # infile: input file name + ("infile", ctypes.c_char * OPJ_PATH_LEN), + # outfile: output file name + ("outfile", ctypes.c_char * OPJ_PATH_LEN), + # decod_format: input file format 0: J2K, 1: JP2, 2: JPT + ("decod_format", ctypes.c_int), + # cod_format: output file format 0: PGX, 1: PxM, 2: BMP + ("cod_format", ctypes.c_int), + # jpwl_correct: activates the JPWL correction capabilities + ("jpwl_correct", ctypes.c_bool), + # jpwl_exp_comps: expected number of components + ("jpwl_exp_comps", ctypes.c_int), + # jpwl_max_tiles: maximum number of tiles + ("jpwl_max_tiles", ctypes.c_int), + # cp_limit_decoding: whether decoding should be done on the + # entire codestream or be limited to the main header + ("cp_limit_decoding", ctypes.c_int), + ("flags", ctypes.c_uint)] + + +def _set_default_decoder_parameters(dparams_p): + """Wrapper for opj_set_default_decoder_parameters. + """ + argtypes = [ctypes.POINTER(dparameters_t)] + _OPENJPEG.opj_set_default_decoder_parameters.argtypes = argtypes + _OPENJPEG.opj_set_default_decoder_parameters(dparams_p) + + +def _version(): + """Wrapper for opj_version library routine.""" + _OPENJPEG.opj_version.restype = ctypes.c_char_p + v = _OPENJPEG.opj_version() + return v.decode('utf-8') diff --git a/glymur/lib/test/test_openjpeg.py b/glymur/lib/test/test_openjpeg.py new file mode 100644 index 0000000..9307191 --- /dev/null +++ b/glymur/lib/test/test_openjpeg.py @@ -0,0 +1,38 @@ +import ctypes +import unittest + +import glymur + +@unittest.skipIf(glymur.lib.openjpeg._OPENJPEG is None, + "Missing openjpeg library.") +class TestOpenJPEG(unittest.TestCase): + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_version(self): + v = glymur.lib.openjpeg._version() + parts = v.split('.') + self.assertEqual(parts[0], '1') + self.assertEqual(parts[1], '5') + + def test_set_default_decoder_parameters(self): + # Verify that we properly set the default decode parameters. + dp = glymur.lib.openjpeg.dparameters_t() + glymur.lib.openjpeg._set_default_decoder_parameters(ctypes.byref(dp)) + + self.assertEqual(dp.cp_reduce, 0) + self.assertEqual(dp.cp_layer, 0) + self.assertEqual(dp.infile, b'') + self.assertEqual(dp.outfile, b'') + self.assertEqual(dp.decod_format, -1) + self.assertEqual(dp.cod_format, -1) + self.assertEqual(dp.jpwl_correct, 0) + self.assertEqual(dp.jpwl_exp_comps, 0) + self.assertEqual(dp.jpwl_max_tiles, 0) + self.assertEqual(dp.cp_limit_decoding, 0) + self.assertEqual(dp.flags, 0) + diff --git a/glymur/test/test_jp2k_15.py b/glymur/test/test_jp2k_15.py new file mode 100644 index 0000000..ef73183 --- /dev/null +++ b/glymur/test/test_jp2k_15.py @@ -0,0 +1,47 @@ +import os +import unittest + +import numpy as np + +import glymur +from glymur import Jp2k +from glymur.lib import openjpeg as opj + +try: + data_root = os.environ['OPJ_DATA_ROOT'] +except KeyError: + data_root = None +except: + raise + + +@unittest.skipIf(glymur.lib.openjpeg._OPENJPEG is None, + "Missing openjpeg library.") +class TestJp2k15(unittest.TestCase): + + def setUp(self): + self.jp2file = glymur.data.nemo() + self.j2kfile = glymur.data.goodstuff() + + def tearDown(self): + pass + + def test_layers(self): + # Layers not allowed for 1.5.1. + self.assertTrue(False) + + def test_area(self): + # Area option not allowed for 1.5.1. + self.assertTrue(False) + + def test_tile(self): + # Tile option not allowed for 1.5.1. + self.assertTrue(False) + + def test_verbose(self): + # Verbose option not allowed for 1.5.1. + self.assertTrue(False) + + +if __name__ == "__main__": + unittest.main()