From 2fbdf146b42fd4b4fdaf0e43f73e7f67a10167ef Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 5 Mar 2014 07:04:10 -0500 Subject: [PATCH 01/15] Verified through r2366. #139 423 of 457 glymur tests passing, 34 skips 21 of 214 openjpeg tests failed --- docs/source/detailed_installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index cfbb168..ae0ce08 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 r2354 works. +via subversion. As of this time of writing, svn revision r2366 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 From b6a17d60f1a4e5bbf388c30d0c290fb749381c4d Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 5 Mar 2014 21:17:35 -0500 Subject: [PATCH 02/15] Starting to implement cinema2k support. #139 Need to add scikit-image with freeimage backend in order to read 16-bit TIFFs. --- glymur/jp2k.py | 61 ++++++++++++++++++++++++++++- glymur/test/test_conformance.py | 54 ++++++++++++++++--------- glymur/test/test_opj_suite_write.py | 10 +++++ 3 files changed, 106 insertions(+), 19 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 6db0198..04fa23e 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -219,6 +219,63 @@ class Jp2k(Jp2kBox): cparams.tcp_numlayers = 1 cparams.cp_disto_alloc = 1 + if 'cinema2K' in kwargs: + cparams.cp_rsiz = kwargs['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_on = 1 + + # tile and image shall be as (0,0) + cparams.cp_tx0 = 0 + cparams.cp_ty0 = 0 + cparams.image_offset_x0 = 0 + cparams.image_offset_y0 = 0 + + # Codeblock size = 32 * 32 + cparams.cblockw_init = 32 + cparams.cblockh_init = 32 + + # code block style, no mode switch enabled. + cparams.mode = 0 + + # no ROI + cparams.roi_compno = -1 + + # no subsampling + cparams.subsampling_dx = 1 + cparams.subsampling_dy = 1 + + # 9-7 transform + cparams.irreversible = 1 + + # number of layers + if cparams.tcp_layers > 1: + # TODO: warning or error + cparams.tcp_numlayers = 1 + + if cparams.numresolution > 6: + # TODO: warning or error + cparams.numresolution = 6 + + # precincts + 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 + + # Progression order shall be CPRL + cparams.prog_order = PROGRESSION_ORDER['CPRL'] + + # progression order changes not allowed for 2K + cparams.numpocs = 0 + return cparams + if 'cbsize' in kwargs: cparams.cblockw_init = kwargs['cbsize'][1] cparams.cblockh_init = kwargs['cbsize'][0] @@ -340,6 +397,8 @@ class Jp2k(Jp2kBox): Image data to be written to file. cbsize : tuple, optional Code block size (DY, DX). + cinema2K : int, optional + either 24 or 48 colorspace : str, optional Either 'rgb' or 'gray'. cratios : iterable @@ -473,7 +532,7 @@ class Jp2k(Jp2kBox): def _write_openjp2(self, img_array, verbose=False, **kwargs): """ - Write JPEG 2000 file using OpenJPEG 1.5 interface. + Write JPEG 2000 file using OpenJPEG 2.0 interface. """ cparams, colorspace = self._process_write_inputs(img_array, **kwargs) diff --git a/glymur/test/test_conformance.py b/glymur/test/test_conformance.py index 8f4496d..765f2f6 100644 --- a/glymur/test/test_conformance.py +++ b/glymur/test/test_conformance.py @@ -11,6 +11,7 @@ import os from os.path import join import re import sys +import tempfile import unittest import glymur @@ -27,6 +28,41 @@ except KeyError: OPJ_DATA_ROOT = None +@unittest.skipIf(sys.hexversion < 0x03020000, + "Requires features introduced in 3.2 (assertWarns)") +class TestSuiteConformance(unittest.TestCase): + """Test suite for conformance.""" + + def setUp(self): + self.j2kfile = glymur.data.goodstuff() + + def tearDown(self): + pass + + @unittest.skipIf(re.match(r"""1\.[0123]""", + glymur.version.openjpeg_version) is not None, + "Needs 1.3+ to catch this.") + def test_truncated_eoc(self): + """Has one byte shaved off of EOC marker.""" + with open(self.j2kfile, 'rb') as ifile: + data = ifile.read() + with tempfile.NamedTemporaryFile(suffix='.j2k') as ofile: + ofile.write(data[:-1]) + ofile.flush() + + j2k = Jp2k(ofile.name) + with self.assertWarns(UserWarning): + codestream = j2k.get_codestream(header_only=False) + + # The last segment is truncated, so there should not be an EOC + # marker. + self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC') + + # The codestream is not as long as claimed. + with self.assertRaises(OSError): + j2k.read(rlevel=-1) + + @unittest.skipIf(FORMAT_CORPUS_DATA_ROOT is None, "FORMAT_CORPUS_DATA_ROOT environment variable not set") @unittest.skipIf(sys.hexversion < 0x03020000, @@ -34,24 +70,6 @@ except KeyError: class TestSuiteFormatCorpus(unittest.TestCase): """Test suite for files in format corpus repository.""" - @unittest.skipIf(re.match(r"""1\.[0123]""", - glymur.version.openjpeg_version) is not None, - "Needs 1.3+ to catch this.") - def test_balloon_trunc1(self): - """Has one byte shaved off of EOC marker.""" - jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT, - 'jp2k-test/byteCorruption/balloon_trunc1.jp2') - j2k = Jp2k(jfile) - with self.assertWarns(UserWarning): - codestream = j2k.get_codestream(header_only=False) - - # The last segment is truncated, so there should not be an EOC marker. - self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC') - - # The codestream is not as long as claimed. - with self.assertRaises(OSError): - j2k.read(rlevel=-1) - @unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version) is not None, "Needs 1.4+ to catch this.") diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 22a99e3..07d623f 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -38,6 +38,16 @@ class TestSuiteWrite(unittest.TestCase): def tearDown(self): pass + @unittest.skip("Cannot read input image using PILLOW???") + 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) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data, 'cinema2K', 24) + def test_NR_ENC_Bretagne1_ppm_1_encode(self): """NR-ENC-Bretagne1.ppm-1-encode""" infile = opj_data_file('input/nonregression/Bretagne1.ppm') From afa611ae543fb166d5f1f2958579d5ae4448185f Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 6 Mar 2014 07:10:29 -0500 Subject: [PATCH 03/15] Can write and parse Cinema2K image. #139 Using scikit-image to read the test data. Testing still not complete. --- glymur/core.py | 20 ++++++++ glymur/jp2k.py | 43 ++++++++++++++--- glymur/test/test_opj_suite_write.py | 74 +++++++++++++++++++++++++++-- 3 files changed, 127 insertions(+), 10 deletions(-) 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""" From 3bad8a4b2e5444484e959d74b966845d7221a39c Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 6 Mar 2014 09:20:21 -0500 Subject: [PATCH 04/15] Added negative test for cinema2k frame rate not 24 or 48. #139 Some refactoring of cinema2k code. --- glymur/jp2k.py | 133 +++++++++++++++------------- glymur/test/fixtures.py | 2 +- glymur/test/test_opj_suite_write.py | 16 +++- glymur/test/test_printing.py | 5 +- 4 files changed, 88 insertions(+), 68 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 890c2fd..255e2be 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -155,6 +155,73 @@ class Jp2k(Jp2kBox): msg += "profile if the file type box brand is 'jp2 '." warnings.warn(msg) + def _set_cinema_params(self, cparams, fps): + """Populate compression parameters structure for cinema2K. + + Parameters + ---------- + fps : int + Frames per second, should be either 24 or 48. + """ + if fps not in [24, 48]: + raise IOError('Cinema2K frame rate must be either 24 or 48.') + cparams.cp_cinema = fps + + 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_flag = ord('C') + cparams.tp_on = 1 + + # tile and image shall be as (0,0) + cparams.cp_tx0 = 0 + cparams.cp_ty0 = 0 + cparams.image_offset_x0 = 0 + cparams.image_offset_y0 = 0 + + # Codeblock size = 32 * 32 + cparams.cblockw_init = 32 + cparams.cblockh_init = 32 + + # code block style, no mode switch enabled. + cparams.mode = 0 + + # no ROI + cparams.roi_compno = -1 + + # no subsampling + cparams.subsampling_dx = 1 + cparams.subsampling_dy = 1 + + # 9-7 transform + cparams.irreversible = 1 + + # number of layers + if cparams.tcp_numlayers > 1: + # TODO: warning or error + cparams.tcp_numlayers = 1 + + if cparams.numresolution > 6: + # TODO: warning or error + cparams.numresolution = 6 + + # precincts + cparams.csty |= 0x01 + cparams.res_spec = cparams.numresolution - 1 + for j in range(cparams.res_spec): + cparams.prcw_init[j] = 256 + cparams.prch_init[j] = 256 + + # Progression order shall be CPRL + cparams.prog_order = PROGRESSION_ORDER['CPRL'] + + # progression order changes not allowed for 2K + cparams.numpocs = 0 + def _populate_cparams(self, **kwargs): """Populate compression parameters structure from input arguments. @@ -221,64 +288,8 @@ class Jp2k(Jp2kBox): cparams.tcp_numlayers = 1 cparams.cp_disto_alloc = 1 - if 'cinema2K' in kwargs: - # 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_flag = ord('C') - cparams.tp_on = 1 - - # tile and image shall be as (0,0) - cparams.cp_tx0 = 0 - cparams.cp_ty0 = 0 - cparams.image_offset_x0 = 0 - cparams.image_offset_y0 = 0 - - # Codeblock size = 32 * 32 - cparams.cblockw_init = 32 - cparams.cblockh_init = 32 - - # code block style, no mode switch enabled. - cparams.mode = 0 - - # no ROI - cparams.roi_compno = -1 - - # no subsampling - cparams.subsampling_dx = 1 - cparams.subsampling_dy = 1 - - # 9-7 transform - cparams.irreversible = 1 - - # number of layers - if cparams.tcp_numlayers > 1: - # TODO: warning or error - cparams.tcp_numlayers = 1 - - if cparams.numresolution > 6: - # TODO: warning or error - cparams.numresolution = 6 - - # precincts - cparams.csty |= 0x01 - cparams.res_spec = cparams.numresolution - 1 - for j in range(cparams.res_spec): - cparams.prcw_init[j] = 256 - cparams.prch_init[j] = 256 - - # Progression order shall be CPRL - cparams.prog_order = PROGRESSION_ORDER['CPRL'] - - # progression order changes not allowed for 2K - cparams.numpocs = 0 + if 'cinema2k' in kwargs: + self._set_cinema_params(cparams, kwargs['cinema2k']) return cparams if 'cbsize' in kwargs: @@ -402,8 +413,8 @@ class Jp2k(Jp2kBox): Image data to be written to file. cbsize : tuple, optional Code block size (DY, DX). - cinema2K : int, optional - either 24 or 48 + cinema2k : int, optional + frames per second, either 24 or 48 colorspace : str, optional Either 'rgb' or 'gray'. cratios : iterable @@ -577,7 +588,7 @@ class Jp2k(Jp2kBox): _populate_image_struct(cparams, image, img_array) - if 'cinema2K' in kwargs: + if 'cinema2k' in kwargs: self._set_cinema_rate(cparams, image) codec = opj2.create_compress(cparams.codec_fmt) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 666fb6b..7e4d9e8 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -22,7 +22,7 @@ try: HAS_PYTHON_XMP_TOOLKIT = True else: HAS_PYTHON_XMP_TOOLKIT = False -except ImportError: +except: HAS_PYTHON_XMP_TOOLKIT = False # Need to know of the libopenjp2 version is the official 2.0.0 release and NOT diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 335202d..21c2578 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -16,7 +16,7 @@ try: import skimage.io skimage.io.use_plugin('freeimage', 'imread') _HAS_SKIMAGE_FREEIMAGE_SUPPORT = True -except ImportError: +except ((ImportError, RuntimeError)): _HAS_SKIMAGE_FREEIMAGE_SUPPORT = False from .fixtures import read_image, NO_READ_BACKEND, NO_READ_BACKEND_MSG @@ -25,7 +25,6 @@ from .fixtures import OPJ_DATA_ROOT, opj_data_file from glymur import Jp2k import glymur - @unittest.skipIf(os.name == "nt", "no write support on windows, period") @unittest.skipIf(re.match(r"""1\.[01234]\.\d""", glymur.version.openjpeg_version) is not None, @@ -53,7 +52,7 @@ class TestSuiteWrite(unittest.TestCase): 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() @@ -111,7 +110,16 @@ class TestSuiteWrite(unittest.TestCase): self.assertEqual(len(codestream.segment[2].spcod), 9) - + @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, + "Cannot read input image without scikit-image/freeimage") + def test_cinema2k_bad_frame_rate(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') + with self.assertRaises(IOError): + j.write(data, cinema2k=36) def test_NR_ENC_Bretagne1_ppm_1_encode(self): diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index f90ade3..c59d73f 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -133,7 +133,6 @@ class TestPrinting(unittest.TestCase): lst = lst[1:] actual = '\n'.join(lst) expected = fixtures.nemo_dump_no_xml - self.maxDiff = None self.assertEqual(actual, expected) def test_printoptions_short(self): @@ -745,7 +744,9 @@ class TestPrinting(unittest.TestCase): with patch('sys.stdout', new=StringIO()) as fake_out: print(j.box[2]) actual = fake_out.getvalue().strip() - self.assertEqual(actual, fixtures.file7_rreq) + self.maxDiff = None + expected = fixtures.file7_rreq + self.assertEqual(actual, expected) @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") From 80326718a595fbfad9f93b6d8975801cd98908a2 Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 6 Mar 2014 21:13:40 -0500 Subject: [PATCH 05/15] Finished Cinema2K upstream tests. #139 --- CHANGES.txt | 3 +- docs/source/changelog.rst | 3 +- glymur/jp2k.py | 25 ++++-- glymur/test/test_opj_suite_write.py | 123 ++++++++++++++++++++++++---- 4 files changed, 127 insertions(+), 27 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 24d9616..c906d29 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ -Feb 09, 2014 - Changed constructor for ChannelDefinition box. Removed support +Mar 06, 2014 - Added Cinema2K 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 and DataReference boxes. Added read support for JPX free, diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index bbba98e..320ff0f 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -5,10 +5,11 @@ ChangeLog 0.6.0 (pending) =============== + * Added Cinema2K write support. * Added lxml requirement. * added set_printoptions, get_printoptions function * dropped support for Python 2.6, added support for Python 3.4 - * dropped windows support + * dropped windows support (it might work, it might not, I don't much care) * added write support for JP2 UUID, dataEntryURL, palette, and component mapping boxes * added read/write support for JPX free, number list, and data reference boxes * Added read support for JPX fragment list and fragment table boxes diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 255e2be..627082b 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -163,9 +163,12 @@ class Jp2k(Jp2kBox): fps : int Frames per second, should be either 24 or 48. """ - if fps not in [24, 48]: + 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_cinema = fps cparams.cp_rsiz = RSIZ['CINEMA2K'] # No tiling @@ -205,7 +208,7 @@ class Jp2k(Jp2kBox): # TODO: warning or error cparams.tcp_numlayers = 1 - if cparams.numresolution > 6: + if cparams.numresolution > 6: #TODO only for cinema2k # TODO: warning or error cparams.numresolution = 6 @@ -219,7 +222,7 @@ class Jp2k(Jp2kBox): # Progression order shall be CPRL cparams.prog_order = PROGRESSION_ORDER['CPRL'] - # progression order changes not allowed for 2K + # progression order changes not allowed for 2K # TODO not for 4K: cparams.numpocs = 0 def _populate_cparams(self, **kwargs): @@ -554,8 +557,12 @@ class Jp2k(Jp2kBox): 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) + num_samples = num_pixels * image.contents.numcomps + rate_numerator = num_samples * image.contents.comps[0].prec + rate_denominator = CINEMA_24_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: @@ -567,7 +574,7 @@ class Jp2k(Jp2kBox): # TODO warning pass - cparams.max_comp_size = COMP_24_CS + cparams.max_comp_size = CINEMA_24_CS def _write_openjp2(self, img_array, verbose=False, **kwargs): """ @@ -1608,6 +1615,10 @@ def _populate_image_struct(cparams, image, imgdata): # Stage the image data to the openjpeg data structure. for k in range(0, num_comps): + if cparams.cp_cinema: + image.contents.comps[k].prec = 12 + image.contents.comps[k].bpp = 12 + layer = np.ascontiguousarray(imgdata[:, :, k], dtype=np.int32) dest = image.contents.comps[k].data src = layer.ctypes.data diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 21c2578..ca3bc83 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_6_2K_24_FULL_CBR_CIRCLE_000_tif_17_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=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), + (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_5_2K_24_235_CBR_STEM24_000_tif_16_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=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), + (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_4_2K_24_185_CBR_WB_000_tif_15_encode(self): @@ -87,27 +189,12 @@ class TestSuiteWrite(unittest.TestCase): # 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[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), - (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) + (32, 32)) # cblksz @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, From 9a74109501fa89cc2daec8ceba8a928c6b30677e Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 7 Mar 2014 07:00:02 -0500 Subject: [PATCH 06/15] Added cinema2k/48 support. #139 --- glymur/jp2k.py | 39 ++++++- glymur/test/test_opj_suite_write.py | 153 ++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+), 2 deletions(-) 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): From af4b6e64bfc70e6c97c7cceda509dd2685c25b68 Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 7 Mar 2014 08:40:14 -0500 Subject: [PATCH 07/15] Refactoring. #139 --- glymur/jp2k.py | 22 +-- glymur/test/test_opj_suite_write.py | 276 +++++----------------------- 2 files changed, 56 insertions(+), 242 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 1d9e1b2..00d3d24 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -566,15 +566,15 @@ class Jp2k(Jp2kBox): temp_rate = 0 cparams.cp_disto_alloc = 1 + 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 = 8 * image.contents.comps[0].dx + rate_denominator *= image.contents.comps[0].dy + if cparams.cp_cinema in [CINEMA_MODE['cinema2k_24'], 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 - rate_denominator = CINEMA_24_CS * 8 - rate_denominator *= image.contents.comps[0].dx - rate_denominator *= image.contents.comps[0].dy - max_rate = rate_numerator / rate_denominator + max_rate = rate_numerator / (rate_denominator * CINEMA_24_CS) if cparams.tcp_rates[0] == 0: cparams.tcp_rates[0] = max_rate else: @@ -589,13 +589,7 @@ class Jp2k(Jp2kBox): 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 + max_rate = rate_numerator / (rate_denominator * CINEMA_48_CS) if cparams.tcp_rates[0] == 0: cparams.tcp_rates[0] = max_rate else: diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index d0e449e..87a6fc4 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_cinema2k_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, 3) + # 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 + + + @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): @@ -55,44 +97,7 @@ class TestSuiteWrite(unittest.TestCase): 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 + self.check_cinema2k_codestream(codestream, (2048, 857)) @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, @@ -106,44 +111,7 @@ class TestSuiteWrite(unittest.TestCase): 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 + self.check_cinema2k_codestream(codestream, (2048, 1080)) @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, @@ -157,44 +125,7 @@ class TestSuiteWrite(unittest.TestCase): 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), - (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 + self.check_cinema2k_codestream(codestream, (2048, 1080)) @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, @@ -208,44 +139,7 @@ class TestSuiteWrite(unittest.TestCase): 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), - (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 + self.check_cinema2k_codestream(codestream, (2048, 857)) @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, @@ -259,44 +153,7 @@ class TestSuiteWrite(unittest.TestCase): 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 + self.check_cinema2k_codestream(codestream, (1998, 1080)) @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, @@ -310,44 +167,7 @@ class TestSuiteWrite(unittest.TestCase): 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.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 + self.check_cinema2k_codestream(codestream, (1998, 1080)) @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, From d68b1aacb1f888adfef13cd2054421989607a21c Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 7 Mar 2014 11:00:20 -0500 Subject: [PATCH 08/15] Cinema4K seems to be working. #139 --- CHANGES.txt | 2 +- docs/source/changelog.rst | 2 +- docs/source/detailed_installation.rst | 2 +- glymur/jp2k.py | 63 +++++++++++++++++++++------ glymur/test/test_opj_suite_write.py | 56 ++++++++++++++++++++++++ 5 files changed, 108 insertions(+), 17 deletions(-) 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): From 4ecc23b6bf8c5de89f3df14ab73efa38980022aa Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 7 Mar 2014 12:08:46 -0500 Subject: [PATCH 09/15] Verified through r2386. #139 --- docs/source/detailed_installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index 9111dbd..5b9f1ff 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 r2369 works. +via subversion. As of this time of writing, svn revision r2386 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 From 47b8dad0038bb7edff8cd067110fd0c7b4d87f6c Mon Sep 17 00:00:00 2001 From: John Evans Date: Sat, 8 Mar 2014 07:46:12 -0500 Subject: [PATCH 10/15] Check for supplying cinema2k/4k with other options. #139 --- glymur/jp2k.py | 4 ++++ glymur/test/test_opj_suite_write.py | 25 +++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index e861053..d17195d 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -422,6 +422,10 @@ class Jp2k(Jp2kBox): Either CLRSPC_SRGB or CLRSPC_GRAY """ + if ('cinema2k' in kwargs or 'cinema4k' in kwargs) and len(set(kwargs)) > 1: + msg = "Cannot specify cinema2k/cinema4k along with other options." + raise IOError(msg) + if 'cratios' in kwargs and 'psnr' in kwargs: msg = "Cannot specify cratios and psnr together." raise IOError(msg) diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 0e7f2d3..6539e21 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -35,8 +35,8 @@ import glymur class TestSuiteWrite(unittest.TestCase): """Tests for writing with openjp2 backend. - These tests roughly correspond with those tests with similar names in the - OpenJPEG test suite. + These tests either roughly correspond with those tests with similar names + in the OpenJPEG test suite or are closely associated. """ def setUp(self): pass @@ -44,6 +44,26 @@ class TestSuiteWrite(unittest.TestCase): def tearDown(self): pass + def test_cinema2K_with_others(self): + """Can't specify cinema2k with any other options.""" + 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') + with self.assertRaises(IOError): + j.write(data, cinema2k=48, cratios=[200, 100, 50]) + + def test_cinema4K_with_others(self): + """Can't specify cinema4k with any other options.""" + 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') + with self.assertRaises(IOError): + j.write(data, cinema4k=True, cratios=[200, 100, 50]) + def check_cinema4k_codestream(self, codestream, image_size): """Common out for cinema2k tests.""" # SIZ: Image and tile size @@ -1052,5 +1072,6 @@ class TestSuiteWrite(unittest.TestCase): glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) + if __name__ == "__main__": unittest.main() From 82f60b328b4c436f96a21e37cd66033899bbf48b Mon Sep 17 00:00:00 2001 From: John Evans Date: Sat, 8 Mar 2014 13:19:17 -0500 Subject: [PATCH 11/15] Validated through r2436. #139 Most cinema code was moved into the library between 2386 and 2436, so most of glymur's was removed period. --- docs/source/detailed_installation.rst | 2 +- glymur/jp2k.py | 141 +------------------------- glymur/test/test_opj_suite.py | 1 + 3 files changed, 5 insertions(+), 139 deletions(-) diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index 5b9f1ff..5c8d7ad 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 r2386 works. +via subversion. As of this time of writing, svn revision r2436 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 d17195d..905dbd8 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -39,16 +39,6 @@ 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 ', 'uuid'] @@ -183,90 +173,10 @@ class Jp2k(Jp2kBox): 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: cparams.cp_cinema = CINEMA_MODE['cinema4k_24'] - cparams.cp_rsiz = RSIZ['CINEMA4K'] - - # No tiling - cparams.tile_size_on = opj2.FALSE - cparams.cp_tdx = 1 - cparams.cp_tdy = 1 - - # One tile part for each component. - cparams.tp_flag = ord('C') - cparams.tp_on = 1 - - # tile and image shall be as (0,0) - cparams.cp_tx0 = 0 - cparams.cp_ty0 = 0 - cparams.image_offset_x0 = 0 - cparams.image_offset_y0 = 0 - - # Codeblock size = 32 * 32 - cparams.cblockw_init = 32 - cparams.cblockh_init = 32 - - # code block style, no mode switch enabled. - cparams.mode = 0 - - # no ROI - cparams.roi_compno = -1 - - # no subsampling - cparams.subsampling_dx = 1 - cparams.subsampling_dy = 1 - - # 9-7 transform - cparams.irreversible = 1 - - # number of layers - if cparams.tcp_numlayers > 1: - # TODO: warning or error - cparams.tcp_numlayers = 1 - - 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 - cparams.res_spec = cparams.numresolution - 1 - for j in range(cparams.res_spec): - cparams.prcw_init[j] = 256 - cparams.prch_init[j] = 256 - - # Progression order shall be CPRL - cparams.prog_order = PROGRESSION_ORDER['CPRL'] - - # 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 + return def _populate_cparams(self, **kwargs): """Populate compression parameters structure from input arguments. @@ -469,6 +379,8 @@ class Jp2k(Jp2kBox): Code block size (DY, DX). cinema2k : int, optional frames per second, either 24 or 48 + cinema4k : bool, optional + Set to True to specify Cinema4K mode, defaults to false. colorspace : str, optional Either 'rgb' or 'gray'. cratios : iterable @@ -600,48 +512,6 @@ class Jp2k(Jp2kBox): self.parse() - def _set_cinema_rate(self, cparams, image): - max_rate = 0 - temp_rate = 0 - cparams.cp_disto_alloc = 1 - - 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 = 8 * image.contents.comps[0].dx - rate_denominator *= image.contents.comps[0].dy - - if cparams.cp_cinema in [CINEMA_MODE['cinema2k_24'], - CINEMA_MODE['cinema4k_24']]: - max_rate = rate_numerator / (rate_denominator * CINEMA_24_CS) - 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 - - else: - max_rate = rate_numerator / (rate_denominator * CINEMA_48_CS) - 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): """ Write JPEG 2000 file using OpenJPEG 2.0 interface. @@ -661,11 +531,6 @@ class Jp2k(Jp2kBox): _populate_image_struct(cparams, image, img_array) - 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.py b/glymur/test/test_opj_suite.py index abe271a..4cff15b 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -6647,6 +6647,7 @@ class TestSuite2point1(unittest.TestCase): Jp2k(jfile).read() self.assertTrue(True) + @unittest.skip("Failing as of r2436") def test_NR_DEC_mem_b2ace68c_1381_jp2_34_decode(self): jfile = opj_data_file('input/nonregression/mem-b2ace68c-1381.jp2') with warnings.catch_warnings(): From ed5c6a37b405e5692273225b7b65108934e36c6a Mon Sep 17 00:00:00 2001 From: John Evans Date: Sat, 8 Mar 2014 13:57:33 -0500 Subject: [PATCH 12/15] Added .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc From 554f066d0b048d6c86b9cbeec9ebe27ff552a709 Mon Sep 17 00:00:00 2001 From: John Evans Date: Sat, 8 Mar 2014 14:13:36 -0500 Subject: [PATCH 13/15] Added alpha member. Updated through r2451. #139 --- docs/source/detailed_installation.rst | 2 +- glymur/lib/openjp2.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index 5c8d7ad..f18d77e 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 r2436 works. +via subversion. As of this time of writing, svn revision r2451 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/lib/openjp2.py b/glymur/lib/openjp2.py index f056db0..5430110 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -35,6 +35,7 @@ CLRSPC_UNSPECIFIED = 0 CLRSPC_SRGB = 1 CLRSPC_GRAY = 2 CLRSPC_YCC = 3 +CLRSPC_EYCC = 4 COLOR_SPACE_TYPE = ctypes.c_int # supported codec @@ -390,7 +391,11 @@ class ImageCompType(ctypes.Structure): ("factor", ctypes.c_uint32), # image component data - ("data", ctypes.POINTER(ctypes.c_int32))] + ("data", ctypes.POINTER(ctypes.c_int32)), + + # alpha channel + # TODO: exclude for 2.0, 1.5 + ("alpha", ctypes.POINTER(ctypes.c_uint16))] class ImageType(ctypes.Structure): From 8827908b284b8292aee439a1c538534cb9ad187c Mon Sep 17 00:00:00 2001 From: John Evans Date: Sat, 8 Mar 2014 15:23:35 -0500 Subject: [PATCH 14/15] Updated through r2651. #139 --- docs/source/detailed_installation.rst | 2 +- glymur/test/test_icc.py | 2 +- glymur/test/test_opj_suite.py | 92 ++++++++++----------------- glymur/test/test_printing.py | 1 + 4 files changed, 38 insertions(+), 59 deletions(-) diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index f18d77e..6bbc2f2 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 r2451 works. +via subversion. As of this time of writing, svn revision r2651 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/test/test_icc.py b/glymur/test/test_icc.py index 46d4345..0ef166b 100644 --- a/glymur/test/test_icc.py +++ b/glymur/test/test_icc.py @@ -31,7 +31,7 @@ class TestICC(unittest.TestCase): """basic ICC profile""" filename = opj_data_file('input/conformance/file5.jp2') j = Jp2k(filename) - profile = j.box[3].box[1].icc_profile + profile = j.box[2].box[1].icc_profile self.assertEqual(profile['Size'], 546) self.assertEqual(profile['Preferred CMM Type'], 0) self.assertEqual(profile['Version'], '2.2.0') diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 4cff15b..426ae02 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -3297,7 +3297,7 @@ class TestSuiteDump(unittest.TestCase): def test_NR_file5_dump(self): # Three 8-bit components in the ROMM-RGB colourspace, encapsulated in a - # JP2 compatible JPX file. The components have been transformed using + # JPX file. The components have been transformed using # the RCT. The colourspace is specified using both a Restricted ICC # profile and using the JPX-defined enumerated code for the ROMM-RGB # colourspace. @@ -3305,49 +3305,38 @@ class TestSuiteDump(unittest.TestCase): jp2 = Jp2k(jfile) ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'colr']) + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) # Signature box. Check for corruption. self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) # File type box. - self.assertEqual(jp2.box[1].brand, 'jpx ') + self.assertEqual(jp2.box[1].brand, 'jp2 ') self.assertEqual(jp2.box[1].minor_version, 0) self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') - self.assertEqual(jp2.box[1].compatibility_list[3], 'jpxb') # Jp2 Header # Image header - self.assertEqual(jp2.box[3].box[0].height, 512) - self.assertEqual(jp2.box[3].box[0].width, 768) - self.assertEqual(jp2.box[3].box[0].num_components, 3) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) + self.assertEqual(jp2.box[2].box[0].height, 512) + self.assertEqual(jp2.box[2].box[0].width, 768) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) # Jp2 Header # Colour specification - self.assertEqual(jp2.box[3].box[1].method, + self.assertEqual(jp2.box[2].box[1].method, glymur.core.RESTRICTED_ICC_PROFILE) # enumerated - self.assertEqual(jp2.box[3].box[1].precedence, 0) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 546) - self.assertIsNone(jp2.box[3].box[1].colorspace) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact + self.assertEqual(jp2.box[2].box[1].icc_profile['Size'], 546) + self.assertIsNone(jp2.box[2].box[1].colorspace) - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[2].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[2].precedence, 1) - self.assertEqual(jp2.box[3].box[2].approximation, 1) # JPX exact - self.assertIsNone(jp2.box[3].box[2].icc_profile) - self.assertEqual(jp2.box[3].box[2].colorspace, - glymur.core.ROMM_RGB) def test_NR_file6_dump(self): jfile = opj_data_file('input/conformance/file6.jp2') @@ -3398,54 +3387,43 @@ class TestSuiteDump(unittest.TestCase): jp2 = Jp2k(jfile) ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'colr']) + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) # Signature box. Check for corruption. self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) # File type box. - self.assertEqual(jp2.box[1].brand, 'jpx ') + self.assertEqual(jp2.box[1].brand, 'jp2 ') self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') - self.assertEqual(jp2.box[1].compatibility_list[3], 'jpxb') self.assertEqual(jp2.box[1].minor_version, 0) # Reader requirements talk. # e-SRGB enumerated colourspace - self.assertTrue(60 in jp2.box[2].standard_flag) + #self.assertTrue(60 in jp2.box[2].standard_flag) # Jp2 Header # Image header - self.assertEqual(jp2.box[3].box[0].height, 640) - self.assertEqual(jp2.box[3].box[0].width, 480) - self.assertEqual(jp2.box[3].box[0].num_components, 3) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 16) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) + self.assertEqual(jp2.box[2].box[0].height, 640) + self.assertEqual(jp2.box[2].box[0].width, 480) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 16) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) # Jp2 Header # Colour specification - self.assertEqual(jp2.box[3].box[1].method, + self.assertEqual(jp2.box[2].box[1].method, glymur.core.RESTRICTED_ICC_PROFILE) - self.assertEqual(jp2.box[3].box[1].precedence, 0) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 13332) - self.assertIsNone(jp2.box[3].box[1].colorspace) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact + self.assertEqual(jp2.box[2].box[1].icc_profile['Size'], 13332) + self.assertIsNone(jp2.box[2].box[1].colorspace) - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[2].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[2].precedence, 1) - self.assertEqual(jp2.box[3].box[2].approximation, 1) # JPX exact - self.assertIsNone(jp2.box[3].box[2].icc_profile) - self.assertEqual(jp2.box[3].box[2].colorspace, - glymur.core.E_SRGB) def test_NR_file8_dump(self): # One 8-bit component in a gamma 1.8 space. The colourspace is diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index c59d73f..bc12216 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -735,6 +735,7 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) + @unittest.skip("file7 no longer has a rreq") @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") def test_rreq(self): From de6984e204c87f18aff56a2030913c5c0ed020fe Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 10 Mar 2014 19:54:01 -0400 Subject: [PATCH 15/15] Validated through 2686. #139 --- glymur/jp2k.py | 9 +++- glymur/test/test_jp2k.py | 5 ++ glymur/test/test_opj_suite_neg.py | 25 ++++++++++ glymur/test/test_opj_suite_write.py | 73 ++++++++++++++++------------- 4 files changed, 78 insertions(+), 34 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 905dbd8..7522d35 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -166,6 +166,11 @@ class Jp2k(Jp2kBox): fps : int Frames per second, should be either 24 or 48. """ + if version.openjpeg_version_tuple[0] == 1: + msg = "Writing Cinema2K or Cinema4K files is not supported with " + msg += 'openjpeg library versions less than 2.0.1.' + raise IOError(msg) + if cinema_mode == 'cinema2k': if fps == 24: cparams.cp_cinema = CINEMA_MODE['cinema2k_24'] @@ -331,8 +336,8 @@ class Jp2k(Jp2kBox): colorspace : int Either CLRSPC_SRGB or CLRSPC_GRAY """ - - if ('cinema2k' in kwargs or 'cinema4k' in kwargs) and len(set(kwargs)) > 1: + if (('cinema2k' in kwargs or 'cinema4k' in kwargs) and + (len(set(kwargs)) > 1)): msg = "Cannot specify cinema2k/cinema4k along with other options." raise IOError(msg) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 386f077..dd55299 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -35,6 +35,7 @@ if HAS_PYTHON_XMP_TOOLKIT: from libxmp import XMPMeta from .fixtures import OPJ_DATA_ROOT, opj_data_file +from . import fixtures # Doc tests should be run as well. @@ -376,6 +377,10 @@ class TestJp2k(unittest.TestCase): creator_tool = xmp.get_property(libxmp.consts.XMP_NS_XMP, 'CreatorTool') self.assertEqual(creator_tool, 'Google') + @unittest.skipIf(fixtures.OPENJP2_IS_V2_OFFICIAL, + "Feature not supported in 2.0.0 official") + @unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1, + "Feature not supported in 1.5") def test_jpx_mult_codestreams_jp2_brand(self): """Read JPX codestream when jp2-compatible.""" # The file in question has multiple codestreams. diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index 4b14f4d..d31ea48 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -23,6 +23,31 @@ from glymur import Jp2k import glymur +@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version), + "Functionality not implemented for 1.3, 1.4") +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_OPJ_DATA_ROOT environment variable not set") +class TestSuiteNegative2pointzero(unittest.TestCase): + """Feature set not supported for versions less than 2.0""" + + def setUp(self): + self.jp2file = glymur.data.nemo() + self.j2kfile = glymur.data.goodstuff() + + def tearDown(self): + pass + + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") + def test_cinema_mode(self): + """Cinema mode not supported for less than 2.0.1.""" + infile = opj_data_file('input/nonregression/Bretagne1.ppm') + data = read_image(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError): + j.write(data, psnr=[30, 35, 40], cratios=[2, 3, 4]) + + @unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version), "Functionality not implemented for 1.3, 1.4") @unittest.skipIf(OPJ_DATA_ROOT is None, diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 6539e21..220115c 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -21,18 +21,21 @@ except ((ImportError, RuntimeError)): from .fixtures import read_image, NO_READ_BACKEND, NO_READ_BACKEND_MSG from .fixtures import OPJ_DATA_ROOT, opj_data_file +from . import fixtures from glymur import Jp2k import glymur +@unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, + "Cannot read input image without scikit-image/freeimage") @unittest.skipIf(os.name == "nt", "no write support on windows, period") -@unittest.skipIf(re.match(r"""1\.[01234]\.\d""", - glymur.version.openjpeg_version) is not None, - "Writing only supported with openjpeg version 1.5+.") -@unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG) +@unittest.skipIf(fixtures.OPENJP2_IS_V2_OFFICIAL, + "Feature not supported in 2.0.0 official") +@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1, + "Feature not supported in 1.5") @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") -class TestSuiteWrite(unittest.TestCase): +class TestSuiteWriteCinema(unittest.TestCase): """Tests for writing with openjp2 backend. These tests either roughly correspond with those tests with similar names @@ -148,8 +151,6 @@ 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) @@ -162,8 +163,6 @@ class TestSuiteWrite(unittest.TestCase): 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): relfile = 'input/nonregression/X_5_2K_24_235_CBR_STEM24_000.tif' infile = opj_data_file(relfile) @@ -176,8 +175,6 @@ class TestSuiteWrite(unittest.TestCase): self.check_cinema2k_codestream(codestream, (2048, 857)) - @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) @@ -190,8 +187,6 @@ class TestSuiteWrite(unittest.TestCase): self.check_cinema2k_codestream(codestream, (2048, 1080)) - @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): relfile = 'input/nonregression/X_6_2K_24_FULL_CBR_CIRCLE_000.tif' infile = opj_data_file(relfile) @@ -204,8 +199,6 @@ class TestSuiteWrite(unittest.TestCase): self.check_cinema2k_codestream(codestream, (2048, 1080)) - @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_16_encode(self): relfile = 'input/nonregression/X_5_2K_24_235_CBR_STEM24_000.tif' infile = opj_data_file(relfile) @@ -218,8 +211,6 @@ class TestSuiteWrite(unittest.TestCase): self.check_cinema2k_codestream(codestream, (2048, 857)) - @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) @@ -232,32 +223,50 @@ class TestSuiteWrite(unittest.TestCase): self.check_cinema2k_codestream(codestream, (1998, 1080)) - @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' - 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=24) +@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") +@unittest.skipIf(re.match(r"""2\.0""", glymur.version.openjpeg_version), + "Functionality implemented for 2.1") +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_OPJ_DATA_ROOT environment variable not set") +class TestSuiteNegative2pointzero(unittest.TestCase): + """Feature set not supported for versions less than 2.0""" - codestream = j.get_codestream() - self.check_cinema2k_codestream(codestream, (1998, 1080)) + def setUp(self): + self.jp2file = glymur.data.nemo() + self.j2kfile = glymur.data.goodstuff() + def tearDown(self): + pass - @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, - "Cannot read input image without scikit-image/freeimage") - def test_cinema2k_bad_frame_rate(self): + def test_cinema_mode(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') with self.assertRaises(IOError): - j.write(data, cinema2k=36) + j.write(data, cinema2k=48) +@unittest.skipIf(os.name == "nt", "no write support on windows, period") +@unittest.skipIf(re.match(r"""1\.[01234]\.\d""", + glymur.version.openjpeg_version) is not None, + "Writing only supported with openjpeg version 1.5+.") +@unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG) +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class TestSuiteWrite(unittest.TestCase): + """Tests for writing with openjp2 backend. + + These tests either roughly correspond with those tests with similar names + in the OpenJPEG test suite or are closely associated. + """ + def setUp(self): + pass + + def tearDown(self): + pass + def test_NR_ENC_Bretagne1_ppm_1_encode(self): """NR-ENC-Bretagne1.ppm-1-encode""" infile = opj_data_file('input/nonregression/Bretagne1.ppm')