From 1e754f0e8568adbed200e729ba7b388483015a42 Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 24 Apr 2014 20:06:15 -0400 Subject: [PATCH] Updated openjp2 API for upstream openjpeg r2837 and r2847. #221 Removed "_v3" suffixes from glymur.lib.openjp2 functions. Changed JP2k object to use both 2.0 and 2.1 means of setting cinema compression parameter values. --- glymur/core.py | 57 ++++++++++++++++++++++++++ glymur/jp2k.py | 62 ++++++++++++++++++---------- glymur/lib/openjp2.py | 71 +++++++++++++++++---------------- glymur/lib/test/test_openjp2.py | 12 +++--- 4 files changed, 139 insertions(+), 63 deletions(-) diff --git a/glymur/core.py b/glymur/core.py index 07949f9..4de6283 100644 --- a/glymur/core.py +++ b/glymur/core.py @@ -44,6 +44,63 @@ CINEMA_MODE = { 'cinema2k_48': CINEMA2K_48, 'cinema4k_24': CINEMA4K_24, } +PROFILE = { + # no profile, conform to 15444-1 + 'none': 0x0000, + # profile 0 as described in 15444-1,Table A.45 + '0': 0x0001, + 'zero': 0x0001, + 0: 0x0001, + # profile 1 as described in 15444-1,Table A.45 + '1': 0x0002, + 'one': 0x0002, + 1: 0x0002, + # at least 1 extension defined in 15444-2 (Part-2) + '2': 0x8000, + 2: 0x8000, + 'two': 0x8000, + # 2K cinema profile defined in 15444-1 AMD1 + 'cinema_2k': 0x0003, + # 4K cinema profile defined in 15444-1 AMD1 + 'cinema_4k': 0x0004, + # scalable 2K cinema profile defined in 15444-1 AMD2 + 'cinema_s2k': 0x0005, + # scalable 4K cinema profile defined in 15444-1 AMD2 + 'cinema_s4k': 0x0006, + # long term storage cinema profile defined in 15444-1 AMD2 + 'cinema_lts': 0x0007, + # Single Multi Tile Broadcast profile defined in 15444-1 AMD3 + 'bc_single': 0x0100, + # Multi Tile Broadcast profile defined in 15444-1 AMD3 + 'bc_multi': 0x0200, + # Multi Tile Reversible Broadcast profile defined in 15444-1 AMD3 + 'bc_multi_r': 0x0300, + # 2K Single Tile Lossy IMF profile defined in 15444-1 AMD 8 + 'imf_2k': 0x0400, + # 4K Single Tile Lossy IMF profile defined in 15444-1 AMD 8 + 'imf_4k': 0x0401, + # 8K Single Tile Lossy IMF profile defined in 15444-1 AMD 8 + 'imf_8k': 0x0402, + # 2K Single/Multi Tile Reversible IMF profile defined in 15444-1 AMD 8 + 'imf_2k_r': 0x0403, + # 4K Single/Multi Tile Reversible IMF profile defined in 15444-1 AMD 8 + 'imf_4k_r': 0x0800, + # 8K Single/Multi Tile Reversible IMF profile defined in 15444-1 AMD 8 + 'imf_8k_r': 0x0801, + } + +# JPEG 2000 codestream and component size limits in cinema profiles +# +# Maximum codestream length for 24fps +OPJ_CINEMA_24_CS = 1302083 +# Maximum codestream length for 48fps +OPJ_CINEMA_48_CS = 651041 +# Maximum size per color component for 2K & 4K @ 24fps +OPJ_CINEMA_24_COMP = 1041666 +# Maximum size per color component for 2K @ 48fps +OPJ_CINEMA_48_COMP = 520833 + + PROGRESSION_ORDER = { 'LRCP': LRCP, 'RLCP': RLCP, diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 1e6a790..5885c1d 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -29,8 +29,9 @@ import numpy as np from .codestream import Codestream from .core import SRGB, GREYSCALE -from .core import PROGRESSION_ORDER, CINEMA_MODE +from .core import PROGRESSION_ORDER from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE +from . import core from .jp2box import Jp2kBox from .jp2box import JPEG2000SignatureBox, FileTypeBox, JP2HeaderBox from .jp2box import ColourSpecificationBox, ContiguousCodestreamBox @@ -173,14 +174,34 @@ class Jp2k(Jp2kBox): raise IOError(msg) if cinema_mode == 'cinema2k': - if fps == 24: - cparams.cp_cinema = CINEMA_MODE['cinema2k_24'] - elif fps == 48: - cparams.cp_cinema = CINEMA_MODE['cinema2k_48'] - else: + if fps not in [24, 48]: raise IOError('Cinema2K frame rate must be either 24 or 48.') + + if re.match("2.0", version.openjpeg_version) is not None: + # 2.0 API + if fps == 24: + cparams.cp_cinema = core.CINEMA_MODE['cinema2k_24'] + else: + cparams.cp_cinema = core.CINEMA_MODE['cinema2k_48'] + else: + # 2.1 API + if fps == 24: + cparams.rsiz = core.PROFILE['cinema_2k'] + cparams.max_comp_size = core.CINEMA_24_COMP + cparams.max_cs_size = core.CINEMA_24_CS + else: + cparams.rsiz = core.PROFILE['cinema_2k'] + cparams.max_comp_size = core.CINEMA_48_COMP + cparams.max_cs_size = core.CINEMA_48_CS + else: - cparams.cp_cinema = CINEMA_MODE['cinema4k_24'] + # cinema4k + if re.match("2.0", version.openjpeg_version) is not None: + # 2.0 API + cparams.cp_cinema = core.CINEMA_MODE['cinema4k_24'] + else: + # 2.1 API + cparams.rsiz = core.PROFILE['cinema_4k'] return @@ -554,9 +575,9 @@ class Jp2k(Jp2kBox): stack.callback(libc.fclose, fptr) else: # Introduced in 2.1 devel series. - strm = opj2.stream_create_default_file_stream_v3(self.filename, - False) - stack.callback(opj2.stream_destroy_v3, strm) + strm = opj2.stream_create_default_file_stream(self.filename, + False) + stack.callback(opj2.stream_destroy, strm) opj2.start_compress(codec, image, strm) opj2.encode(codec, strm) @@ -909,12 +930,10 @@ class Jp2k(Jp2kBox): layer=layer, tile=tile, area=area) with ExitStack() as stack: - if hasattr(opj2.OPENJP2, - 'opj_stream_create_default_file_stream_v3'): + if re.match("2.1", version.openjpeg_version): filename = self.filename - stream = opj2.stream_create_default_file_stream_v3(filename, - True) - stack.callback(opj2.stream_destroy_v3, stream) + stream = opj2.stream_create_default_file_stream(filename, True) + stack.callback(opj2.stream_destroy, stream) else: fptr = libc.fopen(self.filename, 'rb') stack.callback(libc.fclose, fptr) @@ -1080,17 +1099,16 @@ class Jp2k(Jp2kBox): layer=layer, tile=tile, area=area) with ExitStack() as stack: - if re.match("2.0", version.openjpeg_version): + if re.match("2.1", version.openjpeg_version): + # API change in 2.1 + filename = self.filename + stream = opj2.stream_create_default_file_stream(filename, True) + stack.callback(opj2.stream_destroy, stream) + else: fptr = libc.fopen(self.filename, 'rb') stack.callback(libc.fclose, fptr) stream = opj2.stream_create_default_file_stream(fptr, True) stack.callback(opj2.stream_destroy, stream) - else: - # API change in 2.1+ - filename = self.filename - stream = opj2.stream_create_default_file_stream_v3(filename, - True) - stack.callback(opj2.stream_destroy_v3, stream) codec = opj2.create_decompress(self._codec_format) stack.callback(opj2.destroy_codec, codec) diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py index 2da8d75..5b5f3c4 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -5,6 +5,7 @@ Wraps individual functions in openjp2 library. # pylint: disable=C0302,R0903,W0201 import ctypes +import re import sys from .config import glymur_config @@ -12,7 +13,11 @@ OPENJP2, OPENJPEG = glymur_config() def version(): """Wrapper for opj_version library routine.""" - OPENJP2.opj_version.restype = ctypes.c_char_p + try: + OPENJP2.opj_version.restype = ctypes.c_char_p + except: + return "0.0.0" + library_version = OPENJP2.opj_version() if sys.hexversion >= 0x03000000: return library_version.decode('utf-8') @@ -43,6 +48,13 @@ JPWL_MAX_NO_TILESPECS = 16 TRUE = 1 FALSE = 0 +#PROFILE = {'none': 0, # No profile +# 0: 1, # Profile 0 +# 1: 2, # Profile 1 +# 'part2': 0x8000, # At least one extension +# 'Cinema2K': 0x0003, # 2K cinema profile +# 'Cinema4K': 0x0004, # 4K cinema profile + # supported color spaces CLRSPC_UNKNOWN = -1 CLRSPC_UNSPECIFIED = 0 @@ -368,6 +380,16 @@ class CompressionParametersType(ctypes.Structure): # based encoding without offset concerning all the components. ("mct_data", ctypes.c_void_p)] + if _MINOR == '1': + # Maximum size (in bytes) for the whole codestream. + # If == 0, codestream size limitation is not considered. + # If it does not comply with tcp_rates, max_cs_size prevails and a + # warning is issued. + _fields_.append(("max_cs_size", ctypes.c_int32)) + + # To be used to combine OPJ_PROFILE_*, OPJ_EXTENSION_* and (sub)levels + # values. + _fields_.append(("rsiz", ctypes.c_uint16)) class ImageCompType(ctypes.Structure): """Defines a single image component. @@ -1278,7 +1300,7 @@ def start_compress(codec, image, stream): OPENJP2.opj_start_compress(codec, image, stream) -def stream_create_default_file_stream(fptr, isa_read_stream): +def _stream_create_default_file_stream_2p0(fptr, isa_read_stream): """Wraps openjp2 library function opj_stream_create_default_vile_stream. Sets the stream to be a file stream. This is valid only for version 2.0.0 @@ -1303,12 +1325,11 @@ def stream_create_default_file_stream(fptr, isa_read_stream): stream = OPENJP2.opj_stream_create_default_file_stream(fptr, read_stream) return stream - -def stream_create_default_file_stream_v3(fname, isa_read_stream): - """Wraps openjp2 library function opj_stream_create_default_vile_stream_v3. +def _stream_create_default_file_stream_2p1(fname, isa_read_stream): + """Wraps openjp2 library function opj_stream_create_default_vile_stream. Sets the stream to be a file stream. This function is only valid for the - trunk/development 2.0+ version of the openjp2 library. + 2.1 version of the openjp2 library. Parameters ---------- @@ -1323,13 +1344,18 @@ def stream_create_default_file_stream_v3(fname, isa_read_stream): 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 + 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 file_argument = ctypes.c_char_p(fname.encode()) - stream = OPENJP2.opj_stream_create_default_file_stream_v3(file_argument, - read_stream) + stream = OPENJP2.opj_stream_create_default_file_stream(file_argument, + read_stream) return stream + +if re.match(r'''2.0''', version()): + stream_create_default_file_stream = _stream_create_default_file_stream_2p0 +else: + stream_create_default_file_stream = _stream_create_default_file_stream_2p1 def stream_destroy(stream): @@ -1347,21 +1373,6 @@ def stream_destroy(stream): OPENJP2.opj_stream_destroy(stream) -def stream_destroy_v3(stream): - """Wraps openjp2 library function opj_stream_destroy_v3. - - Destroys the stream created by create_stream_v3. - - Parameters - ---------- - stream : STREAM_TYPE_P - 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) - - def write_tile(codec, tile_index, data, data_size, stream): """Wraps openjp2 library function opj_write_tile. @@ -1403,13 +1414,3 @@ def write_tile(codec, tile_index, data, data_size, stream): def set_error_message(msg): """The openjpeg error handler has recorded an error message.""" ERROR_MSG_LST.append(msg) - - -def version(): - """Wrapper for opj_version library routine.""" - OPENJP2.opj_version.restype = ctypes.c_char_p - library_version = OPENJP2.opj_version() - if sys.hexversion >= 0x03000000: - return library_version.decode('utf-8') - else: - return library_version diff --git a/glymur/lib/test/test_openjp2.py b/glymur/lib/test/test_openjp2.py index 67d6750..70ef9b6 100644 --- a/glymur/lib/test/test_openjp2.py +++ b/glymur/lib/test/test_openjp2.py @@ -79,7 +79,7 @@ class TestOpenJP2(unittest.TestCase): openjp2.set_warning_handler(codec, None) openjp2.set_error_handler(codec, None) - stream = openjp2.stream_create_default_file_stream_v3(filename, True) + stream = openjp2.stream_create_default_file_stream(filename, True) openjp2.setup_decoder(codec, dparam) image = openjp2.read_header(stream, codec) @@ -100,7 +100,7 @@ class TestOpenJP2(unittest.TestCase): openjp2.end_decompress(codec, stream) openjp2.destroy_codec(codec) - openjp2.stream_destroy_v3(stream) + openjp2.stream_destroy(stream) openjp2.image_destroy(image) def test_tte0(self): @@ -308,7 +308,7 @@ def tile_encoder(**kwargs): openjp2.setup_encoder(codec, l_param, l_image) - stream = openjp2.stream_create_default_file_stream_v3(kwargs['filename'], + stream = openjp2.stream_create_default_file_stream(kwargs['filename'], False) openjp2.start_compress(codec, l_image, stream) @@ -316,7 +316,7 @@ def tile_encoder(**kwargs): openjp2.write_tile(codec, j, data, tile_size, stream) openjp2.end_compress(codec, stream) - openjp2.stream_destroy_v3(stream) + openjp2.stream_destroy(stream) openjp2.destroy_codec(codec) openjp2.image_destroy(l_image) @@ -325,7 +325,7 @@ def tile_decoder(**kwargs): Reads a tile. That's all it does. """ - stream = openjp2.stream_create_default_file_stream_v3(kwargs['filename'], + stream = openjp2.stream_create_default_file_stream(kwargs['filename'], True) dparam = openjp2.set_default_decoder_parameters() @@ -361,7 +361,7 @@ def tile_decoder(**kwargs): openjp2.end_decompress(codec, stream) openjp2.destroy_codec(codec) - openjp2.stream_destroy_v3(stream) + openjp2.stream_destroy(stream) openjp2.image_destroy(image) def ttx0_setup(filename):