Merge branch 'issue217' into devel
This commit is contained in:
commit
275324fce8
10 changed files with 399 additions and 83 deletions
|
|
@ -2,6 +2,7 @@
|
|||
"""
|
||||
Part of glymur.
|
||||
"""
|
||||
from collections import OrderedDict
|
||||
import pprint
|
||||
import re
|
||||
import struct
|
||||
|
|
@ -10,12 +11,6 @@ import warnings
|
|||
|
||||
import lxml.etree as ET
|
||||
|
||||
if sys.hexversion < 0x02070000:
|
||||
# pylint: disable=F0401,E0611
|
||||
from ordereddict import OrderedDict
|
||||
else:
|
||||
from collections import OrderedDict
|
||||
|
||||
def xml(raw_data):
|
||||
"""
|
||||
XMP data to be parsed as XML.
|
||||
|
|
|
|||
|
|
@ -188,15 +188,7 @@ class Codestream(object):
|
|||
while True:
|
||||
|
||||
read_buffer = fptr.read(2)
|
||||
try:
|
||||
self._marker_id, = struct.unpack('>H', read_buffer)
|
||||
except struct.error:
|
||||
# Treat this as a warning.
|
||||
msg = "Marker had length {0} instead of expected length of 2 "
|
||||
msg += "bytes. Codestream parsing terminated."
|
||||
warnings.warn(msg.format(len(read_buffer)))
|
||||
break
|
||||
|
||||
self._marker_id, = struct.unpack('>H', read_buffer)
|
||||
self._offset = fptr.tell() - 2
|
||||
|
||||
if self._marker_id == 0xff90 and header_only:
|
||||
|
|
|
|||
|
|
@ -1011,7 +1011,7 @@ class ContiguousCodestreamBox(Jp2kBox):
|
|||
if self._main_header is None:
|
||||
if self._filename is not None:
|
||||
with open(self._filename, 'rb') as fptr:
|
||||
fptr.seek(self._offset + 8)
|
||||
fptr.seek(self.main_header_offset)
|
||||
main_header = Codestream(fptr, self._length, header_only=True)
|
||||
self._main_header = main_header
|
||||
return self._main_header
|
||||
|
|
@ -1059,7 +1059,6 @@ class ContiguousCodestreamBox(Jp2kBox):
|
|||
length=length, offset=offset)
|
||||
box._filename = fptr.name
|
||||
box._length = length
|
||||
box._offset = offset
|
||||
return box
|
||||
|
||||
|
||||
|
|
@ -1110,7 +1109,7 @@ class DataReferenceBox(Jp2kBox):
|
|||
"""
|
||||
self._write_validate()
|
||||
|
||||
# Very similar to the say a superbox is written.
|
||||
# Very similar to the way a superbox is written.
|
||||
orig_pos = fptr.tell()
|
||||
fptr.write(struct.pack('>I4s', 0, b'dtbl'))
|
||||
|
||||
|
|
@ -1176,7 +1175,7 @@ class DataReferenceBox(Jp2kBox):
|
|||
box = DataEntryURLBox.parse(box_fptr, 0, box_length)
|
||||
|
||||
# Need to adjust the box start to that of the "real" file.
|
||||
box.start = offset + box_offset
|
||||
box.offset = offset + 8 + box_offset
|
||||
data_entry_url_box_list.append(box)
|
||||
|
||||
# Point to the next embedded URL box.
|
||||
|
|
@ -1341,7 +1340,10 @@ class FragmentListBox(Jp2kBox):
|
|||
self._dispatch_validation_error(msg, writing=writing)
|
||||
|
||||
def __repr__(self):
|
||||
msg = "glymur.jp2box.FragmentListBox()"
|
||||
msg = "glymur.jp2box.FragmentListBox({0}, {1}, {2})"
|
||||
msg = msg.format(str(self.fragment_offset),
|
||||
str(self.fragment_length),
|
||||
str(self.data_reference))
|
||||
return msg
|
||||
|
||||
def __str__(self):
|
||||
|
|
@ -1426,7 +1428,8 @@ class FragmentTableBox(Jp2kBox):
|
|||
self.box = box if box is not None else []
|
||||
|
||||
def __repr__(self):
|
||||
msg = "glymur.jp2box.FragmentTableBox()"
|
||||
msg = "glymur.jp2box.FragmentTableBox(box={0})"
|
||||
msg = msg.format(None) if (len(self.box) == 0) else msg.format(self.box)
|
||||
return msg
|
||||
|
||||
def __str__(self):
|
||||
|
|
@ -1884,6 +1887,12 @@ class PaletteBox(Jp2kBox):
|
|||
msg = "The length of the 'bits_per_component' and the 'signed' "
|
||||
msg += "members must equal the number of columns of the palette."
|
||||
self._dispatch_validation_error(msg, writing=writing)
|
||||
bps = self.bits_per_component
|
||||
if writing and not all(b == bps[0] for b in bps):
|
||||
# We don't support writing palettes with bit depths that are
|
||||
# different.
|
||||
msg = "Writing palettes with varying bit depths is not supported."
|
||||
self._dispatch_validation_error(msg, writing=writing)
|
||||
|
||||
def __repr__(self):
|
||||
msg = "glymur.jp2box.PaletteBox({0}, bits_per_component={1}, "
|
||||
|
|
@ -1925,26 +1934,14 @@ class PaletteBox(Jp2kBox):
|
|||
fptr.write(write_buffer)
|
||||
|
||||
bps = self.bits_per_component
|
||||
if all(b == bps[0] for b in bps):
|
||||
# All components are the same. Writing is straightforward.
|
||||
if self.bits_per_component[0] <= 8:
|
||||
write_buffer = memoryview(self.palette.astype(np.uint8))
|
||||
elif self.bits_per_component[0] <= 16:
|
||||
write_buffer = memoryview(self.palette.astype(np.uint16))
|
||||
elif self.bits_per_component[0] <= 32:
|
||||
write_buffer = memoryview(self.palette.astype(np.uint32))
|
||||
fptr.write(write_buffer)
|
||||
else:
|
||||
# Not all the components are the same. More general, but much rarer
|
||||
# case. Does this even happen.
|
||||
code_dict = {8: 'B', 16: 'H', 32: 'I'}
|
||||
codes = ''
|
||||
for width in bps:
|
||||
codes += code_dict[width]
|
||||
fmt = '>' + codes
|
||||
for row in self.palette:
|
||||
write_buffer = struct.pack(fmt, *row)
|
||||
fptr.write(write_buffer)
|
||||
# All components are the same. Writing is straightforward.
|
||||
if self.bits_per_component[0] <= 8:
|
||||
write_buffer = memoryview(self.palette.astype(np.uint8))
|
||||
elif self.bits_per_component[0] <= 16:
|
||||
write_buffer = memoryview(self.palette.astype(np.uint16))
|
||||
elif self.bits_per_component[0] <= 32:
|
||||
write_buffer = memoryview(self.palette.astype(np.uint32))
|
||||
fptr.write(write_buffer)
|
||||
|
||||
@classmethod
|
||||
def parse(cls, fptr, offset, length):
|
||||
|
|
@ -2635,18 +2632,19 @@ class NumberListBox(Jp2kBox):
|
|||
msg += 'the rendered result'
|
||||
elif (association >> 24) == 1:
|
||||
idx = association & 0x00FFFFFF
|
||||
msg += 'Codestream {0}'
|
||||
msg += 'codestream {0}'
|
||||
msg = msg.format(idx)
|
||||
elif (association >> 24) == 2:
|
||||
idx = association & 0x00FFFFFF
|
||||
msg += 'Compositing Layer {0}'
|
||||
msg += 'compositing layer {0}'
|
||||
msg = msg.format(idx)
|
||||
else:
|
||||
msg += 'unrecognized'
|
||||
return msg
|
||||
|
||||
def __repr__(self):
|
||||
msg = 'glymur.jp2box.NumberListBox()'
|
||||
msg = 'glymur.jp2box.NumberListBox(associations={0})'
|
||||
msg = msg.format(self.associations)
|
||||
return msg
|
||||
|
||||
@classmethod
|
||||
|
|
|
|||
|
|
@ -547,13 +547,13 @@ class Jp2k(Jp2kBox):
|
|||
|
||||
opj2.setup_encoder(codec, cparams, image)
|
||||
|
||||
if _OPENJP2_IS_OFFICIAL_V2:
|
||||
if re.match("2.0", version.openjpeg_version) is not None:
|
||||
fptr = libc.fopen(self.filename, 'wb')
|
||||
strm = opj2.stream_create_default_file_stream(fptr, False)
|
||||
stack.callback(opj2.stream_destroy, strm)
|
||||
stack.callback(libc.fclose, fptr)
|
||||
else:
|
||||
# This routine introduced in 2.0 devel series.
|
||||
# Introduced in 2.1 devel series.
|
||||
strm = opj2.stream_create_default_file_stream_v3(self.filename,
|
||||
False)
|
||||
stack.callback(opj2.stream_destroy_v3, strm)
|
||||
|
|
@ -1080,17 +1080,17 @@ class Jp2k(Jp2kBox):
|
|||
layer=layer, tile=tile, area=area)
|
||||
|
||||
with ExitStack() as stack:
|
||||
if hasattr(opj2.OPENJP2,
|
||||
'opj_stream_create_default_file_stream_v3'):
|
||||
filename = self.filename
|
||||
stream = opj2.stream_create_default_file_stream_v3(filename,
|
||||
True)
|
||||
stack.callback(opj2.stream_destroy_v3, stream)
|
||||
else:
|
||||
if re.match("2.0", version.openjpeg_version):
|
||||
fptr = libc.fopen(self.filename, 'rb')
|
||||
stack.callback(libc.fclose, fptr)
|
||||
stream = opj2.stream_create_default_file_stream(fptr, True)
|
||||
stack.callback(opj2.stream_destroy, stream)
|
||||
else:
|
||||
# API change in 2.1+
|
||||
filename = self.filename
|
||||
stream = opj2.stream_create_default_file_stream_v3(filename,
|
||||
True)
|
||||
stack.callback(opj2.stream_destroy_v3, stream)
|
||||
codec = opj2.create_decompress(self._codec_format)
|
||||
stack.callback(opj2.destroy_codec, codec)
|
||||
|
||||
|
|
@ -1147,12 +1147,6 @@ class Jp2k(Jp2kBox):
|
|||
Bitdepth: (8, 8, 8)
|
||||
Signed: (False, False, False)
|
||||
Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1))
|
||||
|
||||
Raises
|
||||
------
|
||||
IOError
|
||||
If the file is JPX with more than one codestream and no JP2
|
||||
compatibility is advertised.
|
||||
"""
|
||||
with open(self.filename, 'rb') as fptr:
|
||||
if self._codec_format == opj2.CODEC_J2K:
|
||||
|
|
@ -1161,11 +1155,6 @@ class Jp2k(Jp2kBox):
|
|||
else:
|
||||
ftyp = self.box[1]
|
||||
box = [x for x in self.box if x.box_id == 'jp2c']
|
||||
if len(box) > 1 and 'jp2 ' not in ftyp.compatibility_list:
|
||||
msg = "If more than one codestream exists, JP2 "
|
||||
msg += "compatibiltity must be advertised by the FileType "
|
||||
msg += "box."
|
||||
raise RuntimeError(msg)
|
||||
fptr.seek(box[0].offset)
|
||||
read_buffer = fptr.read(8)
|
||||
(box_length, _) = struct.unpack('>I4s', read_buffer)
|
||||
|
|
@ -1183,7 +1172,7 @@ class Jp2k(Jp2kBox):
|
|||
return codestream
|
||||
|
||||
|
||||
def component2dtype(component):
|
||||
def _component2dtype(component):
|
||||
"""Take an OpenJPEG component structure and determine the numpy datatype.
|
||||
|
||||
Parameters
|
||||
|
|
@ -1474,7 +1463,7 @@ def extract_image_cube(image):
|
|||
"""
|
||||
ncomps = image.contents.numcomps
|
||||
component = image.contents.comps[0]
|
||||
dtype = component2dtype(component)
|
||||
dtype = _component2dtype(component)
|
||||
|
||||
nrows = component.h
|
||||
ncols = component.w
|
||||
|
|
@ -1508,7 +1497,7 @@ def extract_image_bands(image):
|
|||
for k in range(image.contents.numcomps):
|
||||
component = image.contents.comps[k]
|
||||
|
||||
dtype = component2dtype(component)
|
||||
dtype = _component2dtype(component)
|
||||
nrows = component.h
|
||||
ncols = component.w
|
||||
|
||||
|
|
@ -1698,7 +1687,7 @@ def _validate_compression_params(img_array, cparams):
|
|||
msg = "{0}D imagery is not allowed.".format(img_array.ndim)
|
||||
raise IOError(msg)
|
||||
|
||||
if _OPENJP2_IS_OFFICIAL_V2:
|
||||
if re.match("2.0", version.openjpeg_version) is not None:
|
||||
if (((img_array.ndim != 2) and
|
||||
(img_array.shape[2] != 1 and img_array.shape[2] != 3))):
|
||||
msg = "Writing images is restricted to single-channel "
|
||||
|
|
@ -1711,14 +1700,6 @@ def _validate_compression_params(img_array, cparams):
|
|||
msg = "Only uint8 and uint16 images are currently supported."
|
||||
raise RuntimeError(msg)
|
||||
|
||||
# Need to known if openjp2 library is the officially release v2.0.0 or not.
|
||||
_OPENJP2_IS_OFFICIAL_V2 = False
|
||||
if opj2.OPENJP2 is not None:
|
||||
if opj2.version() == '2.0.0':
|
||||
if not hasattr(opj2.OPENJP2,
|
||||
'opj_stream_create_default_file_stream_v3'):
|
||||
_OPENJP2_IS_OFFICIAL_V2 = True
|
||||
|
||||
_COLORSPACE_MAP = {'rgb': opj2.CLRSPC_SRGB,
|
||||
'gray': opj2.CLRSPC_GRAY,
|
||||
'grey': opj2.CLRSPC_GRAY,
|
||||
|
|
|
|||
|
|
@ -622,3 +622,57 @@ cinema2k_profile = """SIZ marker segment @ (2, 47)
|
|||
Bitdepth: (12, 12, 12)
|
||||
Signed: (False, False, False)
|
||||
Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1))"""
|
||||
|
||||
jplh_color_group_box = r"""Compositing Layer Header Box (jplh) @ (314227, 31)
|
||||
Colour Group Box (cgrp) @ (314235, 23)
|
||||
Colour Specification Box (colr) @ (314243, 15)
|
||||
Method: enumerated colorspace
|
||||
Precedence: 0
|
||||
Colorspace: sRGB"""
|
||||
|
||||
fragment_list_box = r"""Fragment List Box (flst) @ (-1, 0)
|
||||
Offset 0: 89
|
||||
Fragment Length 0: 1132288
|
||||
Data Reference 0: 0"""
|
||||
|
||||
number_list_box = r"""Number List Box (nlst) @ (-1, 0)
|
||||
Association[0]: the rendered result
|
||||
Association[1]: codestream 0
|
||||
Association[2]: compositing layer 0"""
|
||||
|
||||
|
||||
goodstuff = r"""Codestream:
|
||||
SOC marker segment @ (0, 0)
|
||||
SIZ marker segment @ (2, 47)
|
||||
Profile: no profile
|
||||
Reference Grid Height, Width: (800 x 480)
|
||||
Vertical, Horizontal Reference Grid Offset: (0 x 0)
|
||||
Reference Tile Height, Width: (800 x 480)
|
||||
Vertical, Horizontal Reference Tile Offset: (0 x 0)
|
||||
Bitdepth: (8, 8, 8)
|
||||
Signed: (False, False, False)
|
||||
Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1))
|
||||
COD marker segment @ (51, 12)
|
||||
Coding style:
|
||||
Entropy coder, without partitions
|
||||
SOP marker segments: False
|
||||
EPH marker segments: False
|
||||
Coding style parameters:
|
||||
Progression order: LRCP
|
||||
Number of layers: 1
|
||||
Multiple component transformation usage: reversible
|
||||
Number of resolutions: 6
|
||||
Code block height, width: (64 x 64)
|
||||
Wavelet transform: 5-3 reversible
|
||||
Precinct size: default, 2^15 x 2^15
|
||||
Code block context:
|
||||
Selective arithmetic coding bypass: False
|
||||
Reset context probabilities on coding pass boundaries: False
|
||||
Termination on each coding pass: False
|
||||
Vertically stripe causal context: False
|
||||
Predictable termination: False
|
||||
Segmentation symbols: False
|
||||
QCD marker segment @ (65, 19)
|
||||
Quantization style: no quantization, 2 guard bits
|
||||
Step size: [(0, 8), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10)]"""
|
||||
|
||||
|
|
|
|||
|
|
@ -543,6 +543,16 @@ class TestPaletteBox(unittest.TestCase):
|
|||
pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps,
|
||||
signed=signed)
|
||||
|
||||
def test_writing_with_different_bitdepths(self):
|
||||
"""Bitdepths must be the same when writing."""
|
||||
palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint16)
|
||||
bps = (8, 16, 8)
|
||||
signed = (False, False, False)
|
||||
pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps,
|
||||
signed=signed)
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
with self.assertRaises(IOError):
|
||||
pclr.write(tfile)
|
||||
|
||||
class TestAppend(unittest.TestCase):
|
||||
"""Tests for append method."""
|
||||
|
|
@ -733,6 +743,49 @@ class TestWrap(unittest.TestCase):
|
|||
boxes = [box.box_id for box in jp2.box]
|
||||
self.assertEqual(boxes, ['jP ', 'ftyp', 'jp2h', 'jp2c'])
|
||||
|
||||
def test_wrap_jp2_Lzero(self):
|
||||
"""Wrap jp2 with jp2c box length is zero"""
|
||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||
with open(self.jp2file, 'rb') as ifile:
|
||||
tfile.write(ifile.read())
|
||||
# Rewrite with codestream length as zero.
|
||||
tfile.seek(3223)
|
||||
tfile.write(struct.pack('>I', 0))
|
||||
tfile.flush()
|
||||
jp2 = Jp2k(tfile.name)
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile2:
|
||||
jp2 = jp2.wrap(tfile2.name)
|
||||
boxes = [box for box in jp2.box]
|
||||
self.assertEqual(boxes[3].length, 1132296)
|
||||
|
||||
def test_wrap_jp2_Lone(self):
|
||||
"""Wrap jp2 with jp2c box length is 1, implies Q field"""
|
||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||
with open(self.jp2file, 'rb') as ifile:
|
||||
tfile.write(ifile.read(3223))
|
||||
# Write new L, T, Q fields
|
||||
tfile.write(struct.pack('>I4sQ', 1, b'jp2c', 1132296 + 8))
|
||||
# skip over the old L, T fields
|
||||
ifile.seek(3231)
|
||||
tfile.write(ifile.read())
|
||||
tfile.flush()
|
||||
jp2 = Jp2k(tfile.name)
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile2:
|
||||
jp2 = jp2.wrap(tfile2.name)
|
||||
boxes = [box for box in jp2.box]
|
||||
self.assertEqual(boxes[3].length, 1132296 + 8)
|
||||
|
||||
def test_wrap_compatibility_not_jp2(self):
|
||||
"""File type compatibility must contain jp2"""
|
||||
jp2 = Jp2k(self.jp2file)
|
||||
boxes = [box for box in jp2.box]
|
||||
boxes[1].compatibility_list = ['jpx ']
|
||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||
with self.assertRaises(IOError):
|
||||
jp2.wrap(tfile.name, boxes=boxes)
|
||||
|
||||
def test_empty_jp2h(self):
|
||||
"""JP2H box list cannot be empty."""
|
||||
jp2 = Jp2k(self.jp2file)
|
||||
|
|
@ -992,6 +1045,59 @@ class TestRepr(unittest.TestCase):
|
|||
self.assertTrue(isinstance(newbox, glymur.jp2box.JPEG2000SignatureBox))
|
||||
self.assertEqual(newbox.signature, (13, 10, 135, 10))
|
||||
|
||||
def test_free(self):
|
||||
"""Should be able to instantiate a free box"""
|
||||
free = glymur.jp2box.FreeBox()
|
||||
|
||||
# Test the representation instantiation.
|
||||
newbox = eval(repr(free))
|
||||
self.assertTrue(isinstance(newbox, glymur.jp2box.FreeBox))
|
||||
|
||||
def test_nlst(self):
|
||||
"""Should be able to instantiate a number list box"""
|
||||
assn = (0, 1, 2)
|
||||
nlst = glymur.jp2box.NumberListBox(assn)
|
||||
|
||||
# Test the representation instantiation.
|
||||
newbox = eval(repr(nlst))
|
||||
self.assertTrue(isinstance(newbox, glymur.jp2box.NumberListBox))
|
||||
self.assertEqual(newbox.associations, (0, 1, 2))
|
||||
|
||||
def test_ftbl(self):
|
||||
"""Should be able to instantiate a fragment table box"""
|
||||
ftbl = glymur.jp2box.FragmentTableBox()
|
||||
|
||||
# Test the representation instantiation.
|
||||
newbox = eval(repr(ftbl))
|
||||
self.assertTrue(isinstance(newbox, glymur.jp2box.FragmentTableBox))
|
||||
|
||||
def test_dref(self):
|
||||
"""Should be able to instantiate a data reference box"""
|
||||
dref = glymur.jp2box.DataReferenceBox()
|
||||
|
||||
# Test the representation instantiation.
|
||||
newbox = eval(repr(dref))
|
||||
self.assertTrue(isinstance(newbox, glymur.jp2box.DataReferenceBox))
|
||||
|
||||
def test_flst(self):
|
||||
"""Should be able to instantiate a fragment list box"""
|
||||
flst = glymur.jp2box.FragmentListBox([89], [1132288], [0])
|
||||
|
||||
# Test the representation instantiation.
|
||||
newbox = eval(repr(flst))
|
||||
self.assertTrue(isinstance(newbox, glymur.jp2box.FragmentListBox))
|
||||
self.assertEqual(newbox.fragment_offset, [89])
|
||||
self.assertEqual(newbox.fragment_length, [1132288])
|
||||
self.assertEqual(newbox.data_reference, [0])
|
||||
|
||||
def test_default_cgrp(self):
|
||||
"""Should be able to instantiate a color group box"""
|
||||
cgrp = glymur.jp2box.ColourGroupBox()
|
||||
|
||||
# Test the representation instantiation.
|
||||
newbox = eval(repr(cgrp))
|
||||
self.assertTrue(isinstance(newbox, glymur.jp2box.ColourGroupBox))
|
||||
|
||||
def test_default_ftyp(self):
|
||||
"""Should be able to instantiate a FileTypeBox"""
|
||||
ftyp = glymur.jp2box.FileTypeBox()
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
Test suite specifically targeting JPX box layout.
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
|
|
@ -52,6 +53,7 @@ class TestJPXWrap(unittest.TestCase):
|
|||
tfile1.write(fptr.read())
|
||||
tfile1.flush()
|
||||
jp2_1 = Jp2k(tfile1.name)
|
||||
jp2h = jp2_1.box[2]
|
||||
|
||||
jp2c = [box for box in jp2_1.box if box.box_id == 'jp2c'][0]
|
||||
|
||||
|
|
@ -61,12 +63,13 @@ class TestJPXWrap(unittest.TestCase):
|
|||
clen = []
|
||||
dr_idx = []
|
||||
|
||||
coff.append(jp2c.offset + 8)
|
||||
coff.append(jp2c.main_header_offset)
|
||||
clen.append(jp2c.length - (coff[0] - jp2c.offset))
|
||||
dr_idx.append(1)
|
||||
|
||||
# Make the url box for this codestream.
|
||||
url1 = DataEntryURLBox(0, [0, 0, 0], 'file://' + tfile1.name)
|
||||
url1_name_len = len(url1.url) + 1
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2:
|
||||
|
||||
|
|
@ -74,7 +77,7 @@ class TestJPXWrap(unittest.TestCase):
|
|||
jp2_2 = j2k.wrap(tfile2.name)
|
||||
|
||||
jp2c = [box for box in jp2_2.box if box.box_id == 'jp2c'][0]
|
||||
coff.append(jp2c.offset + 8)
|
||||
coff.append(jp2c.main_header_offset)
|
||||
clen.append(jp2c.length - (coff[0] - jp2c.offset))
|
||||
dr_idx.append(2)
|
||||
|
||||
|
|
@ -85,7 +88,7 @@ class TestJPXWrap(unittest.TestCase):
|
|||
FileTypeBox(brand='jpx ',
|
||||
compatibility_list=['jpx ',
|
||||
'jp2 ', 'jpxb']),
|
||||
jp2_1.box[2]]
|
||||
jp2h]
|
||||
with tempfile.NamedTemporaryFile(suffix='.jpx') as tjpx:
|
||||
for box in boxes:
|
||||
box.write(tjpx)
|
||||
|
|
@ -99,6 +102,15 @@ class TestJPXWrap(unittest.TestCase):
|
|||
dtbl.write(tjpx)
|
||||
tjpx.flush()
|
||||
|
||||
jpx_no_jp2c = Jp2k(tjpx.name)
|
||||
jpx_boxes = [box.box_id for box in jpx_no_jp2c.box]
|
||||
self.assertEqual(jpx_boxes, ['jP ', 'ftyp', 'jp2h',
|
||||
'ftbl', 'dtbl'])
|
||||
self.assertEqual(jpx_no_jp2c.box[4].DR[0].offset, 141)
|
||||
|
||||
offset = 141 + 8 + 4 + url1_name_len
|
||||
self.assertEqual(jpx_no_jp2c.box[4].DR[1].offset, offset)
|
||||
|
||||
def test_jp2_with_jpx_box(self):
|
||||
"""If the brand is jp2, then no jpx boxes are allowed."""
|
||||
jp2 = Jp2k(self.jp2file)
|
||||
|
|
@ -154,8 +166,8 @@ class TestJPXWrap(unittest.TestCase):
|
|||
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"""
|
||||
def test_label_neg(self):
|
||||
"""Can't write a label box embedded in any old box."""
|
||||
jp2 = Jp2k(self.jp2file)
|
||||
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
||||
|
||||
|
|
@ -173,6 +185,26 @@ class TestJPXWrap(unittest.TestCase):
|
|||
with self.assertRaises(IOError):
|
||||
jpx = jp2.wrap(tfile.name, boxes=boxes)
|
||||
|
||||
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']
|
||||
|
||||
the_xml = ET.fromstring('<?xml version="1.0"?><data>0</data>')
|
||||
xmlb = glymur.jp2box.XMLBox(xml=the_xml)
|
||||
box = [xmlb]
|
||||
|
||||
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
|
||||
|
|
@ -459,7 +491,7 @@ class TestJPX(unittest.TestCase):
|
|||
self.assertEqual(jpx.box[2].box_id, 'rreq')
|
||||
self.assertEqual(type(jpx.box[2]),
|
||||
glymur.jp2box.ReaderRequirementsBox)
|
||||
self.assertEqual(jpx.box[2].standard_flag,
|
||||
self.asserwrite_buffertEqual(jpx.box[2].standard_flag,
|
||||
(5, 42, 45, 2, 18, 19, 1, 8, 12, 31, 20))
|
||||
|
||||
@unittest.skip("Requires unnecessarily complicated code")
|
||||
|
|
@ -552,6 +584,52 @@ class TestJPX(unittest.TestCase):
|
|||
self.assertEqual(jpx.box[-1].box[0].fragment_length, (170246,))
|
||||
self.assertEqual(jpx.box[-1].box[0].data_reference, (3,))
|
||||
|
||||
def test_rreq3(self):
|
||||
"""Verify that we can read a rreq box with mask length 3 bytes"""
|
||||
rreq_buffer = ctypes.create_string_buffer(74)
|
||||
struct.pack_into('>I4s', rreq_buffer, 0, 74, b'rreq')
|
||||
|
||||
# mask length
|
||||
struct.pack_into('>B', rreq_buffer, 8, 3)
|
||||
|
||||
# fuam, dcm. 6 bytes, two sets of 3.
|
||||
lst = (255, 224, 0, 0, 31, 252)
|
||||
struct.pack_into('>BBBBBB', rreq_buffer, 9, *lst)
|
||||
|
||||
# number of standard features: 11
|
||||
struct.pack_into('>H', rreq_buffer, 15, 11)
|
||||
|
||||
standard_flags = [5, 42, 45, 2, 18, 19, 1, 8, 12, 31, 20]
|
||||
standard_masks = [8388608, 4194304, 2097152, 1048576, 524288, 262144,
|
||||
131072, 65536, 32768, 16384, 8192]
|
||||
for j in range(len(standard_flags)):
|
||||
mask = (standard_masks[j] >> 16,
|
||||
standard_masks[j] & 0x0000ffff>> 8,
|
||||
standard_masks[j] & 0x000000ff)
|
||||
struct.pack_into('>HBBB', rreq_buffer, 17 + j * 5,
|
||||
standard_flags[j], *mask)
|
||||
|
||||
# num vendor features: 0
|
||||
struct.pack_into('>H', rreq_buffer, 72, 0)
|
||||
|
||||
# Ok, done with the box, we can now insert it into the jpx file after
|
||||
# the ftyp box.
|
||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as ofile:
|
||||
with open(self.jpxfile, 'rb') as ifile:
|
||||
ofile.write(ifile.read(40))
|
||||
ofile.write(rreq_buffer)
|
||||
ofile.write(ifile.read())
|
||||
ofile.flush()
|
||||
|
||||
jpx = Jp2k(ofile.name)
|
||||
|
||||
self.assertEqual(jpx.box[2].box_id, 'rreq')
|
||||
self.assertEqual(type(jpx.box[2]),
|
||||
glymur.jp2box.ReaderRequirementsBox)
|
||||
self.assertEqual(jpx.box[2].standard_flag,
|
||||
(5, 42, 45, 2, 18, 19, 1, 8, 12, 31, 20))
|
||||
|
||||
|
||||
def test_nlst(self):
|
||||
"""Verify that we can handle a number list box."""
|
||||
j = Jp2k(self.jpxfile)
|
||||
|
|
|
|||
|
|
@ -779,6 +779,17 @@ class TestParsing(unittest.TestCase):
|
|||
with self.assertWarns(UserWarning):
|
||||
jp2 = Jp2k(filename)
|
||||
|
||||
@unittest.skip("blah")
|
||||
def test_main_header(self):
|
||||
"""Verify that the main header is not loaded when parsing turned off."""
|
||||
# The hidden _main_header attribute should show up after accessing it.
|
||||
glymur.set_parseoptions(codestream=False)
|
||||
jp2 = Jp2k(self.jp2file)
|
||||
jp2c = jp2.box[4]
|
||||
self.assertIsNone(jp2c._main_header)
|
||||
main_header = jp2c.main_header
|
||||
self.assertIsNotNone(jp2c._main_header)
|
||||
|
||||
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
||||
"OPJ_DATA_ROOT environment variable not set")
|
||||
class TestJp2kOpjDataRoot(unittest.TestCase):
|
||||
|
|
|
|||
|
|
@ -16,6 +16,13 @@ import unittest
|
|||
|
||||
import numpy as np
|
||||
|
||||
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 OPJ_DATA_ROOT, opj_data_file, read_image
|
||||
from .fixtures import NO_READ_BACKEND, NO_READ_BACKEND_MSG
|
||||
|
||||
|
|
@ -35,6 +42,21 @@ class TestSuiteNegative(unittest.TestCase):
|
|||
def tearDown(self):
|
||||
pass
|
||||
|
||||
|
||||
@unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT,
|
||||
"Cannot read input image without scikit-image/freeimage")
|
||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||
def test_cinema2K_bad_frame_rate(self):
|
||||
"""Cinema2k frame rate must be either 24 or 48."""
|
||||
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=36)
|
||||
|
||||
|
||||
@unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG)
|
||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||
def test_psnr_with_cratios(self):
|
||||
|
|
|
|||
|
|
@ -50,6 +50,18 @@ class TestPrinting(unittest.TestCase):
|
|||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_codestream(self):
|
||||
"""Should be able to print a raw codestream."""
|
||||
j = glymur.Jp2k(self.j2kfile)
|
||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||
print(j)
|
||||
actual = fake_out.getvalue().strip()
|
||||
# Remove the file line, as that is filesystem-dependent.
|
||||
lines = actual.split('\n')
|
||||
actual = '\n'.join(lines[1:])
|
||||
|
||||
self.assertEqual(actual, fixtures.codestream)
|
||||
|
||||
def test_version_info(self):
|
||||
"""Should be able to print(glymur.version.info)"""
|
||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||
|
|
@ -598,6 +610,63 @@ class TestPrinting(unittest.TestCase):
|
|||
expected = '\n'.join(lines)
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_flst(self):
|
||||
"""Verify printing of fragment list box."""
|
||||
flst = glymur.jp2box.FragmentListBox([89], [1132288], [0])
|
||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||
print(flst)
|
||||
actual = fake_out.getvalue().strip()
|
||||
self.assertEqual(actual, fixtures.fragment_list_box)
|
||||
|
||||
def test_dref(self):
|
||||
"""Verify printing of data reference box."""
|
||||
dref = glymur.jp2box.DataReferenceBox()
|
||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||
print(dref)
|
||||
actual = fake_out.getvalue().strip()
|
||||
self.assertEqual(actual, 'Data Reference Box (dtbl) @ (-1, 0)')
|
||||
|
||||
def test_jplh_cgrp(self):
|
||||
"""Verify printing of compositing layer header box, color group box."""
|
||||
jpx = glymur.Jp2k(self.jpxfile)
|
||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||
print(jpx.box[7])
|
||||
actual = fake_out.getvalue().strip()
|
||||
self.assertEqual(actual, fixtures.jplh_color_group_box)
|
||||
|
||||
def test_free(self):
|
||||
"""Verify printing of Free box."""
|
||||
free = glymur.jp2box.FreeBox()
|
||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||
print(free)
|
||||
actual = fake_out.getvalue().strip()
|
||||
self.assertEqual(actual, 'Free Box (free) @ (-1, 0)')
|
||||
|
||||
def test_nlst(self):
|
||||
"""Verify printing of number list box."""
|
||||
assn = (0, 16777216, 33554432)
|
||||
nlst = glymur.jp2box.NumberListBox(assn)
|
||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||
print(nlst)
|
||||
actual = fake_out.getvalue().strip()
|
||||
self.assertEqual(actual, fixtures.number_list_box)
|
||||
|
||||
def test_ftbl(self):
|
||||
"""Verify printing of fragment table box."""
|
||||
ftbl = glymur.jp2box.FragmentTableBox()
|
||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||
print(ftbl)
|
||||
actual = fake_out.getvalue().strip()
|
||||
self.assertEqual(actual, 'Fragment Table Box (ftbl) @ (-1, 0)')
|
||||
|
||||
def test_jpch(self):
|
||||
"""Verify printing of JPCH box."""
|
||||
jpx = glymur.Jp2k(self.jpxfile)
|
||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||
print(jpx.box[3])
|
||||
actual = fake_out.getvalue().strip()
|
||||
self.assertEqual(actual, 'Codestream Header Box (jpch) @ (887, 8)')
|
||||
|
||||
@unittest.skipIf(sys.hexversion < 0x03000000,
|
||||
"Ordered dicts not printing well in 2.7")
|
||||
def test_exif_uuid(self):
|
||||
|
|
@ -893,6 +962,17 @@ class TestPrintingOpjDataRoot(unittest.TestCase):
|
|||
expected = '\n'.join(lines)
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_componentmapping_box_alpha(self):
|
||||
"""Verify __repr__ method on cmap box."""
|
||||
cmap = glymur.jp2box.ComponentMappingBox(component_index=(0, 0, 0),
|
||||
mapping_type=(1, 1, 1),
|
||||
palette_index=(0, 1, 2))
|
||||
newbox = eval(repr(cmap))
|
||||
self.assertEqual(newbox.box_id, 'cmap')
|
||||
self.assertEqual(newbox.component_index, (0, 0, 0))
|
||||
self.assertEqual(newbox.mapping_type, (1, 1, 1))
|
||||
self.assertEqual(newbox.palette_index, (0, 1, 2))
|
||||
|
||||
def test_palette7(self):
|
||||
"""verify printing of pclr box"""
|
||||
filename = opj_data_file('input/conformance/file9.jp2')
|
||||
|
|
@ -905,7 +985,6 @@ class TestPrintingOpjDataRoot(unittest.TestCase):
|
|||
expected = '\n'.join(lines)
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
@unittest.skip("file7 no longer has a rreq")
|
||||
def test_rreq(self):
|
||||
"""verify printing of reader requirements box"""
|
||||
filename = opj_data_file('input/nonregression/text_GBR.jp2')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue