diff --git a/glymur/core.py b/glymur/core.py index 4e8f950..95e1cbf 100644 --- a/glymur/core.py +++ b/glymur/core.py @@ -10,6 +10,26 @@ RPCL = 2 PCRL = 3 CPRL = 4 +STD = 0 +CINEMA2K = 3 +CINEMA4K = 4 + +RSIZ = { + 'STD': STD, + 'CINEMA2K': CINEMA2K, + 'CINEMA4K': CINEMA4K} + +OFF = 0 +CINEMA2K_24 = 1 +CINEMA2K_48 = 2 +CINEMA4K_24 = 3 + +CINEMA_MODE = { + 'off': OFF, + 'cinema2k_24': CINEMA2K_24, + 'cinema2k_48': CINEMA2K_48, + 'cinema4k_24': CINEMA4K_24, } + PROGRESSION_ORDER = { 'LRCP': LRCP, 'RLCP': RLCP, diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 04fa23e..890c2fd 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -28,7 +28,7 @@ import numpy as np from .codestream import Codestream from .core import SRGB, GREYSCALE -from .core import PROGRESSION_ORDER +from .core import PROGRESSION_ORDER, RSIZ, CINEMA_MODE from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE from .jp2box import Jp2kBox from .jp2box import JPEG2000SignatureBox, FileTypeBox, JP2HeaderBox @@ -39,6 +39,8 @@ from .lib import openjp2 as opj2 from . import version from .lib import c as libc +CINEMA_24_CS = 1302083 + JP2_IDS = ['colr', 'cdef', 'cmap', 'jp2c', 'ftyp', 'ihdr', 'jp2h', 'jP ', 'pclr', 'res ', 'resc', 'resd', 'xml ', 'ulst', 'uinf', 'url ', 'uuid'] @@ -220,14 +222,17 @@ class Jp2k(Jp2kBox): cparams.cp_disto_alloc = 1 if 'cinema2K' in kwargs: - cparams.cp_rsiz = kwargs['cinema2K'] + # TODO: error if either 24 or 48 + cparams.cp_cinema = kwargs['cinema2K'] + + cparams.cp_rsiz = RSIZ['CINEMA2K'] # No tiling cparams.tile_size_on = opj2.FALSE cparams.cp_tdx = 1 cparams.cp_tdy = 1 # One tile part for each component. - cparams.tp_cflag = ord('C') + cparams.tp_flag = ord('C') cparams.tp_on = 1 # tile and image shall be as (0,0) @@ -254,7 +259,7 @@ class Jp2k(Jp2kBox): cparams.irreversible = 1 # number of layers - if cparams.tcp_layers > 1: + if cparams.tcp_numlayers > 1: # TODO: warning or error cparams.tcp_numlayers = 1 @@ -266,8 +271,8 @@ class Jp2k(Jp2kBox): cparams.csty |= 0x01 cparams.res_spec = cparams.numresolution - 1 for j in range(cparams.res_spec): - cparams.prcw_init[i] = 256 - cparams.prch_init[i] = 256 + cparams.prcw_init[j] = 256 + cparams.prch_init[j] = 256 # Progression order shall be CPRL cparams.prog_order = PROGRESSION_ORDER['CPRL'] @@ -530,6 +535,29 @@ class Jp2k(Jp2kBox): self.parse() + def _set_cinema_rate(self, cparams, image): + max_rate = 0 + temp_rate = 0 + cparams.cp_disto_alloc = 1 + + if cparams.cp_cinema in [CINEMA_MODE['cinema2k_24'], + CINEMA_MODE['cinema2k_48']]: + num_pixels = image.contents.comps[0].w * image.contents.comps[0].h + rate_numerator = num_pixels * image.contents.comps[0].prec + max_rate = rate_numerator / (CINEMA_24_CS * 8 * num_pixels) + if cparams.tcp_rates[0] == 0: + cparams.tcp_rates[0] = max_rate + else: + temp_rate = rate_numerator / (cparams.tcp_rates[0] * 8 * num_pixels) + if temp_rate > CINEMA_24_CS: + # TODO warning, reset + cparams.tcp_rates[0] = max_rate + else: + # TODO warning + pass + + cparams.max_comp_size = COMP_24_CS + def _write_openjp2(self, img_array, verbose=False, **kwargs): """ Write JPEG 2000 file using OpenJPEG 2.0 interface. @@ -549,6 +577,9 @@ class Jp2k(Jp2kBox): _populate_image_struct(cparams, image, img_array) + if 'cinema2K' in kwargs: + self._set_cinema_rate(cparams, image) + codec = opj2.create_compress(cparams.codec_fmt) stack.callback(opj2.destroy_codec, codec) diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 07d623f..335202d 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -12,6 +12,13 @@ import sys import tempfile import unittest +try: + import skimage.io + skimage.io.use_plugin('freeimage', 'imread') + _HAS_SKIMAGE_FREEIMAGE_SUPPORT = True +except ImportError: + _HAS_SKIMAGE_FREEIMAGE_SUPPORT = False + from .fixtures import read_image, NO_READ_BACKEND, NO_READ_BACKEND_MSG from .fixtures import OPJ_DATA_ROOT, opj_data_file @@ -38,15 +45,74 @@ class TestSuiteWrite(unittest.TestCase): def tearDown(self): pass - @unittest.skip("Cannot read input image using PILLOW???") + @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, + "Cannot read input image without scikit-image/freeimage") def test_NR_ENC_X_4_2K_24_185_CBR_WB_000_tif_15_encode(self): relfile = 'input/nonregression/X_4_2K_24_185_CBR_WB_000.tif' - import pdb; pdb.set_trace() infile = opj_data_file(relfile) - data = read_image(infile) + data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, 'wb') - j.write(data, 'cinema2K', 24) + j.write(data, cinema2K=24) + + codestream = j.get_codestream() + + # SIZ: Image and tile size + # Profile: "3" means cinema2K + self.assertEqual(codestream.segment[1].rsiz, 3) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + (1998, 1080)) + # Reference grid offset + self.assertEqual((codestream.segment[1].xosiz, + codestream.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((codestream.segment[1].xtsiz, + codestream.segment[1].ytsiz), + (1998, 1080)) + # Tile offset + self.assertEqual((codestream.segment[1].xtosiz, + codestream.segment[1].ytosiz), + (0, 0)) + # bitdepth + self.assertEqual(codestream.segment[1].bitdepth, (12, 12, 12)) + # signed + self.assertEqual(codestream.segment[1].signed, + (False, False, False)) + # subsampling + self.assertEqual(list(zip(codestream.segment[1].xrsiz, + codestream.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(codestream.segment[2].scod & 2) # no sop + self.assertFalse(codestream.segment[2].scod & 4) # no eph + self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CRLP) + self.assertEqual(codestream.segment[2].layers, 3) # layers = 3 + self.assertEqual(codestream.segment[2].spcod[3], 1) # mct + self.assertEqual(codestream.segment[2].spcod[4], 5) # levels + self.assertEqual(tuple(codestream.segment[2].code_block_size), + (64, 64)) # cblksz + # Selective arithmetic coding bypass + self.assertFalse(codestream.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(codestream.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(codestream.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(codestream.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) + self.assertEqual(codestream.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(codestream.segment[2].spcod), 9) + + + + def test_NR_ENC_Bretagne1_ppm_1_encode(self): """NR-ENC-Bretagne1.ppm-1-encode"""