Merge branch 'issue139' into devel

Conflicts:
	glymur/test/test_jp2k.py
	glymur/test/test_opj_suite.py
	glymur/test/test_printing.py
This commit is contained in:
John Evans 2014-03-10 20:13:07 -04:00
commit c8fdf399f1
14 changed files with 386 additions and 30 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.pyc

View file

@ -1,4 +1,5 @@
Feb 09, 2014 - Changed constructor for ChannelDefinition box. Removed 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
and DataReference boxes. Added read support for JPX free,

View file

@ -5,10 +5,11 @@ ChangeLog
0.6.0 (pending)
===============
* 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
* 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

View file

@ -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 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

View file

@ -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,

View file

@ -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
@ -153,6 +153,36 @@ class Jp2k(Jp2kBox):
msg += "profile if the file type box brand is 'jp2 '."
warnings.warn(msg)
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 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']
elif fps == 48:
cparams.cp_cinema = CINEMA_MODE['cinema2k_48']
else:
raise IOError('Cinema2K frame rate must be either 24 or 48.')
else:
cparams.cp_cinema = CINEMA_MODE['cinema4k_24']
return
def _populate_cparams(self, **kwargs):
"""Populate compression parameters structure from input arguments.
@ -219,6 +249,14 @@ class Jp2k(Jp2kBox):
cparams.tcp_numlayers = 1
cparams.cp_disto_alloc = 1
if 'cinema2k' in kwargs:
self._set_cinema_params(cparams, 'cinema2k', kwargs['cinema2k'])
return cparams
if 'cinema4k' in kwargs:
self._set_cinema_params(cparams, 'cinema4k', kwargs['cinema4k'])
return cparams
if 'cbsize' in kwargs:
cparams.cblockw_init = kwargs['cbsize'][1]
cparams.cblockh_init = kwargs['cbsize'][0]
@ -298,6 +336,10 @@ class Jp2k(Jp2kBox):
colorspace : int
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."
@ -340,6 +382,10 @@ class Jp2k(Jp2kBox):
Image data to be written to file.
cbsize : tuple, optional
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
@ -473,7 +519,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)
@ -1507,6 +1553,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

View file

@ -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):

View file

@ -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

View file

@ -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.")

View file

@ -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,7 +377,10 @@ class TestJp2k(unittest.TestCase):
creator_tool = xmp.get_property(libxmp.consts.XMP_NS_XMP, 'CreatorTool')
self.assertEqual(creator_tool, 'Google')
@unittest.skip("Failing as of r2651.")
@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.

View file

@ -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.
@ -6618,6 +6618,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():

View file

@ -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,

View file

@ -12,12 +12,241 @@ import sys
import tempfile
import unittest
try:
import skimage.io
skimage.io.use_plugin('freeimage', 'imread')
_HAS_SKIMAGE_FREEIMAGE_SUPPORT = True
except ((ImportError, RuntimeError)):
_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
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(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 TestSuiteWriteCinema(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_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
# 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
# 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
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))
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()
self.check_cinema2k_codestream(codestream, (2048, 857))
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()
self.check_cinema2k_codestream(codestream, (2048, 1080))
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()
self.check_cinema2k_codestream(codestream, (2048, 1080))
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()
self.check_cinema2k_codestream(codestream, (2048, 857))
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()
self.check_cinema2k_codestream(codestream, (1998, 1080))
@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"""
def setUp(self):
self.jp2file = glymur.data.nemo()
self.j2kfile = glymur.data.goodstuff()
def tearDown(self):
pass
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=48)
@unittest.skipIf(os.name == "nt", "no write support on windows, period")
@unittest.skipIf(re.match(r"""1\.[01234]\.\d""",
@ -29,8 +258,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
@ -852,5 +1081,6 @@ class TestSuiteWrite(unittest.TestCase):
glymur.core.WAVELET_XFORM_5X3_REVERSIBLE)
self.assertEqual(len(codestream.segment[2].spcod), 9)
if __name__ == "__main__":
unittest.main()

View file

@ -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):
@ -736,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):