Merge branch 'devel' into issue210

This commit is contained in:
John Evans 2014-04-02 09:50:20 -04:00
commit 9b533b72e3
17 changed files with 912 additions and 423 deletions

View file

@ -43,7 +43,7 @@ def tiff_header(read_buffer):
endian = '>'
else:
msg = "Bad byte order indication: {0}".format(read_buffer[6:8])
raise RuntimeError(msg)
raise IOError(msg)
_, offset = struct.unpack(endian + 'HI', read_buffer[8:14])

View file

@ -17,7 +17,6 @@ codestreams.
# the base Segment class.
# pylint: disable=R0903
import collections
import math
import struct
import sys
@ -28,33 +27,36 @@ import numpy as np
from .core import LRCP, RLCP, RPCL, PCRL, CPRL
from .core import WAVELET_XFORM_9X7_IRREVERSIBLE
from .core import WAVELET_XFORM_5X3_REVERSIBLE
from .core import _CAPABILITIES_DISPLAY
from .core import _Keydefaultdict
from .lib import openjp2 as opj2
class _keydefaultdict(collections.defaultdict):
"""Unlisted keys help form their own error message.
Normally defaultdict uses a factory function with no input arguments, but
that's not quite the behavior we want.
"""
def __missing__(self, key):
if self.default_factory is None:
raise KeyError(key)
else:
ret = self[key] = self.default_factory(key)
return ret
_factory = lambda x: '{0} (invalid)'.format(x)
_PROGRESSION_ORDER_DISPLAY = _keydefaultdict(_factory,
_PROGRESSION_ORDER_DISPLAY = _Keydefaultdict(_factory,
{ LRCP: 'LRCP',
RLCP: 'RLCP',
RPCL: 'RPCL',
PCRL: 'PCRL',
CPRL: 'CPRL'})
_WAVELET_TRANSFORM_DISPLAY = {
WAVELET_XFORM_9X7_IRREVERSIBLE: '9-7 irreversible',
WAVELET_XFORM_5X3_REVERSIBLE: '5-3 reversible'}
_WAVELET_TRANSFORM_DISPLAY = _Keydefaultdict(_factory,
{ WAVELET_XFORM_9X7_IRREVERSIBLE: '9-7 irreversible',
WAVELET_XFORM_5X3_REVERSIBLE: '5-3 reversible'})
_NO_PROFILE = 0
_PROFILE_0 = 1
_PROFILE_1 = 2
_PROFILE_3 = 3
_PROFILE_4 = 4
_KNOWN_PROFILES = [_NO_PROFILE, _PROFILE_0, _PROFILE_1, _PROFILE_3, _PROFILE_4]
# How to display the codestream profile.
_CAPABILITIES_DISPLAY = _Keydefaultdict(_factory,
{ _NO_PROFILE: 'no profile',
_PROFILE_0: '0',
_PROFILE_1: '1',
_PROFILE_3: 'Cinema 2K',
_PROFILE_4: 'Cinema 4K'} )
# Need a catch-all list of valid markers.
# See table A-1 in ISO/IEC FCD15444-1.
@ -390,6 +392,11 @@ class Codestream(object):
msg = "Invalid progression order in COD segment: {0}."
warnings.warn(msg.format(spcod[0]))
if spcod[8] not in [WAVELET_XFORM_9X7_IRREVERSIBLE,
WAVELET_XFORM_5X3_REVERSIBLE]:
msg = "Invalid wavelet transform in COD segment: {0}."
warnings.warn(msg.format(spcod[8]))
sop = (scod & 2) > 0
eph = (scod & 4) > 0
@ -667,6 +674,9 @@ class Codestream(object):
data = struct.unpack('>HIIIIIIIIH', xy_buffer)
rsiz = data[0]
if rsiz not in _KNOWN_PROFILES:
warnings.warn("Invalid profile: (Rsiz={0}).".format(rsiz))
xysiz = (data[1], data[2])
xyosiz = (data[3], data[4])
xytsiz = (data[5], data[6])

View file

@ -1,8 +1,22 @@
"""Core definitions to be shared amongst the modules.
"""
import collections
import copy
import lxml.etree as ET
class _Keydefaultdict(collections.defaultdict):
"""Unlisted keys help form their own error message.
Normally defaultdict uses a factory function with no input arguments, but
that's not quite the behavior we want.
"""
def __missing__(self, key):
if self.default_factory is None:
raise KeyError(key)
else:
ret = self[key] = self.default_factory(key)
return ret
# Progression order
LRCP = 0
RLCP = 1
@ -57,24 +71,26 @@ YCC = 18
E_SRGB = 20
ROMM_RGB = 21
_COLORSPACE_MAP_DISPLAY = {
CMYK: 'CMYK',
SRGB: 'sRGB',
GREYSCALE: 'greyscale',
YCC: 'YCC',
E_SRGB: 'e-sRGB',
ROMM_RGB: 'ROMM-RGB'}
_factory = lambda x: '{0} (unrecognized)'.format(x)
_COLORSPACE_MAP_DISPLAY = _Keydefaultdict(_factory,
{ CMYK: 'CMYK',
SRGB: 'sRGB',
GREYSCALE: 'greyscale',
YCC: 'YCC',
E_SRGB: 'e-sRGB',
ROMM_RGB: 'ROMM-RGB'} )
# enumerated color channel types
COLOR = 0
OPACITY = 1
PRE_MULTIPLIED_OPACITY = 2
_UNSPECIFIED = 65535
_COLOR_TYPE_MAP_DISPLAY = {
COLOR: 'color',
OPACITY: 'opacity',
PRE_MULTIPLIED_OPACITY: 'pre-multiplied opacity',
_UNSPECIFIED: 'unspecified'}
_factory = lambda x: '{0} (invalid)'.format(x)
_COLOR_TYPE_MAP_DISPLAY = _Keydefaultdict(_factory,
{ COLOR: 'color',
OPACITY: 'opacity',
PRE_MULTIPLIED_OPACITY: 'pre-multiplied opacity',
_UNSPECIFIED: 'unspecified'})
# color channel definitions.
RED = 1
@ -90,9 +106,3 @@ _COLORSPACE = {SRGB: {"R": 1, "G": 2, "B": 3},
E_SRGB: {"R": 1, "G": 2, "B": 3},
ROMM_RGB: {"R": 1, "G": 2, "B": 3}}
# How to display the codestream profile.
_CAPABILITIES_DISPLAY = {
0: '2',
1: '0',
2: '1',
3: '3'}

Binary file not shown.

View file

@ -34,13 +34,13 @@ def goodstuff():
def jpxfile():
"""Shortcut for specifying path to 12-v6.4.jpx.
"""Shortcut for specifying path to heliov.jpx.
Returns
-------
file : str
Platform-independent path to 12-v6.4.jpx
"""
filename = pkg_resources.resource_filename(__name__, "12-v6.4.jpx")
filename = pkg_resources.resource_filename(__name__, "heliov.jpx")
return filename

BIN
glymur/data/heliov.jpx Normal file

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -29,7 +29,7 @@ import numpy as np
from .codestream import Codestream
from .core import SRGB, GREYSCALE
from .core import PROGRESSION_ORDER, RSIZ, CINEMA_MODE
from .core import PROGRESSION_ORDER, CINEMA_MODE
from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE
from .jp2box import Jp2kBox
from .jp2box import JPEG2000SignatureBox, FileTypeBox, JP2HeaderBox
@ -442,7 +442,7 @@ class Jp2k(Jp2kBox):
If glymur is unable to load the openjp2 library.
"""
if opj2.OPENJP2 is not None:
self._write_openjp2(img_array, verbose=verbose, **kwargs)
self._write_openjp2(img_array, verbose=verbose, **kwargs)
elif opj.OPENJPEG is not None:
self._write_openjpeg(img_array, verbose=verbose, **kwargs)
else:
@ -606,7 +606,12 @@ class Jp2k(Jp2kBox):
self.parse()
def wrap(self, filename, boxes=None):
"""Write the codestream back out to file, wrapped in new JP2 jacket.
"""Create a new JP2/JPX file wrapped in a new set of JP2 boxes.
This method is primarily aimed at wrapping a raw codestream in a set of
of JP2 boxes (turning it into a JP2 file instead of just a raw
codestream), or rewrapping a codestream in a JP2 file in a new "jacket"
of JP2 boxes.
Parameters
----------
@ -616,6 +621,8 @@ class Jp2k(Jp2kBox):
JP2 box definitions to define the JP2 file format. If not
provided, a default ""jacket" is assumed, consisting of JP2
signature, file type, JP2 header, and contiguous codestream boxes.
A JPX file rewrapped without the boxes argument results in a JP2
file encompassing the first codestream.
Returns
-------
@ -631,19 +638,7 @@ class Jp2k(Jp2kBox):
>>> jp2 = j2k.wrap(tfile.name)
"""
if boxes is None:
# Try to create a reasonable default.
boxes = [JPEG2000SignatureBox(),
FileTypeBox(),
JP2HeaderBox(),
ContiguousCodestreamBox()]
codestream = self.get_codestream()
height = codestream.segment[1].ysiz
width = codestream.segment[1].xsiz
num_components = len(codestream.segment[1].xrsiz)
boxes[2].box = [ImageHeaderBox(height=height,
width=width,
num_components=num_components),
ColourSpecificationBox(colorspace=SRGB)]
boxes = self._get_default_jp2_boxes()
_validate_jp2_box_sequence(boxes)
@ -652,34 +647,92 @@ class Jp2k(Jp2kBox):
if box.box_id != 'jp2c':
box.write(ofile)
else:
# The codestream gets written last.
if len(self.box) == 0:
# Am I a raw codestream? If so, then it is pretty
# easy, just write the codestream box header plus all
# of myself out to file.
ofile.write(struct.pack('>I', self.length + 8))
ofile.write('jp2c'.encode())
with open(self.filename, 'rb') as ifile:
ofile.write(ifile.read())
else:
# OK, I'm a jp2 file. Need to find out where the
# raw codestream actually starts.
jp2c = [box for box in self.box
if box.box_id == 'jp2c']
jp2c = jp2c[0]
ofile.write(struct.pack('>I', jp2c.length))
ofile.write('jp2c'.encode())
with open(self.filename, 'rb') as ifile:
# Seek 8 bytes past the L, T fields to get to the
# raw codestream.
ifile.seek(jp2c.offset + 8)
ofile.write(ifile.read(jp2c.length - 8))
self._write_wrapped_codestream(ofile, box)
ofile.flush()
jp2 = Jp2k(filename)
return jp2
def _write_wrapped_codestream(self, ofile, box):
"""Write wrapped codestream."""
# Codestreams require a bit more care.
# Am I a raw codestream?
if len(self.box) == 0:
# Yes, just write the codestream box header plus all
# of myself out to file.
ofile.write(struct.pack('>I', self.length + 8))
ofile.write(b'jp2c')
with open(self.filename, 'rb') as ifile:
ofile.write(ifile.read())
return
# OK, I'm a jp2/jpx file. Need to find out where the raw codestream
# actually starts.
offset = box.offset
if offset == -1:
if self.box[1].brand == 'jpx ':
msg = "The codestream box must have its offset and "
msg += "length attributes fully specified if the file "
msg += "type brand is JPX."
raise IOError(msg)
# Find the first codestream in the file.
jp2c = [box for box in self.box if box.box_id == 'jp2c']
offset = jp2c[0].offset
# Ready to write the codestream.
with open(self.filename, 'rb') as ifile:
ifile.seek(offset)
# Verify that the specified codestream is right.
read_buffer = ifile.read(8)
L, T = struct.unpack_from('>I4s', read_buffer, 0)
if T != b'jp2c':
msg = "Unable to locate the specified codestream."
raise IOError(msg)
if L == 0:
# The length of the box is presumed to last until the end of
# the file. Compute the effective length of the box.
L = os.path.getsize(ifile.name) - ifile.tell() + 8
elif L == 1:
# The length of the box is in the XL field, a 64-bit value.
read_buffer = ifile.read(8)
L, = struct.unpack('>Q', read_buffer)
ifile.seek(offset)
read_buffer = ifile.read(L)
ofile.write(read_buffer)
def _get_default_jp2_boxes(self):
"""Create a default set of JP2 boxes."""
# Try to create a reasonable default.
boxes = [JPEG2000SignatureBox(),
FileTypeBox(),
JP2HeaderBox(),
ContiguousCodestreamBox()]
codestream = self.get_codestream()
height = codestream.segment[1].ysiz
width = codestream.segment[1].xsiz
num_components = len(codestream.segment[1].xrsiz)
if num_components < 3:
colorspace = GREYSCALE
else:
if len(self.box) == 0:
# Best guess is SRGB
colorspace = SRGB
else:
# Take whatever the first jp2 header / color specification
# says.
jp2hs = [box for box in self.box if box.box_id == 'jp2h']
colorspace = jp2hs[0].box[1].colorspace
boxes[2].box = [ImageHeaderBox(height=height, width=width,
num_components=num_components),
ColourSpecificationBox(colorspace=colorspace)]
return boxes
def read(self, **kwargs):
"""Read a JPEG 2000 image.
@ -696,8 +749,9 @@ class Jp2k(Jp2kBox):
(first_row, first_col, last_row, last_col)
tile : int, optional
Number of tile to decode.
no_cxform : bool
Whether or not to apply intended color transforms.
ignore_pclr_cmap_cdef : bool
Whether or not to ignore the pclr, cmap, or cdef boxes during any
color transformation. Defaults to False.
verbose : bool, optional
Print informational messages produced by the OpenJPEG library.
@ -752,7 +806,8 @@ class Jp2k(Jp2kBox):
msg += "the read_bands method instead."
raise RuntimeError(msg)
def _read_openjpeg(self, rlevel=0, no_cxform=False, verbose=False):
def _read_openjpeg(self, rlevel=0, ignore_pclr_cmap_cdef=False,
verbose=False):
"""Read a JPEG 2000 image using libopenjpeg.
Parameters
@ -760,8 +815,9 @@ class Jp2k(Jp2kBox):
rlevel : int, optional
Factor by which to rlevel output resolution. Use -1 to get the
lowest resolution thumbnail.
no_cxform : bool
Whether or not to apply intended color transforms.
ignore_pclr_cmap_cdef : bool
Whether or not to ignore the pclr, cmap, or cdef boxes during any
color transformation. Defaults to False.
verbose : bool, optional
Print informational messages produced by the OpenJPEG library.
@ -786,9 +842,9 @@ class Jp2k(Jp2kBox):
# -1 is shorthand for the largest rlevel
rlevel = max_rlevel
elif rlevel < -1 or rlevel > max_rlevel:
msg = "rlevel must be in the range [-1, {0}] for this image."
msg = msg.format(max_rlevel)
raise IOError(msg)
msg = "rlevel must be in the range [-1, {0}] for this image."
msg = msg.format(max_rlevel)
raise IOError(msg)
with ExitStack() as stack:
try:
@ -797,7 +853,7 @@ class Jp2k(Jp2kBox):
dparameters = opj.DecompressionParametersType()
opj.set_default_decoder_parameters(ctypes.byref(dparameters))
if no_cxform is True:
if ignore_pclr_cmap_cdef is True:
# Return raw codestream components.
dparameters.flags |= 1
@ -845,7 +901,7 @@ class Jp2k(Jp2kBox):
return data
def _read_openjp2(self, rlevel=0, layer=0, area=None, tile=None,
verbose=False, no_cxform=False):
verbose=False, ignore_pclr_cmap_cdef=False):
"""Read a JPEG 2000 image using libopenjp2.
Parameters
@ -875,7 +931,8 @@ class Jp2k(Jp2kBox):
"""
self._subsampling_sanity_check()
dparam = self._populate_dparam(layer, rlevel, area, tile, no_cxform)
dparam = self._populate_dparam(layer, rlevel, area, tile,
ignore_pclr_cmap_cdef)
with ExitStack() as stack:
if hasattr(opj2.OPENJP2,
@ -919,7 +976,8 @@ class Jp2k(Jp2kBox):
return img_array
def _populate_dparam(self, layer, rlevel, area, tile, no_cxform):
def _populate_dparam(self, layer, rlevel, area, tile,
ignore_pclr_cmap_cdef):
"""Populate decompression structure with appropriate input parameters.
Parameters
@ -933,8 +991,9 @@ class Jp2k(Jp2kBox):
(first_row, first_col, last_row, last_col)
tile : int
Number of tile to decode.
no_cxform : bool
Whether or not to apply intended color transforms.
ignore_pclr_cmap_cdef : bool
Whether or not to ignore the pclr, cmap, or cdef boxes during any
color transformation. Defaults to False.
Returns
-------
@ -972,14 +1031,14 @@ class Jp2k(Jp2kBox):
dparam.tile_index = tile
dparam.nb_tile_to_decode = 1
if no_cxform is True:
if ignore_pclr_cmap_cdef is True:
# Return raw codestream components.
dparam.flags |= 1
return dparam
def read_bands(self, rlevel=0, layer=0, area=None, tile=None,
verbose=False, no_cxform=False):
verbose=False, ignore_pclr_cmap_cdef=False):
"""Read a JPEG 2000 image.
The only time you should use this method is when the image has
@ -997,8 +1056,9 @@ class Jp2k(Jp2kBox):
(first_row, first_col, last_row, last_col)
tile : int, optional
Number of tile to decode.
no_cxform : bool
Whether or not to apply intended color transforms.
ignore_pclr_cmap_cdef : bool
Whether or not to ignore the pclr, cmap, or cdef boxes during any
color transformation. Defaults to False.
verbose : bool, optional
Print informational messages produced by the OpenJPEG library.
@ -1028,7 +1088,7 @@ class Jp2k(Jp2kBox):
"of OpenJP2 installed before using "
"this functionality.")
dparam = self._populate_dparam(layer, rlevel, area, tile, no_cxform)
dparam = self._populate_dparam(layer, rlevel, area, tile, ignore_pclr_cmap_cdef)
with ExitStack() as stack:
if hasattr(opj2.OPENJP2,
@ -1090,7 +1150,7 @@ class Jp2k(Jp2kBox):
>>> codestream = jp2.get_codestream()
>>> print(codestream.segment[1])
SIZ marker segment @ (3233, 47)
Profile: 2
Profile: no profile
Reference Grid Height, Width: (1456 x 2592)
Vertical, Horizontal Reference Grid Offset: (0 x 0)
Reference Tile Height, Width: (1456 x 2592)
@ -1190,12 +1250,26 @@ def _validate_jp2_box_sequence(boxes):
if boxes[1].brand == 'jpx ':
_validate_jpx_box_sequence(boxes)
else:
# Validate the JP2 box IDs.
count = _collect_box_count(boxes)
for id in count.keys():
if id not in JP2_IDS:
for box_id in count.keys():
if box_id not in JP2_IDS:
msg = "The presence of a '{0}' box requires that the file type "
msg += "brand be set to 'jpx '."
raise IOError(msg.format(id))
raise IOError(msg.format(box_id))
_validate_jp2_colr(boxes)
def _validate_jp2_colr(boxes):
"""
Validate JP2 requirements on colour specification boxes.
"""
lst = [box for box in boxes if box.box_id == 'jp2h']
jp2h = lst[0]
for colr in [box for box in jp2h.box if box.box_id == 'colr']:
if colr.approximation != 0:
msg = "A JP2 colr box cannot have a non-zero approximation field."
raise IOError(msg)
def _validate_jpx_box_sequence(boxes):
"""Run through series of tests for JPX box legality."""
@ -1296,7 +1370,7 @@ def _check_jp2h_child_boxes(boxes, parent_box_name):
"""Certain boxes can only reside in the JP2 header."""
box_ids = set([box.box_id for box in boxes])
intersection = box_ids.intersection(JP2H_CHILDREN)
if len(intersection) > 0 and parent_box_name != 'jp2h':
if len(intersection) > 0 and parent_box_name not in ['jp2h', 'jpch']:
msg = "A '{0}' box can only be nested in a JP2 header box."
raise IOError(msg.format(list(intersection)[0]))

View file

@ -412,7 +412,7 @@ Contiguous Codestream Box (jp2c) @ (3223, 1132296)
Main header:
SOC marker segment @ (3231, 0)
SIZ marker segment @ (3233, 47)
Profile: 2
Profile: no profile
Reference Grid Height, Width: (1456 x 2592)
Vertical, Horizontal Reference Grid Offset: (0 x 0)
Reference Tile Height, Width: (1456 x 2592)
@ -477,7 +477,7 @@ Contiguous Codestream Box (jp2c) @ (3223, 1132296)
Main header:
SOC marker segment @ (3231, 0)
SIZ marker segment @ (3233, 47)
Profile: 2
Profile: no profile
Reference Grid Height, Width: (1456 x 2592)
Vertical, Horizontal Reference Grid Offset: (0 x 0)
Reference Tile Height, Width: (1456 x 2592)
@ -580,8 +580,9 @@ file1_xml = """XML Box (xml ) @ (36, 439)
issue_182_cmap = """Component Mapping Box (cmap) @ (130, 24)
Component 0 ==> palette column 0
Component 1 ==> palette column 0
Component 2 ==> 2"""
Component 0 ==> palette column 1
Component 0 ==> palette column 2
Component 0 ==> palette column 3"""
issue_183_colr = """Colour Specification Box (colr) @ (62, 12)
Method: restricted ICC profile
@ -610,3 +611,14 @@ issue_186_progression_order = """COD marker segment @ (174, 12)
Vertically stripe causal context: False
Predictable termination: False
Segmentation symbols: False"""
# Cinema 2K profile
cinema2k_profile = """SIZ marker segment @ (2, 47)
Profile: Cinema 2K
Reference Grid Height, Width: (1080 x 1920)
Vertical, Horizontal Reference Grid Offset: (0 x 0)
Reference Tile Height, Width: (1080 x 1920)
Vertical, Horizontal Reference Tile Offset: (0 x 0)
Bitdepth: (12, 12, 12)
Signed: (False, False, False)
Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1))"""

View file

@ -51,6 +51,28 @@ class TestCodestreamOpjData(unittest.TestCase):
def tearDown(self):
pass
def test_bad_rsiz(self):
"""Should warn if RSIZ is bad. Issue196"""
filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2')
if sys.hexversion < 0x03000000:
with warnings.catch_warnings():
warnings.simplefilter("ignore")
j = Jp2k(filename)
else:
with self.assertWarns(UserWarning):
j = Jp2k(filename)
def test_bad_wavelet_transform(self):
"""Should warn if wavelet transform is bad. Issue195"""
filename = opj_data_file('input/nonregression/edf_c2_10025.jp2')
if sys.hexversion < 0x03000000:
with warnings.catch_warnings():
warnings.simplefilter("ignore")
j = Jp2k(filename)
else:
with self.assertWarns(UserWarning):
j = Jp2k(filename)
def test_invalid_progression_order(self):
"""Should still be able to parse even if prog order is invalid."""
jfile = opj_data_file('input/nonregression/2977.pdf.asan.67.2198.jp2')

View file

@ -9,6 +9,7 @@ import datetime
import os
import sys
import unittest
import warnings
import numpy as np
@ -30,7 +31,10 @@ class TestICC(unittest.TestCase):
def test_file5(self):
"""basic ICC profile"""
filename = opj_data_file('input/conformance/file5.jp2')
j = Jp2k(filename)
with warnings.catch_warnings():
# The file has a bad compatibility list entry. Not important here.
warnings.simplefilter("ignore")
j = Jp2k(filename)
profile = j.box[2].box[1].icc_profile
self.assertEqual(profile['Size'], 546)
self.assertEqual(profile['Preferred CMM Type'], 0)

View file

@ -58,6 +58,23 @@ class TestDataEntryURL(unittest.TestCase):
def setUp(self):
self.jp2file = glymur.data.nemo()
def test_wrap_greyscale(self):
"""A single component should be wrapped as GREYSCALE."""
j = Jp2k(self.jp2file)
data = j.read()
red = data[:, :, 0]
# Write it back out as a raw codestream.
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile1:
j2k = glymur.Jp2k(tfile1.name, 'wb')
j2k.write(data[:, :, 0])
# Ok, now rewrap it as JP2. The colorspace should be GREYSCALE.
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile2:
jp2 = j2k.wrap(tfile2.name)
self.assertEqual(jp2.box[2].box[1].colorspace,
glymur.core.GREYSCALE)
def test_basic_url(self):
"""Just your most basic URL box."""
# Wrap our j2k file in a JP2 box along with an interior url box.
@ -340,6 +357,7 @@ class TestChannelDefinition(unittest.TestCase):
with self.assertRaises((IOError, OSError)):
j2k.wrap(tfile.name, boxes=boxes)
@unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.")
def test_bad_type(self):
"""Channel types are limited to 0, 1, 2, 65535
Should reject if not all of index, channel_type, association the
@ -347,17 +365,18 @@ class TestChannelDefinition(unittest.TestCase):
"""
channel_type = (COLOR, COLOR, 3)
association = (RED, GREEN, BLUE)
with self.assertRaises(IOError):
with self.assertWarns(UserWarning):
glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
association=association)
@unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.")
def test_wrong_lengths(self):
"""Should reject if not all of index, channel_type, association the
same length.
"""
channel_type = (COLOR, COLOR)
association = (RED, GREEN, BLUE)
with self.assertRaises(IOError):
with self.assertWarns(UserWarning):
glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
association=association)
@ -373,14 +392,19 @@ class TestFileTypeBox(unittest.TestCase):
def test_brand_unknown(self):
"""A ftyp box brand must be 'jp2 ' or 'jpx '."""
ftyp = glymur.jp2box.FileTypeBox(brand='jp3')
with warnings.catch_warnings():
warnings.simplefilter("ignore")
ftyp = glymur.jp2box.FileTypeBox(brand='jp3')
with self.assertRaises(IOError):
with tempfile.TemporaryFile() as tfile:
ftyp.write(tfile)
def test_cl_entry_unknown(self):
"""A ftyp box cl list can only contain 'jp2 ', 'jpx ', or 'jpxb'."""
ftyp = glymur.jp2box.FileTypeBox(compatibility_list=['jp3'])
with warnings.catch_warnings():
# Bad compatibility list item.
warnings.simplefilter("ignore")
ftyp = glymur.jp2box.FileTypeBox(compatibility_list=['jp3'])
with self.assertRaises(IOError):
with tempfile.TemporaryFile() as tfile:
ftyp.write(tfile)
@ -429,6 +453,18 @@ class TestColourSpecificationBox(unittest.TestCase):
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_bad_approx_jp2_field(self):
"""JP2 has requirements for approx field"""
j2k = Jp2k(self.j2kfile)
boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
colr = ColourSpecificationBox(colorspace=glymur.core.SRGB,
approximation=1)
boxes[2].box = [self.ihdr, colr]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
def test_default_colr(self):
"""basic colr instantiation"""
colr = ColourSpecificationBox(colorspace=glymur.core.SRGB)
@ -438,27 +474,30 @@ class TestColourSpecificationBox(unittest.TestCase):
self.assertEqual(colr.colorspace, glymur.core.SRGB)
self.assertIsNone(colr.icc_profile)
@unittest.skipIf(sys.hexversion < 0x03030000, "Requires 3.3+")
def test_colr_with_cspace_and_icc(self):
"""Colour specification boxes can't have both."""
with self.assertRaises((OSError, IOError)):
with self.assertWarns(UserWarning):
colorspace = glymur.core.SRGB
rawb = b'\x01\x02\x03\x04'
glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
icc_profile=rawb)
@unittest.skipIf(sys.hexversion < 0x03030000, "Requires 3.3+")
def test_colr_with_bad_method(self):
"""colr must have a valid method field"""
colorspace = glymur.core.SRGB
method = -1
with self.assertRaises(IOError):
with self.assertWarns(UserWarning):
glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
method=method)
@unittest.skipIf(sys.hexversion < 0x03030000, "Requires 3.3+")
def test_colr_with_bad_approx(self):
"""colr must have a valid approximation field"""
"""colr should have a valid approximation field"""
colorspace = glymur.core.SRGB
approx = -1
with self.assertRaises(IOError):
with self.assertWarns(UserWarning):
glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
approximation=approx)
@ -484,21 +523,23 @@ class TestPaletteBox(unittest.TestCase):
def tearDown(self):
pass
@unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.")
def test_mismatched_bitdepth_signed(self):
"""bitdepth and signed arguments must have equal length"""
palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint8)
bps = (8, 8, 8)
signed = (False, False)
with self.assertRaises(IOError):
with self.assertWarns(UserWarning):
pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps,
signed=signed)
@unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.")
def test_mismatched_signed_palette(self):
"""bitdepth and signed arguments must have equal length"""
palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint8)
bps = (8, 8, 8, 8)
signed = (False, False, False, False)
with self.assertRaises(IOError):
with self.assertWarns(UserWarning):
pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps,
signed=signed)
@ -666,8 +707,9 @@ class TestWrap(unittest.TestCase):
def test_jpx_to_jp2(self):
"""basic test for rewrapping a jpx file"""
jpx = Jp2k(self.jpxfile)
idx = [0, 1, 3, 6]
boxes = [jpx.box[idx] for idx in [0, 1, 3, 6]]
# Use only the signature, file type, header, and 1st codestream.
lst = [0, 1, 2, 5]
boxes = [jpx.box[idx] for idx in lst]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
jp2 = jpx.wrap(tfile.name, boxes=boxes)
@ -828,6 +870,67 @@ class TestWrap(unittest.TestCase):
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
def test_wrap_jpx_to_jp2_with_unadorned_jpch(self):
"""A JPX file rewrapped with plain jpch is not allowed."""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1:
jpx = Jp2k(self.jpxfile)
boxes = [jpx.box[0], jpx.box[1], jpx.box[2],
glymur.jp2box.ContiguousCodestreamBox()]
with self.assertRaises(IOError):
jpx.wrap(tfile1.name, boxes=boxes)
def test_wrap_jpx_to_jp2_with_incorrect_jp2c_offset(self):
"""Reject A JPX file rewrapped with bad jp2c offset."""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1:
jpx = Jp2k(self.jpxfile)
jpch = jpx.box[5]
# The offset should be 902.
jpch.offset = 901
jpch.length = 313274
boxes = [jpx.box[0], jpx.box[1], jpx.box[2], jpch]
with self.assertRaises(IOError):
jpx.wrap(tfile1.name, boxes=boxes)
def test_wrap_jpx_to_jp2_with_correctly_specified_jp2c(self):
"""Accept A JPX file rewrapped with good jp2c."""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1:
jpx = Jp2k(self.jpxfile)
jpch = jpx.box[5]
# This time get it right.
jpch.offset = 903
jpch.length = 313274
boxes = [jpx.box[0], jpx.box[1], jpx.box[2], jpch]
jp2 = jpx.wrap(tfile1.name, boxes=boxes)
act_ids = [box.box_id for box in jp2.box]
exp_ids = ['jP ', 'ftyp', 'jp2h', 'jp2c']
self.assertEqual(act_ids, exp_ids)
act_offsets = [box.offset for box in jp2.box]
exp_offsets = [0, 12, 40, 887]
self.assertEqual(act_offsets, exp_offsets)
act_lengths = [box.length for box in jp2.box]
exp_lengths = [12, 28, 847, 313274]
self.assertEqual(act_lengths, exp_lengths)
def test_full_blown_jpx(self):
"""Rewrap a jpx file."""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1:
jpx = Jp2k(self.jpxfile)
idx = list(range(5)) + list(range(9, 12)) + list(range(6, 9)) + [12]
boxes = [jpx.box[j] for j in idx]
jpx2 = jpx.wrap(tfile1.name, boxes=boxes)
exp_ids = [box.box_id for box in boxes]
lengths = [box.length for box in jpx.box]
exp_lengths = [lengths[j] for j in idx]
act_ids = [box.box_id for box in jpx2.box]
act_lengths = [box.length for box in jpx2.box]
self.assertEqual(exp_ids, act_ids)
self.assertEqual(exp_lengths, act_lengths)
class TestJp2Boxes(unittest.TestCase):
"""Tests for canonical JP2 boxes."""

View file

@ -8,18 +8,22 @@ import struct
import sys
import tempfile
import unittest
import warnings
import lxml.etree as ET
import glymur
from glymur import Jp2k
from glymur.jp2box import DataEntryURLBox, FileTypeBox, JPEG2000SignatureBox
from glymur.jp2box import DataReferenceBox, FragmentListBox, FragmentTableBox
from glymur.jp2box import ColourSpecificationBox
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
class TestJPXWrap(unittest.TestCase):
"""Test suite for wrapping JPX files."""
def setUp(self):
self.jpxfile = glymur.data.jpxfile()
self.jp2file = glymur.data.nemo()
self.j2kfile = glymur.data.goodstuff()
@ -107,6 +111,68 @@ class TestJPXWrap(unittest.TestCase):
with self.assertRaises(IOError):
jp2.wrap(tfile.name, boxes=boxes)
def test_jpch_jplh(self):
"""Write a codestream header, compositing layer header box."""
jp2 = Jp2k(self.jp2file)
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
# The ftyp box must be modified to jpx.
boxes[1].brand = 'jpx '
boxes[1].compatibility_list = ['jp2 ', 'jpxb']
jpch = glymur.jp2box.CodestreamHeaderBox()
boxes.append(jpch)
jplh = glymur.jp2box.CompositingLayerHeaderBox()
boxes.append(jplh)
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
jpx = jp2.wrap(tfile.name, boxes=boxes)
self.assertEqual(jpx.box[-2].box_id, 'jpch')
self.assertEqual(jpx.box[-1].box_id, 'jplh')
def test_cgrp(self):
"""Write a color group box."""
jp2 = Jp2k(self.jp2file)
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
# The ftyp box must be modified to jpx.
boxes[1].brand = 'jpx '
boxes[1].compatibility_list = ['jp2 ', 'jpxb']
colr_rgb = ColourSpecificationBox(colorspace=glymur.core.SRGB)
colr_gr = ColourSpecificationBox(colorspace=glymur.core.GREYSCALE)
box = [colr_rgb, colr_gr]
cgrp = glymur.jp2box.ColourGroupBox(box=box)
boxes.append(cgrp)
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
jpx = jp2.wrap(tfile.name, boxes=boxes)
self.assertEqual(jpx.box[-1].box_id, 'cgrp')
self.assertEqual(jpx.box[-1].box[0].box_id, 'colr')
self.assertEqual(jpx.box[-1].box[1].box_id, 'colr')
def test_cgrp_neg(self):
"""Can't write a cgrp with anything but colr sub boxes"""
jp2 = Jp2k(self.jp2file)
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
# The ftyp box must be modified to jpx.
boxes[1].brand = 'jpx '
boxes[1].compatibility_list = ['jp2 ', 'jpxb']
lblb = glymur.jp2box.LabelBox("Just a test")
box = [lblb]
cgrp = glymur.jp2box.ColourGroupBox(box=box)
boxes.append(cgrp)
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
with self.assertRaises(IOError):
jpx = jp2.wrap(tfile.name, boxes=boxes)
def test_ftbl(self):
"""Write a fragment table box."""
# Add a negative test where offset < 0
@ -207,13 +273,14 @@ class TestJPXWrap(unittest.TestCase):
with self.assertRaises(IOError):
jp2.wrap(tfile.name, boxes=boxes)
@unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.")
def test_deurl_child_of_dtbl(self):
"""Data reference boxes can only contain data entry url boxes."""
jp2 = Jp2k(self.jp2file)
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
ftyp = glymur.jp2box.FileTypeBox()
with self.assertRaises(IOError):
with self.assertWarns(UserWarning):
dref = glymur.jp2box.DataReferenceBox([ftyp])
# Try to get around it by appending the ftyp box after creation.
@ -337,7 +404,9 @@ class TestJPX(unittest.TestCase):
offset = [89]
length = [1132288]
reference = [0, 0]
flst = glymur.jp2box.FragmentListBox(offset, length, reference)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
flst = glymur.jp2box.FragmentListBox(offset, length, reference)
with self.assertRaises(IOError):
with tempfile.TemporaryFile() as tfile:
flst.write(tfile)
@ -347,8 +416,10 @@ class TestJPX(unittest.TestCase):
offset = [0]
length = [1132288]
reference = [0]
flst = glymur.jp2box.FragmentListBox(offset, length, reference)
with self.assertRaises(IOError):
with warnings.catch_warnings():
warnings.simplefilter("ignore")
flst = glymur.jp2box.FragmentListBox(offset, length, reference)
with self.assertRaises((IOError, OSError)):
with tempfile.TemporaryFile() as tfile:
flst.write(tfile)
@ -357,14 +428,18 @@ class TestJPX(unittest.TestCase):
offset = [89]
length = [0]
reference = [0]
flst = glymur.jp2box.FragmentListBox(offset, length, reference)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
flst = glymur.jp2box.FragmentListBox(offset, length, reference)
with self.assertRaises(IOError):
with tempfile.TemporaryFile() as tfile:
flst.write(tfile)
def test_ftbl_boxes_empty(self):
"""A fragment table box must have at least one child box."""
ftbl = glymur.jp2box.FragmentTableBox()
with warnings.catch_warnings():
warnings.simplefilter("ignore")
ftbl = glymur.jp2box.FragmentTableBox()
with self.assertRaises(IOError):
with tempfile.TemporaryFile() as tfile:
ftbl.write(tfile)
@ -377,6 +452,7 @@ class TestJPX(unittest.TestCase):
with tempfile.TemporaryFile() as tfile:
ftbl.write(tfile)
@unittest.skip("No such jpx file anymore.")
def test_jpx_rreq_mask_length_3(self):
"""There are some JPX files with rreq mask length of 3."""
jpx = Jp2k(self.jpxfile)
@ -386,7 +462,7 @@ class TestJPX(unittest.TestCase):
self.assertEqual(jpx.box[2].standard_flag,
(5, 42, 45, 2, 18, 19, 1, 8, 12, 31, 20))
@unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.")
@unittest.skip("Requires unnecessarily complicated code")
def test_unknown_superbox(self):
"""Verify that we can handle an unknown superbox."""
with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile:
@ -402,15 +478,9 @@ class TestJPX(unittest.TestCase):
with self.assertWarns(UserWarning):
jpx = Jp2k(tfile.name)
self.assertEqual(jpx.box[-1].box_id, 'grp ')
self.assertEqual(jpx.box[-1].box_id, b'grp ')
self.assertEqual(jpx.box[-1].box[0].box_id, 'free')
def test_free_box(self):
"""Verify that we can handle a free box."""
j = Jp2k(self.jpxfile)
self.assertEqual(j.box[16].box[0].box_id, 'free')
self.assertEqual(type(j.box[16].box[0]), glymur.jp2box.FreeBox)
def test_data_reference_requires_dtbl(self):
"""The existance of a data reference box requires a ftbl box as well."""
flag = 0
@ -483,17 +553,17 @@ class TestJPX(unittest.TestCase):
self.assertEqual(jpx.box[-1].box[0].data_reference, (3,))
def test_nlst(self):
"""Verify that we can handle a free box."""
"""Verify that we can handle a number list box."""
j = Jp2k(self.jpxfile)
self.assertEqual(j.box[16].box[1].box[0].box_id, 'nlst')
self.assertEqual(type(j.box[16].box[1].box[0]),
glymur.jp2box.NumberListBox)
nlst = j.box[12].box[0].box[0]
self.assertEqual(nlst.box_id, 'nlst')
self.assertEqual(type(nlst), glymur.jp2box.NumberListBox)
# Two associations.
self.assertEqual(len(j.box[16].box[1].box[0].associations), 2)
self.assertEqual(len(nlst.associations), 2)
# Codestream 0
self.assertEqual(j.box[16].box[1].box[0].associations[0], 1 << 24)
self.assertEqual(nlst.associations[0], 1 << 24)
# Compositing Layer 0
self.assertEqual(j.box[16].box[1].box[0].associations[1], 2 << 24)
self.assertEqual(nlst.associations[1], 2 << 24)

View file

@ -63,6 +63,24 @@ class TestJp2k(unittest.TestCase):
def tearDown(self):
pass
def test_no_cxform_pclr_jpx(self):
"""Indices for pclr jpxfile if no color transform"""
j = Jp2k(self.jpxfile)
rgb = j.read()
idx = j.read(ignore_pclr_cmap_cdef=True)
nr, nc = 1024, 1024
self.assertEqual(rgb.shape, (nr, nc, 3))
self.assertEqual(idx.shape, (nr, nc))
# Should be able to manually reconstruct the RGB image from the palette
# and indices.
palette = j.box[2].box[2].palette
rgb_from_idx = np.zeros(rgb.shape, dtype=np.uint8)
for r in np.arange(nr):
for c in np.arange(nc):
rgb_from_idx[r, c] = palette[idx[r, c]]
np.testing.assert_array_equal(rgb, rgb_from_idx)
def test_repr(self):
"""Verify that results of __repr__ are eval-able."""
j = Jp2k(self.j2kfile)
@ -92,23 +110,6 @@ class TestJp2k(unittest.TestCase):
with self.assertRaises(IOError):
Jp2k(filename)
def test_no_cxform_pclr_jpx(self):
"""Indices for pclr jpxfile if no color transform"""
j = Jp2k(self.jpxfile)
rgb = j.read()
idx = j.read(no_cxform=True)
self.assertEqual(rgb.shape, (1024, 1024, 3))
self.assertEqual(idx.shape, (1024, 1024))
# Should be able to manually reconstruct the RGB image from the palette
# and indices.
palette = j.box[3].box[2].palette
rgb_from_idx = np.zeros(rgb.shape, dtype=np.uint8)
for r in np.arange(1024):
for c in np.arange(1024):
rgb_from_idx[r, c] = palette[idx[r, c]]
np.testing.assert_array_equal(rgb, rgb_from_idx)
def test_file_not_present(self):
"""Should error out if reading from a file that does not exist"""
# Verify that we error out appropriately if not given an existing file
@ -758,12 +759,48 @@ class TestJp2k_2_1(unittest.TestCase):
class TestJp2kOpjDataRoot(unittest.TestCase):
"""These tests should be run by just about all configuration."""
def test_undecodeable_box_id(self):
"""Should warn in case of undecodeable box ID but not error out."""
filename = opj_data_file('input/nonregression/edf_c2_1013627.jp2')
if sys.hexversion < 0x03000000:
with warnings.catch_warnings():
warnings.simplefilter("ignore")
jp2 = Jp2k(filename)
else:
with self.assertWarns(UserWarning):
jp2 = Jp2k(filename)
# Now make sure we got all of the boxes. Ignore the last, which was
# bad.
box_ids = [box.box_id for box in jp2.box[:-1]]
self.assertEqual(box_ids, ['jP ', 'ftyp', 'jp2h', 'jp2c'])
def test_invalid_approximation(self):
"""Should warn in case of bad ftyp brand."""
filename = opj_data_file('input/nonregression/edf_c2_1000290.jp2')
with self.assertWarns(UserWarning):
jp2 = Jp2k(filename)
@unittest.skipIf(sys.hexversion < 0x03000000, "Test requires Python 3.3+")
def test_invalid_approximation(self):
"""Should warn in case of invalid approximation."""
filename = opj_data_file('input/nonregression/edf_c2_1015644.jp2')
with self.assertWarns(UserWarning):
jp2 = Jp2k(filename)
@unittest.skipIf(sys.hexversion < 0x03000000, "Test requires Python 3.3+")
def test_invalid_colorspace(self):
"""Should warn in case of invalid colorspace."""
filename = opj_data_file('input/nonregression/edf_c2_1103421.jp2')
with self.assertWarns(UserWarning):
jp2 = Jp2k(filename)
def test_no_cxform_pclr_jp2(self):
"""Indices for pclr jpxfile if no color transform"""
filename = opj_data_file('input/conformance/file9.jp2')
j = Jp2k(filename)
rgb = j.read()
idx = j.read(no_cxform=True)
idx = j.read(ignore_pclr_cmap_cdef=True)
self.assertEqual(rgb.shape, (512, 768, 3))
self.assertEqual(idx.shape, (512, 768))
@ -803,9 +840,12 @@ class TestJp2kOpjDataRoot(unittest.TestCase):
# This file has the components physically reversed. The cmap box
# tells the decoder how to order them, but this flag prevents that.
filename = opj_data_file('input/conformance/file2.jp2')
j = Jp2k(filename)
with warnings.catch_warnings():
# The file has a bad compatibility list entry. Not important here.
warnings.simplefilter("ignore")
j = Jp2k(filename)
ycbcr = j.read()
crcby = j.read(no_cxform=True)
crcby = j.read(ignore_pclr_cmap_cdef=True)
expected = np.zeros(ycbcr.shape, ycbcr.dtype)
for k in range(crcby.shape[2]):

View file

@ -327,7 +327,10 @@ class TestSuite(unittest.TestCase):
def test_ETS_JP2_file1(self):
jfile = opj_data_file('input/conformance/file1.jp2')
jp2k = Jp2k(jfile)
with warnings.catch_warnings():
# Bad compatibility list item.
warnings.simplefilter("ignore")
jp2k = Jp2k(jfile)
jpdata = jp2k.read()
self.assertEqual(jpdata.shape, (512, 768, 3))
@ -3114,7 +3117,10 @@ class TestSuiteDump(unittest.TestCase):
def test_NR_file1_dump(self):
jfile = opj_data_file('input/conformance/file1.jp2')
jp2 = Jp2k(jfile)
with warnings.catch_warnings():
# Bad compatibility list item.
warnings.simplefilter("ignore")
jp2 = Jp2k(jfile)
ids = [box.box_id for box in jp2.box]
self.assertEqual(ids, ['jP ', 'ftyp', 'xml ', 'jp2h', 'xml ',
@ -3441,7 +3447,7 @@ class TestSuiteDump(unittest.TestCase):
# Image header
self.assertEqual(jp2.box[2].box[0].height, 400)
self.assertEqual(jp2.box[2].box[0].width, 700)
self.assertEqual(jp2.box[2].box[0].num_components, 1)
self.assertEqual(jp2.box[2].box[0].num_components, 3)
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
self.assertEqual(jp2.box[2].box[0].signed, False)
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
@ -5476,7 +5482,7 @@ class TestSuiteDump(unittest.TestCase):
jp2 = Jp2k(jfile)
ids = [box.box_id for box in jp2.box]
self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'XML ', 'jp2c'])
self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', b'XML ', 'jp2c'])
ids = [box.box_id for box in jp2.box[2].box]
self.assertEqual(ids, ['ihdr', 'colr'])
@ -5830,9 +5836,9 @@ class TestSuiteDump(unittest.TestCase):
# Jp2 Header
# Component mapping box
self.assertEqual(jp2.box[3].box[3].component_index, (0, 1, 2))
self.assertEqual(jp2.box[3].box[3].mapping_type, (1, 1, 0))
self.assertEqual(jp2.box[3].box[3].palette_index, (0, 0, 1))
self.assertEqual(jp2.box[3].box[3].component_index, (0, 0, 0, 0))
self.assertEqual(jp2.box[3].box[3].mapping_type, (1, 1, 1, 1))
self.assertEqual(jp2.box[3].box[3].palette_index, (0, 1, 2, 3))
c = jp2.box[4].main_header

View file

@ -50,6 +50,14 @@ class TestPrinting(unittest.TestCase):
def tearDown(self):
pass
def test_version_info(self):
"""Should be able to print(glymur.version.info)"""
with patch('sys.stdout', new=StringIO()) as fake_out:
print(glymur.version.info)
actual = fake_out.getvalue().strip()
self.assertTrue(True)
@unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.")
def test_unknown_superbox(self):
"""Verify that we can handle an unknown superbox."""
@ -60,6 +68,9 @@ class TestPrinting(unittest.TestCase):
# Add the header for an unknwon superbox.
write_buffer = struct.pack('>I4s', 20, 'grp '.encode())
tfile.write(write_buffer)
# Add a free box inside of it. We won't be able to identify it,
# but it's there.
write_buffer = struct.pack('>I4sI', 12, 'free'.encode(), 0)
tfile.write(write_buffer)
tfile.flush()
@ -70,8 +81,7 @@ class TestPrinting(unittest.TestCase):
with patch('sys.stdout', new=StringIO()) as fake_out:
print(jpx.box[-1])
actual = fake_out.getvalue().strip()
lines = ['Unknown Box (grp ) @ (695609, 20)',
' Free Box (free) @ (695617, 12)']
lines = ["Unknown Box (b'grp ') @ (1399071, 20)"]
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@ -341,7 +351,7 @@ class TestPrinting(unittest.TestCase):
actual = fake_out.getvalue().strip()
lines = ['SIZ marker segment @ (3233, 47)',
' Profile: 2',
' Profile: no profile',
' Reference Grid Height, Width: (1456 x 2592)',
' Vertical, Horizontal Reference Grid Offset: (0 x 0)',
' Reference Tile Height, Width: (1456 x 2592)',
@ -414,7 +424,7 @@ class TestPrinting(unittest.TestCase):
lst = ['Codestream:',
' SOC marker segment @ (3231, 0)',
' SIZ marker segment @ (3233, 47)',
' Profile: 2',
' Profile: no profile',
' Reference Grid Height, Width: (1456 x 2592)',
' Vertical, Horizontal Reference Grid Offset: (0 x 0)',
' Reference Tile Height, Width: (1456 x 2592)',
@ -645,6 +655,43 @@ class TestPrintingOpjDataRoot(unittest.TestCase):
def tearDown(self):
pass
def test_cinema_profile(self):
"""Should print Cinema 2K when the profile is 3."""
filename = opj_data_file('input/nonregression/_00042.j2k')
j2k = Jp2k(filename)
with patch('sys.stdout', new=StringIO()) as fake_out:
c = j2k.get_codestream()
print(c.segment[1])
actual = fake_out.getvalue().strip()
self.assertEqual(actual, fixtures.cinema2k_profile)
def test_invalid_colorspace(self):
"""An invalid colorspace shouldn't cause an error."""
filename = opj_data_file('input/nonregression/edf_c2_1103421.jp2')
with warnings.catch_warnings():
warnings.simplefilter("ignore")
jp2 = Jp2k(filename)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(jp2)
def test_bad_rsiz(self):
"""Should still be able to print if rsiz is bad, issue196"""
filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2')
with warnings.catch_warnings():
warnings.simplefilter("ignore")
j = Jp2k(filename)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j)
def test_bad_wavelet_transform(self):
"""Should still be able to print if wavelet xform is bad, issue195"""
filename = opj_data_file('input/nonregression/edf_c2_10025.jp2')
with warnings.catch_warnings():
warnings.simplefilter("ignore")
j = Jp2k(filename)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j)
def test_invalid_progression_order(self):
"""Should still be able to print even if prog order is invalid."""
jfile = opj_data_file('input/nonregression/2977.pdf.asan.67.2198.jp2')
@ -818,7 +865,10 @@ class TestPrintingOpjDataRoot(unittest.TestCase):
def test_channel_definition(self):
"""verify printing of cdef box"""
filename = opj_data_file('input/conformance/file2.jp2')
j = glymur.Jp2k(filename)
with warnings.catch_warnings():
# Bad compatibility list item.
warnings.simplefilter("ignore")
j = glymur.Jp2k(filename)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.box[2].box[2])
actual = fake_out.getvalue().strip()

View file

@ -9,9 +9,11 @@ License: MIT
"""
import sys
import numpy as np
from distutils.version import LooseVersion
import lxml.etree
import numpy as np
from .lib import openjpeg as opj
from .lib import openjp2 as opj2
@ -48,10 +50,12 @@ OPENJPEG {openjpeg}
Python {python}
sys.platform {platform}
sys.maxsize {maxsize}
lxml {elxml}
numpy {numpy}
""".format(glymur=version,
openjpeg=openjpeg_version,
python=sys.version,
platform=sys.platform,
maxsize=sys.maxsize,
elxml=lxml.etree.__version__,
numpy=np.__version__)