diff --git a/CHANGES.txt b/CHANGES.txt index 6d2a22e..8af21bd 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1 +1,7 @@ +May 28, 2013 - Added jp2 boxes to rst docs, XMLBox.indent method made private. + +May 27, 2013 - v0.1.1, Changed write example to not rely on matplotlib. Fixed + readthedocs.org setup to build documentation automatically. Can import + glymur without libopenjp2 actually being present. + May 27, 2013 - Changed write example to not rely on matplotlib. diff --git a/README.md b/README.md index b362bb1..1b2d427 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -========================================= glymur: a Python interface for JPEG 2000 ========================================= @@ -6,8 +5,4 @@ glymur: a Python interface for JPEG 2000 which allows linux and mac users to read and write JPEG 2000 files. **glymur** works on Python 2.7 and 3.3. Python 3.3 is strongly recommended. -Until I figure out how to get readthedocs.org to handle numpydoc, you'll have -to do as follows to build the documentation first:: - - $ make -C docs html - $ open docs/build/html/index.html +Please read the docs, https://glymur.readthedocs.org/en/latest/ diff --git a/docs/source/conf.py b/docs/source/conf.py index baafdd9..a467ed2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -13,6 +13,29 @@ import sys, os +class Mock(object): + def __init__(self, *args, **kwargs): + pass + + def __call__(self, *args, **kwargs): + return Mock() + + @classmethod + def __getattr__(cls, name): + if name in ('__file__', '__path__'): + return '/dev/null' + elif name[0] == name[0].upper(): + mockType = type(name, (), {}) + mockType.__module__ = __name__ + return mockType + else: + return Mock() + +MOCK_MODULES = ['numpy'] +for mod_name in MOCK_MODULES: + sys.modules[mod_name] = Mock() + + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -52,7 +75,7 @@ copyright = u'2013, John Evans' # The short X.Y version. version = '0.1' # The full version, including alpha/beta/rc tags. -release = '0.1.0.a1' +release = '0.1.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index 1428afa..5f4ccb3 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -4,10 +4,10 @@ glymur: a Python interface for JPEG 2000 **glymur** contains a Python interface to the OpenJPEG library which allows linux and mac users to read and write JPEG 2000 files. For more -information about OpenJPEG, please consult http://www.openjpeg.org. **glymur** +information about OpenJPEG, please consult http://www.openjpeg.org. glymur should be considered to be alpha-quality software. -**glymur** tries to support reading (including all metadata) and writing of +glymur tries to support reading (including all metadata) and writing of JP2 and J2C files. Writing J2C/JP2 files is currently limited to images that can fit in memory, however. @@ -18,7 +18,7 @@ metadata can be retrieved from such JPX files. ------------ Requirements ------------ -**glymur** works on Python 2.7 and 3.3. Python 3.3 is strongly recommended. +glymur works on Python 2.7 and 3.3. Python 3.3 is strongly recommended. OpenJPEG ======== @@ -26,7 +26,7 @@ OpenJPEG must be built as a shared library. In addition, you currently must compile OpenJPEG from the developmental source that you can retrieve via subversion. As of this time of writing, svn revision 2345 works. In addition, you should also retrieve their test data, as -you will need it when running **glymur**'s test suite. +you will need it when running glymur's test suite. Earlier versions of OpenJPEG through the 2.0 official release are not supported. @@ -50,7 +50,7 @@ OS Mac OS X -------- -All the necessary packages are available to use **glymur** with Python 3.3 via +All the necessary packages are available to use glymur with Python 3.3 via MacPorts. A minimal set of ports includes * python33 @@ -83,12 +83,12 @@ combinations of RPMs / Python packages is installed. * scikit-image and either Pillow or freeimage * matplotlib and Pillow -The 2nd route is probably the easiest, so go ahead and install **Pillow** -via **pip** since **Pillow** is not yet available in Fedora 18 default +The 2nd route is probably the easiest, so go ahead and install Pillow +via pip since Pillow is not yet available in Fedora 18 default repositories:: $ yum install python3-devel # pip needs this in order to compile Pillow - $ yum install python3-pip # First one must install pip of course! + $ yum install python3-pip $ pip-python3 install Pillow --user $ export PYTHONPATH=$HOME/.local/lib/python3.3/site-packages:$PYTHONPATH @@ -105,7 +105,7 @@ Required RPMs include:: * python-setuptools * numpy -In addition, you must install **contextlib2** via pip. +In addition, you must install contextlib2 via pip. A few tests still will not run, however, unless one of the following combinations of RPMs / Python packages is installed. @@ -113,8 +113,8 @@ combinations of RPMs / Python packages is installed. * scikit-image and either Pillow or freeimage * matplotlib and Pillow -**scikit-image** is not available in the Fedora 17 default repositories, but -it may be installed via **pip**:: +scikit-image is not available in the Fedora 17 default repositories, but +it may be installed via pip:: $ yum install Cython # pip needs this in order to compile scikit-image $ yum install python-devel # pip needs this in order to compile scikit-image @@ -137,7 +137,7 @@ From this point forward, python3 will be referred to as just "python". Installation ============ -In addition to merely installing **glymur**, you should adjust your **$PATH** +In addition to merely installing glymur, you should adjust your **$PATH** environment variable in order to be able to use the **jp2dump** script from the unix command line. @@ -150,8 +150,8 @@ the unix command line. Configuration ============= -glymur uses **ctypes** (for the moment) to access the openjp2 library, and -because **ctypes** access libraries in a platform-dependent manner, it is +glymur uses ctypes (for the moment) to access the openjp2 library, and +because ctypes access libraries in a platform-dependent manner, it is recommended that you create a configuration file to help glymur properly find the openjp2 library. You may create the configuration file as follows:: diff --git a/glymur/__init__.py b/glymur/__init__.py index e5f0350..29e8cc4 100644 --- a/glymur/__init__.py +++ b/glymur/__init__.py @@ -23,8 +23,12 @@ def _glymurrc_fname(): fname = os.path.join(path, 'glymurrc') if os.path.exists(fname): return fname + else: + msg = "glymurrc file hinted at by GLYMURCONFIGDIR does not " + msg += "exist." + warnings.warn(msg, UserWarning) - # HOME/.glymur/jptoolrsc + # HOME/.glymur/glymurrc confdir = _get_configdir() if confdir is not None: fname = os.path.join(_get_configdir(), 'glymurrc') @@ -45,26 +49,22 @@ def _config(): # Read the configuration file for the library location. parser = ConfigParser() parser.read(filename) - try: - libopenjp2_path = parser.get('library', 'openjp2') - if not os.path.exists(libopenjp2_path): - msg = 'OpenJP2 library path specified by configuration file ' - msg += 'does not exist.' - raise ImportError(msg) - except NoOptionError as e: - msg = "Error parsing configuration file '{0}': {1}" - msg = msg.format(fname, e.message) - warnings.warn(msg, UserWarning) - except: - raise + libopenjp2_path = parser.get('library', 'openjp2') else: # No help from the config file, try to find it ourselves. from ctypes.util import find_library libopenjp2_path = find_library('openjp2') - if libopenjp2_path is None: - raise ImportError('OpenJP2 library not found.') - _OPENJP2 = ctypes.CDLL(libopenjp2_path) + if libopenjp2_path is None: + return None + + try: + _OPENJP2 = ctypes.CDLL(libopenjp2_path) + except OSError: + msg = '"Library {0}" could not be loaded. Operating in degraded mode.' + msg = msg.format(libopenjp2_path) + warnings.warn(msg, UserWarning) + _OPENJP2 = None return _OPENJP2 @@ -81,6 +81,7 @@ def _get_configdir(): if 'HOME' in os.environ: return os.path.join(os.environ['HOME'], '.glymur') +import warnings import sys if sys.hexversion <= 0x03000000: from ConfigParser import SafeConfigParser as ConfigParser diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py index 5ba07e6..798f23d 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -528,105 +528,110 @@ class _codestream_info_v2_t(ctypes.Structure): # Restrict the input and output argument types for each function used in the # API. -_OPENJP2.opj_create_compress.argtypes = [_codec_format_t] -_OPENJP2.opj_create_compress.restype = _codec_t_p +if _OPENJP2 is not None: + _OPENJP2.opj_create_compress.argtypes = [_codec_format_t] + _OPENJP2.opj_create_compress.restype = _codec_t_p -_OPENJP2.opj_create_decompress.argtypes = [_codec_format_t] -_OPENJP2.opj_create_decompress.restype = _codec_t_p + _OPENJP2.opj_create_decompress.argtypes = [_codec_format_t] + _OPENJP2.opj_create_decompress.restype = _codec_t_p -_argtypes = [_codec_t_p, _stream_t_p, ctypes.POINTER(_image_t)] -_OPENJP2.opj_decode.argtypes = _argtypes + _argtypes = [_codec_t_p, _stream_t_p, ctypes.POINTER(_image_t)] + _OPENJP2.opj_decode.argtypes = _argtypes -_argtypes = [_codec_t_p, ctypes.c_uint32, - ctypes.POINTER(ctypes.c_uint8), - ctypes.c_uint32, - _stream_t_p] -_OPENJP2.opj_decode_tile_data.argtypes = _argtypes + _argtypes = [_codec_t_p, ctypes.c_uint32, + ctypes.POINTER(ctypes.c_uint8), + ctypes.c_uint32, + _stream_t_p] + _OPENJP2.opj_decode_tile_data.argtypes = _argtypes -_argtypes = [ctypes.POINTER(ctypes.POINTER(_codestream_info_v2_t))] -_OPENJP2.opj_destroy_cstr_info.argtypes = _argtypes -_OPENJP2.opj_destroy_cstr_info.restype = ctypes.c_void_p + _argtypes = [ctypes.POINTER(ctypes.POINTER(_codestream_info_v2_t))] + _OPENJP2.opj_destroy_cstr_info.argtypes = _argtypes + _OPENJP2.opj_destroy_cstr_info.restype = ctypes.c_void_p -_argtypes = [_codec_t_p, _stream_t_p] -_OPENJP2.opj_encode.argtypes = _argtypes + _argtypes = [_codec_t_p, _stream_t_p] + _OPENJP2.opj_encode.argtypes = _argtypes -_OPENJP2.opj_get_cstr_info.argtypes = [_codec_t_p] -_OPENJP2.opj_get_cstr_info.restype = ctypes.POINTER(_codestream_info_v2_t) + _OPENJP2.opj_get_cstr_info.argtypes = [_codec_t_p] + _OPENJP2.opj_get_cstr_info.restype = ctypes.POINTER(_codestream_info_v2_t) -_argtypes = [_codec_t_p, - _stream_t_p, - ctypes.POINTER(_image_t), - ctypes.c_uint32] -_OPENJP2.opj_get_decoded_tile.argtypes = _argtypes + _argtypes = [_codec_t_p, + _stream_t_p, + ctypes.POINTER(_image_t), + ctypes.c_uint32] + _OPENJP2.opj_get_decoded_tile.argtypes = _argtypes -_argtypes = [ctypes.c_uint32, - ctypes.POINTER(_image_comptparm_t), - color_space_t] -_OPENJP2.opj_image_create.argtypes = _argtypes -_OPENJP2.opj_image_create.restype = ctypes.POINTER(_image_t) + _argtypes = [ctypes.c_uint32, + ctypes.POINTER(_image_comptparm_t), + color_space_t] + _OPENJP2.opj_image_create.argtypes = _argtypes + _OPENJP2.opj_image_create.restype = ctypes.POINTER(_image_t) -_argtypes = [ctypes.c_uint32, - ctypes.POINTER(_image_comptparm_t), - color_space_t] -_OPENJP2.opj_image_tile_create.argtypes = _argtypes -_OPENJP2.opj_image_tile_create.restype = ctypes.POINTER(_image_t) + _argtypes = [ctypes.c_uint32, + ctypes.POINTER(_image_comptparm_t), + color_space_t] + _OPENJP2.opj_image_tile_create.argtypes = _argtypes + _OPENJP2.opj_image_tile_create.restype = ctypes.POINTER(_image_t) -_OPENJP2.opj_image_destroy.argtypes = [ctypes.POINTER(_image_t)] + _OPENJP2.opj_image_destroy.argtypes = [ctypes.POINTER(_image_t)] -_argtypes = [_stream_t_p, _codec_t_p, ctypes.POINTER(ctypes.POINTER(_image_t))] -_OPENJP2.opj_read_header.argtypes = _argtypes + _argtypes = [_stream_t_p, _codec_t_p, + ctypes.POINTER(ctypes.POINTER(_image_t))] + _OPENJP2.opj_read_header.argtypes = _argtypes -_argtypes = [_codec_t_p, - _stream_t_p, - ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_uint32), - ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_int32), - ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_int32), - ctypes.POINTER(ctypes.c_uint32), - ctypes.POINTER(_bool_t)] -_OPENJP2.opj_read_tile_header.argtypes = _argtypes + _argtypes = [_codec_t_p, + _stream_t_p, + ctypes.POINTER(ctypes.c_uint32), + ctypes.POINTER(ctypes.c_uint32), + ctypes.POINTER(ctypes.c_int32), + ctypes.POINTER(ctypes.c_int32), + ctypes.POINTER(ctypes.c_int32), + ctypes.POINTER(ctypes.c_int32), + ctypes.POINTER(ctypes.c_uint32), + ctypes.POINTER(_bool_t)] + _OPENJP2.opj_read_tile_header.argtypes = _argtypes -_argtypes = [_codec_t_p, ctypes.POINTER(_image_t), ctypes.c_int32, - ctypes.c_int32, ctypes.c_int32, ctypes.c_int32] -_OPENJP2.opj_set_decode_area.argtypes = _argtypes + _argtypes = [_codec_t_p, ctypes.POINTER(_image_t), ctypes.c_int32, + ctypes.c_int32, ctypes.c_int32, ctypes.c_int32] + _OPENJP2.opj_set_decode_area.argtypes = _argtypes -_argtypes = [ctypes.POINTER(_cparameters_t)] -_OPENJP2.opj_set_default_encoder_parameters.argtypes = _argtypes + _argtypes = [ctypes.POINTER(_cparameters_t)] + _OPENJP2.opj_set_default_encoder_parameters.argtypes = _argtypes -_argtypes = [ctypes.POINTER(_dparameters_t)] -_OPENJP2.opj_set_default_decoder_parameters.argtypes = _argtypes + _argtypes = [ctypes.POINTER(_dparameters_t)] + _OPENJP2.opj_set_default_decoder_parameters.argtypes = _argtypes -_argtypes = [_codec_t_p, ctypes.c_void_p, ctypes.c_void_p] -_OPENJP2.opj_set_error_handler.argtypes = _argtypes -_OPENJP2.opj_set_info_handler.argtypes = _argtypes -_OPENJP2.opj_set_warning_handler.argtypes = _argtypes + _argtypes = [_codec_t_p, ctypes.c_void_p, ctypes.c_void_p] + _OPENJP2.opj_set_error_handler.argtypes = _argtypes + _OPENJP2.opj_set_info_handler.argtypes = _argtypes + _OPENJP2.opj_set_warning_handler.argtypes = _argtypes -_argtypes = [_codec_t_p, ctypes.POINTER(_dparameters_t)] -_OPENJP2.opj_setup_decoder.argtypes = _argtypes + _argtypes = [_codec_t_p, ctypes.POINTER(_dparameters_t)] + _OPENJP2.opj_setup_decoder.argtypes = _argtypes -_argtypes = [_codec_t_p, - ctypes.POINTER(_cparameters_t), - ctypes.POINTER(_image_t)] -_OPENJP2.opj_setup_encoder.argtypes = _argtypes + _argtypes = [_codec_t_p, + ctypes.POINTER(_cparameters_t), + ctypes.POINTER(_image_t)] + _OPENJP2.opj_setup_encoder.argtypes = _argtypes -_argtypes = [ctypes.c_char_p, ctypes.c_int32] -_OPENJP2.opj_stream_create_default_file_stream_v3.argtypes = _argtypes -_OPENJP2.opj_stream_create_default_file_stream_v3.restype = _stream_t_p + _argtypes = [ctypes.c_char_p, ctypes.c_int32] + _OPENJP2.opj_stream_create_default_file_stream_v3.argtypes = _argtypes + _OPENJP2.opj_stream_create_default_file_stream_v3.restype = _stream_t_p -_argtypes = [_codec_t_p, ctypes.POINTER(_image_t), _stream_t_p] -_OPENJP2.opj_start_compress.argtypes = _argtypes + _argtypes = [_codec_t_p, ctypes.POINTER(_image_t), _stream_t_p] + _OPENJP2.opj_start_compress.argtypes = _argtypes -_OPENJP2.opj_end_compress.argtypes = [_codec_t_p, _stream_t_p] -_OPENJP2.opj_end_decompress.argtypes = [_codec_t_p, _stream_t_p] + _OPENJP2.opj_end_compress.argtypes = [_codec_t_p, _stream_t_p] + _OPENJP2.opj_end_decompress.argtypes = [_codec_t_p, _stream_t_p] -_OPENJP2.opj_stream_destroy_v3.argtypes = [_stream_t_p] -_OPENJP2.opj_destroy_codec.argtypes = [_codec_t_p] + _OPENJP2.opj_stream_destroy_v3.argtypes = [_stream_t_p] + _OPENJP2.opj_destroy_codec.argtypes = [_codec_t_p] -_argtypes = [_codec_t_p, - ctypes.c_uint32, - ctypes.POINTER(ctypes.c_uint8), - ctypes.c_uint32, - _stream_t_p] -_OPENJP2.opj_write_tile.argtypes = _argtypes + _argtypes = [_codec_t_p, + ctypes.c_uint32, + ctypes.POINTER(ctypes.c_uint8), + ctypes.c_uint32, + _stream_t_p] + _OPENJP2.opj_write_tile.argtypes = _argtypes def _check_error(status): @@ -655,9 +660,10 @@ _fcns = ['opj_decode', 'opj_decode_tile_data', 'opj_end_compress', 'opj_set_warning_handler', 'opj_setup_decoder', 'opj_setup_encoder', 'opj_start_compress', 'opj_write_tile'] -for _fcn in _fcns: - _attr = getattr(_OPENJP2, _fcn) - setattr(_attr, 'restype', _check_error) +if _OPENJP2 is not None: + for _fcn in _fcns: + _attr = getattr(_OPENJP2, _fcn) + setattr(_attr, 'restype', _check_error) def _create_compress(codec_format): diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 256cade..5234e0b 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -582,7 +582,7 @@ class TestJp2k(unittest.TestCase): with patch.dict('os.environ', {'GLYMURCONFIGDIR': tdir}): # Misconfigured new configuration file should # be rejected. - with self.assertRaises(ImportError) as ce: + with self.assertWarns(UserWarning) as cw: imp.reload(glymur) @unittest.skipIf(sys.hexversion < 0x03020000, @@ -594,7 +594,7 @@ class TestJp2k(unittest.TestCase): with patch.dict('os.environ', {'GLYMURCONFIGDIR': tdir}): # Misconfigured new configuration file should # be rejected. - with self.assertRaises(ImportError) as ce: + with self.assertWarns(UserWarning) as cw: imp.reload(glymur) diff --git a/readthedocs-pip-requirements.txt b/readthedocs-pip-requirements.txt new file mode 100644 index 0000000..061e669 --- /dev/null +++ b/readthedocs-pip-requirements.txt @@ -0,0 +1,3 @@ +numpydoc==0.4 +contextlib2==0.4.0 +mock==1.0.1