From 6eba0a579463fbb1fb12a3880728f8aef07c6fc4 Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 24 Sep 2013 18:24:55 -0400 Subject: [PATCH 01/38] Prepping 0.5.3 release. --- CHANGES.txt | 3 +++ docs/source/conf.py | 2 +- glymur/version.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 7d437d3..838f4a3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +Sep 24, 2013 - v0.5.3 Removed a duplicated channel definition test in + test_jp2box that could cause a segfault in 1.3 if not properly skipped. + Sep 23, 2013 - v0.5.2 Fixed some teests that have been failing since 0.5. under various edge cases. diff --git a/docs/source/conf.py b/docs/source/conf.py index 75404f9..78eb705 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -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.2' +release = '0.5.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/glymur/version.py b/glymur/version.py index 4386ba6..5a2fe1e 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -13,7 +13,7 @@ from distutils.version import LooseVersion from .lib import openjpeg as opj from .lib import openjp2 as opj2 -version = "0.5.2" +version = "0.5.3" _sv = LooseVersion(version) version_tuple = _sv.version From b1edfbe29621cf9cfd9ac0e92f1e6fe4d9390c27 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 25 Sep 2013 10:34:16 -0400 Subject: [PATCH 02/38] When working with 3.3, really should catch both IOError and OSError. On 2.7, the mac seems to issue an IOError, other platforms an OSError. Just catch both. #123 --- glymur/test/test_jp2k.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 2519246..ec174bf 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -146,7 +146,7 @@ class TestJp2k_2_0(unittest.TestCase): def test_extra_components_on_v2(self): """must error out in 1.x with extra components.""" # Extra components seems to require 2.0+. Verify that we error out. - with self.assertRaises(OSError): + with self.assertRaises((IOError, OSError)): with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: j = Jp2k(tfile.name, 'wb') data = np.zeros((128, 128, 4), dtype=np.uint8) From b50f19bed32c467d64a2701c6d941d8df2452c4c Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 25 Sep 2013 20:55:40 -0400 Subject: [PATCH 03/38] Prepping 0.5.4 release. --- CHANGES.txt | 2 ++ docs/source/conf.py | 2 +- glymur/version.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 838f4a3..d0e2bce 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,5 @@ +Sep 24, 2013 - v0.5.4 Fixed test error restricted to v2.0. + Sep 24, 2013 - v0.5.3 Removed a duplicated channel definition test in test_jp2box that could cause a segfault in 1.3 if not properly skipped. diff --git a/docs/source/conf.py b/docs/source/conf.py index 78eb705..e01cc91 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -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.3' +release = '0.5.4' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/glymur/version.py b/glymur/version.py index 5a2fe1e..5e4122b 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -13,7 +13,7 @@ from distutils.version import LooseVersion from .lib import openjpeg as opj from .lib import openjp2 as opj2 -version = "0.5.3" +version = "0.5.4" _sv = LooseVersion(version) version_tuple = _sv.version From 8cc04b2651439bf13934bebc1cd4f58b06f5db63 Mon Sep 17 00:00:00 2001 From: John Evans Date: Sat, 28 Sep 2013 11:50:27 -0400 Subject: [PATCH 04/38] Updated docs for openjpeg r2347. --- docs/source/detailed_installation.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index 7fb5b3b..3f42275 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -13,7 +13,7 @@ both read and write JPEG 2000 files, but you may wish to install version 2.0 or the 2.0+ version from OpenJPEG's development trunk for better performance. If you do that, you should compile it as a shared library (named *openjp2* instead of *openjpeg*) from the developmental source that you can retrieve -via subversion. As of this time of writing, svn revision 2345 works. +via subversion. As of this time of writing, svn revision 2347 works. You should also download the test data for the purpose of configuring and running OpenJPEG's test suite, check their instructions for all this. You should set the **OPJ_DATA_ROOT** environment variable for the purpose @@ -25,9 +25,10 @@ of running Glymur's test suite. :: Glymur uses ctypes to access the openjp2/openjpeg libraries, and because ctypes accesses libraries in a platform-dependent manner, it is recommended that you create a configuration file to help Glymur properly find -the openjpeg or openjp2 libraries (linux users don't need to bother if you are -using OpenJPEG as provided by your package manager). The configuration -format is the same as used by Python's configparser module, i.e. :: +the openjpeg or openjp2 libraries (linux users don't need to bother with this +if you are using OpenJPEG as provided by your package manager). The +configuration format is the same as used by Python's configparser module, +i.e. :: [library] openjp2: /opt/openjp2-svn/lib/libopenjp2.so From 813f5f3a03ac6a108c0e5781b5c1e43a4e11940a Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 30 Sep 2013 14:30:45 -0400 Subject: [PATCH 05/38] Moved argtypes and restype into each respective function. #125 --- glymur/lib/openjp2.py | 251 +++++++++++++++++++++--------------------- 1 file changed, 125 insertions(+), 126 deletions(-) diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py index 8c0ae32..43e4e61 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -553,119 +553,6 @@ class CodestreamInfoV2(ctypes.Structure): # information regarding tiles inside of image ("tile_info", ctypes.POINTER(TileInfoV2))] -# Restrict the input and output argument types for each function used in the -# API. -if OPENJP2 is not None: - OPENJP2.opj_create_compress.restype = CODEC_TYPE - OPENJP2.opj_create_compress.argtypes = [CODEC_FORMAT_TYPE] - - OPENJP2.opj_create_decompress.argtypes = [CODEC_FORMAT_TYPE] - OPENJP2.opj_create_decompress.restype = CODEC_TYPE - - ARGTYPES = [CODEC_TYPE, STREAM_TYPE_P, ctypes.POINTER(ImageType)] - OPENJP2.opj_decode.argtypes = ARGTYPES - - ARGTYPES = [CODEC_TYPE, ctypes.c_uint32, - ctypes.POINTER(ctypes.c_uint8), - ctypes.c_uint32, - STREAM_TYPE_P] - OPENJP2.opj_decode_tile_data.argtypes = ARGTYPES - - ARGTYPES = [ctypes.POINTER(ctypes.POINTER(CodestreamInfoV2))] - OPENJP2.opj_destroy_cstr_info.argtypes = ARGTYPES - OPENJP2.opj_destroy_cstr_info.restype = ctypes.c_void_p - - ARGTYPES = [CODEC_TYPE, STREAM_TYPE_P] - OPENJP2.opj_encode.argtypes = ARGTYPES - - OPENJP2.opj_get_cstr_info.argtypes = [CODEC_TYPE] - OPENJP2.opj_get_cstr_info.restype = ctypes.POINTER(CodestreamInfoV2) - - ARGTYPES = [CODEC_TYPE, - STREAM_TYPE_P, - ctypes.POINTER(ImageType), - ctypes.c_uint32] - OPENJP2.opj_get_decoded_tile.argtypes = ARGTYPES - - ARGTYPES = [ctypes.c_uint32, - ctypes.POINTER(ImageComptParmType), - COLOR_SPACE_TYPE] - OPENJP2.opj_image_create.argtypes = ARGTYPES - OPENJP2.opj_image_create.restype = ctypes.POINTER(ImageType) - - ARGTYPES = [ctypes.c_uint32, - ctypes.POINTER(ImageComptParmType), - COLOR_SPACE_TYPE] - OPENJP2.opj_image_tile_create.argtypes = ARGTYPES - OPENJP2.opj_image_tile_create.restype = ctypes.POINTER(ImageType) - - OPENJP2.opj_image_destroy.argtypes = [ctypes.POINTER(ImageType)] - - ARGTYPES = [STREAM_TYPE_P, CODEC_TYPE, - ctypes.POINTER(ctypes.POINTER(ImageType))] - OPENJP2.opj_read_header.argtypes = ARGTYPES - - ARGTYPES = [CODEC_TYPE, - STREAM_TYPE_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_TYPE)] - OPENJP2.opj_read_tile_header.argtypes = ARGTYPES - - ARGTYPES = [CODEC_TYPE, ctypes.POINTER(ImageType), ctypes.c_int32, - ctypes.c_int32, ctypes.c_int32, ctypes.c_int32] - OPENJP2.opj_set_decode_area.argtypes = ARGTYPES - - ARGTYPES = [ctypes.POINTER(CompressionParametersType)] - OPENJP2.opj_set_default_encoder_parameters.argtypes = ARGTYPES - - ARGTYPES = [ctypes.POINTER(DecompressionParametersType)] - OPENJP2.opj_set_default_decoder_parameters.argtypes = ARGTYPES - - ARGTYPES = [CODEC_TYPE, 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_TYPE, ctypes.POINTER(DecompressionParametersType)] - OPENJP2.opj_setup_decoder.argtypes = ARGTYPES - - ARGTYPES = [CODEC_TYPE, - ctypes.POINTER(CompressionParametersType), - ctypes.POINTER(ImageType)] - OPENJP2.opj_setup_encoder.argtypes = ARGTYPES - - if hasattr(OPENJP2, 'opj_stream_create_default_file_stream_v3'): - 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_TYPE_P - OPENJP2.opj_stream_destroy_v3.argtypes = [STREAM_TYPE_P] - else: - ARGTYPES = [ctypes.c_void_p, ctypes.c_int32] - OPENJP2.opj_stream_create_default_file_stream.argtypes = ARGTYPES - OPENJP2.opj_stream_create_default_file_stream.restype = STREAM_TYPE_P - OPENJP2.opj_stream_destroy.argtypes = [STREAM_TYPE_P] - - ARGTYPES = [CODEC_TYPE, ctypes.POINTER(ImageType), STREAM_TYPE_P] - OPENJP2.opj_start_compress.argtypes = ARGTYPES - - OPENJP2.opj_end_compress.argtypes = [CODEC_TYPE, STREAM_TYPE_P] - OPENJP2.opj_end_decompress.argtypes = [CODEC_TYPE, STREAM_TYPE_P] - - OPENJP2.opj_destroy_codec.argtypes = [CODEC_TYPE] - - ARGTYPES = [CODEC_TYPE, - ctypes.c_uint32, - ctypes.POINTER(ctypes.c_uint8), - ctypes.c_uint32, - STREAM_TYPE_P] - OPENJP2.opj_write_tile.argtypes = ARGTYPES - def check_error(status): """Set a generic function as the restype attribute of all OpenJPEG @@ -684,19 +571,6 @@ def check_error(status): else: raise IOError("OpenJPEG function failure.") -# These library functions all return an error status. Circumvent that and -# force them to raise an exception. -FCNS = ['opj_decode', 'opj_decode_tile_data', 'opj_end_compress', - 'opj_encode', 'opj_end_decompress', 'opj_get_decoded_tile', - 'opj_read_header', 'opj_read_tile_header', 'opj_set_decode_area', - 'opj_set_error_handler', 'opj_set_info_handler', - 'opj_set_warning_handler', - 'opj_setup_decoder', 'opj_setup_encoder', 'opj_start_compress', - 'opj_write_tile'] -if OPENJP2 is not None: - for fcn in FCNS: - setattr(getattr(OPENJP2, fcn), 'restype', check_error) - def create_compress(codec_format): """Creates a J2K/JP2 compress structure. @@ -712,6 +586,9 @@ def create_compress(codec_format): ------- codec : Reference to CODEC_TYPE instance. """ + OPENJP2.opj_create_compress.restype = CODEC_TYPE + OPENJP2.opj_create_compress.argtypes = [CODEC_FORMAT_TYPE] + codec = OPENJP2.opj_create_compress(codec_format) return codec @@ -735,6 +612,10 @@ def decode(codec, stream, image): RuntimeError If the OpenJPEG library routine opj_decode fails. """ + OPENJP2.opj_decode.argtypes = [CODEC_TYPE, STREAM_TYPE_P, + ctypes.POINTER(ImageType)] + OPENJP2.opj_decode.restype = check_error + OPENJP2.opj_decode(codec, stream, image) @@ -761,6 +642,13 @@ def decode_tile_data(codec, tidx, data, data_size, stream): RuntimeError If the OpenJPEG library routine opj_decode fails. """ + OPENJP2.opj_decode_tile_data.argtypes = [CODEC_TYPE, + ctypes.c_uint32, + ctypes.POINTER(ctypes.c_uint8), + ctypes.c_uint32, + STREAM_TYPE_P] + OPENJP2.opj_decode_tile_data.restype = check_error + datap = data.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)) OPENJP2.opj_decode_tile_data(codec, ctypes.c_uint32(tidx), @@ -784,6 +672,9 @@ def create_decompress(codec_format): ------- codec : Reference to CODEC_TYPE instance. """ + OPENJP2.opj_create_decompress.argtypes = [CODEC_FORMAT_TYPE] + OPENJP2.opj_create_decompress.restype = CODEC_TYPE + codec = OPENJP2.opj_create_decompress(codec_format) return codec @@ -798,6 +689,7 @@ def destroy_codec(codec): codec : CODEC_TYPE Decompressor handle to destroy. """ + OPENJP2.opj_destroy_codec.argtypes = [CODEC_TYPE] OPENJP2.opj_destroy_codec(codec) @@ -818,6 +710,9 @@ def encode(codec, stream): RuntimeError If the OpenJPEG library routine opj_encode fails. """ + OPENJP2.opj_encode.argtypes = [CODEC_TYPE, STREAM_TYPE_P] + OPENJP2.opj_encode.restype = check_error + OPENJP2.opj_encode(codec, stream) @@ -836,6 +731,9 @@ def get_cstr_info(codec): cstr_info_p : CodestreamInfoV2 Reference to codestream information. """ + OPENJP2.opj_get_cstr_info.argtypes = [CODEC_TYPE] + OPENJP2.opj_get_cstr_info.restype = ctypes.POINTER(CodestreamInfoV2) + cstr_info_p = OPENJP2.opj_get_cstr_info(codec) return cstr_info_p @@ -861,6 +759,12 @@ def get_decoded_tile(codec, stream, imagep, tile_index): RuntimeError If the OpenJPEG library routine opj_get_decoded_tile fails. """ + OPENJP2.opj_get_decoded_tile.argtypes = [CODEC_TYPE, + STREAM_TYPE_P, + ctypes.POINTER(ImageType), + ctypes.c_uint32] + OPENJP2.opj_get_decoded_tile.restype = check_error + OPENJP2.opj_get_decoded_tile(codec, stream, imagep, tile_index) @@ -874,6 +778,10 @@ def destroy_cstr_info(cstr_info_p): cstr_info_p : CodestreamInfoV2 pointer Pointer to codestream info structure. """ + ARGTYPES = [ctypes.POINTER(ctypes.POINTER(CodestreamInfoV2))] + OPENJP2.opj_destroy_cstr_info.argtypes = ARGTYPES + OPENJP2.opj_destroy_cstr_info.restype = ctypes.c_void_p + OPENJP2.opj_destroy_cstr_info(ctypes.byref(cstr_info_p)) @@ -894,6 +802,8 @@ def end_compress(codec, stream): RuntimeError If the OpenJPEG library routine opj_end_compress fails. """ + OPENJP2.opj_end_compress.argtypes = [CODEC_TYPE, STREAM_TYPE_P] + OPENJP2.opj_end_compress.restype = check_error OPENJP2.opj_end_compress(codec, stream) @@ -914,6 +824,8 @@ def end_decompress(codec, stream): RuntimeError If the OpenJPEG library routine opj_end_decompress fails. """ + OPENJP2.opj_end_decompress.argtypes = [CODEC_TYPE, STREAM_TYPE_P] + OPENJP2.opj_end_decompress.restype = check_error OPENJP2.opj_end_decompress(codec, stream) @@ -927,6 +839,8 @@ def image_destroy(image): image : ImageType pointer Image resource to be disposed. """ + OPENJP2.opj_image_destroy.argtypes = [ctypes.POINTER(ImageType)] + OPENJP2.opj_image_destroy(image) @@ -947,6 +861,11 @@ def image_create(comptparms, clrspc): image : ImageType Reference to ImageType instance. """ + OPENJP2.opj_image_create.argtypes = [ctypes.c_uint32, + ctypes.POINTER(ImageComptParmType), + COLOR_SPACE_TYPE] + OPENJP2.opj_image_create.restype = ctypes.POINTER(ImageType) + image = OPENJP2.opj_image_create(len(comptparms), comptparms, clrspc) @@ -970,6 +889,12 @@ def image_tile_create(comptparms, clrspc): image : ImageType Reference to ImageType instance. """ + ARGTYPES = [ctypes.c_uint32, + ctypes.POINTER(ImageComptParmType), + COLOR_SPACE_TYPE] + OPENJP2.opj_image_tile_create.argtypes = ARGTYPES + OPENJP2.opj_image_tile_create.restype = ctypes.POINTER(ImageType) + image = OPENJP2.opj_image_tile_create(len(comptparms), comptparms, clrspc) @@ -998,6 +923,11 @@ def read_header(stream, codec): RuntimeError If the OpenJPEG library routine opj_read_header fails. """ + ARGTYPES = [STREAM_TYPE_P, CODEC_TYPE, + ctypes.POINTER(ctypes.POINTER(ImageType))] + OPENJP2.opj_read_header.argtypes + OPENJP2.opj_read_header.restype = check_error + imagep = ctypes.POINTER(ImageType)() OPENJP2.opj_read_header(stream, codec, ctypes.byref(imagep)) return imagep @@ -1035,6 +965,19 @@ def read_tile_header(codec, stream): RuntimeError If the OpenJPEG library routine opj_read_tile_header fails. """ + ARGTYPES = [CODEC_TYPE, + STREAM_TYPE_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_TYPE)] + OPENJP2.opj_read_tile_header.argtypes = ARGTYPES + OPENJP2.opj_read_tile_header.restype = check_error + tile_index = ctypes.c_uint32() data_size = ctypes.c_uint32() col0 = ctypes.c_int32() @@ -1086,6 +1029,14 @@ def set_decode_area(codec, image, start_x=0, start_y=0, end_x=0, end_y=0): RuntimeError If the OpenJPEG library routine opj_set_decode_area fails. """ + OPENJP2.opj_set_decode_area.argtypes = [CODEC_TYPE, + ctypes.POINTER(ImageType), + ctypes.c_int32, + ctypes.c_int32, + ctypes.c_int32, + ctypes.c_int32] + OPENJP2.opj_set_decode_area.restype = check_error + OPENJP2.opj_set_decode_area(codec, image, ctypes.c_int32(start_x), ctypes.c_int32(start_y), @@ -1103,6 +1054,9 @@ def set_default_decoder_parameters(): dparam : DecompressionParametersType Decompression parameters. """ + ARGTYPES = [ctypes.POINTER(DecompressionParametersType)] + OPENJP2.opj_set_default_decoder_parameters.argtypes = ARGTYPES + dparams = DecompressionParametersType() OPENJP2.opj_set_default_decoder_parameters(ctypes.byref(dparams)) return dparams @@ -1138,6 +1092,9 @@ def set_default_encoder_parameters(): cparameters : CompressionParametersType Compression parameters. """ + ARGTYPES = [ctypes.POINTER(CompressionParametersType)] + OPENJP2.opj_set_default_encoder_parameters.argtypes = ARGTYPES + cparams = CompressionParametersType() OPENJP2.opj_set_default_encoder_parameters(ctypes.byref(cparams)) return cparams @@ -1162,6 +1119,10 @@ def set_error_handler(codec, handler, data=None): RuntimeError If the OpenJPEG library routine opj_set_error_handler fails. """ + OPENJP2.opj_set_error_handler.argtypes = [CODEC_TYPE, + ctypes.c_void_p, + ctypes.c_void_p] + OPENJP2.opj_set_error_handler.restype = check_error OPENJP2.opj_set_error_handler(codec, handler, data) @@ -1184,6 +1145,10 @@ def set_info_handler(codec, handler, data=None): RuntimeError If the OpenJPEG library routine opj_set_info_handler fails. """ + OPENJP2.opj_set_info_handler.argtypes = [CODEC_TYPE, + ctypes.c_void_p, + ctypes.c_void_p] + OPENJP2.opj_set_info_handler.restype = check_error OPENJP2.opj_set_info_handler(codec, handler, data) @@ -1206,6 +1171,11 @@ def set_warning_handler(codec, handler, data=None): RuntimeError If the OpenJPEG library routine opj_set_warning_handler fails. """ + OPENJP2.opj_set_warning_handler.argtypes = [CODEC_TYPE, + ctypes.c_void_p, + ctypes.c_void_p] + OPENJP2.opj_set_warning_handler.restype = check_error + OPENJP2.opj_set_warning_handler(codec, handler, data) @@ -1226,6 +1196,10 @@ def setup_decoder(codec, dparams): RuntimeError If the OpenJPEG library routine opj_setup_decoder fails. """ + ARGTYPES = [CODEC_TYPE, ctypes.POINTER(DecompressionParametersType)] + OPENJP2.opj_setup_decoder.argtypes = ARGTYPES + OPENJP2.opj_setup_decoder.restype = check_error + OPENJP2.opj_setup_decoder(codec, ctypes.byref(dparams)) @@ -1249,6 +1223,11 @@ def setup_encoder(codec, cparams, image): RuntimeError If the OpenJPEG library routine opj_setup_encoder fails. """ + ARGTYPES = [CODEC_TYPE, + ctypes.POINTER(CompressionParametersType), + ctypes.POINTER(ImageType)] + OPENJP2.opj_setup_encoder.argtypes = ARGTYPES + OPENJP2.opj_setup_encoder.restype = check_error OPENJP2.opj_setup_encoder(codec, ctypes.byref(cparams), image) @@ -1271,6 +1250,11 @@ def start_compress(codec, image, stream): RuntimeError If the OpenJPEG library routine opj_start_compress fails. """ + OPENJP2.opj_start_compress.argtypes = [CODEC_TYPE, + ctypes.POINTER(ImageType), + STREAM_TYPE_P] + OPENJP2.opj_start_compress.restype = check_error + OPENJP2.opj_start_compress(codec, image, stream) @@ -1292,6 +1276,9 @@ def stream_create_default_file_stream(fptr, isa_read_stream): stream : stream_t An OpenJPEG file stream. """ + ARGTYPES = [ctypes.c_void_p, ctypes.c_int32] + OPENJP2.opj_stream_create_default_file_stream.argtypes = ARGTYPES + OPENJP2.opj_stream_create_default_file_stream.restype = STREAM_TYPE_P read_stream = 1 if isa_read_stream else 0 stream = OPENJP2.opj_stream_create_default_file_stream(fptr, read_stream) return stream @@ -1315,6 +1302,9 @@ def stream_create_default_file_stream_v3(fname, isa_read_stream): stream : stream_t An OpenJPEG file stream. """ + 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_TYPE_P read_stream = 1 if isa_read_stream else 0 file_argument = ctypes.c_char_p(fname.encode()) stream = OPENJP2.opj_stream_create_default_file_stream_v3(file_argument, @@ -1332,6 +1322,7 @@ def stream_destroy(stream): stream : STREAM_TYPE_P The file stream. """ + OPENJP2.opj_stream_destroy.argtypes = [STREAM_TYPE_P] OPENJP2.opj_stream_destroy(stream) @@ -1345,6 +1336,7 @@ def stream_destroy_v3(stream): stream : STREAM_TYPE_P The file stream. """ + OPENJP2.opj_stream_destroy_v3.argtypes = [STREAM_TYPE_P] OPENJP2.opj_stream_destroy_v3(stream) @@ -1371,6 +1363,13 @@ def write_tile(codec, tile_index, data, data_size, stream): RuntimeError If the OpenJPEG library routine opj_write_tile fails. """ + OPENJP2.opj_write_tile.argtypes = [CODEC_TYPE, + ctypes.c_uint32, + ctypes.POINTER(ctypes.c_uint8), + ctypes.c_uint32, + STREAM_TYPE_P] + OPENJP2.opj_write_tile.restype = check_error + datap = data.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)) OPENJP2.opj_write_tile(codec, ctypes.c_uint32(int(tile_index)), From 89c086370641cf08e1663b933f42cc3534847352 Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 30 Sep 2013 14:55:20 -0400 Subject: [PATCH 06/38] openjp2's decode_tile_data handler was returning the codec. It should not. #125 --- glymur/lib/openjp2.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py index 43e4e61..82b0cfe 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -655,7 +655,6 @@ def decode_tile_data(codec, tidx, data, data_size, stream): datap, ctypes.c_uint32(data_size), stream) - return codec def create_decompress(codec_format): @@ -690,6 +689,7 @@ def destroy_codec(codec): Decompressor handle to destroy. """ OPENJP2.opj_destroy_codec.argtypes = [CODEC_TYPE] + OPENJP2.opj_destroy_codec.restype = ctypes.c_void_p OPENJP2.opj_destroy_codec(codec) @@ -840,6 +840,7 @@ def image_destroy(image): Image resource to be disposed. """ OPENJP2.opj_image_destroy.argtypes = [ctypes.POINTER(ImageType)] + OPENJP2.opj_image_destroy.restype = ctypes.c_void_p OPENJP2.opj_image_destroy(image) @@ -1056,6 +1057,7 @@ def set_default_decoder_parameters(): """ ARGTYPES = [ctypes.POINTER(DecompressionParametersType)] OPENJP2.opj_set_default_decoder_parameters.argtypes = ARGTYPES + OPENJP2.opj_set_default_decoder_parameters.restype = ctypes.c_void_p dparams = DecompressionParametersType() OPENJP2.opj_set_default_decoder_parameters(ctypes.byref(dparams)) @@ -1094,6 +1096,7 @@ def set_default_encoder_parameters(): """ ARGTYPES = [ctypes.POINTER(CompressionParametersType)] OPENJP2.opj_set_default_encoder_parameters.argtypes = ARGTYPES + OPENJP2.opj_set_default_encoder_parameters.restype = ctypes.c_void_p cparams = CompressionParametersType() OPENJP2.opj_set_default_encoder_parameters(ctypes.byref(cparams)) @@ -1323,6 +1326,7 @@ def stream_destroy(stream): The file stream. """ OPENJP2.opj_stream_destroy.argtypes = [STREAM_TYPE_P] + OPENJP2.opj_stream_destroy.restype = ctypes.c_void_p OPENJP2.opj_stream_destroy(stream) @@ -1337,6 +1341,7 @@ def stream_destroy_v3(stream): The file stream. """ OPENJP2.opj_stream_destroy_v3.argtypes = [STREAM_TYPE_P] + OPENJP2.opj_stream_destroy_v3.restype = ctypes.c_void_p OPENJP2.opj_stream_destroy_v3(stream) From 9422bccffb6a1806652b9e05470c30c5cb4b5c16 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 1 Oct 2013 10:55:28 -0400 Subject: [PATCH 07/38] Forgot to actually set argtypes for openjp2 read_header function. #125 --- glymur/lib/openjp2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py index 82b0cfe..f056db0 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -926,7 +926,7 @@ def read_header(stream, codec): """ ARGTYPES = [STREAM_TYPE_P, CODEC_TYPE, ctypes.POINTER(ctypes.POINTER(ImageType))] - OPENJP2.opj_read_header.argtypes + OPENJP2.opj_read_header.argtypes = ARGTYPES OPENJP2.opj_read_header.restype = check_error imagep = ctypes.POINTER(ImageType)() From 1fc1a635e0aeb1c88d35a23c04c14d1f131b13c7 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 1 Oct 2013 10:56:27 -0400 Subject: [PATCH 08/38] Fixed typo. --- glymur/test/test_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glymur/test/test_config.py b/glymur/test/test_config.py index 16af663..32f067a 100644 --- a/glymur/test/test_config.py +++ b/glymur/test/test_config.py @@ -124,7 +124,7 @@ class TestConfig(unittest.TestCase): @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_write_without_library(self): - """Don't have openjp2 library? Must error out. + """Don't have openjpeg libraries? Must error out. """ data = glymur.Jp2k(self.j2kfile).read() with patch('glymur.lib.openjp2.OPENJP2', new=None): From c65ae274a95320a44463f986ca05d375d8b03481 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 2 Oct 2013 15:32:38 -0400 Subject: [PATCH 09/38] Minor tweaks. --- docs/source/detailed_installation.rst | 9 +++++---- docs/source/how_do_i.rst | 16 ++++++++-------- docs/source/introduction.rst | 22 ++++++---------------- glymur/version.py | 1 + 4 files changed, 20 insertions(+), 28 deletions(-) diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index 3f42275..3e4d9dc 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -7,8 +7,8 @@ Most users won't need to read this! You've been warned... Glymur Configuration '''''''''''''''''''''' -The default glymur installation process relies upon OpenJPEG version -1.X being properly installed on your system. If you have version 1.5 you can +The default glymur installation process relies upon OpenJPEG +being properly installed on your system. If you have version 1.5 you can both read and write JPEG 2000 files, but you may wish to install version 2.0 or the 2.0+ version from OpenJPEG's development trunk for better performance. If you do that, you should compile it as a shared library (named *openjp2* @@ -35,7 +35,7 @@ i.e. :: This assumes, of course, that you've installed OpenJPEG into /opt/openjp2-svn on a linux system. The location of the configuration file -is platform-dependent (of course). If you use either linux or mac, the path +can vary as well (of course). If you use either linux or mac, the path to the configuration file would normally be :: $HOME/.config/glymur/glymurrc @@ -71,7 +71,8 @@ packages/RPMs/ports/whatever without going through pip. Mac OS X -------- All the necessary packages are available to use glymur with Python 2.6, 2.7, -and 3.3 via MacPorts. You should install the following set of ports: +and 3.3 via MacPorts. For python 3.3, you should install the following set of +ports: * python33 * py33-numpy diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index cc0b227..212862d 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -3,7 +3,7 @@ How do I...? ------------ -read the lowest resolution thumbnail? +... read the lowest resolution thumbnail? ===================================== Printing the Jp2k object should reveal the number of resolutions (look in the COD segment section), but you can take a shortcut by supplying -1 as the @@ -14,7 +14,7 @@ resolution level. :: >>> j = glymur.Jp2k(file) >>> thumbnail = j.read(rlevel=-1) -display metadata? +... display metadata? ================= There are two ways. From the unix command line, the script *jp2dump* is available. :: @@ -34,7 +34,7 @@ codestream box, only the main header is printed. It is possible to print >>> print(j.get_codestream()) -add XML metadata? +... add XML metadata? ================= You can append any number of XML boxes to a JP2 file (not to a raw codestream). Consider the following XML file `data.xml` : :: @@ -66,7 +66,7 @@ The **append** method can add an XML box as shown below:: >>> jp2.append(xmlbox) >>> print(jp2) -add metadata in a more general fashion? +... 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 @@ -157,18 +157,18 @@ to add metadata, you should keep in mind that **wrap** produces a new JP2 file, while **append** modifies an existing file and is currently limited to XML boxes. -create an image with an alpha layer? +... create an image with an alpha layer? ==================================== OpenJPEG can create JP2 files with more than 3 components (requires -the development version), but by default, any extra components are +the development version of OpenJPEG), but by default, any extra components are not described as such. In order to do so, we need to rewrap such an image in a set of boxes that includes a channel definition box. This example is based on SciPy example code found at http://scipy-lectures.github.io/advanced/image_processing/#basic-manipulations . -Instead of a circular mask, however, we'll make it an ellipse since the source -image isn't square. +Instead of a circular mask we'll make it an ellipse since the source +image isn't square. :: >>> import numpy as np >>> import glymur diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index 37dcb5a..e1a3b3b 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -4,9 +4,8 @@ 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. -Glymur supports both reading and writing of JPEG 2000 images. Writing -JPEG 2000 images is currently limited to images that can fit in memory, -however. +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 Of particular focus is retrieval of metadata. Reading Exif UUIDs is supported, as is reading XMP UUIDs as the XMP data packet is just XML. There is @@ -24,11 +23,10 @@ only supported with the 1.5 or better, however, and the trunk/development version is strongly recommended. For more information about OpenJPEG, please consult http://www.openjpeg.org. -If you use MacPorts on the mac or if you have a sufficiently recent -version of Linux, your package manager should already provide you -with a version of OpenJPEG 1.X with which glymur can already use -for read-only purposes. If your platform is windows, I suggest -using the windows installers provided to you by the OpenJPEG +If you use MacPorts or if you have a sufficiently recent version of +Linux, your package manager should already provide you with a version of +OpenJPEG 1.X which glymur can already use. If your platform is windows, +I suggest using the windows installers provided to you by the OpenJPEG folks at https://code.google.com/p/openjpeg/downloads/list . Glymur Installation @@ -47,13 +45,5 @@ line, so you should adjust your **$PATH** to take advantage of it. For example, if you install with pip's `--user` option on linux :: - $ export PYTHONPATH=$HOME/.local/lib/python3.3/site-packages $ export PATH=$HOME/.local/bin:$PATH -You can run the tests from within python as follows:: - - >>> import glymur - >>> glymur.runtests() - -Many tests are currently skipped, but the The important thing is whether or -not any tests fail. diff --git a/glymur/version.py b/glymur/version.py index 5e4122b..9603798 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -6,6 +6,7 @@ # # License: MIT +import pdb; pdb.set_trace() import sys import numpy as np from distutils.version import LooseVersion From 62db4f7e0fb2ef7c2b4a6141f5b5ec77e4ca5589 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 2 Oct 2013 17:18:12 -0400 Subject: [PATCH 10/38] Mistakenly committed with a pdb breakpoint set. --- glymur/version.py | 1 - 1 file changed, 1 deletion(-) diff --git a/glymur/version.py b/glymur/version.py index 9603798..5e4122b 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -6,7 +6,6 @@ # # License: MIT -import pdb; pdb.set_trace() import sys import numpy as np from distutils.version import LooseVersion From 7697230a591d3a3cf409e17ea83d2a03dd9c1057 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 3 Oct 2013 07:59:24 -0400 Subject: [PATCH 11/38] Change openjpeg version from base system version to tarball version 1.5. #126 --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4898e65..beff6ab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,9 +5,10 @@ python: before_install: - sudo apt-get update -qq - - sudo apt-get install -qq libopenjpeg2 - sudo apt-get install -qq python-numpy - sudo apt-get install -qq python3-numpy + - wget http://openjpeg.googlecode.com/files/openjpeg-1.5.0-Linux-x86_64.tar.gz + - sudo tar -xvf openjpeg-1.5.0-Linux-x86_64.tar.gz --strip-components=1 -C / # command to install dependencies install: "pip install -r travis-requirements.txt --use-mirrors" From 9bff3731168de3d38eefa01dd42c4b20c5bbc75d Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 3 Oct 2013 08:06:38 -0400 Subject: [PATCH 12/38] Use verbose to be sure of what tests are running. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index beff6ab..0eb0319 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ before_install: install: "pip install -r travis-requirements.txt --use-mirrors" # command to run tests -script: "python -m unittest discover" +script: "python -m unittest discover -v" notifications: email: "john.g.evans.ne@gmail.com" From c5900755ca55e6584b424c16c8075916a6fdabe1 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 3 Oct 2013 10:02:16 -0400 Subject: [PATCH 13/38] Removing verbose option. Closes #126 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0eb0319..beff6ab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ before_install: install: "pip install -r travis-requirements.txt --use-mirrors" # command to run tests -script: "python -m unittest discover -v" +script: "python -m unittest discover" notifications: email: "john.g.evans.ne@gmail.com" From 6edb59ac7e876f1e0e0050f581ddbf86f350fe2d Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 3 Oct 2013 10:10:36 -0400 Subject: [PATCH 14/38] Very stupid, cannot import glymur in setup.py! #127 The install requirements have not yet necessarily been taken care of yet, so pip installs will break on 2.6 and 2.7, probably due to contextlib2. --- setup.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 8d62705..b5b62aa 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,8 @@ kwargs = {'name': 'Glymur', 'package_data': {'glymur': ['data/*.jp2', 'data/*.j2k']}, 'scripts': ['bin/jp2dump'], 'license': 'MIT', - 'test_suite': 'glymur.test'} + 'test_suite': 'glymur.test', + 'version': '0.5.4'} instllrqrs = ['numpy>=1.4.1'] if sys.hexversion < 0x03030000: @@ -38,7 +39,4 @@ clssfrs = ["Programming Language :: Python", "Topic :: Software Development :: Libraries :: Python Modules"] kwargs['classifiers'] = clssfrs -import glymur -kwargs['version'] = glymur.version.version - setup(**kwargs) From fe3964c212bbc08aff42d42cc08c8b2a7a72262e Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 3 Oct 2013 10:41:12 -0400 Subject: [PATCH 15/38] Add some information about how many tests should be expected to skip. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index beff6ab..cd80fa2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,8 @@ before_install: install: "pip install -r travis-requirements.txt --use-mirrors" # command to run tests +# 2.7 should skip no more than 342 tests +# 3.3 should skip no more than 339 tests script: "python -m unittest discover" notifications: From 244fb26d9643144e772f950dd9d6e0373fa64d6e Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 3 Oct 2013 10:41:41 -0400 Subject: [PATCH 16/38] Change the way versioning is included in setup.py #127 Using regular expressions should work so long as the pattern in glymur/version.py does not change. --- glymur/version.py | 2 ++ setup.py | 12 ++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/glymur/version.py b/glymur/version.py index 5e4122b..04004fd 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -13,6 +13,8 @@ from distutils.version import LooseVersion from .lib import openjpeg as opj from .lib import openjp2 as opj2 +# Do not change the format of this next line! Doing so risks breaking +# setup.py version = "0.5.4" _sv = LooseVersion(version) version_tuple = _sv.version diff --git a/setup.py b/setup.py index b5b62aa..89e69f7 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,6 @@ from setuptools import setup, find_packages +import os +import re import sys kwargs = {'name': 'Glymur', @@ -12,8 +14,7 @@ kwargs = {'name': 'Glymur', 'package_data': {'glymur': ['data/*.jp2', 'data/*.j2k']}, 'scripts': ['bin/jp2dump'], 'license': 'MIT', - 'test_suite': 'glymur.test', - 'version': '0.5.4'} + 'test_suite': 'glymur.test'} instllrqrs = ['numpy>=1.4.1'] if sys.hexversion < 0x03030000: @@ -39,4 +40,11 @@ clssfrs = ["Programming Language :: Python", "Topic :: Software Development :: Libraries :: Python Modules"] kwargs['classifiers'] = clssfrs +# Get the version string. Cannot do this by importing glymur! +version_file = os.path.join('glymur', 'version.py') +with open('glymur/version.py', 'rt') as fptr: + contents = fptr.read() + match = re.search('version\s*=\s*"(?P\d*.\d*.\d*.*)"\n', contents) + kwargs['version'] = match.group('version') + setup(**kwargs) From 3b6d34531dc4b3438b5581605105bb40d47e367d Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 3 Oct 2013 11:09:58 -0400 Subject: [PATCH 17/38] Finalizing for 0.5.5 release. --- CHANGES.txt | 4 +++- docs/source/conf.py | 2 +- glymur/version.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index d0e2bce..30387a6 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,9 +1,11 @@ +Oct 03, 2013 - v0.5.5 Fixed pip install error introduced in 0.5.0. + Sep 24, 2013 - v0.5.4 Fixed test error restricted to v2.0. Sep 24, 2013 - v0.5.3 Removed a duplicated channel definition test in test_jp2box that could cause a segfault in 1.3 if not properly skipped. -Sep 23, 2013 - v0.5.2 Fixed some teests that have been failing since 0.5. +Sep 23, 2013 - v0.5.2 Fixed some tests that have been failing since 0.5. under various edge cases. Sep 19, 2013 - v0.5.1 Added more resiliency to XML box parsing. Fixed tests diff --git a/docs/source/conf.py b/docs/source/conf.py index e01cc91..f847e68 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -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.4' +release = '0.5.5' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/glymur/version.py b/glymur/version.py index 04004fd..192859f 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -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.4" +version = "0.5.5" _sv = LooseVersion(version) version_tuple = _sv.version From 86d799bfb75b8eca4e311ac1065b5b53d61f352f Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 7 Oct 2013 16:28:33 -0400 Subject: [PATCH 18/38] Reorganized the jp2k tests, dropped several duplicated ones. Closes #128 --- glymur/test/test_jp2k.py | 954 +++++++++++++++------------------------ 1 file changed, 365 insertions(+), 589 deletions(-) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index ec174bf..636ea9a 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -55,598 +55,11 @@ def load_tests(loader, tests, ignore): return tests -@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2 or - OPENJP2_IS_V2_OFFICIAL, - "Missing openjp2 library version 2.0+.") -class TestJp2k_2_1(unittest.TestCase): - """Test suite for version 2.0+ of openjpeg software""" - - def setUp(self): - self.jp2file = glymur.data.nemo() - self.j2kfile = glymur.data.goodstuff() - - def tearDown(self): - pass - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_grey_with_extra_component(self): - """version 2.0 cannot write gray + extra""" - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = Jp2k(tfile.name, 'wb') - data = np.zeros((128, 128, 2), dtype=np.uint8) - j.write(data) - self.assertEqual(j.box[2].box[0].height, 128) - self.assertEqual(j.box[2].box[0].width, 128) - self.assertEqual(j.box[2].box[0].num_components, 2) - self.assertEqual(j.box[2].box[1].colorspace, - glymur.core.GREYSCALE) - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_rgb_with_extra_component(self): - """v2.0+ should be able to write extra components""" - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = Jp2k(tfile.name, 'wb') - data = np.zeros((128, 128, 4), dtype=np.uint8) - j.write(data) - self.assertEqual(j.box[2].box[0].height, 128) - self.assertEqual(j.box[2].box[0].width, 128) - self.assertEqual(j.box[2].box[0].num_components, 4) - self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB) - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_openjpeg_library_message(self): - """Verify the error message produced by the openjpeg library""" - # This will confirm that the error callback mechanism is working. - with open(self.jp2file, 'rb') as fptr: - data = fptr.read() - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - # Codestream starts at byte 3127. SIZ marker at 3137. - # COD marker at 3186. Subsampling at 3180. - tfile.write(data[0:3179]) - - # Make the DY bytes of the SIZ segment zero. That means that - # a subsampling factor is zero, which is illegal. - tfile.write(b'\x00') - tfile.write(data[3180:3182]) - tfile.write(b'\x00') - tfile.write(data[3184:3186]) - tfile.write(b'\x00') - - tfile.write(data[3186:]) - tfile.flush() - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - j = Jp2k(tfile.name) - regexp = re.compile(r'''OpenJPEG\slibrary\serror:\s+ - Invalid\svalues\sfor\scomp\s=\s0\s+ - :\sdx=1\sdy=0''', re.VERBOSE) - if sys.hexversion < 0x03020000: - with self.assertRaisesRegexp((IOError, OSError), regexp): - j.read(rlevel=1) - else: - with self.assertRaisesRegex((IOError, OSError), regexp): - j.read(rlevel=1) - - -@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2, - "Not tested for 1.x") -class TestJp2k_2_0(unittest.TestCase): - """Test suite requiring at least version 2.0""" - - def setUp(self): - self.jp2file = glymur.data.nemo() - self.j2kfile = glymur.data.goodstuff() - - def tearDown(self): - pass - - @unittest.skipIf(not OPENJP2_IS_V2_OFFICIAL, - "Behavior is specific to 2.0 official.") - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_extra_components_on_v2(self): - """must error out in 1.x with extra components.""" - # Extra components seems to require 2.0+. Verify that we error out. - with self.assertRaises((IOError, OSError)): - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = Jp2k(tfile.name, 'wb') - data = np.zeros((128, 128, 4), dtype=np.uint8) - j.write(data) - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_unrecognized_jp2_clrspace(self): - """We only allow RGB and GRAYSCALE. Should error out with others""" - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = Jp2k(tfile.name, 'wb') - with self.assertRaises(IOError): - data = np.zeros((128, 128, 3), dtype=np.uint8) - j.write(data, colorspace='cmyk') - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_asoc_label_box(self): - """Test asoc and label box""" - # Construct a fake file with an asoc and a label box, as - # OpenJPEG doesn't have such a file. - data = Jp2k(self.jp2file).read(rlevel=1) - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = Jp2k(tfile.name, 'wb') - j.write(data) - - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2: - - # Offset of the codestream is where we start. - read_buffer = tfile.read(77) - tfile2.write(read_buffer) - - # read the rest of the file, it's the codestream. - codestream = tfile.read() - - # Write the asoc superbox. - # Length = 36, id is 'asoc'. - write_buffer = struct.pack('>I4s', int(56), b'asoc') - tfile2.write(write_buffer) - - # Write the contained label box - write_buffer = struct.pack('>I4s', int(13), b'lbl ') - tfile2.write(write_buffer) - tfile2.write('label'.encode()) - - # Write the xml box - # Length = 36, id is 'xml '. - write_buffer = struct.pack('>I4s', int(35), b'xml ') - tfile2.write(write_buffer) - - write_buffer = 'this is a test' - write_buffer = write_buffer.encode() - tfile2.write(write_buffer) - - # Now append the codestream. - tfile2.write(codestream) - tfile2.flush() - - jasoc = Jp2k(tfile2.name) - self.assertEqual(jasoc.box[3].box_id, 'asoc') - self.assertEqual(jasoc.box[3].box[0].box_id, 'lbl ') - self.assertEqual(jasoc.box[3].box[0].label, 'label') - self.assertEqual(jasoc.box[3].box[1].box_id, 'xml ') - - -@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None, - "Missing openjp2 library.") -class TestJp2k_1_x(unittest.TestCase): - """Test suite for versions up to 1.5.1 of openjpeg but no further""" - - def setUp(self): - self.jp2file = glymur.data.nemo() - self.j2kfile = glymur.data.goodstuff() - - def tearDown(self): - pass - - def test_rlevel_max(self): - """Verify that rlevel=-1 gets us the lowest resolution image""" - j = Jp2k(self.j2kfile) - thumbnail1 = j.read(rlevel=-1) - thumbnail2 = j.read(rlevel=5) - np.testing.assert_array_equal(thumbnail1, thumbnail2) - self.assertEqual(thumbnail1.shape, (25, 15, 3)) - - def test_bad_area_parameter(self): - """Should error out appropriately if given a bad area parameter.""" - j = Jp2k(self.jp2file) - with self.assertRaises(IOError): - # Start corner must be >= 0 - j.read(area=(-1, -1, 1, 1)) - with self.assertRaises(IOError): - # End corner must be > 0 - j.read(area=(10, 10, 0, 0)) - with self.assertRaises(IOError): - # End corner must be >= start corner - j.read(area=(10, 10, 8, 8)) - - def test_rlevel_too_high(self): - """Should error out appropriately if reduce level too high""" - j = Jp2k(self.jp2file) - with self.assertRaises(IOError): - j.read(rlevel=6) - - def test_jp2_boxes(self): - """Verify the boxes of a JP2 file. Basic jp2 test.""" - jp2k = Jp2k(self.jp2file) - - # top-level boxes - self.assertEqual(len(jp2k.box), 6) - - self.assertEqual(jp2k.box[0].box_id, 'jP ') - self.assertEqual(jp2k.box[0].offset, 0) - self.assertEqual(jp2k.box[0].length, 12) - self.assertEqual(jp2k.box[0].longname, 'JPEG 2000 Signature') - - self.assertEqual(jp2k.box[1].box_id, 'ftyp') - self.assertEqual(jp2k.box[1].offset, 12) - self.assertEqual(jp2k.box[1].length, 20) - self.assertEqual(jp2k.box[1].longname, 'File Type') - - self.assertEqual(jp2k.box[2].box_id, 'jp2h') - self.assertEqual(jp2k.box[2].offset, 32) - self.assertEqual(jp2k.box[2].length, 45) - self.assertEqual(jp2k.box[2].longname, 'JP2 Header') - - self.assertEqual(jp2k.box[3].box_id, 'uuid') - self.assertEqual(jp2k.box[3].offset, 77) - self.assertEqual(jp2k.box[3].length, 638) - - self.assertEqual(jp2k.box[4].box_id, 'uuid') - self.assertEqual(jp2k.box[4].offset, 715) - self.assertEqual(jp2k.box[4].length, 2412) - - self.assertEqual(jp2k.box[5].box_id, 'jp2c') - self.assertEqual(jp2k.box[5].offset, 3127) - self.assertEqual(jp2k.box[5].length, 1132296) - - # jp2h super box - self.assertEqual(len(jp2k.box[2].box), 2) - - self.assertEqual(jp2k.box[2].box[0].box_id, 'ihdr') - self.assertEqual(jp2k.box[2].box[0].offset, 40) - self.assertEqual(jp2k.box[2].box[0].length, 22) - self.assertEqual(jp2k.box[2].box[0].longname, 'Image Header') - self.assertEqual(jp2k.box[2].box[0].height, 1456) - self.assertEqual(jp2k.box[2].box[0].width, 2592) - self.assertEqual(jp2k.box[2].box[0].num_components, 3) - self.assertEqual(jp2k.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2k.box[2].box[0].signed, False) - self.assertEqual(jp2k.box[2].box[0].compression, 7) - self.assertEqual(jp2k.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2k.box[2].box[0].ip_provided, False) - - self.assertEqual(jp2k.box[2].box[1].box_id, 'colr') - self.assertEqual(jp2k.box[2].box[1].offset, 62) - self.assertEqual(jp2k.box[2].box[1].length, 15) - self.assertEqual(jp2k.box[2].box[1].longname, 'Colour Specification') - self.assertEqual(jp2k.box[2].box[1].precedence, 0) - self.assertEqual(jp2k.box[2].box[1].approximation, 0) - self.assertEqual(jp2k.box[2].box[1].colorspace, glymur.core.SRGB) - self.assertIsNone(jp2k.box[2].box[1].icc_profile) - - def test_j2k_box(self): - """A J2K/J2C file must not have any boxes.""" - # Verify that a J2K file has no boxes. - jp2k = Jp2k(self.j2kfile) - self.assertEqual(len(jp2k.box), 0) - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_64bit_xl_field(self): - """XL field should be supported""" - # Verify that boxes with the XL field are properly read. - # Don't have such a file on hand, so we create one. Copy our example - # file, but making the codestream have a 64-bit XL field. - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - with open(self.jp2file, 'rb') as ifile: - # Everything up until the jp2c box. - write_buffer = ifile.read(3127) - tfile.write(write_buffer) - - # The L field must be 1 in order to signal the presence of the - # XL field. The actual length of the jp2c box increased by 8 - # (8 bytes for the XL field). - length = 1 - typ = b'jp2c' - xlen = 1133427 + 8 - write_buffer = struct.pack('>I4sQ', int(length), typ, xlen) - tfile.write(write_buffer) - - # Get the rest of the input file (minus the 8 bytes for L and - # T. - ifile.seek(8, 1) - write_buffer = ifile.read() - tfile.write(write_buffer) - tfile.flush() - - jp2k = Jp2k(tfile.name) - - self.assertEqual(jp2k.box[5].box_id, 'jp2c') - self.assertEqual(jp2k.box[5].offset, 3127) - self.assertEqual(jp2k.box[5].length, 1133427 + 8) - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_length_field_is_zero(self): - """L=0 (length field in box header) is allowed""" - # Verify that boxes with the L field as zero are correctly read. - # This should only happen in the last box of a JPEG 2000 file. - # Our example image has its last box at byte 588458. - baseline_jp2 = Jp2k(self.jp2file) - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - with open(self.jp2file, 'rb') as ifile: - # Everything up until the jp2c box. - write_buffer = ifile.read(588458) - tfile.write(write_buffer) - - length = 0 - typ = b'uuid' - write_buffer = struct.pack('>I4s', int(length), typ) - tfile.write(write_buffer) - - # Get the rest of the input file (minus the 8 bytes for L and - # T. - ifile.seek(8, 1) - write_buffer = ifile.read() - tfile.write(write_buffer) - tfile.flush() - - new_jp2 = Jp2k(tfile.name) - - # The top level boxes in each file should match. - for j in range(len(baseline_jp2.box)): - self.assertEqual(new_jp2.box[j].box_id, - baseline_jp2.box[j].box_id) - self.assertEqual(new_jp2.box[j].offset, - baseline_jp2.box[j].offset) - self.assertEqual(new_jp2.box[j].length, - baseline_jp2.box[j].length) - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_cblkh_different_than_width(self): - """Verify that we can set a code block size where height does not equal - width. - """ - data = np.zeros((128, 128), dtype=np.uint8) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - - # The code block dimensions are given as rows x columns. - j.write(data, cbsize=(16, 32)) - - codestream = j.get_codestream() - - # Code block size is reported as XY in the codestream. - self.assertEqual(tuple(codestream.segment[2].spcod[5:7]), (3, 2)) - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_too_many_dimensions(self): - """OpenJP2 only allows 2D or 3D images.""" - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - with self.assertRaises(IOError): - data = np.zeros((128, 128, 2, 2), dtype=np.uint8) - j.write(data) - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_2d_rgb(self): - """RGB must have at least 3 components.""" - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = Jp2k(tfile.name, 'wb') - with self.assertRaises(IOError): - data = np.zeros((128, 128, 2), dtype=np.uint8) - j.write(data, colorspace='rgb') - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_colorspace_with_j2k(self): - """Specifying a colorspace with J2K does not make sense""" - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - with self.assertRaises(IOError): - data = np.zeros((128, 128, 3), dtype=np.uint8) - j.write(data, colorspace='rgb') - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_specify_rgb(self): - """specify RGB explicitly""" - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = Jp2k(tfile.name, 'wb') - data = np.zeros((128, 128, 3), dtype=np.uint8) - j.write(data, colorspace='rgb') - self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB) - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_specify_gray(self): - """test gray explicitly specified (that's GRAY, not GREY)""" - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = Jp2k(tfile.name, 'wb') - data = np.zeros((128, 128), dtype=np.uint8) - j.write(data, colorspace='gray') - self.assertEqual(j.box[2].box[1].colorspace, - glymur.core.GREYSCALE) - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_specify_grey(self): - """test grey explicitly specified""" - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = Jp2k(tfile.name, 'wb') - data = np.zeros((128, 128), dtype=np.uint8) - j.write(data, colorspace='grey') - self.assertEqual(j.box[2].box[1].colorspace, - glymur.core.GREYSCALE) - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_grey_with_two_extra_comps(self): - """should be able to write gray + two extra components""" - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = Jp2k(tfile.name, 'wb') - data = np.zeros((128, 128, 3), dtype=np.uint8) - j.write(data, colorspace='gray') - self.assertEqual(j.box[2].box[0].height, 128) - self.assertEqual(j.box[2].box[0].width, 128) - self.assertEqual(j.box[2].box[0].num_components, 3) - self.assertEqual(j.box[2].box[1].colorspace, - glymur.core.GREYSCALE) - - def test_specify_ycc(self): - """Should reject YCC""" - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = Jp2k(tfile.name, 'wb') - with self.assertRaises(IOError): - data = np.zeros((128, 128, 3), dtype=np.uint8) - j.write(data, colorspace='ycc') - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_write_with_jp2_in_caps(self): - """should be able to write with JP2 suffix.""" - j2k = Jp2k(self.j2kfile) - expdata = j2k.read() - with tempfile.NamedTemporaryFile(suffix='.JP2') as tfile: - ofile = Jp2k(tfile.name, 'wb') - ofile.write(expdata) - actdata = ofile.read() - np.testing.assert_array_equal(actdata, expdata) - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_write_srgb_without_mct(self): - """should be able to write RGB without specifying mct""" - j2k = Jp2k(self.j2kfile) - expdata = j2k.read() - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - ofile = Jp2k(tfile.name, 'wb') - ofile.write(expdata, mct=False) - actdata = ofile.read() - np.testing.assert_array_equal(actdata, expdata) - - codestream = ofile.get_codestream() - self.assertEqual(codestream.segment[2].spcod[3], 0) # no mct - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_write_grayscale_with_mct(self): - """MCT usage makes no sense for grayscale images.""" - j2k = Jp2k(self.j2kfile) - expdata = j2k.read() - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - ofile = Jp2k(tfile.name, 'wb') - with self.assertRaises(IOError): - ofile.write(expdata[:, :, 0], mct=True) - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_write_cprl(self): - """Must be able to write a CPRL progression order file""" - # Issue 17 - j = Jp2k(self.jp2file) - expdata = j.read(rlevel=1) - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - ofile = Jp2k(tfile.name, 'wb') - ofile.write(expdata, prog='CPRL') - actdata = ofile.read() - np.testing.assert_array_equal(actdata, expdata) - - codestream = ofile.get_codestream() - self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL) - - -@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is not None, - "Don't bother if openjp2 is present.") -@unittest.skipIf(glymur.lib.openjpeg.OPENJPEG is None, - "Missing openjpeg library.") -class TestJp2k15(unittest.TestCase): - """Test suite for openjpeg 1.x, not appropriate for 2.x""" - - def setUp(self): - self.jp2file = glymur.data.nemo() - self.j2kfile = glymur.data.goodstuff() - - def tearDown(self): - pass - - def test_area(self): - """Area option not allowed for 1.5.1. - """ - j2k = Jp2k(self.j2kfile) - with self.assertRaises(TypeError): - j2k.read(area=(0, 0, 100, 100)) - - def test_tile(self): - """tile option not allowed for 1.5.1. - """ - j2k = Jp2k(self.j2kfile) - with self.assertRaises(TypeError): - j2k.read(tile=0) - - def test_layer(self): - """layer option not allowed for 1.5.1. - """ - j2k = Jp2k(self.j2kfile) - with self.assertRaises(TypeError): - j2k.read(layer=1) - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - @unittest.skipIf(glymur.version.openjpeg_version_tuple[1] < 5, - "Writing only supported with openjpeg version 1.5+.") - def test_2d_rgb(self): - """RGB must have at least 3 components.""" - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = Jp2k(tfile.name, 'wb') - with self.assertRaises(IOError): - data = np.zeros((128, 128, 2), dtype=np.uint8) - j.write(data, colorspace='rgb') - - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_xml_with_trailing_nulls(self): - """ElementTree doesn't like trailing null chars after valid XML text""" - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - with open(self.jp2file, 'rb') as ifile: - # Everything up until the jp2c box. - write_buffer = ifile.read(77) - tfile.write(write_buffer) - - # Write the xml box - # Length = 36, id is 'xml '. - write_buffer = struct.pack('>I4s', int(36), b'xml ') - tfile.write(write_buffer) - - write_buffer = 'this is a test' + chr(0) - write_buffer = write_buffer.encode() - tfile.write(write_buffer) - - # Get the rest of the input file. - write_buffer = ifile.read() - tfile.write(write_buffer) - tfile.flush() - - jp2k = Jp2k(tfile.name) - - self.assertEqual(jp2k.box[3].box_id, 'xml ') - self.assertEqual(jp2k.box[3].offset, 77) - self.assertEqual(jp2k.box[3].length, 36) - self.assertEqual(ET.tostring(jp2k.box[3].xml.getroot()), - b'this is a test') - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - @unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2, - "Segfault on official v1.x series.") - def test_openjpeg_library_message(self): - """Verify the error message produced by the openjpeg library""" - # This will confirm that the error callback mechanism is working. - with open(self.jp2file, 'rb') as fptr: - data = fptr.read() - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - # Codestream starts at byte 3127. SIZ marker at 3137. - # COD marker at 3186. Subsampling at 3180. - tfile.write(data[0:3179]) - - # Make the DY bytes of the SIZ segment zero. That means that - # a subsampling factor is zero, which is illegal. - tfile.write(b'\x00') - tfile.write(data[3180:3182]) - tfile.write(b'\x00') - tfile.write(data[3184:3186]) - tfile.write(b'\x00') - - tfile.write(data[3186:]) - tfile.flush() - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - j = Jp2k(tfile.name) - regexp = re.compile(r'''OpenJPEG\slibrary\serror:\s+ - Invalid\svalues\sfor\scomp\s=\s0\s+ - :\sdx=1\sdy=0''', re.VERBOSE) - if sys.hexversion < 0x03020000: - with self.assertRaisesRegexp((IOError, OSError), regexp): - j.read(rlevel=1) - else: - with self.assertRaisesRegex((IOError, OSError), regexp): - j.read(rlevel=1) - - class TestJp2k(unittest.TestCase): """Test suite for openjpeg software starting at 1.3""" + # These tests should be run by just about all configuration. + def setUp(self): self.jp2file = glymur.data.nemo() self.j2kfile = glymur.data.goodstuff() @@ -985,5 +398,368 @@ class TestJp2k(unittest.TestCase): self.assertFalse('Make' in exif['Image'].keys()) +@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version), + "Requires at least version 1.5") +class TestJp2k_write(unittest.TestCase): + """Write tests, can be run by versions 1.5+""" + + def setUp(self): + self.jp2file = glymur.data.nemo() + self.j2kfile = glymur.data.goodstuff() + + def tearDown(self): + pass + + + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + def test_cblkh_different_than_width(self): + """Verify that we can set a code block size where height does not equal + width. + """ + data = np.zeros((128, 128), dtype=np.uint8) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + + # The code block dimensions are given as rows x columns. + j.write(data, cbsize=(16, 32)) + + codestream = j.get_codestream() + + # Code block size is reported as XY in the codestream. + self.assertEqual(tuple(codestream.segment[2].spcod[5:7]), (3, 2)) + + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + def test_too_many_dimensions(self): + """OpenJP2 only allows 2D or 3D images.""" + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError): + data = np.zeros((128, 128, 2, 2), dtype=np.uint8) + j.write(data) + + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + def test_2d_rgb(self): + """RGB must have at least 3 components.""" + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError): + data = np.zeros((128, 128, 2), dtype=np.uint8) + j.write(data, colorspace='rgb') + + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + def test_colorspace_with_j2k(self): + """Specifying a colorspace with J2K does not make sense""" + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError): + data = np.zeros((128, 128, 3), dtype=np.uint8) + j.write(data, colorspace='rgb') + + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + def test_specify_rgb(self): + """specify RGB explicitly""" + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') + data = np.zeros((128, 128, 3), dtype=np.uint8) + j.write(data, colorspace='rgb') + self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB) + + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + def test_specify_gray(self): + """test gray explicitly specified (that's GRAY, not GREY)""" + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') + data = np.zeros((128, 128), dtype=np.uint8) + j.write(data, colorspace='gray') + self.assertEqual(j.box[2].box[1].colorspace, + glymur.core.GREYSCALE) + + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + def test_specify_grey(self): + """test grey explicitly specified""" + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') + data = np.zeros((128, 128), dtype=np.uint8) + j.write(data, colorspace='grey') + self.assertEqual(j.box[2].box[1].colorspace, + glymur.core.GREYSCALE) + + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + def test_grey_with_two_extra_comps(self): + """should be able to write gray + two extra components""" + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') + data = np.zeros((128, 128, 3), dtype=np.uint8) + j.write(data, colorspace='gray') + self.assertEqual(j.box[2].box[0].height, 128) + self.assertEqual(j.box[2].box[0].width, 128) + self.assertEqual(j.box[2].box[0].num_components, 3) + self.assertEqual(j.box[2].box[1].colorspace, + glymur.core.GREYSCALE) + + def test_specify_ycc(self): + """Should reject YCC""" + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError): + data = np.zeros((128, 128, 3), dtype=np.uint8) + j.write(data, colorspace='ycc') + + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + def test_write_with_jp2_in_caps(self): + """should be able to write with JP2 suffix.""" + j2k = Jp2k(self.j2kfile) + expdata = j2k.read() + with tempfile.NamedTemporaryFile(suffix='.JP2') as tfile: + ofile = Jp2k(tfile.name, 'wb') + ofile.write(expdata) + actdata = ofile.read() + np.testing.assert_array_equal(actdata, expdata) + + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + def test_write_srgb_without_mct(self): + """should be able to write RGB without specifying mct""" + j2k = Jp2k(self.j2kfile) + expdata = j2k.read() + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + ofile = Jp2k(tfile.name, 'wb') + ofile.write(expdata, mct=False) + actdata = ofile.read() + np.testing.assert_array_equal(actdata, expdata) + + codestream = ofile.get_codestream() + self.assertEqual(codestream.segment[2].spcod[3], 0) # no mct + + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + def test_write_grayscale_with_mct(self): + """MCT usage makes no sense for grayscale images.""" + j2k = Jp2k(self.j2kfile) + expdata = j2k.read() + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + ofile = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError): + ofile.write(expdata[:, :, 0], mct=True) + + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + def test_write_cprl(self): + """Must be able to write a CPRL progression order file""" + # Issue 17 + j = Jp2k(self.jp2file) + expdata = j.read(rlevel=1) + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + ofile = Jp2k(tfile.name, 'wb') + ofile.write(expdata, prog='CPRL') + actdata = ofile.read() + np.testing.assert_array_equal(actdata, expdata) + + codestream = ofile.get_codestream() + self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL) + + +@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] >= 2, + "Negative tests only for version 1.x") +class TestJp2k_1_x(unittest.TestCase): + """Test suite for openjpeg 1.x, not appropriate for 2.x""" + + def setUp(self): + self.jp2file = glymur.data.nemo() + self.j2kfile = glymur.data.goodstuff() + + def tearDown(self): + pass + + def test_area(self): + """Area option not allowed for 1.x. + """ + j2k = Jp2k(self.j2kfile) + with self.assertRaises(TypeError): + j2k.read(area=(0, 0, 100, 100)) + + def test_tile(self): + """tile option not allowed for 1.x. + """ + j2k = Jp2k(self.j2kfile) + with self.assertRaises(TypeError): + j2k.read(tile=0) + + def test_layer(self): + """layer option not allowed for 1.x. + """ + j2k = Jp2k(self.j2kfile) + with self.assertRaises(TypeError): + j2k.read(layer=1) + + +@unittest.skipIf(not OPENJP2_IS_V2_OFFICIAL, + "Tests only to be run on 2.0 official.") +class TestJp2k_2_0_official(unittest.TestCase): + """Test suite to only be run on v2.0 official.""" + + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + def test_extra_components_on_v2(self): + """Can only write 4 components on 2.0+, should error out otherwise.""" + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') + data = np.zeros((128, 128, 4), dtype=np.uint8) + with self.assertRaises(IOError): + j.write(data) + + +@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2, + "Requires as least v2.0") +class TestJp2k_2_0(unittest.TestCase): + """Test suite requiring at least version 2.0""" + + def setUp(self): + self.jp2file = glymur.data.nemo() + self.j2kfile = glymur.data.goodstuff() + + def tearDown(self): + pass + + def test_bad_area_parameter(self): + """Should error out appropriately if given a bad area parameter.""" + j = Jp2k(self.jp2file) + with self.assertRaises(IOError): + # Start corner must be >= 0 + j.read(area=(-1, -1, 1, 1)) + with self.assertRaises(IOError): + # End corner must be > 0 + j.read(area=(10, 10, 0, 0)) + with self.assertRaises(IOError): + # End corner must be >= start corner + j.read(area=(10, 10, 8, 8)) + + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + def test_unrecognized_jp2_clrspace(self): + """We only allow RGB and GRAYSCALE. Should error out with others""" + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError): + data = np.zeros((128, 128, 3), dtype=np.uint8) + j.write(data, colorspace='cmyk') + + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + def test_asoc_label_box(self): + """Test asoc and label box""" + # Construct a fake file with an asoc and a label box, as + # OpenJPEG doesn't have such a file. + data = Jp2k(self.jp2file).read(rlevel=1) + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data) + + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2: + + # Offset of the codestream is where we start. + read_buffer = tfile.read(77) + tfile2.write(read_buffer) + + # read the rest of the file, it's the codestream. + codestream = tfile.read() + + # Write the asoc superbox. + # Length = 36, id is 'asoc'. + write_buffer = struct.pack('>I4s', int(56), b'asoc') + tfile2.write(write_buffer) + + # Write the contained label box + write_buffer = struct.pack('>I4s', int(13), b'lbl ') + tfile2.write(write_buffer) + tfile2.write('label'.encode()) + + # Write the xml box + # Length = 36, id is 'xml '. + write_buffer = struct.pack('>I4s', int(35), b'xml ') + tfile2.write(write_buffer) + + write_buffer = 'this is a test' + write_buffer = write_buffer.encode() + tfile2.write(write_buffer) + + # Now append the codestream. + tfile2.write(codestream) + tfile2.flush() + + jasoc = Jp2k(tfile2.name) + self.assertEqual(jasoc.box[3].box_id, 'asoc') + self.assertEqual(jasoc.box[3].box[0].box_id, 'lbl ') + self.assertEqual(jasoc.box[3].box[0].label, 'label') + self.assertEqual(jasoc.box[3].box[1].box_id, 'xml ') + + +@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2 or + OPENJP2_IS_V2_OFFICIAL, + "Missing openjp2 library version 2.0+.") +class TestJp2k_2_1(unittest.TestCase): + """Only to be run in 2.0+.""" + + def setUp(self): + self.jp2file = glymur.data.nemo() + self.j2kfile = glymur.data.goodstuff() + + def tearDown(self): + pass + + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + def test_grey_with_extra_component(self): + """version 2.0 cannot write gray + extra""" + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') + data = np.zeros((128, 128, 2), dtype=np.uint8) + j.write(data) + self.assertEqual(j.box[2].box[0].height, 128) + self.assertEqual(j.box[2].box[0].width, 128) + self.assertEqual(j.box[2].box[0].num_components, 2) + self.assertEqual(j.box[2].box[1].colorspace, + glymur.core.GREYSCALE) + + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + def test_rgb_with_extra_component(self): + """v2.0+ should be able to write extra components""" + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') + data = np.zeros((128, 128, 4), dtype=np.uint8) + j.write(data) + self.assertEqual(j.box[2].box[0].height, 128) + self.assertEqual(j.box[2].box[0].width, 128) + self.assertEqual(j.box[2].box[0].num_components, 4) + self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB) + + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + def test_openjpeg_library_message(self): + """Verify the error message produced by the openjpeg library""" + # This will confirm that the error callback mechanism is working. + with open(self.jp2file, 'rb') as fptr: + data = fptr.read() + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + # Codestream starts at byte 3127. SIZ marker at 3137. + # COD marker at 3186. Subsampling at 3180. + tfile.write(data[0:3179]) + + # Make the DY bytes of the SIZ segment zero. That means that + # a subsampling factor is zero, which is illegal. + tfile.write(b'\x00') + tfile.write(data[3180:3182]) + tfile.write(b'\x00') + tfile.write(data[3184:3186]) + tfile.write(b'\x00') + + tfile.write(data[3186:]) + tfile.flush() + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + j = Jp2k(tfile.name) + regexp = re.compile(r'''OpenJPEG\slibrary\serror:\s+ + Invalid\svalues\sfor\scomp\s=\s0\s+ + :\sdx=1\sdy=0''', re.VERBOSE) + if sys.hexversion < 0x03020000: + with self.assertRaisesRegexp((IOError, OSError), regexp): + j.read(rlevel=1) + else: + with self.assertRaisesRegex((IOError, OSError), regexp): + j.read(rlevel=1) + + if __name__ == "__main__": unittest.main() From f0db9dfa2d9872f4e0440c3853e6343b41bd6db2 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 8 Oct 2013 09:51:43 -0400 Subject: [PATCH 19/38] openjp2 write method rewritten for exitstack support. #45 --- glymur/jp2k.py | 64 +++++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 00b71c2..1bd36a4 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -474,41 +474,37 @@ class Jp2k(Jp2kBox): comptparms = _populate_comptparms(img_array, cparams) - image = opj2.image_create(comptparms, colorspace) - _populate_image_struct(cparams, image, img_array) - - codec = opj2.create_compress(cparams.codec_fmt) - - info_handler = _INFO_CALLBACK if verbose else None - opj2.set_info_handler(codec, info_handler) - opj2.set_warning_handler(codec, _WARNING_CALLBACK) - opj2.set_error_handler(codec, _ERROR_CALLBACK) - - opj2.setup_encoder(codec, cparams, image) - - if _OPENJP2_IS_OFFICIAL_V2: - fptr = libc.fopen(self.filename, 'wb') - strm = opj2.stream_create_default_file_stream(fptr, False) - else: - # This routine introduced in 2.0 devel series. - strm = opj2.stream_create_default_file_stream_v3(self.filename, - False) - - # Start to clean up after ourselves. - opj2.start_compress(codec, image, strm) - opj2.encode(codec, strm) - opj2.end_compress(codec, strm) - - if _OPENJP2_IS_OFFICIAL_V2: - opj2.stream_destroy(strm) - libc.fclose(fptr) - else: - # This routine introduced in 2.0 devel series. - opj2.stream_destroy_v3(strm) - - opj2.destroy_codec(codec) - opj2.image_destroy(image) + with ExitStack() as stack: + image = opj2.image_create(comptparms, colorspace) + stack.callback(opj2.image_destroy, image) + _populate_image_struct(cparams, image, img_array) + + codec = opj2.create_compress(cparams.codec_fmt) + stack.callback(opj2.destroy_codec, codec) + + info_handler = _INFO_CALLBACK if verbose else None + opj2.set_info_handler(codec, info_handler) + opj2.set_warning_handler(codec, _WARNING_CALLBACK) + opj2.set_error_handler(codec, _ERROR_CALLBACK) + + opj2.setup_encoder(codec, cparams, image) + + if _OPENJP2_IS_OFFICIAL_V2: + fptr = libc.fopen(self.filename, 'wb') + strm = opj2.stream_create_default_file_stream(fptr, False) + stack.callback(opj2.stream_destroy, strm) + stack.callback(libc.fclose, fptr) + else: + # This routine introduced in 2.0 devel series. + strm = opj2.stream_create_default_file_stream_v3(self.filename, + False) + stack.callback(opj2.stream_destroy_v3, strm) + + opj2.start_compress(codec, image, strm) + opj2.encode(codec, strm) + opj2.end_compress(codec, strm) + # Refresh the metadata. self.parse() From d6ef966a23bbb675ac3c9cff872ac1262fef85b9 Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 9 Oct 2013 20:47:50 -0400 Subject: [PATCH 20/38] Trying to have cake and eat it too. Adding 2.6 to travis matrix. --- .travis.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index cd80fa2..eba8919 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ language: python python: + - "2.6" - "2.7" - "3.3" @@ -9,14 +10,15 @@ before_install: - sudo apt-get install -qq python3-numpy - wget http://openjpeg.googlecode.com/files/openjpeg-1.5.0-Linux-x86_64.tar.gz - sudo tar -xvf openjpeg-1.5.0-Linux-x86_64.tar.gz --strip-components=1 -C / + - if [ "${TRAVIS_PYTHON_VERSION::1}" == 3 ]; then export TEST_PKG=unittest2py3k; else export TEST_PKG=unittest2; fi # command to install dependencies -install: "pip install -r travis-requirements.txt --use-mirrors" +install: + - pip install $TEST_PKG + - pip install -r travis-requirements.txt --use-mirrors # command to run tests -# 2.7 should skip no more than 342 tests -# 3.3 should skip no more than 339 tests -script: "python -m unittest discover" +script: "unit2 discover" notifications: email: "john.g.evans.ne@gmail.com" From 78915c5d959ed40476c28f33cf0fab8325e04957 Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 9 Oct 2013 21:00:34 -0400 Subject: [PATCH 21/38] Taking cue from tornado travis setup. Trying separate 2.6, 2.7, 3.3 setups. --- .travis.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index eba8919..a7335bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,15 +10,17 @@ before_install: - sudo apt-get install -qq python3-numpy - wget http://openjpeg.googlecode.com/files/openjpeg-1.5.0-Linux-x86_64.tar.gz - sudo tar -xvf openjpeg-1.5.0-Linux-x86_64.tar.gz --strip-components=1 -C / - - if [ "${TRAVIS_PYTHON_VERSION::1}" == 3 ]; then export TEST_PKG=unittest2py3k; else export TEST_PKG=unittest2; fi # command to install dependencies install: - - pip install $TEST_PKG - - pip install -r travis-requirements.txt --use-mirrors + - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install --use-mirrors contextlib2 mock ordereddict unittest2; fi + - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors contextlib2 mock; fi # command to run tests -script: "unit2 discover" +script: + - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then unit2 discover; fi + - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then python -m unittest discover; fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then python -m unittest discover; fi notifications: email: "john.g.evans.ne@gmail.com" From f5dd49cc1bf8349e5c91a1647d7efa376bfbed41 Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 9 Oct 2013 21:07:16 -0400 Subject: [PATCH 22/38] Must use pip to install numpy on python3, it would seem. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a7335bb..9434d04 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,6 @@ python: before_install: - sudo apt-get update -qq - sudo apt-get install -qq python-numpy - - sudo apt-get install -qq python3-numpy - wget http://openjpeg.googlecode.com/files/openjpeg-1.5.0-Linux-x86_64.tar.gz - sudo tar -xvf openjpeg-1.5.0-Linux-x86_64.tar.gz --strip-components=1 -C / @@ -15,6 +14,7 @@ before_install: install: - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install --use-mirrors contextlib2 mock ordereddict unittest2; fi - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors contextlib2 mock; fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install --use-mirrors numpy; fi # command to run tests script: From b49e113d16744c1f39c2b42b47abdb801429a8ab Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 9 Oct 2013 21:15:19 -0400 Subject: [PATCH 23/38] No need for this file anymore, it's hardcoded into .travis.yml. --- travis-requirements.txt | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 travis-requirements.txt diff --git a/travis-requirements.txt b/travis-requirements.txt deleted file mode 100644 index 9dd042a..0000000 --- a/travis-requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -numpy -contextlib2>=0.4.0 -mock>=1.0.1 From f6596dceb33d34aea99328a184618b3ed0dcf686 Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 11 Oct 2013 12:01:33 -0400 Subject: [PATCH 24/38] Fixed docstring errors. Closes #129 --- glymur/jp2box.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index f05e9de..901f9e9 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -568,7 +568,7 @@ class CodestreamHeaderBox(Jp2kBox): Returns ------- - AssociationBox instance + CodestreamHeaderBox instance """ box = CodestreamHeaderBox(length=length, offset=offset) @@ -627,7 +627,7 @@ class CompositingLayerHeaderBox(Jp2kBox): Returns ------- - AssociationBox instance + CompositingLayerHeaderBox instance """ box = CompositingLayerHeaderBox(length=length, offset=offset) @@ -638,7 +638,7 @@ class CompositingLayerHeaderBox(Jp2kBox): class ComponentMappingBox(Jp2kBox): - """Container for channel identification information. + """Container for component mapping information. Attributes ---------- @@ -1637,7 +1637,7 @@ class CaptureResolutionBox(Jp2kBox): @staticmethod def parse(fptr, offset, length): - """Parse Resolution box. + """Parse CaptureResolutionBox. Parameters ---------- @@ -1694,7 +1694,7 @@ class DisplayResolutionBox(Jp2kBox): @staticmethod def parse(fptr, offset, length): - """Parse Resolution box. + """Parse display resolution box. Parameters ---------- @@ -2047,7 +2047,7 @@ class DataEntryURLBox(Jp2kBox): @staticmethod def parse(fptr, offset, length): - """Parse Data Entry URL box. + """Parse data entry URL box. Parameters ---------- From 4c0da0dd856f3cd60e39770fc30af002ef39439b Mon Sep 17 00:00:00 2001 From: John Evans Date: Sat, 12 Oct 2013 18:57:35 -0400 Subject: [PATCH 25/38] Added xml box write test with utf-8 content. #131 --- glymur/test/test_jp2box_xml.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/glymur/test/test_jp2box_xml.py b/glymur/test/test_jp2box_xml.py index ba1ff15..0bd9a59 100644 --- a/glymur/test/test_jp2box_xml.py +++ b/glymur/test/test_jp2box_xml.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Test suite specifically targeting JP2 box layout. """ @@ -94,8 +95,6 @@ class TestXML(unittest.TestCase): with self.assertRaises((IOError, OSError)): glymur.jp2box.XMLBox(filename=self.xmlfile, xml=xml_object) - @unittest.skipIf(os.name == "nt", - "Problems using NamedTemporaryFile on windows.") def test_basic_xml(self): """Should be able to write a basic XMLBox""" j2k = Jp2k(self.j2kfile) @@ -116,8 +115,26 @@ class TestXML(unittest.TestCase): self.assertEqual(ET.tostring(jp2.box[3].xml.getroot()), b'0') - @unittest.skipIf(os.name == "nt", - "Problems using NamedTemporaryFile on windows.") + def test_utf8_xml(self): + """Should be able to write/read an XMLBox with utf-8 encoding.""" + j2k = Jp2k(self.j2kfile) + + self.jp2h.box = [self.ihdr, self.colr] + + xml_header = u'' + xml_string = u'Россия' + the_xml = ET.fromstring((xml_header + xml_string).encode('utf-8')) + xmlb = glymur.jp2box.XMLBox(xml=the_xml) + self.assertEqual(ET.tostring(xmlb.xml, encoding='utf-8').decode('utf-8'), xml_string) + + boxes = [self.jp2b, self.ftyp, self.jp2h, xmlb, self.jp2c] + + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + j2k.wrap(tfile.name, boxes=boxes) + jp2 = Jp2k(tfile.name) + self.assertEqual(ET.tostring(jp2.box[3].xml.getroot(), encoding='utf-8').decode('utf-8'), + xml_string) + def test_xml_from_file(self): """Must be able to create an XML box from an XML file.""" j2k = Jp2k(self.j2kfile) From 79d2c134c80db0dfd8a5f500e5c86d60efde8b93 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 12 Oct 2013 21:31:11 -0400 Subject: [PATCH 26/38] XML text now constructed via utf-8, not ascii. #131 --- glymur/jp2box.py | 4 +- glymur/test/test_jp2box_xml.py | 81 +++++++++++++++++++++++++--------- 2 files changed, 61 insertions(+), 24 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 901f9e9..f213cab 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1870,10 +1870,8 @@ class XMLBox(Jp2kBox): # Strip out any trailing nulls, as they can foul up XML parsing. text = text.rstrip(chr(0)) - # Scan for the start of the xml declaration. - try: - elt = ET.fromstring(text) + elt = ET.fromstring(text.encode('utf-8')) xml = ET.ElementTree(elt) except ParseError as parse_error: msg = 'A problem was encountered while parsing an XML box:' diff --git a/glymur/test/test_jp2box_xml.py b/glymur/test/test_jp2box_xml.py index 0bd9a59..7e6980f 100644 --- a/glymur/test/test_jp2box_xml.py +++ b/glymur/test/test_jp2box_xml.py @@ -24,6 +24,16 @@ import tempfile import warnings import xml.etree.cElementTree as ET +if sys.hexversion < 0x03000000: + from StringIO import StringIO +else: + from io import StringIO + +if sys.hexversion <= 0x03030000: + from mock import patch +else: + from unittest.mock import patch + if sys.hexversion < 0x02070000: import unittest2 as unittest else: @@ -115,26 +125,6 @@ class TestXML(unittest.TestCase): self.assertEqual(ET.tostring(jp2.box[3].xml.getroot()), b'0') - def test_utf8_xml(self): - """Should be able to write/read an XMLBox with utf-8 encoding.""" - j2k = Jp2k(self.j2kfile) - - self.jp2h.box = [self.ihdr, self.colr] - - xml_header = u'' - xml_string = u'Россия' - the_xml = ET.fromstring((xml_header + xml_string).encode('utf-8')) - xmlb = glymur.jp2box.XMLBox(xml=the_xml) - self.assertEqual(ET.tostring(xmlb.xml, encoding='utf-8').decode('utf-8'), xml_string) - - boxes = [self.jp2b, self.ftyp, self.jp2h, xmlb, self.jp2c] - - with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: - j2k.wrap(tfile.name, boxes=boxes) - jp2 = Jp2k(tfile.name) - self.assertEqual(ET.tostring(jp2.box[3].xml.getroot(), encoding='utf-8').decode('utf-8'), - xml_string) - def test_xml_from_file(self): """Must be able to create an XML box from an XML file.""" j2k = Jp2k(self.j2kfile) @@ -159,6 +149,56 @@ class TestXML(unittest.TestCase): self.assertEqual(neighbor.attrib['direction'], 'N') +@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") +class TestUTF8XML(unittest.TestCase): + """Test suite for UTF-8 XML boxes.""" + + def setUp(self): + """Create a JP2 file with a UTF-8 XML box.""" + self.j2kfile = glymur.data.goodstuff() + + # 'Россия' is 'Russia' in Cyrillic, not that it matters. + + xml = u""" + Россия""" + with tempfile.NamedTemporaryFile(suffix=".xml", delete=False) as tfile: + tfile.write(xml.encode('utf-8')) + tfile.flush() + self.xmlfile = tfile.name + + j2k = glymur.Jp2k(self.j2kfile) + with tempfile.NamedTemporaryFile(suffix=".jp2", delete=False) as tfile: + jp2 = j2k.wrap(tfile.name) + xmlbox = glymur.jp2box.XMLBox(filename=self.xmlfile) + jp2.append(xmlbox) + self.jp2_xml_file = tfile.name + + def tearDown(self): + os.unlink(self.xmlfile) + os.unlink(self.jp2_xml_file) + + def test_utf8_xml(self): + """Should be able to write/read an XMLBox with utf-8 encoding.""" + jp2 = Jp2k(self.jp2_xml_file) + box_xml = jp2.box[-1].xml.getroot() + box_xml_str = ET.tostring(box_xml, encoding='utf-8').decode('utf-8') + self.assertEqual(box_xml_str, + u'Россия') + + @unittest.skip("Does not print properly.") + def test_printing_utf8_xml(self): + """Should be able to print an XMLBox with utf-8 encoding.""" + jp2 = Jp2k(self.jp2_xml_file) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(jp2.box[-1]) + actual = fake_out.getvalue().strip() + lines = ["XML Box (xml ) @ (115305, 39)", + " u'Россия'"] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") class TestJp2kBadXmlFile(unittest.TestCase): """Test suite for bad XML box situations""" @@ -196,7 +236,6 @@ class TestJp2kBadXmlFile(unittest.TestCase): def setUp(self): self.jp2file = glymur.data.nemo() - self.j2kfile = glymur.data.goodstuff() def tearDown(self): pass From ff97e0fb8244048d18c26206f9abea87beac290b Mon Sep 17 00:00:00 2001 From: John Evans Date: Sun, 13 Oct 2013 11:40:54 -0400 Subject: [PATCH 27/38] Printing of XML and UUID boxes with non-ascii characters only fully supported in 3.x In 2.x, the XML contents will be printed as entity references instead. It's just too difficult to get both 2.x and 3.x to be entirely consistent, and so 3.x gets the preferential treatment. Closes #131. --- glymur/jp2box.py | 15 ++++++-- glymur/test/test_jp2box_xml.py | 62 ++++++++++------------------------ glymur/test/test_printing.py | 42 +++++++++++++++++++++++ 3 files changed, 71 insertions(+), 48 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index f213cab..bf88e4d 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2747,9 +2747,18 @@ def _pretty_print_xml(xml, level=0): """ xml = copy.deepcopy(xml) _indent(xml.getroot(), level=level) - xmltext = ET.tostring(xml.getroot()).decode('utf-8') + xmltext = ET.tostring(xml.getroot(), encoding='utf-8').decode('utf-8') # Indent it a bit. lst = [(' ' + x) for x in xmltext.split('\n')] - xml = '\n'.join(lst) - return '\n{0}'.format(xml) + try: + xml = '\n'.join(lst) + return '\n{0}'.format(xml) + except UnicodeEncodeError: + # This can happen on python 2.x if the character set contains certain + # non-ascii characters. Just print out the corresponding xml char + # entities instead. + xml = u'\n'.join(lst) + text = u'\n{0}'.format(xml) + text = text.encode('ascii', 'xmlcharrefreplace') + return text diff --git a/glymur/test/test_jp2box_xml.py b/glymur/test/test_jp2box_xml.py index 7e6980f..b875188 100644 --- a/glymur/test/test_jp2box_xml.py +++ b/glymur/test/test_jp2box_xml.py @@ -148,54 +148,26 @@ class TestXML(unittest.TestCase): self.assertEqual(neighbor.attrib['name'], 'Malaysia') self.assertEqual(neighbor.attrib['direction'], 'N') - -@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") -class TestUTF8XML(unittest.TestCase): - """Test suite for UTF-8 XML boxes.""" - - def setUp(self): - """Create a JP2 file with a UTF-8 XML box.""" - self.j2kfile = glymur.data.goodstuff() - - # 'Россия' is 'Russia' in Cyrillic, not that it matters. - - xml = u""" - Россия""" - with tempfile.NamedTemporaryFile(suffix=".xml", delete=False) as tfile: - tfile.write(xml.encode('utf-8')) - tfile.flush() - self.xmlfile = tfile.name - - j2k = glymur.Jp2k(self.j2kfile) - with tempfile.NamedTemporaryFile(suffix=".jp2", delete=False) as tfile: - jp2 = j2k.wrap(tfile.name) - xmlbox = glymur.jp2box.XMLBox(filename=self.xmlfile) - jp2.append(xmlbox) - self.jp2_xml_file = tfile.name - - def tearDown(self): - os.unlink(self.xmlfile) - os.unlink(self.jp2_xml_file) - def test_utf8_xml(self): """Should be able to write/read an XMLBox with utf-8 encoding.""" - jp2 = Jp2k(self.jp2_xml_file) - box_xml = jp2.box[-1].xml.getroot() - box_xml_str = ET.tostring(box_xml, encoding='utf-8').decode('utf-8') - self.assertEqual(box_xml_str, - u'Россия') + # 'Россия' is 'Russia' in Cyrillic, not that it matters. + xml = u""" + Россия""" + with tempfile.NamedTemporaryFile(suffix=".xml") as xmlfile: + xmlfile.write(xml.encode('utf-8')) + xmlfile.flush() - @unittest.skip("Does not print properly.") - def test_printing_utf8_xml(self): - """Should be able to print an XMLBox with utf-8 encoding.""" - jp2 = Jp2k(self.jp2_xml_file) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(jp2.box[-1]) - actual = fake_out.getvalue().strip() - lines = ["XML Box (xml ) @ (115305, 39)", - " u'Россия'"] - expected = '\n'.join(lines) - self.assertEqual(actual, expected) + j2k = glymur.Jp2k(self.j2kfile) + with tempfile.NamedTemporaryFile(suffix=".jp2") as jfile: + jp2 = j2k.wrap(jfile.name) + xmlbox = glymur.jp2box.XMLBox(filename=xmlfile.name) + jp2.append(xmlbox) + + box_xml = jp2.box[-1].xml.getroot() + box_xml_str = ET.tostring(box_xml, + encoding='utf-8').decode('utf-8') + self.assertEqual(box_xml_str, + u'Россия') diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 2694aa2..ef2b7f2 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Test suite for printing. """ # C0302: don't care too much about having too many lines in a test module @@ -15,6 +16,7 @@ import struct import sys import tempfile import warnings +from xml.etree import cElementTree as ET if sys.hexversion < 0x02070000: import unittest2 as unittest @@ -730,6 +732,46 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) + @unittest.skipIf(sys.hexversion < 0x02070000, + "Differences in XML printing between 2.6 and 2.7") + def test_xml_latin1(self): + """Should be able to print an XMLBox with utf-8 encoding (latin1).""" + text = u""" + Strömung""" + if sys.hexversion < 0x03000000: + xml = ET.parse(StringIO(text.encode('utf-8'))) + else: + xml = ET.parse(StringIO(text)) + + xmlbox = glymur.jp2box.XMLBox(xml=xml) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(xmlbox) + actual = fake_out.getvalue().strip() + lines = ["XML Box (xml ) @ (-1, 0)", + " Strömung"] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(sys.hexversion < 0x02070000, + "Differences in XML printing between 2.6 and 2.7") + def test_xml_cyrrilic(self): + """Should be able to print an XMLBox with utf-8 encoding (cyrrillic).""" + text = u""" + Россия""" + if sys.hexversion < 0x03000000: + xml = ET.parse(StringIO(text.encode('utf-8'))) + else: + xml = ET.parse(StringIO(text)) + + xmlbox = glymur.jp2box.XMLBox(xml=xml) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(xmlbox) + actual = fake_out.getvalue().strip() + lines = ["XML Box (xml ) @ (-1, 0)", + " Россия"] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") def test_channel_definition(self): From a886234f015fac98c97689788d0518f98ac14436 Mon Sep 17 00:00:00 2001 From: John Evans Date: Sun, 13 Oct 2013 12:02:23 -0400 Subject: [PATCH 28/38] Must use hex escapes to confirm 2.7 output. Closes #131 (again) --- glymur/test/test_printing.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index ef2b7f2..9d3374e 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -747,8 +747,12 @@ class TestPrinting(unittest.TestCase): with patch('sys.stdout', new=StringIO()) as fake_out: print(xmlbox) actual = fake_out.getvalue().strip() - lines = ["XML Box (xml ) @ (-1, 0)", - " Strömung"] + if sys.hexversion < 0x03000000: + lines = ["XML Box (xml ) @ (-1, 0)", + " Str\xc3\xb6mung"] + else: + lines = ["XML Box (xml ) @ (-1, 0)", + " Strömung"] expected = '\n'.join(lines) self.assertEqual(actual, expected) @@ -767,8 +771,13 @@ class TestPrinting(unittest.TestCase): with patch('sys.stdout', new=StringIO()) as fake_out: print(xmlbox) actual = fake_out.getvalue().strip() - lines = ["XML Box (xml ) @ (-1, 0)", - " Россия"] + if sys.hexversion < 0x03000000: + lines = ["XML Box (xml ) @ (-1, 0)", + " \xd0\xa0\xd0\xbe\xd1\x81\xd1\x81\xd0\xb8\xd1\x8f"] + else: + lines = ["XML Box (xml ) @ (-1, 0)", + " Россия"] + expected = '\n'.join(lines) self.assertEqual(actual, expected) From 4c09d3ad3afed1c392ad62e6c5e85850d32169de Mon Sep 17 00:00:00 2001 From: John Evans Date: Sun, 13 Oct 2013 12:12:20 -0400 Subject: [PATCH 29/38] Disabling test_xml_cyrrilic and test_xml_latin1 on 2.7 There seems to be inconsistencies between different versions of 2.x as to how non-ascii XML chars get printed, so disable those tests for now. Closes #131 (again). --- glymur/test/test_printing.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 9d3374e..bb7da3e 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -732,10 +732,15 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) - @unittest.skipIf(sys.hexversion < 0x02070000, - "Differences in XML printing between 2.6 and 2.7") + @unittest.skipIf(sys.hexversion < 0x03000000, + "Only trusting python3 for printing non-ascii chars") def test_xml_latin1(self): """Should be able to print an XMLBox with utf-8 encoding (latin1).""" + # Seems to be inconsistencies between different versions of python2.x + # as to what gets printed. + # + # 2.7.5 (fedora 19) prints xml entities. + # 2.7.3 seems to want to print hex escapes. text = u""" Strömung""" if sys.hexversion < 0x03000000: @@ -756,10 +761,15 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) - @unittest.skipIf(sys.hexversion < 0x02070000, - "Differences in XML printing between 2.6 and 2.7") + @unittest.skipIf(sys.hexversion < 0x03000000, + "Only trusting python3 for printing non-ascii chars") def test_xml_cyrrilic(self): """Should be able to print an XMLBox with utf-8 encoding (cyrrillic).""" + # Seems to be inconsistencies between different versions of python2.x + # as to what gets printed. + # + # 2.7.5 (fedora 19) prints xml entities. + # 2.7.3 seems to want to print hex escapes. text = u""" Россия""" if sys.hexversion < 0x03000000: @@ -773,7 +783,7 @@ class TestPrinting(unittest.TestCase): actual = fake_out.getvalue().strip() if sys.hexversion < 0x03000000: lines = ["XML Box (xml ) @ (-1, 0)", - " \xd0\xa0\xd0\xbe\xd1\x81\xd1\x81\xd0\xb8\xd1\x8f"] + " Россия"] else: lines = ["XML Box (xml ) @ (-1, 0)", " Россия"] From f08f1d9bb9059bf61c0faa72cf5974575c5f93ef Mon Sep 17 00:00:00 2001 From: John Evans Date: Sun, 13 Oct 2013 12:30:28 -0400 Subject: [PATCH 30/38] Finalizing 0.5.6 release. --- CHANGES.txt | 3 +++ docs/source/conf.py | 2 +- glymur/version.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 30387a6..40846e4 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +Oct 13, 2013 - v0.5.6 Fixed handling of non-ascii chars in XML boxes. Fixed + some docstring errors in jp2box module. + Oct 03, 2013 - v0.5.5 Fixed pip install error introduced in 0.5.0. Sep 24, 2013 - v0.5.4 Fixed test error restricted to v2.0. diff --git a/docs/source/conf.py b/docs/source/conf.py index f847e68..44fc9e0 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -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.5' +release = '0.5.6' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/glymur/version.py b/glymur/version.py index 192859f..af7523e 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -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.5" +version = "0.5.6" _sv = LooseVersion(version) version_tuple = _sv.version From 425b13a7f1eb3d748ec06723222072c87bbe4d89 Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 28 Oct 2013 16:43:27 -0400 Subject: [PATCH 31/38] Simpler configuration check allows not-installed to be caught earlier. Issue #138 --- glymur/lib/config.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/glymur/lib/config.py b/glymur/lib/config.py index e3a5c1d..f14c3d9 100644 --- a/glymur/lib/config.py +++ b/glymur/lib/config.py @@ -50,20 +50,21 @@ def load_openjpeg(libopenjpeg_path): # Let ctypes try to find it. libopenjpeg_path = find_library('openjpeg') - # If we could not find it, then look in some likely locations. + # If we could not find it, then look in some likely locations on mac + # and win. if libopenjpeg_path is None: if platform.system() == 'Darwin': - path = '/opt/local/lib/libopenjpeg.dylib' - if os.path.exists(path): - libopenjpeg_path = path + if os.path.exists('/opt/local/lib/libopenjpeg.dylib'): + libopenjpeg_path = '/opt/local/lib/libopenjpeg.dylib' elif os.name == 'nt': path = os.path.join('C:\\', 'Program files', 'OpenJPEG 1.5', 'bin', 'openjpeg.dll') if os.path.exists(path): libopenjpeg_path = path - else: - # No sense trying further on Linux - return None + + # If still no location, then no bother going further. + if libopenjpeg_path is None: + return None try: if os.name == "nt": From 14d4b60896e3e6b668ccec8525a183ad18ba885f Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 28 Oct 2013 20:04:17 -0400 Subject: [PATCH 32/38] Abstracted out loading of openjp2, openjpeg libraries. #138 That much was common to both load_openjp2 and load_openjpeg. Reduces number of different moving parts. --- glymur/lib/config.py | 85 ++++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 46 deletions(-) diff --git a/glymur/lib/config.py b/glymur/lib/config.py index f14c3d9..e0c82e6 100644 --- a/glymur/lib/config.py +++ b/glymur/lib/config.py @@ -54,27 +54,55 @@ def load_openjpeg(libopenjpeg_path): # and win. if libopenjpeg_path is None: if platform.system() == 'Darwin': - if os.path.exists('/opt/local/lib/libopenjpeg.dylib'): - libopenjpeg_path = '/opt/local/lib/libopenjpeg.dylib' + # MacPorts + path = '/opt/local/lib/libopenjpeg.dylib' elif os.name == 'nt': path = os.path.join('C:\\', 'Program files', 'OpenJPEG 1.5', 'bin', 'openjpeg.dll') - if os.path.exists(path): - libopenjpeg_path = path + if os.path.exists(path): + libopenjpeg_path = path - # If still no location, then no bother going further. - if libopenjpeg_path is None: + return load_library(libopenjpeg_path) + + +def load_openjp2(libopenjp2_path): + """Load the openjp2 library, falling back on defaults if necessary. + """ + if libopenjp2_path is None: + # No help from the config file, try to find it ourselves. + libopenjp2_path = find_library('openjp2') + + if libopenjp2_path is None: + if platform.system() == 'Darwin': + # MacPorts + path = '/opt/local/lib/libopenjp2.dylib' + elif os.name == 'nt': + path = os.path.join('C:\\', 'Program files', 'OpenJPEG 2.0', + 'bin', 'openjp2.dll') + if os.path.exists(path): + libopenjp2_path = path + + return load_library(libopenjp2_path) + + +def load_library(path): + """Load the library, return the ctypes handle.""" + + if path is None: return None try: if os.name == "nt": - openjpeg_lib = ctypes.windll.LoadLibrary(libopenjpeg_path) + opj_lib = ctypes.windll.LoadLibrary(path) else: - openjpeg_lib = ctypes.CDLL(libopenjpeg_path) - except OSError: - openjpeg_lib = None + opj_lib = ctypes.CDLL(path) + except (TypeError, OSError): + msg = '"Library {0}" could not be loaded. Operating in degraded mode.' + msg = msg.format(path) + warnings.warn(msg, UserWarning) + opj_lib = None - return openjpeg_lib + return opj_lib def read_config_file(): @@ -99,41 +127,6 @@ def read_config_file(): return lib -def load_openjp2(libopenjp2_path): - """Load the openjp2 library, falling back on defaults if necessary. - """ - if libopenjp2_path is None: - # No help from the config file, try to find it ourselves. - libopenjp2_path = find_library('openjp2') - - if libopenjp2_path is None: - if platform.system() == 'Darwin': - path = '/opt/local/lib/libopenjp2.dylib' - if os.path.exists(path): - libopenjp2_path = path - elif os.name == 'nt': - path = os.path.join('C:\\', 'Program files', 'OpenJPEG 2.0', - 'bin', 'openjp2.dll') - if os.path.exists(path): - libopenjp2_path = path - - if libopenjp2_path is None: - return None - - try: - if os.name == "nt": - openjp2_lib = ctypes.windll.LoadLibrary(libopenjp2_path) - else: - openjp2_lib = ctypes.CDLL(libopenjp2_path) - except (TypeError, OSError): - msg = '"Library {0}" could not be loaded. Operating in degraded mode.' - msg = msg.format(libopenjp2_path) - warnings.warn(msg, UserWarning) - openjp2_lib = None - - return openjp2_lib - - def glymur_config(): """Try to ascertain locations of openjp2, openjpeg libraries. """ From f321c0d29b6e2efc1d6d42a07ac5f07600f7671d Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 28 Oct 2013 21:20:52 -0400 Subject: [PATCH 33/38] More refactoring, fewer branches. #138 --- glymur/lib/config.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/glymur/lib/config.py b/glymur/lib/config.py index e0c82e6..2a85cbe 100644 --- a/glymur/lib/config.py +++ b/glymur/lib/config.py @@ -43,49 +43,45 @@ def glymurrc_fname(): return None -def load_openjpeg(libopenjpeg_path): +def load_openjpeg(path): """Load the openjpeg library, falling back on defaults if necessary. """ - if libopenjpeg_path is None: + if path is None: # Let ctypes try to find it. - libopenjpeg_path = find_library('openjpeg') + path = find_library('openjpeg') # If we could not find it, then look in some likely locations on mac # and win. - if libopenjpeg_path is None: + if path is None: if platform.system() == 'Darwin': # MacPorts path = '/opt/local/lib/libopenjpeg.dylib' elif os.name == 'nt': path = os.path.join('C:\\', 'Program files', 'OpenJPEG 1.5', 'bin', 'openjpeg.dll') - if os.path.exists(path): - libopenjpeg_path = path - return load_library(libopenjpeg_path) + return load_library_handle(path) -def load_openjp2(libopenjp2_path): +def load_openjp2(path): """Load the openjp2 library, falling back on defaults if necessary. """ - if libopenjp2_path is None: + if path is None: # No help from the config file, try to find it ourselves. - libopenjp2_path = find_library('openjp2') + path = find_library('openjp2') - if libopenjp2_path is None: + if path is None: if platform.system() == 'Darwin': # MacPorts path = '/opt/local/lib/libopenjp2.dylib' elif os.name == 'nt': path = os.path.join('C:\\', 'Program files', 'OpenJPEG 2.0', 'bin', 'openjp2.dll') - if os.path.exists(path): - libopenjp2_path = path - return load_library(libopenjp2_path) + return load_library_handle(path) -def load_library(path): +def load_library_handle(path): """Load the library, return the ctypes handle.""" if path is None: From 527f479545796d56428416e15b2458fb90862dd6 Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 28 Oct 2013 21:43:39 -0400 Subject: [PATCH 34/38] Prepping 0.5.7 release. --- CHANGES.txt | 3 +++ docs/source/conf.py | 2 +- glymur/version.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 40846e4..68fe102 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +Oct 28, 2013 - v0.5.7 Fixed bad import error message when libopenjpeg library + not installed on mac. + Oct 13, 2013 - v0.5.6 Fixed handling of non-ascii chars in XML boxes. Fixed some docstring errors in jp2box module. diff --git a/docs/source/conf.py b/docs/source/conf.py index 44fc9e0..90db6a3 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -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.6' +release = '0.5.7' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/glymur/version.py b/glymur/version.py index af7523e..6068a93 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -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.6" +version = "0.5.7" _sv = LooseVersion(version) version_tuple = _sv.version From dd19340db21791edd2178cdac86cd62106cf603d Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 29 Oct 2013 09:17:32 -0400 Subject: [PATCH 35/38] Add additional path existance check to avoid unnecessary warning. #138 The warnings would come about if running on mac or windows where the library could not be found via ctypes or the configuration file. --- glymur/lib/config.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/glymur/lib/config.py b/glymur/lib/config.py index 2a85cbe..69d258e 100644 --- a/glymur/lib/config.py +++ b/glymur/lib/config.py @@ -45,6 +45,12 @@ def glymurrc_fname(): def load_openjpeg(path): """Load the openjpeg library, falling back on defaults if necessary. + + Parameters + ---------- + path : str + Path to openjpeg 1.5 library as specified by configuration file. Will + be None if no configuration file specified. """ if path is None: # Let ctypes try to find it. @@ -84,7 +90,9 @@ def load_openjp2(path): def load_library_handle(path): """Load the library, return the ctypes handle.""" - if path is None: + if path is None or (path is not None and not os.path.exists(path)): + # Either could not find a library via ctypes or user-configuration-file, + # or we could not find it in any of the default locations. return None try: @@ -93,10 +101,10 @@ def load_library_handle(path): else: opj_lib = ctypes.CDLL(path) except (TypeError, OSError): - msg = '"Library {0}" could not be loaded. Operating in degraded mode.' - msg = msg.format(path) - warnings.warn(msg, UserWarning) - opj_lib = None + msg = '"Library {0}" could not be loaded. Operating in degraded mode.' + msg = msg.format(path) + warnings.warn(msg, UserWarning) + opj_lib = None return opj_lib From 453e00db8c1fb01191e66db0591a801ff05b8a8d Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 29 Oct 2013 09:29:39 -0400 Subject: [PATCH 36/38] Prepping for 0.5.8 --- CHANGES.txt | 3 +++ docs/source/conf.py | 2 +- glymur/version.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 68fe102..981672d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +Oct 29, 2013 - v0.5.8 Fixed unnecessary warnings when default locations for + openjpeg libraries on mac do not specify the actual library. + Oct 28, 2013 - v0.5.7 Fixed bad import error message when libopenjpeg library not installed on mac. diff --git a/docs/source/conf.py b/docs/source/conf.py index 90db6a3..0338c5a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -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.7' +release = '0.5.8' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/glymur/version.py b/glymur/version.py index 6068a93..0fb4b86 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -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.7" +version = "0.5.8" _sv = LooseVersion(version) version_tuple = _sv.version From 75cb8957fc45453c2d91cded649c638bcb1e27ce Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 29 Oct 2013 10:04:47 -0400 Subject: [PATCH 37/38] Have to put the check for path existance into the library default stanza. Issue #138 --- glymur/lib/config.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/glymur/lib/config.py b/glymur/lib/config.py index 69d258e..fdb5a92 100644 --- a/glymur/lib/config.py +++ b/glymur/lib/config.py @@ -59,6 +59,7 @@ def load_openjpeg(path): # If we could not find it, then look in some likely locations on mac # and win. if path is None: + # Could not find a library via ctypes if platform.system() == 'Darwin': # MacPorts path = '/opt/local/lib/libopenjpeg.dylib' @@ -66,6 +67,10 @@ def load_openjpeg(path): path = os.path.join('C:\\', 'Program files', 'OpenJPEG 1.5', 'bin', 'openjpeg.dll') + if path is not None and not os.path.exists(path): + # the mac/win default location does not exist. + return None + return load_library_handle(path) @@ -73,10 +78,11 @@ def load_openjp2(path): """Load the openjp2 library, falling back on defaults if necessary. """ if path is None: - # No help from the config file, try to find it ourselves. + # No help from the config file, try to find it via ctypes. path = find_library('openjp2') if path is None: + # Could not find a library via ctypes if platform.system() == 'Darwin': # MacPorts path = '/opt/local/lib/libopenjp2.dylib' @@ -84,15 +90,20 @@ def load_openjp2(path): path = os.path.join('C:\\', 'Program files', 'OpenJPEG 2.0', 'bin', 'openjp2.dll') + if path is not None and not os.path.exists(path): + # the mac/win default location does not exist. + return None + return load_library_handle(path) def load_library_handle(path): """Load the library, return the ctypes handle.""" - if path is None or (path is not None and not os.path.exists(path)): + if path is None: # Either could not find a library via ctypes or user-configuration-file, # or we could not find it in any of the default locations. + # This is probably a very old linux. return None try: From f551646af8072037179e12cf1846c7f6ba7f8ad1 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 29 Oct 2013 10:15:24 -0400 Subject: [PATCH 38/38] Prepping 0.5.9 release. --- CHANGES.txt | 2 ++ docs/source/conf.py | 2 +- glymur/version.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 981672d..5e62c1c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,5 @@ +Oct 29, 2013 - v0.5.9 Fixed bad library load on linux as a result of 0.5.8 + Oct 29, 2013 - v0.5.8 Fixed unnecessary warnings when default locations for openjpeg libraries on mac do not specify the actual library. diff --git a/docs/source/conf.py b/docs/source/conf.py index 0338c5a..b14af27 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -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.8' +release = '0.5.9' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/glymur/version.py b/glymur/version.py index 0fb4b86..6116920 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -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.8" +version = "0.5.9" _sv = LooseVersion(version) version_tuple = _sv.version