diff --git a/CHANGES.txt b/CHANGES.txt index c906d29..44d6832 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,4 @@ -Mar 06, 2014 - Added Cinema2K write support. +Mar 06, 2014 - Added Cinema2K, Cinema4K write support. Changed constructor for ChannelDefinition box. Removed support for Python 2.6. Added write support for JP2 UUID, DataEntryURL, Palette and Component Mapping boxes, JPX Association, NumberList diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 320ff0f..7a96571 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -5,7 +5,7 @@ ChangeLog 0.6.0 (pending) =============== - * Added Cinema2K write support. + * Added Cinema2K, Cinema4K write support. * Added lxml requirement. * added set_printoptions, get_printoptions function * dropped support for Python 2.6, added support for Python 3.4 diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index ae0ce08..9111dbd 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 r2366 works. +via subversion. As of this time of writing, svn revision r2369 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 diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 00d3d24..e861053 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -163,22 +163,32 @@ class Jp2k(Jp2kBox): msg += "profile if the file type box brand is 'jp2 '." warnings.warn(msg) - def _set_cinema_params(self, cparams, fps): + def _set_cinema_params(self, cparams, cinema_mode, fps): """Populate compression parameters structure for cinema2K. Parameters ---------- + params : ctypes struct + Corresponds to compression parameters structure used by the + library. + cinema_mode : str + Either 'cinema2k' or 'cinema4k' fps : int Frames per second, should be either 24 or 48. """ - if fps == 24: - cparams.cp_cinema = CINEMA_MODE['cinema2k_24'] - elif fps == 48: - cparams.cp_cinema = CINEMA_MODE['cinema2k_48'] + 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: + raise IOError('Cinema2K frame rate must be either 24 or 48.') + cparams.cp_rsiz = RSIZ['CINEMA2K'] else: - raise IOError('Cinema2K frame rate must be either 24 or 48.') + cparams.cp_cinema = CINEMA_MODE['cinema4k_24'] + cparams.cp_rsiz = RSIZ['CINEMA4K'] + - cparams.cp_rsiz = RSIZ['CINEMA2K'] # No tiling cparams.tile_size_on = opj2.FALSE cparams.cp_tdx = 1 @@ -216,9 +226,17 @@ class Jp2k(Jp2kBox): # TODO: warning or error cparams.tcp_numlayers = 1 - if cparams.numresolution > 6: #TODO only for cinema2k - # TODO: warning or error - cparams.numresolution = 6 + if cinema_mode == 'cinema2k': + if cparams.numresolution > 6: + # TODO: warning or error + cparams.numresolution = 6 + else: + if cparams.numresolution < 2: + # TODO: warning or error + cparams.numresolution = 1 + elif cparams.numresolution > 7: + cparams.numresolution = 7 + # precincts cparams.csty |= 0x01 @@ -230,8 +248,25 @@ class Jp2k(Jp2kBox): # Progression order shall be CPRL cparams.prog_order = PROGRESSION_ORDER['CPRL'] - # progression order changes not allowed for 2K # TODO not for 4K: - cparams.numpocs = 0 + # progression order changes not allowed for 2K + if cinema_mode == 'cinema2k': + cparams.numpocs = 0 + else: + cparams.poc[0].tile = 1 + cparams.poc[0].resno0 = 0 + cparams.poc[0].compno0 = 0 + cparams.poc[0].layno1 = 1 + cparams.poc[0].resno1 = cparams.numresolution - 1 + cparams.poc[0].compno1 = 3 + cparams.poc[0].prg1 = PROGRESSION_ORDER['CPRL'] + cparams.poc[1].tile = 1 + cparams.poc[1].resno0 = 0 + cparams.poc[1].compno0 = 0 + cparams.poc[1].layno1 = 1 + cparams.poc[1].resno1 = cparams.numresolution + cparams.poc[1].compno1 = 3 + cparams.poc[1].prg1 = PROGRESSION_ORDER['CPRL'] + cparams.numpocs = 2 def _populate_cparams(self, **kwargs): """Populate compression parameters structure from input arguments. @@ -300,11 +335,11 @@ class Jp2k(Jp2kBox): cparams.cp_disto_alloc = 1 if 'cinema2k' in kwargs: - self._set_cinema_params(cparams, kwargs['cinema2k']) + self._set_cinema_params(cparams, 'cinema2k', kwargs['cinema2k']) return cparams if 'cinema4k' in kwargs: - self._set_cinema_params(cparams, kwargs['cinema2k']) + self._set_cinema_params(cparams, 'cinema4k', kwargs['cinema4k']) return cparams if 'cbsize' in kwargs: diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 87a6fc4..0e7f2d3 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -44,6 +44,48 @@ class TestSuiteWrite(unittest.TestCase): def tearDown(self): pass + def check_cinema4k_codestream(self, codestream, image_size): + """Common out for cinema2k tests.""" + # SIZ: Image and tile size + # Profile: "3" means cinema2K + self.assertEqual(codestream.segment[1].rsiz, 4) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + image_size) + # 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), + image_size) + # 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 + + + def check_cinema2k_codestream(self, codestream, image_size): """Common out for cinema2k tests.""" # SIZ: Image and tile size @@ -86,6 +128,20 @@ class TestSuiteWrite(unittest.TestCase): + @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, + "Cannot read input image without scikit-image/freeimage") + def test_NR_ENC_ElephantDream_4K_tif_21_encode(self): + relfile = 'input/nonregression/ElephantDream_4K.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, cinema4k=True) + + codestream = j.get_codestream() + self.check_cinema4k_codestream(codestream, (4096, 2160)) + + @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):