Merge branch 'issue212' into devel
This commit is contained in:
commit
718eb9dd6e
5 changed files with 230 additions and 110 deletions
|
|
@ -9,6 +9,7 @@ __version__ = version.version
|
||||||
from .jp2k import Jp2k
|
from .jp2k import Jp2k
|
||||||
from .jp2dump import jp2dump
|
from .jp2dump import jp2dump
|
||||||
from .jp2box import get_printoptions, set_printoptions
|
from .jp2box import get_printoptions, set_printoptions
|
||||||
|
from .jp2box import get_parseoptions, set_parseoptions
|
||||||
|
|
||||||
from . import data
|
from . import data
|
||||||
|
|
||||||
|
|
|
||||||
231
glymur/jp2box.py
231
glymur/jp2box.py
|
|
@ -15,6 +15,7 @@ References
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import datetime
|
import datetime
|
||||||
|
import io
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import pprint
|
import pprint
|
||||||
|
|
@ -165,10 +166,11 @@ class Jp2kBox(object):
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
fptr : file
|
fptr : file
|
||||||
Open file object.
|
Open file object, currently points to start of box payload, not the
|
||||||
|
start of the box.
|
||||||
box_id : str
|
box_id : str
|
||||||
4-letter identifier for the current box.
|
4-letter identifier for the current box.
|
||||||
start, num_bytes: int
|
start, num_bytes : int
|
||||||
Byte offset and length of the current box.
|
Byte offset and length of the current box.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
|
|
@ -189,7 +191,17 @@ class Jp2kBox(object):
|
||||||
|
|
||||||
return box
|
return box
|
||||||
|
|
||||||
box = parser(fptr, start, num_bytes)
|
try:
|
||||||
|
box = parser(fptr, start, num_bytes)
|
||||||
|
except ValueError as err:
|
||||||
|
msg = "Encountered an unrecoverable ValueError while parsing a {0} "
|
||||||
|
msg += "box at byte offset {1}. The original error message was "
|
||||||
|
msg += "\"{2}\""
|
||||||
|
msg = msg.format(box_id.decode('utf-8'), start, str(err))
|
||||||
|
warnings.warn(msg, UserWarning)
|
||||||
|
box = UnknownBox(box_id.decode('utf-8'),
|
||||||
|
length=num_bytes, offset=start, longname='Unknown')
|
||||||
|
|
||||||
return box
|
return box
|
||||||
|
|
||||||
def parse_superbox(self, fptr):
|
def parse_superbox(self, fptr):
|
||||||
|
|
@ -985,10 +997,23 @@ class ContiguousCodestreamBox(Jp2kBox):
|
||||||
"""
|
"""
|
||||||
def __init__(self, main_header=None, length=0, offset=-1):
|
def __init__(self, main_header=None, length=0, offset=-1):
|
||||||
Jp2kBox.__init__(self, box_id='jp2c', longname='Contiguous Codestream')
|
Jp2kBox.__init__(self, box_id='jp2c', longname='Contiguous Codestream')
|
||||||
self.main_header = main_header
|
self._main_header = main_header
|
||||||
self.length = length
|
self.length = length
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
|
|
||||||
|
# The filename can be set if lazy loading is desired.
|
||||||
|
self._filename = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def main_header(self):
|
||||||
|
if self._main_header is None:
|
||||||
|
if self._filename is not None:
|
||||||
|
with open(self._filename, 'rb') as fptr:
|
||||||
|
fptr.seek(self._offset + 8)
|
||||||
|
main_header = Codestream(fptr, self._length, header_only=True)
|
||||||
|
self._main_header = main_header
|
||||||
|
return self._main_header
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
msg = "glymur.jp2box.ContiguousCodeStreamBox(main_header={0})"
|
msg = "glymur.jp2box.ContiguousCodeStreamBox(main_header={0})"
|
||||||
return msg.format(repr(self.main_header))
|
return msg.format(repr(self.main_header))
|
||||||
|
|
@ -1023,8 +1048,15 @@ class ContiguousCodestreamBox(Jp2kBox):
|
||||||
-------
|
-------
|
||||||
ContiguousCodestreamBox instance
|
ContiguousCodestreamBox instance
|
||||||
"""
|
"""
|
||||||
main_header = Codestream(fptr, length, header_only=True)
|
if _parseoptions['codestream'] is True:
|
||||||
return cls(main_header, length=length, offset=offset)
|
main_header = Codestream(fptr, length, header_only=True)
|
||||||
|
else:
|
||||||
|
main_header = None
|
||||||
|
box = cls(main_header, length=length, offset=offset)
|
||||||
|
box._filename = fptr.name
|
||||||
|
box._length = length
|
||||||
|
box._offset = offset
|
||||||
|
return box
|
||||||
|
|
||||||
|
|
||||||
class DataReferenceBox(Jp2kBox):
|
class DataReferenceBox(Jp2kBox):
|
||||||
|
|
@ -1121,22 +1153,32 @@ class DataReferenceBox(Jp2kBox):
|
||||||
-------
|
-------
|
||||||
DataReferenceBox instance
|
DataReferenceBox instance
|
||||||
"""
|
"""
|
||||||
|
num_bytes = offset + length - fptr.tell()
|
||||||
|
read_buffer = fptr.read(num_bytes)
|
||||||
|
|
||||||
# Read the number of data references
|
# Read the number of data references
|
||||||
read_buffer = fptr.read(2)
|
ndr, = struct.unpack_from('>H', read_buffer, offset=0)
|
||||||
ndr, = struct.unpack('>H', read_buffer)
|
|
||||||
|
# Need to keep track of where the next url box starts.
|
||||||
|
box_offset = 2
|
||||||
|
|
||||||
# Read each data entry url box.
|
|
||||||
data_entry_url_box_list = []
|
data_entry_url_box_list = []
|
||||||
for _ in range(ndr):
|
for j in range(ndr):
|
||||||
start = fptr.tell()
|
|
||||||
read_buffer = fptr.read(8)
|
|
||||||
(box_length, box_id) = struct.unpack('>I4s', read_buffer)
|
|
||||||
if sys.hexversion >= 0x03000000:
|
|
||||||
box_id = box_id.decode('utf-8')
|
|
||||||
|
|
||||||
box = DataEntryURLBox.parse(fptr, start, box_length)
|
# Create an in-memory binary stream for each URL box.
|
||||||
|
box_fptr = io.BytesIO(read_buffer[box_offset:])
|
||||||
|
box_buffer = box_fptr.read(8)
|
||||||
|
(box_length, box_id) = struct.unpack_from('>I4s', box_buffer,
|
||||||
|
offset=0)
|
||||||
|
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
|
||||||
data_entry_url_box_list.append(box)
|
data_entry_url_box_list.append(box)
|
||||||
|
|
||||||
|
# Point to the next embedded URL box.
|
||||||
|
box_offset += box_length
|
||||||
|
|
||||||
return cls(data_entry_url_box_list, length=length, offset=offset)
|
return cls(data_entry_url_box_list, length=length, offset=offset)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1237,7 +1279,8 @@ class FileTypeBox(Jp2kBox):
|
||||||
-------
|
-------
|
||||||
FileTypeBox instance
|
FileTypeBox instance
|
||||||
"""
|
"""
|
||||||
read_buffer = fptr.read(length - 8)
|
num_bytes = offset + length - fptr.tell()
|
||||||
|
read_buffer = fptr.read(num_bytes)
|
||||||
# Extract the brand, minor version.
|
# Extract the brand, minor version.
|
||||||
(brand, minor_version) = struct.unpack_from('>4sI', read_buffer, 0)
|
(brand, minor_version) = struct.unpack_from('>4sI', read_buffer, 0)
|
||||||
if sys.hexversion >= 0x030000:
|
if sys.hexversion >= 0x030000:
|
||||||
|
|
@ -1923,11 +1966,12 @@ class PaletteBox(Jp2kBox):
|
||||||
-------
|
-------
|
||||||
PaletteBox instance
|
PaletteBox instance
|
||||||
"""
|
"""
|
||||||
read_buffer = fptr.read(3)
|
num_bytes = offset + length - fptr.tell()
|
||||||
(nrows, ncols) = struct.unpack('>HB', read_buffer)
|
read_buffer = fptr.read(num_bytes)
|
||||||
|
nrows, ncols = struct.unpack_from('>HB', read_buffer, offset=0)
|
||||||
|
|
||||||
read_buffer = fptr.read(ncols)
|
bps_signed = struct.unpack_from('>' + 'B' * ncols, read_buffer,
|
||||||
bps_signed = struct.unpack('>' + 'B' * ncols, read_buffer)
|
offset=3)
|
||||||
bps = [((x & 0x7f) + 1) for x in bps_signed]
|
bps = [((x & 0x7f) + 1) for x in bps_signed]
|
||||||
signed = [((x & 0x80) > 1) for x in bps_signed]
|
signed = [((x & 0x80) > 1) for x in bps_signed]
|
||||||
|
|
||||||
|
|
@ -1944,8 +1988,7 @@ class PaletteBox(Jp2kBox):
|
||||||
nbytes_per_row = 3 * ncols
|
nbytes_per_row = 3 * ncols
|
||||||
dtype = np.uint32
|
dtype = np.uint32
|
||||||
|
|
||||||
read_buffer = fptr.read(nrows * nbytes_per_row)
|
palette = np.frombuffer(read_buffer[3 + ncols:], dtype=dtype)
|
||||||
palette = np.frombuffer(read_buffer, dtype=dtype)
|
|
||||||
palette = np.reshape(palette, (nrows, ncols))
|
palette = np.reshape(palette, (nrows, ncols))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
@ -1963,11 +2006,10 @@ class PaletteBox(Jp2kBox):
|
||||||
# That means a list comprehension does this in one shot.
|
# That means a list comprehension does this in one shot.
|
||||||
row_nbytes = sum([int(math.ceil(x/8.0)) for x in bps])
|
row_nbytes = sum([int(math.ceil(x/8.0)) for x in bps])
|
||||||
|
|
||||||
read_buffer = fptr.read(nrows * row_nbytes)
|
|
||||||
palette = np.zeros((nrows, ncols), dtype=np.int32)
|
palette = np.zeros((nrows, ncols), dtype=np.int32)
|
||||||
for j in range(nrows):
|
for j in range(nrows):
|
||||||
palette[j] = struct.unpack_from(fmt, read_buffer,
|
poff = 3 + ncols + j * row_nbytes
|
||||||
offset=j * row_nbytes)
|
palette[j] = struct.unpack_from(fmt, read_buffer, offset=poff)
|
||||||
|
|
||||||
return cls(palette, bps, signed, length=length, offset=offset)
|
return cls(palette, bps, signed, length=length, offset=offset)
|
||||||
|
|
||||||
|
|
@ -2152,29 +2194,34 @@ class ReaderRequirementsBox(Jp2kBox):
|
||||||
-------
|
-------
|
||||||
ReaderRequirementsBox instance
|
ReaderRequirementsBox instance
|
||||||
"""
|
"""
|
||||||
read_buffer = fptr.read(1)
|
num_bytes = offset + length - fptr.tell()
|
||||||
mask_length, = struct.unpack('>B', read_buffer)
|
read_buffer = fptr.read(num_bytes)
|
||||||
|
mask_length, = struct.unpack_from('>B', read_buffer, offset=0)
|
||||||
|
|
||||||
if mask_length == 3:
|
if mask_length == 3:
|
||||||
return _parse_rreq3(fptr, length, offset)
|
return _parse_rreq3(read_buffer, length, offset)
|
||||||
|
|
||||||
# Fully Understands Aspect Mask
|
# Fully Understands Aspect Mask
|
||||||
# Decodes Completely Mask
|
# Decodes Completely Mask
|
||||||
read_buffer = fptr.read(2 * mask_length)
|
|
||||||
|
|
||||||
fuam = dcm = standard_flag = standard_mask = []
|
fuam = dcm = standard_flag = standard_mask = []
|
||||||
vendor_feature = vendor_mask = []
|
vendor_feature = vendor_mask = []
|
||||||
|
|
||||||
# The mask length tells us the format string to use when unpacking
|
# The mask length tells us the format string to use when unpacking
|
||||||
# from the buffer read from file.
|
# from the buffer read from file.
|
||||||
|
|
||||||
try:
|
try:
|
||||||
mask_format = {1: 'B', 2: 'H', 4: 'I', 8: 'Q'}[mask_length]
|
mask_format = {1: 'B', 2: 'H', 4: 'I', 8: 'Q'}[mask_length]
|
||||||
fuam, dcm = struct.unpack('>' + mask_format * 2, read_buffer)
|
fuam, dcm = struct.unpack_from('>' + mask_format * 2, read_buffer,
|
||||||
standard_flag, standard_mask = _parse_standard_flag(fptr,
|
offset=1)
|
||||||
mask_length)
|
std_flg_offset = 1 + 2 * mask_length
|
||||||
vendor_feature, vendor_mask = _parse_vendor_features(fptr,
|
data = _parse_standard_flag(read_buffer[std_flg_offset:],
|
||||||
mask_length)
|
mask_length)
|
||||||
|
standard_flag, standard_mask = data
|
||||||
|
|
||||||
|
nflags = len(standard_flag)
|
||||||
|
vendor_offset = 1 + 2 * mask_length + 2 + (2 + mask_length) * nflags
|
||||||
|
data = _parse_vendor_features(read_buffer[vendor_offset:],
|
||||||
|
mask_length)
|
||||||
|
vendor_feature, vendor_mask = data
|
||||||
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
msg = 'The ReaderRequirements box (rreq) has a mask length of {0} '
|
msg = 'The ReaderRequirements box (rreq) has a mask length of {0} '
|
||||||
|
|
@ -2187,27 +2234,23 @@ class ReaderRequirementsBox(Jp2kBox):
|
||||||
length=length, offset=offset)
|
length=length, offset=offset)
|
||||||
|
|
||||||
|
|
||||||
def _parse_rreq3(fptr, length, offset):
|
def _parse_rreq3(read_buffer, length, offset):
|
||||||
"""Parse a reader requirements box. Special case when mask length is 3."""
|
"""Parse a reader requirements box. Special case when mask length is 3."""
|
||||||
# Fully Understands Aspect Mask
|
# Fully Understands Aspect Mask
|
||||||
# Decodes Completely Mask
|
# Decodes Completely Mask
|
||||||
read_buffer = fptr.read(2 * 3)
|
|
||||||
|
|
||||||
fuam = dcm = standard_flag = standard_mask = []
|
fuam = dcm = standard_flag = standard_mask = []
|
||||||
vendor_feature = vendor_mask = []
|
vendor_feature = vendor_mask = []
|
||||||
|
|
||||||
# The mask length tells us the format string to use when unpacking
|
# The mask length tells us the format string to use when unpacking
|
||||||
# from the buffer read from file.
|
# from the buffer read from file.
|
||||||
lst = struct.unpack('>BBBBBB', read_buffer)
|
lst = struct.unpack_from('>BBBBBB', read_buffer, offset=1)
|
||||||
fuam = lst[0] << 16 | lst[1] << 8 | lst[2]
|
fuam = lst[0] << 16 | lst[1] << 8 | lst[2]
|
||||||
dcm = lst[3] << 16 | lst[4] << 8 | lst[5]
|
dcm = lst[3] << 16 | lst[4] << 8 | lst[5]
|
||||||
|
|
||||||
read_buffer = fptr.read(2)
|
num_standard_features, = struct.unpack_from('>H', read_buffer, offset=7)
|
||||||
num_standard_features, = struct.unpack('>H', read_buffer)
|
|
||||||
|
|
||||||
fmt = '>' + 'HBBB' * num_standard_features
|
fmt = '>' + 'HBBB' * num_standard_features
|
||||||
read_buffer = fptr.read(num_standard_features * 5)
|
lst = struct.unpack(fmt, read_buffer, offset=9)
|
||||||
lst = struct.unpack(fmt, read_buffer)
|
|
||||||
|
|
||||||
standard_flag = lst[0::4]
|
standard_flag = lst[0::4]
|
||||||
standard_mask = []
|
standard_mask = []
|
||||||
|
|
@ -2216,21 +2259,23 @@ def _parse_rreq3(fptr, length, offset):
|
||||||
mask = items[0] << 16 | items[1] << 8 | items[2]
|
mask = items[0] << 16 | items[1] << 8 | items[2]
|
||||||
standard_mask.append(mask)
|
standard_mask.append(mask)
|
||||||
|
|
||||||
read_buffer = fptr.read(2)
|
boffset = 9 + num_standard_features * 5
|
||||||
num_vendor_features, = struct.unpack('>H', read_buffer)
|
num_vendor_features, = struct.unpack_from('>H', read_buffer,
|
||||||
|
offset=boffset)
|
||||||
|
|
||||||
fmt = '>' + 'HBBB' * num_vendor_features
|
fmt = '>' + 'HBBB' * num_vendor_features
|
||||||
read_buffer = fptr.read(num_vendor_features * 5)
|
buffer_offset = 11 + num_standard_features * 5
|
||||||
lst = struct.unpack(fmt, read_buffer)
|
lst = struct.unpack_from(fmt, read_buffer, offset=buffer_offset)
|
||||||
|
|
||||||
# Each vendor feature consists of a 16-byte UUID plus a mask whose
|
# Each vendor feature consists of a 16-byte UUID plus a mask whose
|
||||||
# length is specified by, you guessed it, "mask_length".
|
# length is specified by, you guessed it, "mask_length".
|
||||||
entry_length = 16 + 3
|
entry_length = 16 + 3
|
||||||
read_buffer = fptr.read(num_vendor_features * entry_length)
|
|
||||||
vendor_feature = []
|
vendor_feature = []
|
||||||
vendor_mask = []
|
vendor_mask = []
|
||||||
|
read_buffer = read_buffer[9 + num_standard_features * 10:]
|
||||||
for j in range(num_vendor_features):
|
for j in range(num_vendor_features):
|
||||||
ubuffer = read_buffer[j * entry_length:(j + 1) * entry_length]
|
uslice = slice(j * entry_length, (j + 1) * entry_length)
|
||||||
|
ubuffer = read_buffer[slice]
|
||||||
vendor_feature.append(uuid.UUID(bytes=ubuffer[0:16]))
|
vendor_feature.append(uuid.UUID(bytes=ubuffer[0:16]))
|
||||||
|
|
||||||
lst = struct.unpack('>BBB', ubuffer[16:])
|
lst = struct.unpack('>BBB', ubuffer[16:])
|
||||||
|
|
@ -2243,7 +2288,7 @@ def _parse_rreq3(fptr, length, offset):
|
||||||
return box
|
return box
|
||||||
|
|
||||||
|
|
||||||
def _parse_standard_flag(fptr, mask_length):
|
def _parse_standard_flag(read_buffer, mask_length):
|
||||||
"""Construct standard flag, standard mask data from the file.
|
"""Construct standard flag, standard mask data from the file.
|
||||||
|
|
||||||
Specifically working on Reader Requirements box.
|
Specifically working on Reader Requirements box.
|
||||||
|
|
@ -2259,16 +2304,16 @@ def _parse_standard_flag(fptr, mask_length):
|
||||||
# from the buffer read from file.
|
# from the buffer read from file.
|
||||||
mask_format = {1: 'B', 2: 'H', 4: 'I'}[mask_length]
|
mask_format = {1: 'B', 2: 'H', 4: 'I'}[mask_length]
|
||||||
|
|
||||||
read_buffer = fptr.read(2)
|
#read_buffer = fptr.read(2)
|
||||||
num_standard_flags, = struct.unpack('>H', read_buffer)
|
num_standard_flags, = struct.unpack_from('>H', read_buffer, offset=0)
|
||||||
|
|
||||||
# Read in standard flags and standard masks. Each standard flag should
|
# Read in standard flags and standard masks. Each standard flag should
|
||||||
# be two bytes, but the standard mask flag is as long as specified by
|
# be two bytes, but the standard mask flag is as long as specified by
|
||||||
# the mask length.
|
# the mask length.
|
||||||
read_buffer = fptr.read(num_standard_flags * (2 + mask_length))
|
#read_buffer = fptr.read(num_standard_flags * (2 + mask_length))
|
||||||
|
|
||||||
fmt = '>' + ('H' + mask_format) * num_standard_flags
|
fmt = '>' + ('H' + mask_format) * num_standard_flags
|
||||||
data = struct.unpack(fmt, read_buffer)
|
data = struct.unpack_from(fmt, read_buffer, offset=2)
|
||||||
|
|
||||||
standard_flag = data[0:num_standard_flags * 2:2]
|
standard_flag = data[0:num_standard_flags * 2:2]
|
||||||
standard_mask = data[1:num_standard_flags * 2:2]
|
standard_mask = data[1:num_standard_flags * 2:2]
|
||||||
|
|
@ -2276,7 +2321,7 @@ def _parse_standard_flag(fptr, mask_length):
|
||||||
return standard_flag, standard_mask
|
return standard_flag, standard_mask
|
||||||
|
|
||||||
|
|
||||||
def _parse_vendor_features(fptr, mask_length):
|
def _parse_vendor_features(read_buffer, mask_length):
|
||||||
"""Construct vendor features, vendor mask data from the file.
|
"""Construct vendor features, vendor mask data from the file.
|
||||||
|
|
||||||
Specifically working on Reader Requirements box.
|
Specifically working on Reader Requirements box.
|
||||||
|
|
@ -2292,17 +2337,17 @@ def _parse_vendor_features(fptr, mask_length):
|
||||||
# from the buffer read from file.
|
# from the buffer read from file.
|
||||||
mask_format = {1: 'B', 2: 'H', 4: 'I'}[mask_length]
|
mask_format = {1: 'B', 2: 'H', 4: 'I'}[mask_length]
|
||||||
|
|
||||||
read_buffer = fptr.read(2)
|
num_vendor_features, = struct.unpack_from('>H', read_buffer)
|
||||||
num_vendor_features, = struct.unpack('>H', read_buffer)
|
|
||||||
|
|
||||||
# Each vendor feature consists of a 16-byte UUID plus a mask whose
|
# Each vendor feature consists of a 16-byte UUID plus a mask whose
|
||||||
# length is specified by, you guessed it, "mask_length".
|
# length is specified by, you guessed it, "mask_length".
|
||||||
entry_length = 16 + mask_length
|
entry_length = 16 + mask_length
|
||||||
read_buffer = fptr.read(num_vendor_features * entry_length)
|
#read_buffer = fptr.read(num_vendor_features * entry_length)
|
||||||
vendor_feature = []
|
vendor_feature = []
|
||||||
vendor_mask = []
|
vendor_mask = []
|
||||||
for j in range(num_vendor_features):
|
for j in range(num_vendor_features):
|
||||||
ubuffer = read_buffer[j * entry_length:(j + 1) * entry_length]
|
uslice = slice(2 + j * entry_length, 2 + (j + 1) * entry_length)
|
||||||
|
ubuffer = read_buffer[uslice]
|
||||||
vendor_feature.append(uuid.UUID(bytes=ubuffer[0:16]))
|
vendor_feature.append(uuid.UUID(bytes=ubuffer[0:16]))
|
||||||
|
|
||||||
vmask = struct.unpack('>' + mask_format, ubuffer[16:])
|
vmask = struct.unpack('>' + mask_format, ubuffer[16:])
|
||||||
|
|
@ -2985,14 +3030,13 @@ class DataEntryURLBox(Jp2kBox):
|
||||||
-------
|
-------
|
||||||
DataEntryURLbox instance
|
DataEntryURLbox instance
|
||||||
"""
|
"""
|
||||||
read_buffer = fptr.read(4)
|
num_bytes = offset + length - fptr.tell()
|
||||||
data = struct.unpack('>BBBB', read_buffer)
|
read_buffer = fptr.read(num_bytes)
|
||||||
|
data = struct.unpack_from('>BBBB', read_buffer)
|
||||||
version = data[0]
|
version = data[0]
|
||||||
flag = data[1:4]
|
flag = data[1:4]
|
||||||
|
|
||||||
numbytes = offset + length - fptr.tell()
|
url = read_buffer[4:].decode('utf-8').rstrip(chr(0))
|
||||||
read_buffer = fptr.read(numbytes)
|
|
||||||
url = read_buffer.decode('utf-8').rstrip(chr(0))
|
|
||||||
return cls(version, flag, url, length=length, offset=offset)
|
return cls(version, flag, url, length=length, offset=offset)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -3159,13 +3203,10 @@ class UUIDBox(Jp2kBox):
|
||||||
-------
|
-------
|
||||||
UUIDBox instance
|
UUIDBox instance
|
||||||
"""
|
"""
|
||||||
|
num_bytes = offset + length - fptr.tell()
|
||||||
read_buffer = fptr.read(16)
|
read_buffer = fptr.read(num_bytes)
|
||||||
the_uuid = uuid.UUID(bytes=read_buffer)
|
the_uuid = uuid.UUID(bytes=read_buffer[0:16])
|
||||||
|
return cls(the_uuid, read_buffer[16:], length=length, offset=offset)
|
||||||
numbytes = offset + length - fptr.tell()
|
|
||||||
read_buffer = fptr.read(numbytes)
|
|
||||||
return cls(the_uuid, read_buffer, length=length, offset=offset)
|
|
||||||
|
|
||||||
|
|
||||||
# Map each box ID to the corresponding class.
|
# Map each box ID to the corresponding class.
|
||||||
|
|
@ -3199,6 +3240,50 @@ _BOX_WITH_ID = {
|
||||||
b'uuid': UUIDBox,
|
b'uuid': UUIDBox,
|
||||||
b'xml ': XMLBox}
|
b'xml ': XMLBox}
|
||||||
|
|
||||||
|
_parseoptions = {'codestream': True}
|
||||||
|
|
||||||
|
def set_parseoptions(codestream=True):
|
||||||
|
"""Set parsing options.
|
||||||
|
|
||||||
|
These options determine the way JPEG 2000 boxes are parsed.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
codestream : bool, defaults to True
|
||||||
|
When False, the codestream header is only parsed when accessed. This
|
||||||
|
can results in faster JP2/JPX parsing.
|
||||||
|
|
||||||
|
See also
|
||||||
|
--------
|
||||||
|
get_parseoptions
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
To put back the default options, you can use:
|
||||||
|
|
||||||
|
>>> import glymur
|
||||||
|
>>> glymur.set_parseoptions(codestream=True)
|
||||||
|
"""
|
||||||
|
_parseoptions['codestream'] = codestream
|
||||||
|
|
||||||
|
def get_parseoptions():
|
||||||
|
"""Return the current parsing options.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
print_opts : dict
|
||||||
|
Dictionary of current print options with keys
|
||||||
|
|
||||||
|
- codestream : bool
|
||||||
|
|
||||||
|
For a full description of these options, see `set_parseoptions`.
|
||||||
|
|
||||||
|
See also
|
||||||
|
--------
|
||||||
|
set_parseoptions
|
||||||
|
"""
|
||||||
|
return _parseoptions
|
||||||
|
|
||||||
_printoptions = {'short': False, 'xml': True, 'codestream': True}
|
_printoptions = {'short': False, 'xml': True, 'codestream': True}
|
||||||
|
|
||||||
def set_printoptions(**kwargs):
|
def set_printoptions(**kwargs):
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ class TestICC(unittest.TestCase):
|
||||||
# The file has a bad compatibility list entry. Not important here.
|
# The file has a bad compatibility list entry. Not important here.
|
||||||
warnings.simplefilter("ignore")
|
warnings.simplefilter("ignore")
|
||||||
j = Jp2k(filename)
|
j = Jp2k(filename)
|
||||||
profile = j.box[2].box[1].icc_profile
|
profile = j.box[3].box[1].icc_profile
|
||||||
self.assertEqual(profile['Size'], 546)
|
self.assertEqual(profile['Size'], 546)
|
||||||
self.assertEqual(profile['Preferred CMM Type'], 0)
|
self.assertEqual(profile['Preferred CMM Type'], 0)
|
||||||
self.assertEqual(profile['Version'], '2.2.0')
|
self.assertEqual(profile['Version'], '2.2.0')
|
||||||
|
|
|
||||||
|
|
@ -754,6 +754,31 @@ class TestJp2k_2_1(unittest.TestCase):
|
||||||
with self.assertRaisesRegex((IOError, OSError), regexp):
|
with self.assertRaisesRegex((IOError, OSError), regexp):
|
||||||
j.read(rlevel=1)
|
j.read(rlevel=1)
|
||||||
|
|
||||||
|
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
||||||
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
|
class TestParsing(unittest.TestCase):
|
||||||
|
"""Tests for verifying how paring may be altered."""
|
||||||
|
def setUp(self):
|
||||||
|
# Reset parseoptions for every test.
|
||||||
|
glymur.set_parseoptions(codestream=True)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_bad_rsiz(self):
|
||||||
|
"""Should not warn if RSIZ when parsing is turned off."""
|
||||||
|
# Actually there are three warning triggered by this codestream.
|
||||||
|
filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2')
|
||||||
|
glymur.set_parseoptions(codestream=False)
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
j = Jp2k(filename)
|
||||||
|
self.assertEqual(len(w), 0)
|
||||||
|
|
||||||
|
glymur.set_parseoptions(codestream=True)
|
||||||
|
if sys.hexversion >= 0x03000000:
|
||||||
|
with self.assertWarns(UserWarning):
|
||||||
|
jp2 = Jp2k(filename)
|
||||||
|
|
||||||
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
||||||
"OPJ_DATA_ROOT environment variable not set")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
class TestJp2kOpjDataRoot(unittest.TestCase):
|
class TestJp2kOpjDataRoot(unittest.TestCase):
|
||||||
|
|
|
||||||
|
|
@ -358,7 +358,11 @@ class TestSuite(unittest.TestCase):
|
||||||
|
|
||||||
def test_ETS_JP2_file5(self):
|
def test_ETS_JP2_file5(self):
|
||||||
jfile = opj_data_file('input/conformance/file5.jp2')
|
jfile = opj_data_file('input/conformance/file5.jp2')
|
||||||
jp2k = Jp2k(jfile)
|
with warnings.catch_warnings():
|
||||||
|
# There's a warning for an unknown compatibility entry.
|
||||||
|
# Ignore it here.
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
jpdata = jp2k.read()
|
jpdata = jp2k.read()
|
||||||
self.assertEqual(jpdata.shape, (512, 768, 3))
|
self.assertEqual(jpdata.shape, (512, 768, 3))
|
||||||
|
|
||||||
|
|
@ -3306,40 +3310,44 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
# profile and using the JPX-defined enumerated code for the ROMM-RGB
|
# profile and using the JPX-defined enumerated code for the ROMM-RGB
|
||||||
# colourspace.
|
# colourspace.
|
||||||
jfile = opj_data_file('input/conformance/file5.jp2')
|
jfile = opj_data_file('input/conformance/file5.jp2')
|
||||||
jp2 = Jp2k(jfile)
|
with warnings.catch_warnings():
|
||||||
|
# There's a warning for an unknown compatibility entry.
|
||||||
|
# Ignore it here.
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
jp2 = Jp2k(jfile)
|
||||||
|
|
||||||
ids = [box.box_id for box in jp2.box]
|
ids = [box.box_id for box in jp2.box]
|
||||||
self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c'])
|
self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c'])
|
||||||
|
|
||||||
ids = [box.box_id for box in jp2.box[2].box]
|
ids = [box.box_id for box in jp2.box[3].box]
|
||||||
self.assertEqual(ids, ['ihdr', 'colr'])
|
self.assertEqual(ids, ['ihdr', 'colr', 'colr'])
|
||||||
|
|
||||||
# Signature box. Check for corruption.
|
# Signature box. Check for corruption.
|
||||||
self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10))
|
self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10))
|
||||||
|
|
||||||
# File type box.
|
# File type box.
|
||||||
self.assertEqual(jp2.box[1].brand, 'jp2 ')
|
self.assertEqual(jp2.box[1].brand, 'jpx ')
|
||||||
self.assertEqual(jp2.box[1].minor_version, 0)
|
self.assertEqual(jp2.box[1].minor_version, 0)
|
||||||
self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ')
|
self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ')
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
# Image header
|
# Image header
|
||||||
self.assertEqual(jp2.box[2].box[0].height, 512)
|
self.assertEqual(jp2.box[3].box[0].height, 512)
|
||||||
self.assertEqual(jp2.box[2].box[0].width, 768)
|
self.assertEqual(jp2.box[3].box[0].width, 768)
|
||||||
self.assertEqual(jp2.box[2].box[0].num_components, 3)
|
self.assertEqual(jp2.box[3].box[0].num_components, 3)
|
||||||
self.assertEqual(jp2.box[2].box[0].signed, False)
|
self.assertEqual(jp2.box[3].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
|
self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
# Colour specification
|
# Colour specification
|
||||||
self.assertEqual(jp2.box[2].box[1].method,
|
self.assertEqual(jp2.box[3].box[1].method,
|
||||||
glymur.core.RESTRICTED_ICC_PROFILE) # enumerated
|
glymur.core.RESTRICTED_ICC_PROFILE) # enumerated
|
||||||
self.assertEqual(jp2.box[2].box[1].precedence, 0)
|
self.assertEqual(jp2.box[3].box[1].precedence, 0)
|
||||||
self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact
|
self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact
|
||||||
self.assertEqual(jp2.box[2].box[1].icc_profile['Size'], 546)
|
self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 546)
|
||||||
self.assertIsNone(jp2.box[2].box[1].colorspace)
|
self.assertIsNone(jp2.box[3].box[1].colorspace)
|
||||||
|
|
||||||
def test_NR_file6_dump(self):
|
def test_NR_file6_dump(self):
|
||||||
jfile = opj_data_file('input/conformance/file6.jp2')
|
jfile = opj_data_file('input/conformance/file6.jp2')
|
||||||
|
|
@ -3390,37 +3398,37 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
jp2 = Jp2k(jfile)
|
jp2 = Jp2k(jfile)
|
||||||
|
|
||||||
ids = [box.box_id for box in jp2.box]
|
ids = [box.box_id for box in jp2.box]
|
||||||
self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c'])
|
self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c'])
|
||||||
|
|
||||||
ids = [box.box_id for box in jp2.box[2].box]
|
ids = [box.box_id for box in jp2.box[3].box]
|
||||||
self.assertEqual(ids, ['ihdr', 'colr'])
|
self.assertEqual(ids, ['ihdr', 'colr', 'colr'])
|
||||||
|
|
||||||
# Signature box. Check for corruption.
|
# Signature box. Check for corruption.
|
||||||
self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10))
|
self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10))
|
||||||
|
|
||||||
# File type box.
|
# File type box.
|
||||||
self.assertEqual(jp2.box[1].brand, 'jp2 ')
|
self.assertEqual(jp2.box[1].brand, 'jpx ')
|
||||||
self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ')
|
self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ')
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
# Image header
|
# Image header
|
||||||
self.assertEqual(jp2.box[2].box[0].height, 640)
|
self.assertEqual(jp2.box[3].box[0].height, 640)
|
||||||
self.assertEqual(jp2.box[2].box[0].width, 480)
|
self.assertEqual(jp2.box[3].box[0].width, 480)
|
||||||
self.assertEqual(jp2.box[2].box[0].num_components, 3)
|
self.assertEqual(jp2.box[3].box[0].num_components, 3)
|
||||||
self.assertEqual(jp2.box[2].box[0].bits_per_component, 16)
|
self.assertEqual(jp2.box[3].box[0].bits_per_component, 16)
|
||||||
self.assertEqual(jp2.box[2].box[0].signed, False)
|
self.assertEqual(jp2.box[3].box[0].signed, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet
|
||||||
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
|
self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
self.assertEqual(jp2.box[3].box[0].ip_provided, False)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
# Colour specification
|
# Colour specification
|
||||||
self.assertEqual(jp2.box[2].box[1].method,
|
self.assertEqual(jp2.box[3].box[1].method,
|
||||||
glymur.core.RESTRICTED_ICC_PROFILE)
|
glymur.core.RESTRICTED_ICC_PROFILE)
|
||||||
self.assertEqual(jp2.box[2].box[1].precedence, 0)
|
self.assertEqual(jp2.box[3].box[1].precedence, 0)
|
||||||
self.assertEqual(jp2.box[2].box[1].approximation, 1)
|
self.assertEqual(jp2.box[3].box[1].approximation, 1)
|
||||||
self.assertEqual(jp2.box[2].box[1].icc_profile['Size'], 13332)
|
self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 13332)
|
||||||
self.assertIsNone(jp2.box[2].box[1].colorspace)
|
self.assertIsNone(jp2.box[3].box[1].colorspace)
|
||||||
|
|
||||||
def test_NR_file8_dump(self):
|
def test_NR_file8_dump(self):
|
||||||
# One 8-bit component in a gamma 1.8 space. The colourspace is
|
# One 8-bit component in a gamma 1.8 space. The colourspace is
|
||||||
|
|
@ -3447,7 +3455,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
# Image header
|
# Image header
|
||||||
self.assertEqual(jp2.box[2].box[0].height, 400)
|
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].width, 700)
|
||||||
self.assertEqual(jp2.box[2].box[0].num_components, 3)
|
self.assertEqual(jp2.box[2].box[0].num_components, 1)
|
||||||
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
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].signed, False)
|
||||||
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet
|
||||||
|
|
@ -5784,6 +5792,7 @@ class TestSuiteDump(unittest.TestCase):
|
||||||
self.assertEqual(c.segment[4].ccme.decode('latin-1'),
|
self.assertEqual(c.segment[4].ccme.decode('latin-1'),
|
||||||
"Kakadu-v5.2.1")
|
"Kakadu-v5.2.1")
|
||||||
|
|
||||||
|
@unittest.skip("Bad PCLR box")
|
||||||
def test_NR_mem_b2ace68c_1381_dump(self):
|
def test_NR_mem_b2ace68c_1381_dump(self):
|
||||||
jfile = opj_data_file('input/nonregression/mem-b2ace68c-1381.jp2')
|
jfile = opj_data_file('input/nonregression/mem-b2ace68c-1381.jp2')
|
||||||
with warnings.catch_warnings():
|
with warnings.catch_warnings():
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue