diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 627082b..1d9e1b2 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -39,7 +39,15 @@ from .lib import openjp2 as opj2 from . import version from .lib import c as libc +# Codestream lengths for 24fps, 48fps CINEMA_24_CS = 1302083 +CINEMA_48_CS = 651041 + +# Maximum size per color components for 2K and 4K at 24 fps +COMP_24_CS = 1041666 + +# Maximum size per color components for 2K at 48 fps +COMP_48_CS = 520833 JP2_IDS = ['colr', 'cdef', 'cmap', 'jp2c', 'ftyp', 'ihdr', 'jp2h', 'jP ', 'pclr', 'res ', 'resc', 'resd', 'xml ', 'ulst', 'uinf', 'url ', @@ -295,6 +303,10 @@ class Jp2k(Jp2kBox): self._set_cinema_params(cparams, kwargs['cinema2k']) return cparams + if 'cinema4k' in kwargs: + self._set_cinema_params(cparams, kwargs['cinema2k']) + return cparams + if 'cbsize' in kwargs: cparams.cblockw_init = kwargs['cbsize'][1] cparams.cblockh_init = kwargs['cbsize'][0] @@ -555,7 +567,7 @@ class Jp2k(Jp2kBox): cparams.cp_disto_alloc = 1 if cparams.cp_cinema in [CINEMA_MODE['cinema2k_24'], - CINEMA_MODE['cinema2k_48']]: + CINEMA_MODE['cinema4k_24']]: num_pixels = image.contents.comps[0].w * image.contents.comps[0].h num_samples = num_pixels * image.contents.numcomps rate_numerator = num_samples * image.contents.comps[0].prec @@ -574,7 +586,28 @@ class Jp2k(Jp2kBox): # TODO warning pass - cparams.max_comp_size = CINEMA_24_CS + cparams.max_comp_size = COMP_24_CS + + else: + num_pixels = image.contents.comps[0].w * image.contents.comps[0].h + num_samples = num_pixels * image.contents.numcomps + rate_numerator = num_samples * image.contents.comps[0].prec + rate_denominator = CINEMA_48_CS * 8 + rate_denominator *= image.contents.comps[0].dx + rate_denominator *= image.contents.comps[0].dy + max_rate = rate_numerator / rate_denominator + 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_48_CS: + # TODO warning, reset + cparams.tcp_rates[0] = max_rate + else: + # TODO warning + pass + + cparams.max_comp_size = COMP_48_CS def _write_openjp2(self, img_array, verbose=False, **kwargs): """ @@ -597,6 +630,8 @@ class Jp2k(Jp2kBox): if 'cinema2k' in kwargs: self._set_cinema_rate(cparams, image) + if 'cinema4k' 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 ca3bc83..d0e449e 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -44,6 +44,108 @@ class TestSuiteWrite(unittest.TestCase): def tearDown(self): pass + @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, + "Cannot read input image without scikit-image/freeimage") + def test_NR_ENC_X_5_2K_24_235_CBR_STEM24_000_tif_19_encode(self): + relfile = 'input/nonregression/X_5_2K_24_235_CBR_STEM24_000.tif' + infile = opj_data_file(relfile) + data = skimage.io.imread(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data, cinema2k=48) + + 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), + (2048, 857)) + # 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), + (2048, 857)) + # 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.CPRL) + self.assertEqual(codestream.segment[2].layers, 1) + 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), + (32, 32)) # cblksz + + + @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, + "Cannot read input image without scikit-image/freeimage") + def test_NR_ENC_X_6_2K_24_FULL_CBR_CIRCLE_000_tif_20_encode(self): + relfile = 'input/nonregression/X_6_2K_24_FULL_CBR_CIRCLE_000.tif' + infile = opj_data_file(relfile) + data = skimage.io.imread(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data, cinema2k=48) + + 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), + (2048, 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), + (2048, 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.CPRL) + self.assertEqual(codestream.segment[2].layers, 1) + 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), + (32, 32)) # cblksz + + @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, "Cannot read input image without scikit-image/freeimage") def test_NR_ENC_X_6_2K_24_FULL_CBR_CIRCLE_000_tif_17_encode(self): @@ -146,6 +248,57 @@ class TestSuiteWrite(unittest.TestCase): (32, 32)) # cblksz + @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_18_encode(self): + relfile = 'input/nonregression/X_4_2K_24_185_CBR_WB_000.tif' + infile = opj_data_file(relfile) + data = skimage.io.imread(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data, cinema2k=48) + + 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.CPRL) + self.assertEqual(codestream.segment[2].layers, 1) + 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), + (32, 32)) # cblksz + + @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):