diff --git a/CHANGES.txt b/CHANGES.txt
index 42b5b4b..1874042 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,7 @@
+Aug 13, 2013 - v0.3.1 Exposed mantissa, exponent, and guard_bits fields in QCC
+ and QCD segments. Exposed layers and code_block_size in COD segment.
+ Exposed precinct_size in COC segment.
+
Jul 31, 2013 - v0.3.0 Added support for official 2.0.0.
Jul 27, 2013 - v0.2.8 Fixed inconsistency regarding configuration
diff --git a/glymur/__init__.py b/glymur/__init__.py
index fb1c504..cc80a72 100644
--- a/glymur/__init__.py
+++ b/glymur/__init__.py
@@ -8,6 +8,8 @@ from .jp2dump import jp2dump
from . import data
+# unittest2 only in python-2.6 (pylint/python2.7 issue)
+# pylint: disable=F0401
def runtests():
"""Discover and run all tests for the glymur package.
"""
diff --git a/glymur/codestream.py b/glymur/codestream.py
index 2c24bef..7a32190 100644
--- a/glymur/codestream.py
+++ b/glymur/codestream.py
@@ -3,7 +3,19 @@
The module contains classes used to store information parsed from JPEG 2000
codestreams.
"""
-# pylint: disable=C0302,R0902,R0903,R0913
+
+# The number of lines in the module is long and that's ok. It would not help
+# matters to move anything out to another file.
+# pylint: disable=C0302
+
+# "Too many instance attributes", "Too many arguments"
+# Some segments just have a lot of information.
+# It doesn't make sense to subclass just for that.
+# pylint: disable=R0902,R0913
+
+# "Too few public methods" Some segments don't define any new methods from
+# the base Segment class.
+# pylint: disable=R0903
import math
import struct
@@ -775,12 +787,15 @@ class Segment(object):
length : int
Length of marker segment in bytes. This number does not include the
two bytes constituting the marker.
+ data : bytes iterable or None
+ Uninterpreted buffer of raw bytes, only used where a segment is not
+ well understood.
"""
def __init__(self, marker_id='', offset=-1, length=-1, data=None):
self.marker_id = marker_id
self.offset = offset
self.length = length
- self._data = data
+ self.data = data
def __str__(self):
msg = '{0} marker segment @ ({1}, {2})'.format(self.marker_id,
@@ -807,6 +822,8 @@ class COCsegment(Segment):
Coding style for this component.
spcoc : byte array
Coding style parameters for this component.
+ precinct_size : list of tuples
+ Dimensions of precinct.
References
----------
@@ -820,13 +837,13 @@ class COCsegment(Segment):
self.scoc = scoc
self.spcoc = spcoc
- self._code_block_size = (4 * math.pow(2, self.spcoc[2]),
- 4 * math.pow(2, self.spcoc[1]))
+ self.code_block_size = (4 * math.pow(2, self.spcoc[2]),
+ 4 * math.pow(2, self.spcoc[1]))
if len(self.spcoc) > 5:
- self._precinct_size = _parse_precinct_size(self.spcoc[5:])
+ self.precinct_size = _parse_precinct_size(self.spcoc[5:])
else:
- self._precinct_size = None
+ self.precinct_size = None
self.length = length
self.offset = offset
@@ -847,16 +864,16 @@ class COCsegment(Segment):
msg += '\n Code block height, width: ({1} x {2})'
msg += '\n Wavelet transform: {3}'
msg = msg.format(self.spcoc[0] + 1,
- int(self._code_block_size[0]),
- int(self._code_block_size[1]),
+ int(self.code_block_size[0]),
+ int(self.code_block_size[1]),
_WAVELET_TRANSFORM_DISPLAY[self.spcoc[4]])
msg += '\n '
msg += _context_string(self.spcoc[3])
- if self._precinct_size is not None:
+ if self.precinct_size is not None:
msg += '\n Precinct size: '
- for pps in self._precinct_size:
+ for pps in self.precinct_size:
msg += '(%d, %d)'.format(pps)
return msg
@@ -876,10 +893,16 @@ class CODsegment(Segment):
two bytes constituting the marker.
scod : int
Default coding style.
+ layers : int
+ Quality layers.
+ code_block_size : tuple
+ Size of code block.
spcod : bytes
- Coding style parameters, including quality layers, multicomponent
- transform usage, decomposition levels, code block size, style of code-
- block passes, and which wavelet transform is used.
+ Encoded coding style parameters, including quality layers,
+ multi component transform usage, decomposition levels, code block size,
+ style of code-block passes, and which wavelet transform is used.
+ precinct_size : list of tuples
+ Dimensions of precinct.
References
----------
@@ -895,7 +918,7 @@ class CODsegment(Segment):
self.offset = offset
params = struct.unpack('>BHBBBBBB', self.spcod[0:9])
- self._layers = params[1]
+ self.layers = params[1]
self._numresolutions = params[3]
if params[3] > opj2.J2K_MAXRLVLS:
@@ -906,12 +929,12 @@ class CODsegment(Segment):
cblk_width = 4 * math.pow(2, params[4])
cblk_height = 4 * math.pow(2, params[5])
code_block_size = (cblk_height, cblk_width)
- self._code_block_size = code_block_size
+ self.code_block_size = code_block_size
if len(self.spcod) > 9:
- self._precinct_size = _parse_precinct_size(self.spcod[9:])
+ self.precinct_size = _parse_precinct_size(self.spcod[9:])
else:
- self._precinct_size = None
+ self.precinct_size = None
def __str__(self):
msg = Segment.__str__(self)
@@ -944,18 +967,18 @@ class CODsegment(Segment):
msg += '\n '.join(lines)
msg = msg.format(_PROGRESSION_ORDER_DISPLAY[self.spcod[0]],
- self._layers,
+ self.layers,
mct,
self.spcod[4] + 1,
- int(self._code_block_size[0]),
- int(self._code_block_size[1]),
+ int(self.code_block_size[0]),
+ int(self.code_block_size[1]),
_WAVELET_TRANSFORM_DISPLAY[self.spcod[8]])
msg += '\n Precinct size: '
- if self._precinct_size is None:
+ if self.precinct_size is None:
msg += 'default, 2^15 x 2^15'
else:
- for pps in self._precinct_size:
+ for pps in self.precinct_size:
msg += '({0}, {1})'.format(pps[0], pps[1])
msg += '\n '
@@ -1195,8 +1218,8 @@ class PPMsegment(Segment):
Segment.__init__(self, marker_id='PPM')
self.zppm = zppm
- # both Nppm and Ippms information stored in _data
- self._data = data
+ # both Nppm and Ippms information stored in data
+ self.data = data
self.length = length
self.offset = offset
@@ -1205,7 +1228,7 @@ class PPMsegment(Segment):
msg = Segment.__str__(self)
msg += '\n Index: {0}'
msg += '\n Data: {1} uninterpreted bytes'
- msg = msg.format(self.zppm, len(self._data))
+ msg = msg.format(self.zppm, len(self.data))
return msg
@@ -1265,6 +1288,10 @@ class QCCsegment(Segment):
Quantization style for this component.
spqcc : iterable bytes
Quantization value for each sub-band.
+ mantissa, exponent : iterable
+ Defines quantization factors.
+ guard_bits : int
+ Number of guard bits.
References
----------
@@ -1280,18 +1307,18 @@ class QCCsegment(Segment):
self.length = length
self.offset = offset
- self._mantissa, self._exponent = parse_quantization(self.spqcc,
+ self.mantissa, self.exponent = parse_quantization(self.spqcc,
self.sqcc)
- self._guard_bits = (self.sqcc & 0xe0) >> 5
+ self.guard_bits = (self.sqcc & 0xe0) >> 5
def __str__(self):
msg = Segment.__str__(self)
msg += '\n Associated Component: {0}'.format(self.cqcc)
msg += _print_quantization_style(self.sqcc)
- msg += '{0} guard bits'.format(self._guard_bits)
+ msg += '{0} guard bits'.format(self.guard_bits)
- step_size = zip(self._mantissa, self._exponent)
+ step_size = zip(self.mantissa, self.exponent)
msg += '\n Step size: ' + str(list(step_size))
return msg
@@ -1312,6 +1339,10 @@ class QCDsegment(Segment):
Quantization style for all components.
spqcd : iterable bytes
Quantization step size values (uninterpreted).
+ mantissa, exponent : iterable
+ Defines quantization factors.
+ guard_bits : int
+ Number of guard bits.
References
----------
@@ -1328,18 +1359,18 @@ class QCDsegment(Segment):
self.offset = offset
mantissa, exponent = parse_quantization(self.spqcd, self.sqcd)
- self._mantissa = mantissa
- self._exponent = exponent
- self._guard_bits = (self.sqcd & 0xe0) >> 5
+ self.mantissa = mantissa
+ self.exponent = exponent
+ self.guard_bits = (self.sqcd & 0xe0) >> 5
def __str__(self):
msg = Segment.__str__(self)
msg += _print_quantization_style(self.sqcd)
- msg += '{0} guard bits'.format(self._guard_bits)
+ msg += '{0} guard bits'.format(self.guard_bits)
- step_size = zip(self._mantissa, self._exponent)
+ step_size = zip(self.mantissa, self.exponent)
msg += '\n Step size: ' + str(list(step_size))
return msg
@@ -1411,7 +1442,11 @@ class SIZsegment(Segment):
xtosiz, ytosiz : int
Horizontal and vertical offsets of tile from origin of reference grid.
ssiz : iterable bytes
- Precision (depth) in bits and sign of each component.
+ Encoded precision (depth) in bits and sign of each component.
+ bitdepth : iterable bytes
+ Precision (depth) in bits of each component.
+ signed : iterable bool
+ Signedness of each component.
xrsiz, yrsiz : int
Horizontal and vertical sample separations with respect to reference
grid.
@@ -1459,8 +1494,8 @@ class SIZsegment(Segment):
self.xrsiz = data[1::3]
self.yrsiz = data[2::3]
- self._bitdepth = tuple(((x & 0x7f) + 1) for x in self.ssiz)
- self._signed = tuple(((x & 0xb0) > 0) for x in self.ssiz)
+ self.bitdepth = tuple(((x & 0x7f) + 1) for x in self.ssiz)
+ self.signed = tuple(((x & 0xb0) > 0) for x in self.ssiz)
self.length = length
self.offset = offset
@@ -1483,8 +1518,8 @@ class SIZsegment(Segment):
self.yosiz, self.xosiz,
self.ytsiz, self.xtsiz,
self.ytosiz, self.xtosiz,
- self._bitdepth,
- self._signed,
+ self.bitdepth,
+ self.signed,
tuple(zip(self.yrsiz, self.xrsiz)))
return msg
diff --git a/glymur/jp2box.py b/glymur/jp2box.py
index 5bbe733..6d5c3ee 100644
--- a/glymur/jp2box.py
+++ b/glymur/jp2box.py
@@ -24,6 +24,7 @@ import uuid
import warnings
import xml.etree.cElementTree as ET
if sys.hexversion < 0x02070000:
+ # pylint: disable=F0401,E0611
from ordereddict import OrderedDict
from xml.etree.cElementTree import XMLParserError as ParseError
else:
@@ -361,7 +362,8 @@ class _ICCProfile(object):
header['Connection Space'] = data
data = struct.unpack('>HHHHHH', self._raw_buffer[24:36])
- header['Datetime'] = datetime.datetime(*data)
+ header['Datetime'] = datetime.datetime(data[0], data[1], data[2],
+ data[3], data[4], data[5])
header['File Signature'] = read_buffer[36:40].decode('utf-8')
if read_buffer[40:44] == b'\x00\x00\x00\x00':
header['Platform'] = 'unrecognized'
@@ -369,10 +371,9 @@ class _ICCProfile(object):
header['Platform'] = read_buffer[40:44].decode('utf-8')
fval, = struct.unpack('>I', read_buffer[44:48])
- flags = 'embedded, ' if fval & 0x01 else 'not embedded, '
- flags += 'cannot ' if fval & 0x02 else 'can '
- flags += 'be used independently'
- header['Flags'] = flags
+ flags = "{0}embedded, {1} be used independently"
+ header['Flags'] = flags.format('' if fval & 0x01 else 'not ',
+ 'cannot' if fval & 0x02 else 'can')
header['Device Manufacturer'] = read_buffer[48:52].decode('utf-8')
if read_buffer[52:56] == b'\x00\x00\x00\x00':
@@ -382,11 +383,11 @@ class _ICCProfile(object):
header['Device Model'] = device_model
val, = struct.unpack('>Q', read_buffer[56:64])
- attr = 'transparency, ' if val & 0x01 else 'reflective, '
- attr += 'matte, ' if val & 0x02 else 'glossy, '
- attr += 'negative ' if val & 0x04 else 'positive '
- attr += 'media polarity, '
- attr += 'black and white media' if val & 0x08 else 'color media'
+ attr = "{0}, {1}, {2} media polarity, {3} media"
+ attr = attr.format('transparency' if val & 0x01 else 'reflective',
+ 'matte' if val & 0x02 else 'glossy',
+ 'negative' if val & 0x04 else 'positive',
+ 'black and white' if val & 0x08 else 'color')
header['Device Attributes'] = attr
rval, = struct.unpack('>I', read_buffer[64:68])
@@ -1237,43 +1238,68 @@ class PaletteBox(Jp2kBox):
bps = [((x & 0x07f) + 1) for x in data]
signed = [((x & 0x80) > 1) for x in data]
+ # Each palette component is padded out to the next largest byte.
+ # That means a list comprehension does this in one shot.
+ row_nbytes = sum([int(math.ceil(x/8.0)) for x in bps])
+
# Form the format string so that we can intelligently unpack the
# colormap. We have to do this because it is possible that the
# colormap columns could have different datatypes.
#
# This means that we store the palette as a list of 1D arrays,
# which reverses the usual indexing scheme.
- palette = []
- fmt = '>'
- row_nbytes = 0
- for j in range(num_columns):
- if bps[j] <= 8:
- fmt += 'B'
- row_nbytes += 1
- palette.append(np.zeros(num_entries, dtype=np.uint8))
- elif bps[j] <= 16:
- fmt += 'H'
- row_nbytes += 2
- palette.append(np.zeros(num_entries, dtype=np.uint16))
- elif bps[j] <= 32:
- fmt += 'I'
- row_nbytes += 4
- palette.append(np.zeros(num_entries, dtype=np.uint32))
- else:
- msg = 'Unsupported palette bitdepth (%d).'.format(bps[j])
- raise IOError(msg)
read_buffer = fptr.read(num_entries * row_nbytes)
+ palette = _buffer2palette(read_buffer, num_entries, num_columns, bps)
- for j in range(num_entries):
- row_buffer = read_buffer[(row_nbytes * j):(row_nbytes * (j + 1))]
- row = struct.unpack(fmt, row_buffer)
- for k in range(num_columns):
- palette[k][j] = row[k]
-
- box = PaletteBox(palette, bps, signed, length=length,
- offset=offset)
+ box = PaletteBox(palette, bps, signed, length=length, offset=offset)
return box
+
+def _buffer2palette(read_buffer, num_rows, num_cols, bps):
+ """Construct the palette from the buffer read from file.
+
+ Parameters
+ ----------
+ read_buffer : iterable
+ Byte array of palette information read from file.
+ num_rows, num_cols : int
+ Size of palette.
+ bps : iterable
+ Bits per sample for each channel.
+
+ Returns
+ -------
+ palette : list of 1D arrays
+ Each 1D array corresponds to a channel.
+ """
+ row_nbytes = 0
+ palette = []
+ fmt = '>'
+ for j in range(num_cols):
+ if bps[j] <= 8:
+ row_nbytes += 1
+ fmt += 'B'
+ palette.append(np.zeros(num_rows, dtype=np.uint8))
+ elif bps[j] <= 16:
+ row_nbytes += 2
+ fmt += 'H'
+ palette.append(np.zeros(num_rows, dtype=np.uint16))
+ elif bps[j] <= 32:
+ row_nbytes += 4
+ fmt += 'I'
+ palette.append(np.zeros(num_rows, dtype=np.uint32))
+ else:
+ msg = 'Unsupported palette bitdepth (%d).'.format(bps[j])
+ raise IOError(msg)
+
+ for j in range(num_rows):
+ row_buffer = read_buffer[(row_nbytes * j):(row_nbytes * (j + 1))]
+ row = struct.unpack(fmt, row_buffer)
+ for k in range(num_cols):
+ palette[k][j] = row[k]
+
+ return palette
+
# Map rreq codes to display text.
_READER_REQUIREMENTS_DISPLAY = {
0: 'File not completely understood',
@@ -1434,52 +1460,18 @@ class ReaderRequirementsBox(Jp2kBox):
"""
read_buffer = fptr.read(1)
mask_length, = struct.unpack('>B', read_buffer)
- if mask_length == 1:
- mask_format = 'B'
- elif mask_length == 2:
- mask_format = 'H'
- elif mask_length == 4:
- mask_format = 'I'
- else:
- msg = 'Unhandled reader requirements box mask length (%d).'
- msg %= mask_length
- raise RuntimeError(msg)
# Fully Understands Aspect Mask
# Decodes Completely Mask
read_buffer = fptr.read(2 * mask_length)
- data = struct.unpack('>' + mask_format * 2, read_buffer)
- fuam = data[0]
- dcm = data[1]
- read_buffer = fptr.read(2)
- num_standard_flags, = struct.unpack('>H', read_buffer)
+ # The mask length tells us the format string to use when unpacking
+ # from the buffer read from file.
+ mask_format = {1: 'B', 2: 'H', 4: 'I'}[mask_length]
+ fuam, dcm = struct.unpack('>' + mask_format * 2, read_buffer)
- # 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
- # the mask length.
- read_buffer = fptr.read(num_standard_flags * (2 + mask_length))
- data = struct.unpack('>' + ('H' + mask_format) * num_standard_flags,
- read_buffer)
- standard_flag = data[0:num_standard_flags * 2:2]
- standard_mask = data[1:num_standard_flags * 2:2]
-
- # Vendor features
- read_buffer = fptr.read(2)
- num_vendor_features, = struct.unpack('>H', read_buffer)
-
- # Each vendor feature consists of a 16-byte UUID plus a mask whose
- # length is specified by, you guessed it, "mask_length".
- entry_length = 16 + mask_length
- read_buffer = fptr.read(num_vendor_features * entry_length)
- vendor_feature = []
- vendor_mask = []
- for j in range(num_vendor_features):
- ubuffer = read_buffer[j * entry_length:(j + 1) * entry_length]
- vendor_feature.append(uuid.UUID(bytes=ubuffer[0:16]))
-
- vmask = struct.unpack('>' + mask_format, ubuffer[16:])
- vendor_mask.append(vmask)
+ standard_flag, standard_mask = _parse_standard_flag(fptr, mask_length)
+ vendor_feature, vendor_mask = _parse_vendor_features(fptr, mask_length)
box = ReaderRequirementsBox(fuam, dcm, standard_flag, standard_mask,
vendor_feature, vendor_mask,
@@ -1487,6 +1479,74 @@ class ReaderRequirementsBox(Jp2kBox):
return box
+def _parse_standard_flag(fptr, mask_length):
+ """Construct standard flag, standard mask data from the file.
+
+ Specifically working on Reader Requirements box.
+
+ Parameters
+ ----------
+ fptr : file object
+ File object for JP2K file.
+ mask_length : int
+ Length of standard mask flag
+ """
+ # The mask length tells us the format string to use when unpacking
+ # from the buffer read from file.
+ mask_format = {1: 'B', 2: 'H', 4: 'I'}[mask_length]
+
+ read_buffer = fptr.read(2)
+ num_standard_flags, = struct.unpack('>H', read_buffer)
+
+ # 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
+ # the mask length.
+ read_buffer = fptr.read(num_standard_flags * (2 + mask_length))
+
+ fmt = '>' + ('H' + mask_format) * num_standard_flags
+ data = struct.unpack(fmt, read_buffer)
+
+ standard_flag = data[0:num_standard_flags * 2:2]
+ standard_mask = data[1:num_standard_flags * 2:2]
+
+ return standard_flag, standard_mask
+
+
+def _parse_vendor_features(fptr, mask_length):
+ """Construct vendor features, vendor mask data from the file.
+
+ Specifically working on Reader Requirements box.
+
+ Parameters
+ ----------
+ fptr : file object
+ File object for JP2K file.
+ mask_length : int
+ Length of vendor mask flag
+ """
+ # The mask length tells us the format string to use when unpacking
+ # from the buffer read from file.
+ mask_format = {1: 'B', 2: 'H', 4: 'I'}[mask_length]
+
+ read_buffer = fptr.read(2)
+ num_vendor_features, = struct.unpack('>H', read_buffer)
+
+ # Each vendor feature consists of a 16-byte UUID plus a mask whose
+ # length is specified by, you guessed it, "mask_length".
+ entry_length = 16 + mask_length
+ read_buffer = fptr.read(num_vendor_features * entry_length)
+ vendor_feature = []
+ vendor_mask = []
+ for j in range(num_vendor_features):
+ ubuffer = read_buffer[j * entry_length:(j + 1) * entry_length]
+ vendor_feature.append(uuid.UUID(bytes=ubuffer[0:16]))
+
+ vmask = struct.unpack('>' + mask_format, ubuffer[16:])
+ vendor_mask.append(vmask)
+
+ return vendor_feature, vendor_mask
+
+
class ResolutionBox(Jp2kBox):
"""Container for Resolution superbox information.
diff --git a/glymur/jp2k.py b/glymur/jp2k.py
index ff3b92a..ea8ef5e 100644
--- a/glymur/jp2k.py
+++ b/glymur/jp2k.py
@@ -3,13 +3,15 @@
License: MIT
"""
-# pylint: disable=C0302
-
import sys
+
+# Exitstack not found in contextlib in 2.7
+# pylint: disable=E0611
if sys.hexversion >= 0x03030000:
from contextlib import ExitStack
else:
from contextlib2 import ExitStack
+
import ctypes
import math
import os
@@ -24,62 +26,12 @@ from .core import GREYSCALE
from .core import PROGRESSION_ORDER
from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE
from .jp2box import Jp2kBox
-from .jp2box import JPEG2000SignatureBox
-from .jp2box import FileTypeBox
-from .jp2box import JP2HeaderBox
-from .jp2box import ContiguousCodestreamBox
+from .jp2box import JPEG2000SignatureBox, FileTypeBox, JP2HeaderBox
+from .jp2box import ColourSpecificationBox, ContiguousCodestreamBox
from .jp2box import ImageHeaderBox
-from .jp2box import ColourSpecificationBox
-from .lib import _openjpeg as _opj
-from .lib import _openjp2 as _opj2
-from .lib import c as _libc
-
-# 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,
- 'ycc': _opj2.CLRSPC_YCC}
-
-# Setup the default callback handlers. See the callback functions subsection
-# in the ctypes section of the Python documentation for a solid explanation of
-# what's going on here.
-_CMPFUNC = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_char_p, ctypes.c_void_p)
-
-
-def _default_error_handler(msg, _):
- """Default error handler callback for openjpeg library."""
- msg = "OpenJPEG library error: {0}".format(msg.decode('utf-8').rstrip())
- _opj2.set_error_message(msg)
-
-
-def _default_info_handler(msg, _):
- """Default info handler callback for openjpeg library."""
- print("[INFO] {0}".format(msg.decode('utf-8').rstrip()))
-
-
-def _default_warning_handler(library_msg, _):
- """Default warning handler callback for openjpeg library."""
- library_msg = library_msg.decode('utf-8').rstrip()
- msg = "OpenJPEG library warning: {0}".format(library_msg)
- warnings.warn(msg)
-
-_ERROR_CALLBACK = _CMPFUNC(_default_error_handler)
-_INFO_CALLBACK = _CMPFUNC(_default_info_handler)
-_WARNING_CALLBACK = _CMPFUNC(_default_warning_handler)
-
-
-class LibraryNotFoundError(IOError):
- """Raised if functionality is requested without the necessary library.
- """
- def __init__(self, msg):
- IOError.__init__(self, msg)
+from .lib import openjpeg as opj
+from .lib import openjp2 as opj2
+from .lib import c as libc
class Jp2k(Jp2kBox):
@@ -143,12 +95,12 @@ class Jp2k(Jp2kBox):
read_buffer = fptr.read(2)
signature, = struct.unpack('>H', read_buffer)
if signature == 0xff4f:
- self._codec_format = _opj2.CODEC_J2K
+ self._codec_format = opj2.CODEC_J2K
# That's it, we're done. The codestream object is only
# produced upon explicit request.
return
- self._codec_format = _opj2.CODEC_JP2
+ self._codec_format = opj2.CODEC_JP2
# Should be JP2.
# First 4 bytes should be 12, the length of the 'jP ' box.
@@ -187,99 +139,181 @@ class Jp2k(Jp2kBox):
msg += "profile if the file type box brand is 'jp2 '."
warnings.warn(msg)
- def _validate_write_parameters(self, img_array, code_block_size,
- precinct_sizes, cratios, psnr, colorspace,
- codec_fmt):
- """Check that the input parameters to the write function are valid.
+ def _populate_cparams(self, **kwargs):
+ """Populate compression parameters structure from input arguments.
+
+ Parameters
+ ----------
+ cbsize : tuple, optional
+ Code block size (DY, DX).
+ cratios : iterable
+ Compression ratios for successive layers.
+ eph : bool, optional
+ If true, write SOP marker after each header packet.
+ grid_offset : tuple, optional
+ Offset (DY, DX) of the origin of the image in the reference grid.
+ mct : bool, optional
+ Specifies usage of the multi component transform. If not
+ specified, defaults to True if the colorspace is RGB.
+ modesw : int, optional
+ Mode switch.
+ 1 = BYPASS(LAZY)
+ 2 = RESET
+ 4 = RESTART(TERMALL)
+ 8 = VSC
+ 16 = ERTERM(SEGTERM)
+ 32 = SEGMARK(SEGSYM)
+ numres : int, optional
+ Number of resolutions.
+ prog : str, optional
+ Progression order, one of "LRCP" "RLCP", "RPCL", "PCRL", "CPRL".
+ psnr : iterable, optional
+ Different PSNR for successive layers.
+ psizes : list, optional
+ List of precinct sizes. Each precinct size tuple is defined in
+ (height x width).
+ sop : bool, optional
+ If true, write SOP marker before each packet.
+ subsam : tuple, optional
+ Subsampling factors (dy, dx).
+ tilesize : tuple, optional
+ Numeric tuple specifying tile size in terms of (numrows, numcols),
+ not (X, Y).
+
+ Returns
+ -------
+ cparams : CompressionParametersType(ctypes.Structure)
+ Corresponds to cparameters_t type in openjp2 headers.
+ """
+ cparams = opj2.set_default_encoder_parameters()
+
+ outfile = self.filename.encode()
+ num_pad_bytes = opj2.PATH_LEN - len(outfile)
+ outfile += b'0' * num_pad_bytes
+ cparams.outfile = outfile
+
+ if self.filename[-4:].endswith(('.jp2', '.JP2')):
+ cparams.codec_fmt = opj2.CODEC_JP2
+ else:
+ cparams.codec_fmt = opj2.CODEC_J2K
+
+ # Set defaults to lossless to begin.
+ cparams.tcp_rates[0] = 0
+ cparams.tcp_numlayers = 1
+ cparams.cp_disto_alloc = 1
+
+ if 'cbsize' in kwargs:
+ cparams.cblockw_init = kwargs['cbsize'][1]
+ cparams.cblockh_init = kwargs['cbsize'][0]
+
+ if 'cratios' in kwargs:
+ cparams.tcp_numlayers = len(kwargs['cratios'])
+ for j, cratio in enumerate(kwargs['cratios']):
+ cparams.tcp_rates[j] = cratio
+ cparams.cp_disto_alloc = 1
+
+ if 'eph' in kwargs:
+ cparams.csty |= 0x04
+
+ if 'grid_offset' in kwargs:
+ cparams.image_offset_x0 = kwargs['grid_offset'][1]
+ cparams.image_offset_y0 = kwargs['grid_offset'][0]
+
+ if 'modesw' in kwargs:
+ for shift in range(6):
+ power_of_two = 1 << shift
+ if kwargs['modesw'] & power_of_two:
+ cparams.mode |= power_of_two
+
+ if 'numres' in kwargs:
+ cparams.numresolution = kwargs['numres']
+
+ if 'prog' in kwargs:
+ prog = kwargs['prog'].upper()
+ cparams.prog_order = PROGRESSION_ORDER[prog]
+
+ if 'psnr' in kwargs:
+ cparams.tcp_numlayers = len(kwargs['psnr'])
+ for j, snr_layer in enumerate(kwargs['psnr']):
+ cparams.tcp_distoratio[j] = snr_layer
+ cparams.cp_fixed_quality = 1
+
+ if 'psizes' in kwargs:
+ for j, (prch, prcw) in enumerate(kwargs['psizes']):
+ cparams.prcw_init[j] = prcw
+ cparams.prch_init[j] = prch
+ cparams.csty |= 0x01
+ cparams.res_spec = len(kwargs['psizes'])
+
+ if 'sop' in kwargs:
+ cparams.csty |= 0x02
+
+ if 'subsam' in kwargs:
+ cparams.subsampling_dy = kwargs['subsam'][0]
+ cparams.subsampling_dx = kwargs['subsam'][1]
+
+ if 'tilesize' in kwargs:
+ cparams.cp_tdx = kwargs['tilesize'][1]
+ cparams.cp_tdy = kwargs['tilesize'][0]
+ cparams.tile_size_on = opj2.TRUE
+
+ return cparams
+
+ def _process_write_inputs(self, img_array, colorspace=None, **kwargs):
+ """Directs processing of write method arguments.
+
+ It's somewhat awkward to process all the kwargs arguments at once.
+ The "colorspace" is not a parameter that gets processed into the
+ compression parameters structure, and it unfortunately must be handled
+ in the middle of the compression parameter processing.
Parameters
----------
img_array : ndarray
Image data to be written to file.
- code_block_size : tuple
- Code block size (DY, DX).
- precinct_sizes : list
- List of precinct sizes. Each precinct size tuple is defined in
- (height x width).
- cratios : iterable
- Compression ratios for successive layers.
- psnr : iterable
- Different PSNR for successive layers.
- mct : bool
- Specifies usage of the multi component transform. If not
- specified, defaults to True if the colorspace is RGB.
colorspace : str, optional
Either 'rgb' or 'gray'.
- codec_fmt : int
- Are we writing a JP2 file or a J2K file?
+
+ Returns
+ -------
+ cparams : CompressionParametersType(ctypes.Structure)
+ Corresponds to cparameters_t type in openjp2 headers.
+ colorspace : int
+ Either CLRSPC_SRGB or CLRSPC_GRAY
+ mct : bool, optional
+ Specifies usage of the multi component transform. If not
+ specified, defaults to True if the colorspace is RGB.
"""
- # Validate code block size and precinct sizes.
- if code_block_size is not None:
- width = code_block_size[1]
- height = code_block_size[0]
- if height * width > 4096 or height < 4 or width < 4:
- msg = "Code block area cannot exceed 4096. "
- msg += "Code block height and width must be larger than 4."
- raise IOError(msg)
- if ((math.log(height, 2) != math.floor(math.log(height, 2)) or
- math.log(width, 2) != math.floor(math.log(width, 2)))):
- msg = "Bad code block size ({0}, {1}), "
- msg += "must be powers of 2."
- raise IOError(msg.format(height, width))
- if precinct_sizes is not None:
- for j, (prch, prcw) in enumerate(precinct_sizes):
- if j == 0 and code_block_size is not None:
- cblkh, cblkw = code_block_size
- if cblkh * 2 > prch or cblkw * 2 > prcw:
- msg = "Highest Resolution precinct size must be at "
- msg += "least twice that of the code block dimensions."
- raise IOError(msg)
- if ((math.log(prch, 2) != math.floor(math.log(prch, 2)) or
- math.log(prcw, 2) != math.floor(math.log(prcw, 2)))):
- msg = "Bad precinct sizes ({0}, {1}), "
- msg += "must be powers of 2."
- raise IOError(msg.format(prch, prcw))
-
- if cratios is not None and psnr is not None:
+ if 'cratios' in kwargs and 'psnr' in kwargs:
msg = "Cannot specify cratios and psnr together."
raise IOError(msg)
- # What would the point of 1D images be?
- if img_array.ndim == 1 or img_array.ndim > 3:
- msg = "{0}D imagery is not allowed.".format(img_array.ndim)
- raise IOError(msg)
+ cparams = self._populate_cparams(**kwargs)
+ _validate_compression_params(img_array, cparams)
- if _OPENJP2_IS_OFFICIAL_V2:
- 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 "
- msg += "greyscale images or three-channel RGB images when "
- msg += "the OpenJPEG library version is the official 2.0.0 "
- msg += "release."
- raise IOError(msg)
+ colorspace = _unpack_colorspace(colorspace, img_array, cparams)
- if colorspace is not None:
- if codec_fmt == _opj2.CODEC_J2K:
- msg = 'Do not specify a colorspace when writing a raw '
- msg += 'codestream.'
- raise IOError(msg)
- if colorspace.lower() not in ('rgb', 'grey', 'gray'):
- msg = 'Invalid colorspace "{0}"'.format(colorspace)
- raise IOError(msg)
- elif colorspace.lower() == 'rgb' and img_array.shape[2] < 3:
- msg = 'RGB colorspace requires at least 3 components.'
+ try:
+ mct = kwargs['mct']
+ if mct and colorspace == opj2.CLRSPC_GRAY:
+ # Cannot check for this in the validate routine, as we need
+ # to know what the target colorspace has been determined to be.
+ msg = "Cannot specify usage of the multi component transform "
+ msg += "if the colorspace is gray."
raise IOError(msg)
+ cparams.tcp_mct = 1 if mct else 0
+ except KeyError:
+ # If the multi component transform was not specified, we infer
+ # that it should be used if the color space is RGB.
+ if colorspace == opj2.CLRSPC_SRGB:
+ cparams.tcp_mct = 1
+ else:
+ cparams.tcp_mct = 0
- if img_array.dtype != np.uint8 and img_array.dtype != np.uint16:
- msg = "Only uint8 and uint16 images are currently supported."
- raise RuntimeError(msg)
+ return cparams, colorspace
- # pylint: disable-msg=W0221
- def write(self, img_array, cratios=None, eph=False, psnr=None, numres=None,
- cbsize=None, psizes=None, grid_offset=None, sop=False,
- subsam=None, tilesize=None, prog=None, modesw=None,
- colorspace=None, verbose=False, mct=None):
+ def write(self, img_array, verbose=False, **kwargs):
"""Write image data to a JP2/JPX/J2k file. Intended usage of the
various parameters follows that of OpenJPEG's opj_compress utility.
@@ -290,11 +324,6 @@ class Jp2k(Jp2kBox):
----------
img_array : ndarray
Image data to be written to file.
- callbacks : bool, optional
- If true, enable default info handler such that INFO messages
- produced by the OpenJPEG library are output to the console. By
- default, OpenJPEG warning and error messages are captured by
- Python's own warning and error mechanisms.
cbsize : tuple, optional
Code block size (DY, DX).
colorspace : str, optional
@@ -351,193 +380,54 @@ class Jp2k(Jp2kBox):
glymur.LibraryNotFoundError
If glymur is unable to load the openjp2 library.
"""
- if _opj2.OPENJP2 is None:
+ if opj2.OPENJP2 is None:
raise LibraryNotFoundError("You must have the openjp2 library "
"installed before using this "
"functionality.")
- if self.filename[-4:].lower() == '.jp2':
- codec_fmt = _opj2.CODEC_JP2
- else:
- codec_fmt = _opj2.CODEC_J2K
-
- self._validate_write_parameters(img_array, cbsize, psizes, cratios,
- psnr, colorspace, codec_fmt)
-
- cparams = _opj2.set_default_encoder_parameters()
-
- outfile = self.filename.encode()
- num_pad_bytes = _opj2.PATH_LEN - len(outfile)
- outfile += b'0' * num_pad_bytes
- cparams.outfile = outfile
-
- cparams.cod_format = codec_fmt
-
- # Set defaults to lossless to begin.
- cparams.tcp_rates[0] = 0
- cparams.tcp_numlayers = 1
- cparams.cp_disto_alloc = 1
-
- if cbsize is not None:
- width = cbsize[1]
- height = cbsize[0]
- cparams.cblockw_init = width
- cparams.cblockh_init = height
-
- if cratios is not None:
- cparams.tcp_numlayers = len(cratios)
- for j, cratio in enumerate(cratios):
- cparams.tcp_rates[j] = cratio
- cparams.cp_disto_alloc = 1
-
- if eph:
- cparams.csty |= 0x04
-
- if grid_offset is not None:
- cparams.image_offset_x0 = grid_offset[1]
- cparams.image_offset_y0 = grid_offset[0]
-
- if modesw is not None:
- for shift in range(6):
- power_of_two = 1 << shift
- if modesw & power_of_two:
- cparams.mode |= power_of_two
-
- if numres is not None:
- cparams.numresolution = numres
-
- if prog is not None:
- prog = prog.upper()
- cparams.prog_order = PROGRESSION_ORDER[prog]
-
- if psnr is not None:
- cparams.tcp_numlayers = len(psnr)
- for j, snr_layer in enumerate(psnr):
- cparams.tcp_distoratio[j] = snr_layer
- cparams.cp_fixed_quality = 1
-
- if psizes is not None:
- for j, (prch, prcw) in enumerate(psizes):
- cparams.prcw_init[j] = prcw
- cparams.prch_init[j] = prch
- cparams.csty |= 0x01
- cparams.res_spec = len(psizes)
-
- if sop:
- cparams.csty |= 0x02
-
- if subsam is not None:
- cparams.subsampling_dy = subsam[0]
- cparams.subsampling_dx = subsam[1]
-
- if tilesize is not None:
- cparams.cp_tdx = tilesize[1]
- cparams.cp_tdy = tilesize[0]
- cparams.tile_size_on = _opj2.TRUE
+ cparams, colorspace = self._process_write_inputs(img_array, **kwargs)
if img_array.ndim == 2:
- # Force it to be 3D. Just makes things easier later on.
+ # Force the image to be 3D. Just makes things easier later on.
numrows, numcols = img_array.shape
img_array = img_array.reshape(numrows, numcols, 1)
- numrows, numcols, num_comps = img_array.shape
+ comptparms = _populate_comptparms(img_array, cparams)
- if colorspace is None:
- # Must infer the colorspace from the image dimensions.
- if img_array.shape[2] == 1 or img_array.shape[2] == 2:
- # A single channel image or an image with two channels is going
- # to be greyscale.
- colorspace = _opj2.CLRSPC_GRAY
- else:
- # Anything else must be RGB, right?
- colorspace = _opj2.CLRSPC_SRGB
- else:
- # Turn the colorspace from a string to the enumerated value that
- # the library expects.
- colorspace = _COLORSPACE_MAP[colorspace.lower()]
+ image = opj2.image_create(comptparms, colorspace)
+ _populate_image_struct(cparams, image, img_array)
- if mct is None:
- # If the multi component transform was not specified, we infer
- # that it should be used if the color space is RGB.
- if colorspace == _opj2.CLRSPC_SRGB:
- cparams.tcp_mct = 1
- else:
- cparams.tcp_mct = 0
- else:
- if mct and colorspace == _opj2.CLRSPC_GRAY:
- # Cannot check for this in the validate routine, as we need
- # to know what the target colorspace has been determined to be.
- msg = "Cannot specify usage of the multi component transform "
- msg += "if the colorspace is gray."
- raise IOError(msg)
- cparams.tcp_mct = 1 if mct else 0
+ codec = opj2.create_compress(cparams.codec_fmt)
- if img_array.dtype == np.uint8:
- comp_prec = 8
- else:
- # We already know it cannot be anything else than uint16.
- comp_prec = 16
+ info_handler = _INFO_CALLBACK if verbose else None
+ opj2.set_info_handler(codec, info_handler)
+ opj2.set_warning_handler(codec, _WARNING_CALLBACK)
+ opj2.set_error_handler(codec, _ERROR_CALLBACK)
- comptparms = (_opj2.ImageComptParmType * num_comps)()
- for j in range(num_comps):
- comptparms[j].dx = cparams.subsampling_dx
- comptparms[j].dy = cparams.subsampling_dy
- comptparms[j].w = numcols
- comptparms[j].h = numrows
- comptparms[j].x0 = cparams.image_offset_x0
- comptparms[j].y0 = cparams.image_offset_y0
- comptparms[j].prec = comp_prec
- comptparms[j].bpp = comp_prec
- comptparms[j].sgnd = 0
-
- image = _opj2.image_create(comptparms, colorspace)
-
- # set image offset and reference grid
- image.contents.x0 = cparams.image_offset_x0
- image.contents.y0 = cparams.image_offset_y0
- image.contents.x1 = (image.contents.x0 +
- (numcols - 1) * cparams.subsampling_dx + 1)
- image.contents.y1 = (image.contents.y0 +
- (numrows - 1) * cparams.subsampling_dy + 1)
-
- # Stage the image data to the openjpeg data structure.
- for k in range(0, num_comps):
- layer = np.ascontiguousarray(img_array[:, :, k], dtype=np.int32)
- dest = image.contents.comps[k].data
- src = layer.ctypes.data
- ctypes.memmove(dest, src, layer.nbytes)
-
- codec = _opj2.create_compress(codec_fmt)
-
- if verbose:
- _opj2.set_info_handler(codec, _INFO_CALLBACK)
- else:
- _opj2.set_info_handler(codec, None)
-
- _opj2.set_warning_handler(codec, _WARNING_CALLBACK)
- _opj2.set_error_handler(codec, _ERROR_CALLBACK)
- _opj2.setup_encoder(codec, cparams, image)
+ opj2.setup_encoder(codec, cparams, image)
if _OPENJP2_IS_OFFICIAL_V2:
- fptr = _libc.fopen(self.filename, 'wb')
- strm = _opj2.stream_create_default_file_stream(fptr, False)
+ fptr = libc.fopen(self.filename, 'wb')
+ strm = opj2.stream_create_default_file_stream(fptr, False)
else:
- strm = _opj2.stream_create_default_file_stream_v3(self.filename,
- False)
+ # This routine introduced in 2.0 devel series.
+ strm = opj2.stream_create_default_file_stream_v3(self.filename,
+ False)
# Start to clean up after ourselves.
- _opj2.start_compress(codec, image, strm)
- _opj2.encode(codec, strm)
- _opj2.end_compress(codec, strm)
+ opj2.start_compress(codec, image, strm)
+ opj2.encode(codec, strm)
+ opj2.end_compress(codec, strm)
if _OPENJP2_IS_OFFICIAL_V2:
- _opj2.stream_destroy(strm)
- _libc.fclose(fptr)
+ opj2.stream_destroy(strm)
+ libc.fclose(fptr)
else:
- _opj2.stream_destroy_v3(strm)
+ # This routine introduced in 2.0 devel series.
+ opj2.stream_destroy_v3(strm)
- _opj2.destroy_codec(codec)
- _opj2.image_destroy(image)
+ opj2.destroy_codec(codec)
+ opj2.image_destroy(image)
# Refresh the metadata.
self.parse()
@@ -582,72 +472,7 @@ class Jp2k(Jp2kBox):
num_components=num_components),
ColourSpecificationBox(colorspace=SRGB)]
- # Check for a bad sequence of boxes.
- # 1st two boxes must be 'jP ' and 'ftyp'
- if boxes[0].box_id != 'jP ' or boxes[1].box_id != 'ftyp':
- msg = "The first box must be the signature box and the second "
- msg += "must be the file type box."
- raise IOError(msg)
-
- # jp2c must be preceeded by jp2h
- jp2h_lst = [idx for (idx, box) in enumerate(boxes)
- if box.box_id == 'jp2h']
- jp2h_idx = jp2h_lst[0]
- jp2c_lst = [idx for (idx, box) in enumerate(boxes)
- if box.box_id == 'jp2c']
- if len(jp2c_lst) == 0:
- msg = "A codestream box must be defined in the outermost "
- msg += "list of boxes."
- raise IOError(msg)
-
- jp2c_idx = jp2c_lst[0]
- if jp2h_idx >= jp2c_idx:
- msg = "The codestream box must be preceeded by a jp2 header box."
- raise IOError(msg)
-
- # 1st jp2 header box must be ihdr
- jp2h = boxes[jp2h_idx]
- if jp2h.box[0].box_id != 'ihdr':
- msg = "The first box in the jp2 header box must be the image "
- msg += "header box."
- raise IOError(msg)
-
- # colr must be present in jp2 header box.
- colr_lst = [j for (j, box) in enumerate(jp2h.box)
- if box.box_id == 'colr']
- if len(colr_lst) == 0:
- msg = "The jp2 header box must contain a color definition box."
- raise IOError(msg)
- colr = jp2h.box[colr_lst[0]]
-
- # Any cdef box must be in the jp2 header following the image header.
- cdef_lst = [j for (j, box) in enumerate(boxes) if box.box_id == 'cdef']
- if len(cdef_lst) != 0:
- msg = "Any channel defintion box must be in the JP2 header "
- msg += "following the image header."
- raise IOError(msg)
-
- cdef_lst = [j for (j, box) in enumerate(jp2h.box)
- if box.box_id == 'cdef']
- if len(cdef_lst) > 1:
- msg = "Only one channel definition box is allowed in the "
- msg += "JP2 header."
- raise IOError(msg)
- elif len(cdef_lst) == 1:
- cdef = jp2h.box[cdef_lst[0]]
- assn = cdef.association
- typ = cdef.channel_type
- if colr.colorspace == SRGB:
- if any([chan + 1 not in assn or typ[chan] != 0
- for chan in [0, 1, 2]]):
- msg = "All color channels must be defined in the "
- msg += "channel definition box."
- raise IOError(msg)
- elif colr.colorspace == GREYSCALE:
- if 0 not in typ:
- msg = "All color channels must be defined in the "
- msg += "channel definition box."
- raise IOError(msg)
+ _validate_jp2_box_sequence(boxes)
with open(filename, 'wb') as ofile:
for box in boxes:
@@ -729,9 +554,9 @@ class Jp2k(Jp2kBox):
>>> thumbnail.shape
(728, 1296, 3)
"""
- if _opj2.OPENJP2 is not None:
+ if opj2.OPENJP2 is not None:
img = self._read_openjp2(**kwargs)
- elif _opj.OPENJPEG is not None:
+ elif opj.OPENJPEG is not None:
img = self._read_openjpeg(**kwargs)
else:
raise LibraryNotFoundError("You must have either a recent version "
@@ -740,6 +565,18 @@ class Jp2k(Jp2kBox):
"using this functionality.")
return img
+ def _subsampling_sanity_check(self):
+ """Check for differing subsample factors.
+ """
+ codestream = self.get_codestream(header_only=True)
+ dxs = np.array(codestream.segment[1].xrsiz)
+ dys = np.array(codestream.segment[1].yrsiz)
+ if np.any(dxs - dxs[0]) or np.any(dys - dys[0]):
+ msg = "Components must all have the same subsampling factors "
+ msg += "to use this method. Please consider using OPENJP2 and "
+ msg += "the read_bands method instead."
+ raise RuntimeError(msg)
+
def _read_openjpeg(self, rlevel=0, verbose=False):
"""Read a JPEG 2000 image using libopenjpeg.
@@ -761,96 +598,48 @@ class Jp2k(Jp2kBox):
RuntimeError
If the image has differing subsample factors.
"""
- # Check for differing subsample factors.
- codestream = self.get_codestream(header_only=True)
- dxs = np.array(codestream.segment[1].xrsiz)
- dys = np.array(codestream.segment[1].yrsiz)
- if np.any(dxs - dxs[0]) or np.any(dys - dys[0]):
- msg = "Components must all have the same subsampling factors "
- msg += "to use this method with OpenJPEG 1.5.1. Please consider "
- msg += "using OPENJP2 instead."
- raise RuntimeError(msg)
+ self._subsampling_sanity_check()
with ExitStack() as stack:
# Set decoding parameters.
- dparameters = _opj.DecompressionParametersType()
- _opj.set_default_decoder_parameters(ctypes.byref(dparameters))
+ dparameters = opj.DecompressionParametersType()
+ opj.set_default_decoder_parameters(ctypes.byref(dparameters))
dparameters.cp_reduce = rlevel
dparameters.decod_format = self._codec_format
infile = self.filename.encode()
- nelts = _opj.PATH_LEN - len(infile)
+ nelts = opj.PATH_LEN - len(infile)
infile += b'0' * nelts
dparameters.infile = infile
- dinfo = _opj.create_decompress(dparameters.decod_format)
+ dinfo = opj.create_decompress(dparameters.decod_format)
- event_mgr = _opj.EventMgrType()
+ event_mgr = opj.EventMgrType()
info_handler = ctypes.cast(_INFO_CALLBACK, ctypes.c_void_p)
event_mgr.info_handler = info_handler if verbose else None
event_mgr.warning_handler = ctypes.cast(_WARNING_CALLBACK,
ctypes.c_void_p)
event_mgr.error_handler = ctypes.cast(_ERROR_CALLBACK,
ctypes.c_void_p)
- _opj.set_event_mgr(dinfo, ctypes.byref(event_mgr))
+ opj.set_event_mgr(dinfo, ctypes.byref(event_mgr))
- _opj.setup_decoder(dinfo, dparameters)
+ opj.setup_decoder(dinfo, dparameters)
with open(self.filename, 'rb') as fptr:
src = fptr.read()
- cio = _opj.cio_open(dinfo, src)
+ cio = opj.cio_open(dinfo, src)
- image = _opj.decode(dinfo, cio)
+ image = opj.decode(dinfo, cio)
- stack.callback(_opj.image_destroy, image)
- stack.callback(_opj.destroy_decompress, dinfo)
- stack.callback(_opj.cio_close, cio)
+ stack.callback(opj.image_destroy, image)
+ stack.callback(opj.destroy_decompress, dinfo)
+ stack.callback(opj.cio_close, cio)
- ncomps = image.contents.numcomps
- component = image.contents.comps[0]
- if component.sgnd:
- if component.prec <= 8:
- dtype = np.int8
- elif component.prec <= 16:
- dtype = np.int16
- else:
- raise RuntimeError("Unhandled precision, datatype")
- else:
- if component.prec <= 8:
- dtype = np.uint8
- elif component.prec <= 16:
- dtype = np.uint16
- else:
- raise RuntimeError("Unhandled precision, datatype")
-
- nrows = image.contents.comps[0].h
- ncols = image.contents.comps[0].w
- ncomps = image.contents.numcomps
- data = np.zeros((nrows, ncols, ncomps), dtype)
-
- for k in range(image.contents.numcomps):
- component = image.contents.comps[k]
- nrows = component.h
- ncols = component.w
-
- if nrows == 0 or ncols == 0:
- # Letting this situation continue would segfault
- # Python.
- msg = "Component {0} has dimensions {1} x {2}"
- msg = msg.format(k, nrows, ncols)
- raise IOError(msg)
-
- addr = ctypes.addressof(component.data.contents)
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- nelts = nrows * ncols
- band = np.ctypeslib.as_array(
- (ctypes.c_int32 * nelts).from_address(addr))
- data[:, :, k] = np.reshape(band.astype(dtype),
- (nrows, ncols))
+ data = extract_image_cube(image)
if data.shape[2] == 1:
- data = data.view()
+ # The third dimension has just a single layer. Make the image
+ # data 2D instead of 3D.
data.shape = data.shape[0:2]
return data
@@ -884,30 +673,54 @@ class Jp2k(Jp2kBox):
RuntimeError
If the image has differing subsample factors.
"""
- # Check for differing subsample factors.
- codestream = self.get_codestream(header_only=True)
- dxs = np.array(codestream.segment[1].xrsiz)
- dys = np.array(codestream.segment[1].yrsiz)
- if np.any(dxs - dxs[0]) or np.any(dys - dys[0]):
- msg = "Components must all have the same subsampling factors."
- raise RuntimeError(msg)
+ self._subsampling_sanity_check()
- img_array = self._read_common(rlevel=rlevel,
- layer=layer,
- area=area,
- tile=tile,
- verbose=verbose,
- as_bands=False)
+ dparam = self._populate_dparam(layer, rlevel, area, tile)
+
+ 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:
+ 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)
+ codec = opj2.create_decompress(self._codec_format)
+ stack.callback(opj2.destroy_codec, codec)
+
+ opj2.set_error_handler(codec, _ERROR_CALLBACK)
+ opj2.set_warning_handler(codec, _WARNING_CALLBACK)
+ if verbose:
+ opj2.set_info_handler(codec, _INFO_CALLBACK)
+ else:
+ opj2.set_info_handler(codec, None)
+
+ opj2.setup_decoder(codec, dparam)
+ image = opj2.read_header(stream, codec)
+ stack.callback(opj2.image_destroy, image)
+
+ if dparam.nb_tile_to_decode:
+ opj2.get_decoded_tile(codec, stream, image, dparam.tile_index)
+ else:
+ opj2.set_decode_area(codec, image,
+ dparam.DA_x0, dparam.DA_y0,
+ dparam.DA_x1, dparam.DA_y1)
+ opj2.decode(codec, stream, image)
+ opj2.end_decompress(codec, stream)
+
+ img_array = extract_image_cube(image)
if img_array.shape[2] == 1:
- img_array = img_array.view()
img_array.shape = img_array.shape[0:2]
return img_array
- def _read_common(self, rlevel=0, layer=0, area=None, tile=None,
- verbose=False, as_bands=False):
- """Read a JPEG 2000 image.
+ def _populate_dparam(self, layer, rlevel, area, tile):
+ """Populate decompression structure with appropriate input parameters.
Parameters
----------
@@ -920,20 +733,16 @@ class Jp2k(Jp2kBox):
(first_row, first_col, last_row, last_col)
tile : int, optional
Number of tile to decode.
- verbose : bool, optional
- Print informational messages produced by the OpenJPEG library.
- as_bands : bool, optional
- If true, return the individual 2D components in a list.
Returns
-------
- img_array : ndarray
- The individual image components or a single array.
+ dparam : DecompressionParametersType (ctypes)
+ Corresponds to openjp2 decompression parameters structure.
"""
- dparam = _opj2.set_default_decoder_parameters()
+ dparam = opj2.set_default_decoder_parameters()
infile = self.filename.encode()
- nelts = _opj2.PATH_LEN - len(infile)
+ nelts = opj2.PATH_LEN - len(infile)
infile += b'0' * nelts
dparam.infile = infile
@@ -945,17 +754,13 @@ class Jp2k(Jp2kBox):
# Get the lowest resolution thumbnail.
codestream = self.get_codestream()
rlevel = codestream.segment[2].spcod[4]
-
dparam.cp_reduce = rlevel
+
if area is not None:
- if area[0] < 0 or area[1] < 0:
- msg = "Upper left corner coordinates must be nonnegative: {0}"
- msg = msg.format(area)
- raise IOError(msg)
- if area[2] <= 0 or area[3] <= 0:
- msg = "Lower right corner coordinates must be positive: {0}"
- msg = msg.format(area)
- raise IOError(msg)
+ if area[0] < 0 or area[1] < 0 or area[2] <= 0 or area[3] <= 0:
+ msg = "Upper left corner coordinates must be nonnegative and "
+ msg += "lower right corner coordinates must be positive: {0}"
+ raise IOError(msg.format(area))
dparam.DA_y0 = area[0]
dparam.DA_x0 = area[1]
dparam.DA_y1 = area[2]
@@ -965,89 +770,7 @@ class Jp2k(Jp2kBox):
dparam.tile_index = tile
dparam.nb_tile_to_decode = 1
- 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:
- 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)
- codec = _opj2.create_decompress(self._codec_format)
- stack.callback(_opj2.destroy_codec, codec)
-
- _opj2.set_error_handler(codec, _ERROR_CALLBACK)
- _opj2.set_warning_handler(codec, _WARNING_CALLBACK)
- if verbose:
- _opj2.set_info_handler(codec, _INFO_CALLBACK)
- else:
- _opj2.set_info_handler(codec, None)
-
- _opj2.setup_decoder(codec, dparam)
- image = _opj2.read_header(stream, codec)
- stack.callback(_opj2.image_destroy, image)
-
- if dparam.nb_tile_to_decode:
- _opj2.get_decoded_tile(codec, stream, image, dparam.tile_index)
- else:
- _opj2.set_decode_area(codec, image,
- dparam.DA_x0, dparam.DA_y0,
- dparam.DA_x1, dparam.DA_y1)
- _opj2.decode(codec, stream, image)
- _opj2.end_decompress(codec, stream)
-
- component = image.contents.comps[0]
- if component.sgnd:
- if component.prec <= 8:
- dtype = np.int8
- elif component.prec <= 16:
- dtype = np.int16
- else:
- raise RuntimeError("Unhandled precision, datatype")
- else:
- if component.prec <= 8:
- dtype = np.uint8
- elif component.prec <= 16:
- dtype = np.uint16
- else:
- raise RuntimeError("Unhandled precision, datatype")
-
- if as_bands:
- data = []
- else:
- nrows = image.contents.comps[0].h
- ncols = image.contents.comps[0].w
- ncomps = image.contents.numcomps
- data = np.zeros((nrows, ncols, ncomps), dtype)
-
- for k in range(image.contents.numcomps):
- component = image.contents.comps[k]
- nrows = component.h
- ncols = component.w
-
- if nrows == 0 or ncols == 0:
- # Letting this situation continue would segfault
- # Python.
- msg = "Component {0} has dimensions {1} x {2}"
- msg = msg.format(k, nrows, ncols)
- raise IOError(msg)
-
- addr = ctypes.addressof(component.data.contents)
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- band = np.ctypeslib.as_array(
- (ctypes.c_int32 * nrows * ncols).from_address(addr))
- if as_bands:
- data.append(np.reshape(band.astype(dtype), (nrows, ncols)))
- else:
- data[:, :, k] = np.reshape(band.astype(dtype),
- (nrows, ncols))
-
- return data
+ return dparam
def read_bands(self, rlevel=0, layer=0, area=None, tile=None,
verbose=False):
@@ -1092,17 +815,49 @@ class Jp2k(Jp2kBox):
glymur.LibraryNotFoundError
If glymur is unable to load the openjp2 library.
"""
- if _opj2.OPENJP2 is None:
+ if opj2.OPENJP2 is None:
raise LibraryNotFoundError("You must have the development version "
"of OpenJP2 installed before using "
"this functionality.")
- lst = self._read_common(rlevel=rlevel,
- layer=layer,
- area=area,
- tile=tile,
- verbose=verbose,
- as_bands=True)
+ dparam = self._populate_dparam(layer, rlevel, area, tile)
+
+ 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:
+ 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)
+ codec = opj2.create_decompress(self._codec_format)
+ stack.callback(opj2.destroy_codec, codec)
+
+ opj2.set_error_handler(codec, _ERROR_CALLBACK)
+ opj2.set_warning_handler(codec, _WARNING_CALLBACK)
+ if verbose:
+ opj2.set_info_handler(codec, _INFO_CALLBACK)
+ else:
+ opj2.set_info_handler(codec, None)
+
+ opj2.setup_decoder(codec, dparam)
+ image = opj2.read_header(stream, codec)
+ stack.callback(opj2.image_destroy, image)
+
+ if dparam.nb_tile_to_decode:
+ opj2.get_decoded_tile(codec, stream, image, dparam.tile_index)
+ else:
+ opj2.set_decode_area(codec, image,
+ dparam.DA_x0, dparam.DA_y0,
+ dparam.DA_x1, dparam.DA_y1)
+ opj2.decode(codec, stream, image)
+ opj2.end_decompress(codec, stream)
+
+ lst = extract_image_bands(image)
return lst
@@ -1142,7 +897,7 @@ class Jp2k(Jp2kBox):
If the file is JPX with more than one codestream.
"""
with open(self.filename, 'rb') as fptr:
- if self._codec_format == _opj2.CODEC_J2K:
+ if self._codec_format == opj2.CODEC_J2K:
codestream = Codestream(fptr, self.length,
header_only=header_only)
else:
@@ -1165,3 +920,397 @@ class Jp2k(Jp2kBox):
header_only=header_only)
return codestream
+
+
+def component2dtype(component):
+ """Take an OpenJPEG component structure and determine the numpy datatype.
+
+ Parameters
+ ----------
+ component : ctypes pointer to ImageCompType (image_comp_t)
+ single image component structure.
+
+ Returns
+ -------
+ dtype : builtins.type
+ numpy datatype to be used to construct an image array.
+ """
+ if component.sgnd:
+ if component.prec <= 8:
+ dtype = np.int8
+ elif component.prec <= 16:
+ dtype = np.int16
+ else:
+ raise RuntimeError("Unhandled precision, datatype")
+ else:
+ if component.prec <= 8:
+ dtype = np.uint8
+ elif component.prec <= 16:
+ dtype = np.uint16
+ else:
+ raise RuntimeError("Unhandled precision, datatype")
+
+ return dtype
+
+
+def _validate_nonzero_image_size(nrows, ncols, component_index):
+ """The image cannot have area of zero.
+ """
+ if nrows == 0 or ncols == 0:
+ # Letting this situation continue would segfault Python.
+ msg = "Component {0} has dimensions {1} x {2}"
+ msg = msg.format(component_index, nrows, ncols)
+ raise IOError(msg)
+
+
+def _validate_jp2_box_sequence(boxes):
+ """Run through series of tests for JP2 box legality.
+
+ This is non-exhaustive.
+ """
+ # Check for a bad sequence of boxes.
+ # 1st two boxes must be 'jP ' and 'ftyp'
+ if boxes[0].box_id != 'jP ' or boxes[1].box_id != 'ftyp':
+ msg = "The first box must be the signature box and the second "
+ msg += "must be the file type box."
+ raise IOError(msg)
+
+ # jp2c must be preceeded by jp2h
+ jp2h_lst = [idx for (idx, box) in enumerate(boxes)
+ if box.box_id == 'jp2h']
+ jp2h_idx = jp2h_lst[0]
+ jp2c_lst = [idx for (idx, box) in enumerate(boxes)
+ if box.box_id == 'jp2c']
+ if len(jp2c_lst) == 0:
+ msg = "A codestream box must be defined in the outermost "
+ msg += "list of boxes."
+ raise IOError(msg)
+
+ jp2c_idx = jp2c_lst[0]
+ if jp2h_idx >= jp2c_idx:
+ msg = "The codestream box must be preceeded by a jp2 header box."
+ raise IOError(msg)
+
+ # 1st jp2 header box must be ihdr
+ jp2h = boxes[jp2h_idx]
+ if jp2h.box[0].box_id != 'ihdr':
+ msg = "The first box in the jp2 header box must be the image "
+ msg += "header box."
+ raise IOError(msg)
+
+ # colr must be present in jp2 header box.
+ colr_lst = [j for (j, box) in enumerate(jp2h.box)
+ if box.box_id == 'colr']
+ if len(colr_lst) == 0:
+ msg = "The jp2 header box must contain a color definition box."
+ raise IOError(msg)
+ colr = jp2h.box[colr_lst[0]]
+
+ # Any cdef box must be in the jp2 header following the image header.
+ cdef_lst = [j for (j, box) in enumerate(boxes) if box.box_id == 'cdef']
+ if len(cdef_lst) != 0:
+ msg = "Any channel defintion box must be in the JP2 header "
+ msg += "following the image header."
+ raise IOError(msg)
+
+ cdef_lst = [j for (j, box) in enumerate(jp2h.box)
+ if box.box_id == 'cdef']
+ if len(cdef_lst) > 1:
+ msg = "Only one channel definition box is allowed in the "
+ msg += "JP2 header."
+ raise IOError(msg)
+ elif len(cdef_lst) == 1:
+ cdef = jp2h.box[cdef_lst[0]]
+ if colr.colorspace == SRGB:
+ if any([chan + 1 not in cdef.association
+ or cdef.channel_type[chan] != 0
+ for chan in [0, 1, 2]]):
+ msg = "All color channels must be defined in the "
+ msg += "channel definition box."
+ raise IOError(msg)
+ elif colr.colorspace == GREYSCALE:
+ if 0 not in cdef.channel_type:
+ msg = "All color channels must be defined in the "
+ msg += "channel definition box."
+ raise IOError(msg)
+
+
+def extract_image_cube(image):
+ """Extract 3D image from openjpeg data structure.
+ """
+ ncomps = image.contents.numcomps
+ component = image.contents.comps[0]
+ dtype = component2dtype(component)
+
+ nrows = component.h
+ ncols = component.w
+ data = np.zeros((nrows, ncols, ncomps), dtype)
+
+ for k in range(image.contents.numcomps):
+ component = image.contents.comps[k]
+ nrows = component.h
+ ncols = component.w
+
+ _validate_nonzero_image_size(nrows, ncols, k)
+
+ addr = ctypes.addressof(component.data.contents)
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ nelts = nrows * ncols
+ band = np.ctypeslib.as_array(
+ (ctypes.c_int32 * nelts).from_address(addr))
+ data[:, :, k] = np.reshape(band.astype(dtype), (nrows, ncols))
+
+ return data
+
+
+def extract_image_bands(image):
+ """Extract unequally-sized image bands.
+
+ This routine need only be called when subsampling differs across image
+ components, such as is often the case with YCbCr imagery.
+ """
+ data = []
+ for k in range(image.contents.numcomps):
+ component = image.contents.comps[k]
+
+ dtype = component2dtype(component)
+ nrows = component.h
+ ncols = component.w
+
+ _validate_nonzero_image_size(nrows, ncols, k)
+
+ addr = ctypes.addressof(component.data.contents)
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ band = np.ctypeslib.as_array(
+ (ctypes.c_int32 * nrows * ncols).from_address(addr))
+ data.append(np.reshape(band.astype(dtype), (nrows, ncols)))
+
+ return data
+
+
+def _unpack_colorspace(colorspace, img_array, cparams):
+ """Determine the colorspace from the supplied inputs.
+
+ Parameters
+ ----------
+ colorspace : int
+ Either CLRSPC_SRGB or CLRSPC_GRAY
+ img_array : ndarray
+ Image data to be written to file.
+ cparams : CompressionParametersType(ctypes.Structure)
+ Corresponds to cparameters_t type in openjp2 headers.
+ """
+ if colorspace is None:
+ # Must infer the colorspace from the image dimensions.
+ if img_array.ndim < 3:
+ # A single channel image is grayscale.
+ colorspace = opj2.CLRSPC_GRAY
+ elif img_array.shape[2] == 1 or img_array.shape[2] == 2:
+ # A single channel image or an image with two channels is going
+ # to be greyscale.
+ colorspace = opj2.CLRSPC_GRAY
+ else:
+ # Anything else must be RGB, right?
+ colorspace = opj2.CLRSPC_SRGB
+ else:
+ # Supplied a string colorspace, so we must validate it.
+ if cparams.codec_fmt == opj2.CODEC_J2K:
+ msg = 'Do not specify a colorspace when writing a raw '
+ msg += 'codestream.'
+ raise IOError(msg)
+ if colorspace.lower() not in ('rgb', 'grey', 'gray'):
+ msg = 'Invalid colorspace "{0}"'.format(colorspace)
+ raise IOError(msg)
+ elif colorspace.lower() == 'rgb' and img_array.shape[2] < 3:
+ msg = 'RGB colorspace requires at least 3 components.'
+ raise IOError(msg)
+
+ # Turn the colorspace from a string to the enumerated value that
+ # the library expects.
+ colorspace = _COLORSPACE_MAP[colorspace.lower()]
+
+ return colorspace
+
+def _populate_comptparms(img_array, cparams):
+ """Instantiate and populate comptparms structure.
+
+ This structure defines the image components.
+
+ Parameters
+ ----------
+ img_array : ndarray
+ Image data to be written to file.
+ cparams : CompressionParametersType(ctypes.Structure)
+ Corresponds to cparameters_t type in openjp2 headers.
+
+ Returns
+ -------
+ comptparms : ImageCompType(ctypes.Structure)
+ Corresponds to image_comp_t type in openjp2 headers.
+ """
+ # Only two precisions are possible.
+ if img_array.dtype == np.uint8:
+ comp_prec = 8
+ else:
+ comp_prec = 16
+
+ numrows, numcols, num_comps = img_array.shape
+ comptparms = (opj2.ImageComptParmType * num_comps)()
+ for j in range(num_comps):
+ comptparms[j].dx = cparams.subsampling_dx
+ comptparms[j].dy = cparams.subsampling_dy
+ comptparms[j].w = numcols
+ comptparms[j].h = numrows
+ comptparms[j].x0 = cparams.image_offset_x0
+ comptparms[j].y0 = cparams.image_offset_y0
+ comptparms[j].prec = comp_prec
+ comptparms[j].bpp = comp_prec
+ comptparms[j].sgnd = 0
+
+ return comptparms
+
+def _populate_image_struct(cparams, image, imgdata):
+ """Populates image struct needed for compression.
+
+ Parameters
+ ----------
+ cparams : CompressionParametersType(ctypes.Structure)
+ Corresponds to cparameters_t type in openjp2 headers.
+ image : ImageType(ctypes.Structure)
+ Corresponds to image_t type in openjp2 headers.
+ imgarray : ndarray
+ Image data to be written to file.
+ """
+
+ numrows, numcols, num_comps = imgdata.shape
+
+ # set image offset and reference grid
+ image.contents.x0 = cparams.image_offset_x0
+ image.contents.y0 = cparams.image_offset_y0
+ image.contents.x1 = (image.contents.x0 +
+ (numcols - 1) * cparams.subsampling_dx + 1)
+ image.contents.y1 = (image.contents.y0 +
+ (numrows - 1) * cparams.subsampling_dy + 1)
+
+ # Stage the image data to the openjpeg data structure.
+ for k in range(0, num_comps):
+ layer = np.ascontiguousarray(imgdata[:, :, k], dtype=np.int32)
+ dest = image.contents.comps[k].data
+ src = layer.ctypes.data
+ ctypes.memmove(dest, src, layer.nbytes)
+
+ return image
+
+def _validate_compression_params(img_array, cparams):
+ """Check that the compression parameters are valid.
+
+ Parameters
+ ----------
+ img_array : ndarray
+ Image data to be written to file.
+ cparams : CompressionParametersType(ctypes.Structure)
+ Corresponds to cparameters_t type in openjp2 headers.
+ """
+
+ # Code block size
+ code_block_specified = False
+ if cparams.cblockw_init != 0 and cparams.cblockh_init != 0:
+ # These fields ARE zero if uninitialized.
+ width = cparams.cblockw_init
+ height = cparams.cblockh_init
+ code_block_specified = True
+ if height * width > 4096 or height < 4 or width < 4:
+ msg = "Code block area cannot exceed 4096. "
+ msg += "Code block height and width must be larger than 4."
+ raise IOError(msg)
+ if ((math.log(height, 2) != math.floor(math.log(height, 2)) or
+ math.log(width, 2) != math.floor(math.log(width, 2)))):
+ msg = "Bad code block size ({0}, {1}), "
+ msg += "must be powers of 2."
+ raise IOError(msg.format(height, width))
+
+ # Precinct size
+ if cparams.res_spec != 0:
+ # precinct size was not specified if this field is zero.
+ for j in range(cparams.res_spec):
+ prch = cparams.prch_init[j]
+ prcw = cparams.prcw_init[j]
+ if j == 0 and code_block_specified:
+ height, width = cparams.cblockh_init, cparams.cblockw_init
+ if height * 2 > prch or width * 2 > prcw:
+ msg = "Highest Resolution precinct size must be at "
+ msg += "least twice that of the code block dimensions."
+ raise IOError(msg)
+ if ((math.log(prch, 2) != math.floor(math.log(prch, 2)) or
+ math.log(prcw, 2) != math.floor(math.log(prcw, 2)))):
+ msg = "Bad precinct sizes ({0}, {1}), "
+ msg += "must be powers of 2."
+ raise IOError(msg.format(prch, prcw))
+
+ # What would the point of 1D images be?
+ if img_array.ndim == 1 or img_array.ndim > 3:
+ msg = "{0}D imagery is not allowed.".format(img_array.ndim)
+ raise IOError(msg)
+
+ if _OPENJP2_IS_OFFICIAL_V2:
+ 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 "
+ msg += "greyscale images or three-channel RGB images when "
+ msg += "the OpenJPEG library version is the official 2.0.0 "
+ msg += "release."
+ raise IOError(msg)
+
+ if img_array.dtype != np.uint8 and img_array.dtype != np.uint16:
+ 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,
+ 'ycc': opj2.CLRSPC_YCC}
+
+# Setup the default callback handlers. See the callback functions subsection
+# in the ctypes section of the Python documentation for a solid explanation of
+# what's going on here.
+_CMPFUNC = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_char_p, ctypes.c_void_p)
+
+
+def _default_error_handler(msg, _):
+ """Default error handler callback for openjpeg library."""
+ msg = "OpenJPEG library error: {0}".format(msg.decode('utf-8').rstrip())
+ opj2.set_error_message(msg)
+
+
+def _default_info_handler(msg, _):
+ """Default info handler callback for openjpeg library."""
+ print("[INFO] {0}".format(msg.decode('utf-8').rstrip()))
+
+
+def _default_warning_handler(library_msg, _):
+ """Default warning handler callback for openjpeg library."""
+ library_msg = library_msg.decode('utf-8').rstrip()
+ msg = "OpenJPEG library warning: {0}".format(library_msg)
+ warnings.warn(msg)
+
+_ERROR_CALLBACK = _CMPFUNC(_default_error_handler)
+_INFO_CALLBACK = _CMPFUNC(_default_info_handler)
+_WARNING_CALLBACK = _CMPFUNC(_default_warning_handler)
+
+
+class LibraryNotFoundError(IOError):
+ """Raised if functionality is requested without the necessary library.
+ """
+ def __init__(self, msg):
+ IOError.__init__(self, msg)
+
diff --git a/glymur/lib/__init__.py b/glymur/lib/__init__.py
index 20fee5f..a283f7f 100644
--- a/glymur/lib/__init__.py
+++ b/glymur/lib/__init__.py
@@ -1,4 +1,4 @@
"""This package organizes individual libraries employed by glymur."""
-from . import openjp2 as _openjp2
-from . import openjpeg as _openjpeg
+from . import openjp2 as openjp2
+from . import openjpeg as openjpeg
from . import c
diff --git a/glymur/lib/config.py b/glymur/lib/config.py
index e2b780a..e3a5c1d 100644
--- a/glymur/lib/config.py
+++ b/glymur/lib/config.py
@@ -1,6 +1,9 @@
"""
Configure glymur to use installed libraries if possible.
"""
+# configparser is new in python3 (pylint/python-2.7)
+# pylint: disable=F0401
+
import ctypes
from ctypes.util import find_library
import os
diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py
index 66a3cba..f0f72c0 100644
--- a/glymur/lib/openjp2.py
+++ b/glymur/lib/openjp2.py
@@ -2,7 +2,7 @@
Wraps individual functions in openjp2 library.
"""
-# pylint: disable=C0302,R0903
+# pylint: disable=C0302,R0903,W0201
import ctypes
import sys
@@ -1381,7 +1381,6 @@ def write_tile(codec, tile_index, data, data_size, stream):
def set_error_message(msg):
"""The openjpeg error handler has recorded an error message."""
- global ERROR_MSG_LST
ERROR_MSG_LST.append(msg)
diff --git a/glymur/lib/test/__init__.py b/glymur/lib/test/__init__.py
index 5aea3ab..47a3d86 100644
--- a/glymur/lib/test/__init__.py
+++ b/glymur/lib/test/__init__.py
@@ -1 +1,3 @@
-#from .test_openjp2 import TestOpenJP2 as openjp2
+"""
+Test suite for openjp2, openjpeg low-level functionality.
+"""
diff --git a/glymur/lib/test/test_openjp2.py b/glymur/lib/test/test_openjp2.py
index 710f265..54d8254 100644
--- a/glymur/lib/test/test_openjp2.py
+++ b/glymur/lib/test/test_openjp2.py
@@ -1,9 +1,14 @@
-#pylint: disable-all
-import doctest
+"""
+Tests for libopenjp2 wrapping functions.
+"""
+# R0904: Seems like pylint is fooled in this situation
+# W0142: using kwargs is ok in this context
+# pylint: disable=R0904,W0142
+
+# unittest2 is python-2.6 only (pylint/python-2.7)
+# pylint: disable=F0401
+
import os
-import pkg_resources
-import shutil
-import struct
import sys
import tempfile
@@ -15,28 +20,28 @@ else:
import numpy as np
import glymur
+from glymur.lib import openjp2
OPENJP2_IS_V2_OFFICIAL = False
-if glymur.lib.openjp2.OPENJP2 is not None:
- if not hasattr(glymur.lib.openjp2.OPENJP2,
+if openjp2.OPENJP2 is not None:
+ if not hasattr(openjp2.OPENJP2,
'opj_stream_create_default_file_stream_v3'):
OPENJP2_IS_V2_OFFICIAL = True
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
-@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
+@unittest.skipIf(openjp2.OPENJP2 is None,
"Missing openjp2 library.")
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL, "API followed here specific to V2.0+")
class TestOpenJP2(unittest.TestCase):
+ """Test openjp2 library functionality.
- def setUp(self):
- pass
+ Some tests correspond to those in the openjpeg test suite.
+ """
- def tearDown(self):
- pass
-
- def test_set_default_encoder_parameters(self):
- cparams = glymur.lib._openjp2.set_default_encoder_parameters()
+ def test_default_encoder_parameters(self):
+ """Ensure that the encoder structure is clean upon init."""
+ cparams = openjp2.set_default_encoder_parameters()
self.assertEqual(cparams.res_spec, 0)
self.assertEqual(cparams.cblockw_init, 64)
@@ -52,8 +57,9 @@ class TestOpenJP2(unittest.TestCase):
self.assertEqual(cparams.irreversible, 0)
- def test_set_default_decoder_parameters(self):
- dparams = glymur.lib._openjp2.set_default_decoder_parameters()
+ def test_default_decoder_parameters(self):
+ """Tests that the structure is clean upon initialization"""
+ dparams = openjp2.set_default_decoder_parameters()
self.assertEqual(dparams.DA_x0, 0)
self.assertEqual(dparams.DA_y0, 0)
@@ -61,35 +67,34 @@ class TestOpenJP2(unittest.TestCase):
self.assertEqual(dparams.DA_y1, 0)
def tile_macro(self, codec, stream, imagep, tidx):
- # called only by j2k_random_tile_access
- glymur.lib._openjp2.get_decoded_tile(codec, stream, imagep, tidx)
+ """called only by j2k_random_tile_access"""
+ openjp2.get_decoded_tile(codec, stream, imagep, tidx)
for j in range(imagep.contents.numcomps):
self.assertIsNotNone(imagep.contents.comps[j].data)
def j2k_random_tile_access(self, filename, codec_format=None):
- # called by the test_rtaX methods
- dparam = glymur.lib._openjp2.set_default_decoder_parameters()
+ """fixture called by the test_rtaX methods"""
+ dparam = openjp2.set_default_decoder_parameters()
infile = filename.encode()
- nelts = glymur.lib._openjp2.PATH_LEN - len(infile)
+ nelts = openjp2.PATH_LEN - len(infile)
infile += b'0' * nelts
dparam.infile = infile
dparam.decod_format = codec_format
- codec = glymur.lib._openjp2.create_decompress(codec_format)
+ codec = openjp2.create_decompress(codec_format)
- glymur.lib._openjp2.set_info_handler(codec, None)
- glymur.lib._openjp2.set_warning_handler(codec, None)
- glymur.lib._openjp2.set_error_handler(codec, None)
+ openjp2.set_info_handler(codec, None)
+ openjp2.set_warning_handler(codec, None)
+ openjp2.set_error_handler(codec, None)
- x = (filename, True)
- stream = glymur.lib._openjp2.stream_create_default_file_stream_v3(*x)
+ stream = openjp2.stream_create_default_file_stream_v3(filename, True)
- glymur.lib._openjp2.setup_decoder(codec, dparam)
- image = glymur.lib._openjp2.read_header(stream, codec)
+ openjp2.setup_decoder(codec, dparam)
+ image = openjp2.read_header(stream, codec)
- cstr_info = glymur.lib._openjp2.get_cstr_info(codec)
+ cstr_info = openjp2.get_cstr_info(codec)
tile_ul = 0
tile_ur = cstr_info.contents.tw - 1
@@ -101,159 +106,39 @@ class TestOpenJP2(unittest.TestCase):
self.tile_macro(codec, stream, image, tile_lr)
self.tile_macro(codec, stream, image, tile_ll)
- glymur.lib._openjp2.destroy_cstr_info(cstr_info)
+ openjp2.destroy_cstr_info(cstr_info)
- glymur.lib._openjp2.end_decompress(codec, stream)
- glymur.lib._openjp2.destroy_codec(codec)
- glymur.lib._openjp2.stream_destroy_v3(stream)
- glymur.lib._openjp2.image_destroy(image)
-
- def tile_decoder(self, x0=None, y0=None, x1=None, y1=None, filename=None,
- codec_format=None):
- x = (filename, True)
- stream = glymur.lib._openjp2.stream_create_default_file_stream_v3(*x)
- dparam = glymur.lib._openjp2.set_default_decoder_parameters()
-
- dparam.decod_format = codec_format
-
- # Do not use layer decoding limitation.
- dparam.cp_layer = 0
-
- # do not use resolution reductions.
- dparam.cp_reduce = 0
-
- codec = glymur.lib._openjp2.create_decompress(codec_format)
-
- glymur.lib._openjp2.set_info_handler(codec, None)
- glymur.lib._openjp2.set_warning_handler(codec, None)
- glymur.lib._openjp2.set_error_handler(codec, None)
-
- glymur.lib._openjp2.setup_decoder(codec, dparam)
- image = glymur.lib._openjp2.read_header(stream, codec)
- glymur.lib._openjp2.set_decode_area(codec, image, x0, y0, x1, y1)
-
- data = np.zeros((1150, 2048, 3), dtype=np.uint8)
- while True:
- rargs = glymur.lib._openjp2.read_tile_header(codec, stream)
- tidx = rargs[0]
- sz = rargs[1]
- go_on = rargs[-1]
- if not go_on:
- break
- glymur.lib._openjp2.decode_tile_data(codec, tidx, data, sz, stream)
-
- glymur.lib._openjp2.end_decompress(codec, stream)
- glymur.lib._openjp2.destroy_codec(codec)
- glymur.lib._openjp2.stream_destroy_v3(stream)
- glymur.lib._openjp2.image_destroy(image)
-
- def tile_encoder(self, num_comps=None, tile_width=None, tile_height=None,
- filename=None, codec=None, comp_prec=None,
- image_width=None, image_height=None,
- irreversible=None):
- num_tiles = (image_width / tile_width) * (image_height / tile_height)
- tile_size = tile_width * tile_height * num_comps * comp_prec / 8
-
- data = np.random.random((tile_height, tile_width, num_comps))
- data = (data * 255).astype(np.uint8)
-
- l_param = glymur.lib._openjp2.set_default_encoder_parameters()
-
- l_param.tcp_numlayers = 1
- l_param.cp_fixed_quality = 1
- l_param.tcp_distoratio[0] = 20
-
- # position of the tile grid aligned with the image
- l_param.cp_tx0 = 0
- l_param.cp_ty0 = 0
-
- # tile size, we are using tile based encoding
- l_param.tile_size_on = 1
- l_param.cp_tdx = tile_width
- l_param.cp_tdy = tile_height
-
- # use irreversible encoding
- l_param.irreversible = irreversible
-
- l_param.numresolution = 6
-
- l_param.prog_order = glymur.core.LRCP
-
- l_params = (glymur.lib._openjp2.ImageComptParmType * num_comps)()
- for j in range(num_comps):
- l_params[j].dx = 1
- l_params[j].dy = 1
- l_params[j].h = image_height
- l_params[j].w = image_width
- l_params[j].sgnd = 0
- l_params[j].prec = comp_prec
- l_params[j].x0 = 0
- l_params[j].y0 = 0
-
- codec = glymur.lib._openjp2.create_compress(codec)
-
- glymur.lib._openjp2.set_info_handler(codec, None)
- glymur.lib._openjp2.set_warning_handler(codec, None)
- glymur.lib._openjp2.set_error_handler(codec, None)
-
- cspace = glymur.lib._openjp2.CLRSPC_SRGB
- l_image = glymur.lib._openjp2.image_tile_create(l_params, cspace)
-
- l_image.contents.x0 = 0
- l_image.contents.y0 = 0
- l_image.contents.x1 = image_width
- l_image.contents.y1 = image_height
- l_image.contents.color_space = glymur.lib._openjp2.CLRSPC_SRGB
-
- glymur.lib._openjp2.setup_encoder(codec, l_param, l_image)
-
- x = (filename, False)
- stream = glymur.lib._openjp2.stream_create_default_file_stream_v3(*x)
- glymur.lib._openjp2.start_compress(codec, l_image, stream)
-
- for j in np.arange(num_tiles):
- glymur.lib._openjp2.write_tile(codec, j, data, tile_size, stream)
-
- glymur.lib._openjp2.end_compress(codec, stream)
- glymur.lib._openjp2.stream_destroy_v3(stream)
- glymur.lib._openjp2.destroy_codec(codec)
- glymur.lib._openjp2.image_destroy(l_image)
-
- def tte0_setup(self, filename):
- kwargs = {'filename': filename,
- 'codec': glymur.lib._openjp2.CODEC_J2K,
- 'comp_prec': 8,
- 'irreversible': 1,
- 'num_comps': 3,
- 'image_height': 200,
- 'image_width': 200,
- 'tile_height': 100,
- 'tile_width': 100}
- self.tile_encoder(**kwargs)
+ openjp2.end_decompress(codec, stream)
+ openjp2.destroy_codec(codec)
+ openjp2.stream_destroy_v3(stream)
+ openjp2.image_destroy(image)
def test_tte0(self):
- # Runs test designated tte0 in OpenJPEG test suite.
+ """Runs test designated tte0 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
- self.tte0_setup(tfile.name)
+ ttx0_setup(tfile.name)
+ self.assertTrue(True)
def test_ttd0(self):
- # Runs test designated ttd0 in OpenJPEG test suite.
+ """Runs test designated ttd0 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
# Produce the tte0 output file for ttd0 input.
- self.tte0_setup(tfile.name)
+ ttx0_setup(tfile.name)
kwargs = {'x0': 0,
'y0': 0,
'x1': 1000,
'y1': 1000,
'filename': tfile.name,
- 'codec_format': glymur.lib._openjp2.CODEC_J2K}
- self.tile_decoder(**kwargs)
+ 'codec_format': openjp2.CODEC_J2K}
+ tile_decoder(**kwargs)
+ self.assertTrue(True)
- def tte1_setup(self, filename):
+ def xtx1_setup(self, filename):
+ """Runs tests tte1, rta1."""
kwargs = {'filename': filename,
- 'codec': glymur.lib._openjp2.CODEC_J2K,
+ 'codec': openjp2.CODEC_J2K,
'comp_prec': 8,
'irreversible': 1,
'num_comps': 3,
@@ -261,149 +146,298 @@ class TestOpenJP2(unittest.TestCase):
'image_width': 256,
'tile_height': 128,
'tile_width': 128}
- self.tile_encoder(**kwargs)
+ tile_encoder(**kwargs)
+ self.assertTrue(True)
def test_tte1(self):
+ """Runs test designated tte1 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
- # Runs test designated tte1 in OpenJPEG test suite.
- self.tte1_setup(tfile.name)
+ self.xtx1_setup(tfile.name)
def test_ttd1(self):
- # Runs test designated ttd1 in OpenJPEG test suite.
+ """Runs test designated ttd1 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
# Produce the tte0 output file for ttd0 input.
- self.tte1_setup(tfile.name)
+ self.xtx1_setup(tfile.name)
kwargs = {'x0': 0,
'y0': 0,
'x1': 128,
'y1': 128,
'filename': tfile.name,
- 'codec_format': glymur.lib._openjp2.CODEC_J2K}
- self.tile_decoder(**kwargs)
+ 'codec_format': openjp2.CODEC_J2K}
+ tile_decoder(**kwargs)
+ self.assertTrue(True)
def test_rta1(self):
+ """Runs test designated rta1 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
- # Runs test designated rta1 in OpenJPEG test suite.
- self.tte1_setup(tfile.name)
+ self.xtx1_setup(tfile.name)
- kwargs = {'codec_format': glymur.lib._openjp2.CODEC_J2K}
- self.j2k_random_tile_access(tfile.name, **kwargs)
-
- def tte2_setup(self, filename):
- kwargs = {'filename': filename,
- 'codec': glymur.lib._openjp2.CODEC_JP2,
- 'comp_prec': 8,
- 'irreversible': 1,
- 'num_comps': 3,
- 'image_height': 256,
- 'image_width': 256,
- 'tile_height': 128,
- 'tile_width': 128}
- self.tile_encoder(**kwargs)
+ codec_format = openjp2.CODEC_J2K
+ self.j2k_random_tile_access(tfile.name, codec_format)
+ self.assertTrue(True)
def test_tte2(self):
- # Runs test designated tte2 in OpenJPEG test suite.
+ """Runs test designated tte2 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
- self.tte2_setup(tfile.name)
+ xtx2_setup(tfile.name)
+ self.assertTrue(True)
def test_ttd2(self):
- # Runs test designated ttd2 in OpenJPEG test suite.
+ """Runs test designated ttd2 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
# Produce the tte0 output file for ttd0 input.
- self.tte2_setup(tfile.name)
+ xtx2_setup(tfile.name)
kwargs = {'x0': 0,
'y0': 0,
'x1': 128,
'y1': 128,
'filename': tfile.name,
- 'codec_format': glymur.lib._openjp2.CODEC_JP2}
- self.tile_decoder(**kwargs)
+ 'codec_format': openjp2.CODEC_JP2}
+ tile_decoder(**kwargs)
+ self.assertTrue(True)
def test_rta2(self):
+ """Runs test designated rta2 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
- # Runs test designated rta2 in OpenJPEG test suite.
- self.tte2_setup(tfile.name)
+ xtx2_setup(tfile.name)
- kwargs = {'codec_format': glymur.lib._openjp2.CODEC_JP2}
- self.j2k_random_tile_access(tfile.name, **kwargs)
-
- def tte3_setup(self, filename):
- kwargs = {'filename': filename,
- 'codec': glymur.lib._openjp2.CODEC_J2K,
- 'comp_prec': 8,
- 'irreversible': 1,
- 'num_comps': 1,
- 'image_height': 256,
- 'image_width': 256,
- 'tile_height': 128,
- 'tile_width': 128}
- self.tile_encoder(**kwargs)
+ codec_format = openjp2.CODEC_JP2
+ self.j2k_random_tile_access(tfile.name, codec_format)
def test_tte3(self):
+ """Runs test designated tte3 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
- # Runs test designated tte3 in OpenJPEG test suite.
- self.tte3_setup(tfile.name)
+ xtx3_setup(tfile.name)
+ self.assertTrue(True)
def test_rta3(self):
- # Runs test designated rta3 in OpenJPEG test suite.
+ """Runs test designated rta3 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
- self.tte3_setup(tfile.name)
+ xtx3_setup(tfile.name)
- kwargs = {'codec_format': glymur.lib._openjp2.CODEC_J2K}
- self.j2k_random_tile_access(tfile.name, **kwargs)
-
- def tte4_setup(self, filename):
- kwargs = {'filename': filename,
- 'codec': glymur.lib._openjp2.CODEC_J2K,
- 'comp_prec': 8,
- 'irreversible': 0,
- 'num_comps': 1,
- 'image_height': 256,
- 'image_width': 256,
- 'tile_height': 128,
- 'tile_width': 128}
- self.tile_encoder(**kwargs)
+ codec_format = openjp2.CODEC_J2K
+ self.j2k_random_tile_access(tfile.name, codec_format)
+ self.assertTrue(True)
def test_tte4(self):
- # Runs test designated tte4 in OpenJPEG test suite.
+ """Runs test designated tte4 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
- self.tte4_setup(tfile.name)
+ xtx4_setup(tfile.name)
+ self.assertTrue(True)
def test_rta4(self):
- # Runs test designated rta4 in OpenJPEG test suite.
+ """Runs test designated rta4 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
- self.tte4_setup(tfile.name)
+ xtx4_setup(tfile.name)
- kwargs = {'codec_format': glymur.lib._openjp2.CODEC_J2K}
- self.j2k_random_tile_access(tfile.name, **kwargs)
-
- def tte5_setup(self, filename):
- kwargs = {'filename': filename,
- 'codec': glymur.lib._openjp2.CODEC_J2K,
- 'comp_prec': 8,
- 'irreversible': 0,
- 'num_comps': 1,
- 'image_height': 512,
- 'image_width': 512,
- 'tile_height': 256,
- 'tile_width': 256}
- self.tile_encoder(**kwargs)
+ codec_format = openjp2.CODEC_J2K
+ self.j2k_random_tile_access(tfile.name, codec_format)
def test_tte5(self):
- # Runs test designated tte5 in OpenJPEG test suite.
+ """Runs test designated tte5 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
- self.tte5_setup(tfile.name)
+ xtx5_setup(tfile.name)
+ self.assertTrue(True)
def test_rta5(self):
- # Runs test designated rta5 in OpenJPEG test suite.
+ """Runs test designated rta5 in OpenJPEG test suite."""
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
- self.tte5_setup(tfile.name)
+ xtx5_setup(tfile.name)
- kwargs = {'codec_format': glymur.lib._openjp2.CODEC_J2K}
- self.j2k_random_tile_access(tfile.name, **kwargs)
+ codec_format = openjp2.CODEC_J2K
+ self.j2k_random_tile_access(tfile.name, codec_format)
+
+
+#def tile_encoder(num_comps=None, tile_width=None, tile_height=None,
+# filename=None, codec=None, comp_prec=None,
+# image_width=None, image_height=None,
+# irreversible=None):
+def tile_encoder(**kwargs):
+ """Fixture used by many tests."""
+ num_tiles = ((kwargs['image_width'] / kwargs['tile_width']) *
+ (kwargs['image_height'] / kwargs['tile_height']))
+ tile_size = ((kwargs['tile_width'] * kwargs['tile_height']) *
+ (kwargs['num_comps'] * kwargs['comp_prec'] / 8))
+
+ data = np.random.random((kwargs['tile_height'],
+ kwargs['tile_width'],
+ kwargs['num_comps']))
+ data = (data * 255).astype(np.uint8)
+
+ l_param = openjp2.set_default_encoder_parameters()
+
+ l_param.tcp_numlayers = 1
+ l_param.cp_fixed_quality = 1
+ l_param.tcp_distoratio[0] = 20
+
+ # position of the tile grid aligned with the image
+ l_param.cp_tx0 = 0
+ l_param.cp_ty0 = 0
+
+ # tile size, we are using tile based encoding
+ l_param.tile_size_on = 1
+ l_param.cp_tdx = kwargs['tile_width']
+ l_param.cp_tdy = kwargs['tile_height']
+
+ # use irreversible encoding
+ l_param.irreversible = kwargs['irreversible']
+
+ l_param.numresolution = 6
+
+ l_param.prog_order = glymur.core.LRCP
+
+ l_params = (openjp2.ImageComptParmType * kwargs['num_comps'])()
+ for j in range(kwargs['num_comps']):
+ l_params[j].dx = 1
+ l_params[j].dy = 1
+ l_params[j].h = kwargs['image_height']
+ l_params[j].w = kwargs['image_width']
+ l_params[j].sgnd = 0
+ l_params[j].prec = kwargs['comp_prec']
+ l_params[j].x0 = 0
+ l_params[j].y0 = 0
+
+ codec = openjp2.create_compress(kwargs['codec'])
+
+ openjp2.set_info_handler(codec, None)
+ openjp2.set_warning_handler(codec, None)
+ openjp2.set_error_handler(codec, None)
+
+ cspace = openjp2.CLRSPC_SRGB
+ l_image = openjp2.image_tile_create(l_params, cspace)
+
+ l_image.contents.x0 = 0
+ l_image.contents.y0 = 0
+ l_image.contents.x1 = kwargs['image_width']
+ l_image.contents.y1 = kwargs['image_height']
+ l_image.contents.color_space = openjp2.CLRSPC_SRGB
+
+ openjp2.setup_encoder(codec, l_param, l_image)
+
+ stream = openjp2.stream_create_default_file_stream_v3(kwargs['filename'],
+ False)
+ openjp2.start_compress(codec, l_image, stream)
+
+ for j in np.arange(num_tiles):
+ openjp2.write_tile(codec, j, data, tile_size, stream)
+
+ openjp2.end_compress(codec, stream)
+ openjp2.stream_destroy_v3(stream)
+ openjp2.destroy_codec(codec)
+ openjp2.image_destroy(l_image)
+
+def tile_decoder(**kwargs):
+ """Fixture called with various configurations by many tests.
+
+ Reads a tile. That's all it does.
+ """
+ stream = openjp2.stream_create_default_file_stream_v3(kwargs['filename'],
+ True)
+ dparam = openjp2.set_default_decoder_parameters()
+
+ dparam.decod_format = kwargs['codec_format']
+
+ # Do not use layer decoding limitation.
+ dparam.cp_layer = 0
+
+ # do not use resolution reductions.
+ dparam.cp_reduce = 0
+
+ codec = openjp2.create_decompress(kwargs['codec_format'])
+
+ openjp2.set_info_handler(codec, None)
+ openjp2.set_warning_handler(codec, None)
+ openjp2.set_error_handler(codec, None)
+
+ openjp2.setup_decoder(codec, dparam)
+ image = openjp2.read_header(stream, codec)
+ openjp2.set_decode_area(codec, image,
+ kwargs['x0'], kwargs['y0'],
+ kwargs['x1'], kwargs['y1'])
+
+ data = np.zeros((1150, 2048, 3), dtype=np.uint8)
+ while True:
+ rargs = openjp2.read_tile_header(codec, stream)
+ tidx = rargs[0]
+ size = rargs[1]
+ go_on = rargs[-1]
+ if not go_on:
+ break
+ openjp2.decode_tile_data(codec, tidx, data, size, stream)
+
+ openjp2.end_decompress(codec, stream)
+ openjp2.destroy_codec(codec)
+ openjp2.stream_destroy_v3(stream)
+ openjp2.image_destroy(image)
+
+def ttx0_setup(filename):
+ """Runs tests tte0, tte0."""
+ kwargs = {'filename': filename,
+ 'codec': openjp2.CODEC_J2K,
+ 'comp_prec': 8,
+ 'irreversible': 1,
+ 'num_comps': 3,
+ 'image_height': 200,
+ 'image_width': 200,
+ 'tile_height': 100,
+ 'tile_width': 100}
+ tile_encoder(**kwargs)
+
+def xtx2_setup(filename):
+ """Runs tests rta2, tte2, ttd2."""
+ kwargs = {'filename': filename,
+ 'codec': openjp2.CODEC_JP2,
+ 'comp_prec': 8,
+ 'irreversible': 1,
+ 'num_comps': 3,
+ 'image_height': 256,
+ 'image_width': 256,
+ 'tile_height': 128,
+ 'tile_width': 128}
+ tile_encoder(**kwargs)
+
+def xtx3_setup(filename):
+ """Runs tests tte3, rta3."""
+ kwargs = {'filename': filename,
+ 'codec': openjp2.CODEC_J2K,
+ 'comp_prec': 8,
+ 'irreversible': 1,
+ 'num_comps': 1,
+ 'image_height': 256,
+ 'image_width': 256,
+ 'tile_height': 128,
+ 'tile_width': 128}
+ tile_encoder(**kwargs)
+
+def xtx4_setup(filename):
+ """Runs tests rta4, tte4."""
+ kwargs = {'filename': filename,
+ 'codec': openjp2.CODEC_J2K,
+ 'comp_prec': 8,
+ 'irreversible': 0,
+ 'num_comps': 1,
+ 'image_height': 256,
+ 'image_width': 256,
+ 'tile_height': 128,
+ 'tile_width': 128}
+ tile_encoder(**kwargs)
+
+def xtx5_setup(filename):
+ """Runs tests rta5, tte5."""
+ kwargs = {'filename': filename,
+ 'codec': openjp2.CODEC_J2K,
+ 'comp_prec': 8,
+ 'irreversible': 0,
+ 'num_comps': 1,
+ 'image_height': 512,
+ 'image_width': 512,
+ 'tile_height': 256,
+ 'tile_width': 256}
+ tile_encoder(**kwargs)
if __name__ == "__main__":
unittest.main()
diff --git a/glymur/lib/test/test_openjpeg.py b/glymur/lib/test/test_openjpeg.py
index 3dc7628..91e6d84 100644
--- a/glymur/lib/test/test_openjpeg.py
+++ b/glymur/lib/test/test_openjpeg.py
@@ -1,4 +1,11 @@
-#pylint: disable-all
+"""
+Tests for OpenJPEG module.
+"""
+# unittest2 is python2.6 only (pylint/python-2.7)
+# pylint: disable=F0401
+
+# pylint: disable=E1101,R0904
+
import ctypes
import re
import sys
@@ -10,43 +17,38 @@ else:
import glymur
-
-@unittest.skipIf(glymur.lib._openjpeg.OPENJPEG is None,
+@unittest.skipIf(glymur.lib.openjpeg.OPENJPEG is None,
"Missing openjpeg library.")
class TestOpenJPEG(unittest.TestCase):
-
- def setUp(self):
- pass
-
- def tearDown(self):
- pass
+ """Test suite for openjpeg functions we choose to expose."""
def test_version(self):
- version = glymur.lib._openjpeg.version()
+ """Only versions 1.3, 1.4, and 1.5 are supported."""
+ version = glymur.lib.openjpeg.version()
regex = re.compile('1.[345].[0-9]')
if sys.hexversion <= 0x03020000:
self.assertRegexpMatches(version, regex)
else:
self.assertRegex(version, regex)
- def test_set_default_decoder_parameters(self):
- # Verify that we properly set the default decode parameters.
- version = glymur.lib._openjpeg.version()
+ def test_default_decoder_parameters(self):
+ """Verify that we properly set the default decode parameters."""
+ version = glymur.lib.openjpeg.version()
minor = int(version.split('.')[1])
- dp = glymur.lib._openjpeg.DecompressionParametersType()
- glymur.lib._openjpeg.set_default_decoder_parameters(ctypes.byref(dp))
+ dcp = glymur.lib.openjpeg.DecompressionParametersType()
+ glymur.lib.openjpeg.set_default_decoder_parameters(ctypes.byref(dcp))
- self.assertEqual(dp.cp_reduce, 0)
- self.assertEqual(dp.cp_layer, 0)
- self.assertEqual(dp.infile, b'')
- self.assertEqual(dp.outfile, b'')
- self.assertEqual(dp.decod_format, -1)
- self.assertEqual(dp.cod_format, -1)
- self.assertEqual(dp.jpwl_correct, 0)
- self.assertEqual(dp.jpwl_exp_comps, 0)
- self.assertEqual(dp.jpwl_max_tiles, 0)
- self.assertEqual(dp.cp_limit_decoding, 0)
+ self.assertEqual(dcp.cp_reduce, 0)
+ self.assertEqual(dcp.cp_layer, 0)
+ self.assertEqual(dcp.infile, b'')
+ self.assertEqual(dcp.outfile, b'')
+ self.assertEqual(dcp.decod_format, -1)
+ self.assertEqual(dcp.cod_format, -1)
+ self.assertEqual(dcp.jpwl_correct, 0)
+ self.assertEqual(dcp.jpwl_exp_comps, 0)
+ self.assertEqual(dcp.jpwl_max_tiles, 0)
+ self.assertEqual(dcp.cp_limit_decoding, 0)
if minor > 4:
# Introduced in 1.5.x
- self.assertEqual(dp.flags, 0)
+ self.assertEqual(dcp.flags, 0)
diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py
index b6ba85d..ad4fe6d 100644
--- a/glymur/test/fixtures.py
+++ b/glymur/test/fixtures.py
@@ -36,8 +36,8 @@ except ImportError:
def read_image(infile):
"""Read image using matplotlib backend.
-
- Hopefully PIL(low) is installed as matplotlib's backend. It issues
+
+ Hopefully PIL(low) is installed as matplotlib's backend. It issues
warnings which we do not care about, so suppress them.
"""
with warnings.catch_warnings():
@@ -101,6 +101,7 @@ def read_pgx(pgx_file):
return(data.byteswap(swapbytes))
+
def determine_pgx_datatype(signed, bitdepth):
"""Determine the datatype of the PGX file.
@@ -128,6 +129,7 @@ def determine_pgx_datatype(signed, bitdepth):
return dtype
+
def read_pgx_header(pgx_file):
"""Open the file in ascii mode (not really) and read the header line.
Will look something like
@@ -150,4 +152,3 @@ def read_pgx_header(pgx_file):
header = header.rstrip()
return header, pos
-
diff --git a/glymur/test/test_callbacks.py b/glymur/test/test_callbacks.py
index 712dd5f..992ca3a 100644
--- a/glymur/test/test_callbacks.py
+++ b/glymur/test/test_callbacks.py
@@ -1,6 +1,13 @@
-#pylint: disable-all
+"""
+Test suite for openjpeg's callback functions.
+"""
+# R0904: Seems like pylint is fooled in this situation
+# pylint: disable=R0904
+
+# 'mock' most certainly is in unittest (Python 3.3)
+# pylint: disable=E0611,F0401
+
import os
-import pkg_resources
import re
import sys
import tempfile
@@ -24,6 +31,7 @@ import glymur
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
"Missing openjp2 library.")
class TestCallbacks(unittest.TestCase):
+ """Test suite for callbacks."""
def setUp(self):
self.jp2file = glymur.data.nemo()
@@ -34,7 +42,7 @@ class TestCallbacks(unittest.TestCase):
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_info_callback_on_write(self):
- # Verify the messages printed when writing an image in verbose mode.
+ """Verify messages printed when writing an image in verbose mode."""
j = glymur.Jp2k(self.jp2file)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
@@ -47,12 +55,14 @@ class TestCallbacks(unittest.TestCase):
expected = '[INFO] tile number 1 / 1'
self.assertEqual(actual, expected)
- def test_info_warning_callbacks_on_read(self):
+ def test_info_callbacks_on_read(self):
+ """stdio output when info callback handler is enabled"""
+
# Verify that we get the expected stdio output when our internal info
# callback handler is enabled.
j = glymur.Jp2k(self.j2kfile)
with patch('sys.stdout', new=StringIO()) as fake_out:
- d = j.read(rlevel=1, verbose=True, area=(0, 0, 200, 150))
+ j.read(rlevel=1, verbose=True, area=(0, 0, 200, 150))
actual = fake_out.getvalue().strip()
lines = ['[INFO] Start to read j2k main header (0).',
@@ -80,13 +90,16 @@ class TestCallbacks15(unittest.TestCase):
pass
def test_info_callbacks_on_read(self):
- # Verify that we get the expected stdio output when our internal info
- # callback handler is enabled.
+ """Verify stdout when reading.
+
+ Verify that we get the expected stdio output when our internal info
+ callback handler is enabled.
+ """
with patch('glymur.lib.openjp2.OPENJP2', new=None):
# Force to use OPENJPEG instead of OPENJP2.
j = glymur.Jp2k(self.j2kfile)
with patch('sys.stdout', new=StringIO()) as fake_out:
- d = j.read(rlevel=1, verbose=True)
+ j.read(rlevel=1, verbose=True)
actual = fake_out.getvalue().strip()
regex = re.compile(r"""\[INFO\]\stile\s1\sof\s1\s+
@@ -97,6 +110,9 @@ class TestCallbacks15(unittest.TestCase):
\[INFO\]\s-\stile\sdecoded\sin\s
[0-9]+\.[0-9]+\ss""",
re.VERBOSE)
+
+ # assertRegex in Python 3.3 (python2.7/pylint issue)
+ # pylint: disable=E1101
if sys.hexversion <= 0x03020000:
self.assertRegexpMatches(actual, regex)
else:
diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py
index f0d3e8a..f9d4d75 100644
--- a/glymur/test/test_codestream.py
+++ b/glymur/test/test_codestream.py
@@ -1,4 +1,16 @@
-#pylint: disable-all
+"""
+Test suite for codestream parsing.
+"""
+
+# unittest doesn't work well with R0904.
+# pylint: disable=R0904
+
+# tempfile.TemporaryDirectory, unittest.assertWarns introduced in 3.2
+# pylint: disable=E1101
+
+# unittest2 is python2.6 only (pylint/python-2.7)
+# pylint: disable=F0401
+
import os
import struct
import sys
@@ -9,23 +21,21 @@ if sys.hexversion < 0x02070000:
else:
import unittest
-import numpy as np
-import pkg_resources
-
from glymur import Jp2k
import glymur
try:
- data_root = os.environ['OPJ_DATA_ROOT']
+ DATA_ROOT = os.environ['OPJ_DATA_ROOT']
except KeyError:
- data_root = None
+ DATA_ROOT = None
except:
raise
-@unittest.skipIf(data_root is None,
+@unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
class TestCodestream(unittest.TestCase):
+ """Test suite for unusual codestream cases."""
def setUp(self):
self.jp2file = glymur.data.nemo()
@@ -35,75 +45,76 @@ class TestCodestream(unittest.TestCase):
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_reserved_marker_segment(self):
+ """Reserved marker segments are ok."""
+
# Some marker segments were reserved in FCD15444-1. Since that
# standard is old, some of them may have come into use.
#
# Let's inject a reserved marker segment into a file that
# we know something about to make sure we can still parse it.
- filename = os.path.join(data_root, 'input/conformance/p0_01.j2k')
+ filename = os.path.join(DATA_ROOT, 'input/conformance/p0_01.j2k')
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
with open(filename, 'rb') as ifile:
# Everything up until the first QCD marker.
- buffer = ifile.read(45)
- tfile.write(buffer)
+ read_buffer = ifile.read(45)
+ tfile.write(read_buffer)
# Write the new marker segment, 0xff6f = 65391
- buffer = struct.pack('>HHB', int(65391), int(3), int(0))
- tfile.write(buffer)
+ read_buffer = struct.pack('>HHB', int(65391), int(3), int(0))
+ tfile.write(read_buffer)
# Get the rest of the input file.
- buffer = ifile.read()
- tfile.write(buffer)
+ read_buffer = ifile.read()
+ tfile.write(read_buffer)
tfile.flush()
- j = Jp2k(tfile.name)
- c = j.get_codestream()
+ codestream = Jp2k(tfile.name).get_codestream()
- self.assertEqual(c.segment[2].marker_id, '0xff6f')
- self.assertEqual(c.segment[2].length, 3)
- self.assertEqual(c.segment[2]._data, b'\x00')
+ self.assertEqual(codestream.segment[2].marker_id, '0xff6f')
+ self.assertEqual(codestream.segment[2].length, 3)
+ self.assertEqual(codestream.segment[2].data, b'\x00')
@unittest.skipIf(sys.hexversion < 0x03020000,
"Uses features introduced in 3.2.")
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_unknown_marker_segment(self):
+ """Should warn for an unknown marker."""
# Let's inject a marker segment whose marker does not appear to
# be valid. We still parse the file, but warn about the offending
# marker.
- filename = os.path.join(data_root, 'input/conformance/p0_01.j2k')
+ filename = os.path.join(DATA_ROOT, 'input/conformance/p0_01.j2k')
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
with open(filename, 'rb') as ifile:
# Everything up until the first QCD marker.
- buffer = ifile.read(45)
- tfile.write(buffer)
+ read_buffer = ifile.read(45)
+ tfile.write(read_buffer)
# Write the new marker segment, 0xff79 = 65401
- buffer = struct.pack('>HHB', int(65401), int(3), int(0))
- tfile.write(buffer)
+ read_buffer = struct.pack('>HHB', int(65401), int(3), int(0))
+ tfile.write(read_buffer)
# Get the rest of the input file.
- buffer = ifile.read()
- tfile.write(buffer)
+ read_buffer = ifile.read()
+ tfile.write(read_buffer)
tfile.flush()
with self.assertWarns(UserWarning):
- j = Jp2k(tfile.name)
- c = j.get_codestream()
+ codestream = Jp2k(tfile.name).get_codestream()
- self.assertEqual(c.segment[2].marker_id, '0xff79')
- self.assertEqual(c.segment[2].length, 3)
- self.assertEqual(c.segment[2]._data, b'\x00')
+ self.assertEqual(codestream.segment[2].marker_id, '0xff79')
+ self.assertEqual(codestream.segment[2].length, 3)
+ self.assertEqual(codestream.segment[2].data, b'\x00')
def test_psot_is_zero(self):
- # Psot=0 in SOT is perfectly legal. Issue #78.
- filename = os.path.join(data_root,
+ """Psot=0 in SOT is perfectly legal. Issue #78."""
+ filename = os.path.join(DATA_ROOT,
'input/nonregression/123.j2c')
j = Jp2k(filename)
- c = j.get_codestream(header_only=False)
+ codestream = j.get_codestream(header_only=False)
# The codestream is valid, so we should be able to get the entire
# codestream, so the last one is EOC.
- self.assertEqual(c.segment[-1].marker_id, 'EOC')
+ self.assertEqual(codestream.segment[-1].marker_id, 'EOC')
if __name__ == "__main__":
unittest.main()
diff --git a/glymur/test/test_config.py b/glymur/test/test_config.py
index 7833865..b475b91 100644
--- a/glymur/test/test_config.py
+++ b/glymur/test/test_config.py
@@ -1,7 +1,15 @@
"""These tests are for edge cases where OPENJPEG does not exist, but
OPENJP2 may be present in some form or other.
"""
-#pylint: disable-all
+# unittest doesn't work well with R0904.
+# pylint: disable=R0904
+
+# tempfile.TemporaryDirectory, unittest.assertWarns introduced in 3.2
+# pylint: disable=E1101
+
+# unittest.mock only in Python 3.3 (python2.7/pylint import issue)
+# pylint: disable=E0611,F0401
+
import imp
import os
@@ -17,13 +25,9 @@ if sys.hexversion <= 0x03030000:
from mock import patch
else:
from unittest.mock import patch
-import warnings
-
-import pkg_resources
import glymur
from glymur import Jp2k
-from glymur.lib import openjp2 as opj2
@unittest.skipIf(sys.hexversion < 0x03020000,
@@ -31,6 +35,7 @@ from glymur.lib import openjp2 as opj2
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
"Needs openjp2 library first before these tests make sense.")
class TestSuite(unittest.TestCase):
+ """Test suite for configuration file operation."""
@classmethod
def setUpClass(cls):
@@ -56,29 +61,33 @@ class TestSuite(unittest.TestCase):
filename = os.path.join(configdir, 'glymurrc')
with open(filename, 'wt') as tfile:
tfile.write('[library]\n')
+
+ # Need to reliably recover the location of the openjp2 library,
+ # so using '_name' appears to be the only way to do it.
+ # pylint: disable=W0212
libloc = glymur.lib.openjp2.OPENJP2._name
line = 'openjp2: {0}\n'.format(libloc)
tfile.write(line)
tfile.flush()
with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
imp.reload(glymur.lib.openjp2)
- j = Jp2k(self.jp2file)
+ Jp2k(self.jp2file)
- def test_config_file_via_environ_is_wrong(self):
- # A non-existant library location should be rejected.
+ def test_xdg_env_config_file_is_bad(self):
+ """A non-existant library location should be rejected."""
with tempfile.TemporaryDirectory() as tdir:
configdir = os.path.join(tdir, 'glymur')
os.mkdir(configdir)
fname = os.path.join(configdir, 'glymurrc')
- with open(fname, 'w') as fp:
+ with open(fname, 'w') as fptr:
with tempfile.NamedTemporaryFile(suffix='.dylib') as tfile:
- fp.write('[library]\n')
- fp.write('openjp2: {0}.not.there\n'.format(tfile.name))
- fp.flush()
+ fptr.write('[library]\n')
+ fptr.write('openjp2: {0}.not.there\n'.format(tfile.name))
+ fptr.flush()
with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
# Misconfigured new configuration file should
# be rejected.
- with self.assertWarns(UserWarning) as cw:
+ with self.assertWarns(UserWarning):
imp.reload(glymur.lib.openjp2)
if __name__ == "__main__":
diff --git a/glymur/test/test_conformance.py b/glymur/test/test_conformance.py
index 9e1ad1a..3c7c9c4 100644
--- a/glymur/test/test_conformance.py
+++ b/glymur/test/test_conformance.py
@@ -1,9 +1,17 @@
"""
These tests deal with JPX/JP2/J2K images in the format-corpus repository.
"""
-#pylint: disable-all
+# R0904: Not too many methods in unittest.
+# pylint: disable=R0904
+
+# E1101: assertWarns introduced in python 3.2
+# pylint: disable=E1101
+
+# unittest2 is python2.6 only (pylint/python-2.7)
+# pylint: disable=F0401
import os
+from os.path import join
import sys
if sys.hexversion < 0x02070000:
@@ -11,106 +19,98 @@ if sys.hexversion < 0x02070000:
else:
import unittest
-import warnings
-
from glymur import Jp2k
-import glymur
try:
- format_corpus_data_root = os.environ['FORMAT_CORPUS_DATA_ROOT']
+ FORMAT_CORPUS_DATA_ROOT = os.environ['FORMAT_CORPUS_DATA_ROOT']
except KeyError:
- format_corpus_data_root = None
+ FORMAT_CORPUS_DATA_ROOT = None
try:
- opj_data_root = os.environ['OPJ_DATA_ROOT']
+ OPJ_DATA_ROOT = os.environ['OPJ_DATA_ROOT']
except KeyError:
- opj_data_root = None
+ OPJ_DATA_ROOT = None
-@unittest.skipIf(format_corpus_data_root is None,
+@unittest.skipIf(FORMAT_CORPUS_DATA_ROOT is None,
"FORMAT_CORPUS_DATA_ROOT environment variable not set")
@unittest.skipIf(sys.hexversion < 0x03020000,
"Requires features introduced in 3.2 (assertWarns)")
class TestSuiteFormatCorpus(unittest.TestCase):
-
- def setUp(self):
- pass
-
- def tearDown(self):
- pass
+ """Test suite for files in format corpus repository."""
def test_balloon_trunc1(self):
- # Has one byte shaved off of EOC marker.
- jfile = os.path.join(format_corpus_data_root,
+ """Has one byte shaved off of EOC marker."""
+ jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT,
'jp2k-test/byteCorruption/balloon_trunc1.jp2')
j2k = Jp2k(jfile)
with self.assertWarns(UserWarning):
- c = j2k.get_codestream(header_only=False)
+ codestream = j2k.get_codestream(header_only=False)
# The last segment is truncated, so there should not be an EOC marker.
- self.assertNotEqual(c.segment[-1].marker_id, 'EOC')
+ self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC')
# The codestream is not as long as claimed.
with self.assertRaises(OSError):
j2k.read(rlevel=-1)
def test_balloon_trunc2(self):
- # Shortened by 5000 bytes.
- jfile = os.path.join(format_corpus_data_root,
+ """Shortened by 5000 bytes."""
+ jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT,
'jp2k-test/byteCorruption/balloon_trunc2.jp2')
j2k = Jp2k(jfile)
with self.assertWarns(UserWarning):
- c = j2k.get_codestream(header_only=False)
+ codestream = j2k.get_codestream(header_only=False)
# The last segment is truncated, so there should not be an EOC marker.
- self.assertNotEqual(c.segment[-1].marker_id, 'EOC')
+ self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC')
# The codestream is not as long as claimed.
with self.assertRaises(OSError):
j2k.read(rlevel=-1)
def test_balloon_trunc3(self):
- # Most of last tile is missing.
- jfile = os.path.join(format_corpus_data_root,
+ """Most of last tile is missing."""
+ jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT,
'jp2k-test/byteCorruption/balloon_trunc3.jp2')
j2k = Jp2k(jfile)
with self.assertWarns(UserWarning):
- c = j2k.get_codestream(header_only=False)
+ codestream = j2k.get_codestream(header_only=False)
# The last segment is truncated, so there should not be an EOC marker.
- self.assertNotEqual(c.segment[-1].marker_id, 'EOC')
+ self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC')
# Should error out, it does not.
#with self.assertRaises(OSError):
# j2k.read(rlevel=-1)
- def test_jp2_brand_vs_any_icc_profile(self):
- # If 'jp2 ', then the method cannot be any icc profile.
- jfile = os.path.join(format_corpus_data_root,
+ def test_jp2_brand_any_icc_profile(self):
+ """If 'jp2 ', then the method cannot be any icc profile."""
+ jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT,
'jp2k-test', 'icc',
'balloon_eciRGBv2_ps_adobeplugin.jpf')
with self.assertWarns(UserWarning):
- j2k = Jp2k(jfile)
+ Jp2k(jfile)
- def test_jp2_brand_vs_any_icc_profile_multiple_colr(self):
- # Has colr box, one that conforms, one that does not.
+ def test_jp2_brand_iccpr_mult_colr(self):
+ """Has colr box, one that conforms, one that does not."""
# Wrong 'brand' field; contains two versions of ICC profile: one
# embedded using "Any ICC" method; other embedded using "Restricted
# ICC" method, with description ("Modified eciRGB v2") and profileClass
# ("Input Device") changed relative to original profile.
- lst = [format_corpus_data_root, 'jp2k-test', 'icc',
- 'balloon_eciRGBv2_ps_adobeplugin_jp2compatible.jpf']
- jfile = os.path.join(*lst)
+ jfile = join(FORMAT_CORPUS_DATA_ROOT, 'jp2k-test', 'icc',
+ 'balloon_eciRGBv2_ps_adobeplugin_jp2compatible.jpf')
with self.assertWarns(UserWarning):
- j2k = Jp2k(jfile)
+ Jp2k(jfile)
-@unittest.skipIf(opj_data_root is None,
+@unittest.skipIf(OPJ_DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
@unittest.skipIf(sys.hexversion < 0x03020000,
"Requires features introduced in 3.2 (assertWarns)")
class TestSuiteOpj(unittest.TestCase):
+ """Test suite for files in openjpeg repository."""
def setUp(self):
pass
@@ -118,12 +118,12 @@ class TestSuiteOpj(unittest.TestCase):
def tearDown(self):
pass
- def test_jp2_brand_vs_any_icc_profile(self):
- # If 'jp2 ', then the method cannot be any icc profile.
- filename = os.path.join(opj_data_root,
+ def test_jp2_brand_any_icc_profile(self):
+ """If 'jp2 ', then the method cannot be any icc profile."""
+ filename = os.path.join(OPJ_DATA_ROOT,
'input/nonregression/text_GBR.jp2')
with self.assertWarns(UserWarning):
- j2k = Jp2k(filename)
+ Jp2k(filename)
if __name__ == "__main__":
unittest.main()
diff --git a/glymur/test/test_icc.py b/glymur/test/test_icc.py
index 4334645..bf589a5 100644
--- a/glymur/test/test_icc.py
+++ b/glymur/test/test_icc.py
@@ -1,35 +1,38 @@
-#pylint: disable-all
+"""
+ICC profile tests.
+"""
+
+# unittest doesn't work well with R0904.
+# pylint: disable=R0904
+
+# unittest2 is python2.6 only (pylint/python-2.7)
+# pylint: disable=F0401
+
import datetime
import os
-import struct
import sys
-import tempfile
if sys.hexversion < 0x02070000:
import unittest2 as unittest
else:
import unittest
-import warnings
-from xml.etree import cElementTree as ET
-
import numpy as np
-import pkg_resources
from glymur import Jp2k
-import glymur
try:
- data_root = os.environ['OPJ_DATA_ROOT']
+ DATA_ROOT = os.environ['OPJ_DATA_ROOT']
except KeyError:
- data_root = None
+ DATA_ROOT = None
except:
raise
-@unittest.skipIf(data_root is None,
+@unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
class TestICC(unittest.TestCase):
+ """ICC profile tests"""
def setUp(self):
pass
@@ -38,7 +41,8 @@ class TestICC(unittest.TestCase):
pass
def test_file5(self):
- filename = os.path.join(data_root, 'input/conformance/file5.jp2')
+ """basic ICC profile"""
+ filename = os.path.join(DATA_ROOT, 'input/conformance/file5.jp2')
j = Jp2k(filename)
profile = j.box[3].box[1].icc_profile
self.assertEqual(profile['Size'], 546)
@@ -70,10 +74,14 @@ class TestICC(unittest.TestCase):
@unittest.skipIf(sys.hexversion < 0x03020000,
"Uses features introduced in 3.2.")
def test_invalid_profile_header(self):
- jfile = os.path.join(data_root,
+ """invalid ICC header data should cause UserWarning"""
+ jfile = os.path.join(DATA_ROOT,
'input/nonregression/orb-blue10-lin-jp2.jp2')
- with self.assertWarns(UserWarning) as cw:
- j = Jp2k(jfile)
+
+ # assertWarns in Python 3.3 (python2.7/pylint issue)
+ # pylint: disable=E1101
+ with self.assertWarns(UserWarning):
+ Jp2k(jfile)
if __name__ == "__main__":
unittest.main()
diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py
index a563b58..15c0209 100644
--- a/glymur/test/test_jp2box.py
+++ b/glymur/test/test_jp2box.py
@@ -1,4 +1,21 @@
-#pylint: disable-all
+"""
+Test suite specifically targeting JP2 box layout.
+"""
+# E1103: return value from read may be list or np array
+# pylint: disable=E1103
+
+# F0401: unittest2 is needed on python-2.6 (pylint on 2.7)
+# pylint: disable=F0401
+
+# R0902: More than 7 instance attributes are just fine for testing.
+# pylint: disable=R0902
+
+# R0904: Seems like pylint is fooled in this situation
+# pylint: disable=R0904
+
+# W0613: load_tests doesn't need to use ignore or loader arguments.
+# pylint: disable=W0613
+
import doctest
import os
import sys
@@ -11,24 +28,25 @@ else:
import unittest
import numpy as np
-import pkg_resources
import glymur
from glymur import Jp2k
-from glymur.jp2box import *
+from glymur.jp2box import ColourSpecificationBox, ContiguousCodestreamBox
+from glymur.jp2box import FileTypeBox, ImageHeaderBox, JP2HeaderBox
+from glymur.jp2box import JPEG2000SignatureBox
from glymur.core import COLOR, OPACITY
from glymur.core import RED, GREEN, BLUE, GREY, WHOLE_IMAGE
from .fixtures import OPENJP2_IS_V2_OFFICIAL
try:
- format_corpus_data_root = os.environ['FORMAT_CORPUS_DATA_ROOT']
+ FORMAT_CORPUS_DATA_ROOT = os.environ['FORMAT_CORPUS_DATA_ROOT']
except KeyError:
- format_corpus_data_root = None
+ FORMAT_CORPUS_DATA_ROOT = None
-# Doc tests should be run as well.
def load_tests(loader, tests, ignore):
+ """Run doc tests as well."""
if os.name == "nt":
# Can't do it on windows, temporary file issue.
return tests
@@ -42,6 +60,7 @@ def load_tests(loader, tests, ignore):
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
"Missing openjp2 library.")
class TestChannelDefinition(unittest.TestCase):
+ """Test suite for channel definition boxes."""
@classmethod
def setUpClass(cls):
@@ -78,12 +97,12 @@ class TestChannelDefinition(unittest.TestCase):
self.j2kfile = glymur.data.goodstuff()
j2k = Jp2k(self.j2kfile)
- c = j2k.get_codestream()
- height = c.segment[1].ysiz
- width = c.segment[1].xsiz
- num_components = len(c.segment[1].xrsiz)
+ codestream = j2k.get_codestream()
+ height = codestream.segment[1].ysiz
+ width = codestream.segment[1].xsiz
+ num_components = len(codestream.segment[1].xrsiz)
- self.jP = JPEG2000SignatureBox()
+ self.jp2b = JPEG2000SignatureBox()
self.ftyp = FileTypeBox()
self.jp2h = JP2HeaderBox()
self.jp2c = ContiguousCodestreamBox()
@@ -110,7 +129,7 @@ class TestChannelDefinition(unittest.TestCase):
association=association)
boxes = [self.ihdr, self.colr_rgb, cdef]
self.jp2h.box = boxes
- boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
+ boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
@@ -133,7 +152,7 @@ class TestChannelDefinition(unittest.TestCase):
association=association)
boxes = [self.ihdr, self.colr_rgb, cdef]
self.jp2h.box = boxes
- boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
+ boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
@@ -156,7 +175,7 @@ class TestChannelDefinition(unittest.TestCase):
association=association)
boxes = [self.ihdr, self.colr_rgb, cdef]
self.jp2h.box = boxes
- boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
+ boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
@@ -177,9 +196,9 @@ class TestChannelDefinition(unittest.TestCase):
association=association)
boxes = [self.ihdr, self.colr_rgb, cdef]
self.jp2h.box = boxes
- boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
+ boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
- with self.assertRaises(IOError) as ce:
+ with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
def test_grey(self):
@@ -191,7 +210,7 @@ class TestChannelDefinition(unittest.TestCase):
association=association)
boxes = [self.ihdr, self.colr_gr, cdef]
self.jp2h.box = boxes
- boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
+ boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
@@ -212,7 +231,7 @@ class TestChannelDefinition(unittest.TestCase):
association=association)
boxes = [self.ihdr, self.colr_gr, cdef]
self.jp2h.box = boxes
- boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
+ boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
@@ -236,12 +255,12 @@ class TestChannelDefinition(unittest.TestCase):
association=association)
boxes = [self.ihdr, self.colr_gr, cdef]
self.jp2h.box = boxes
- boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
+ boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
- with self.assertRaises((OSError, IOError)) as ce:
+ with self.assertRaises((OSError, IOError)):
j2k.wrap(tfile.name, boxes=boxes)
- def test_only_one_cdef_in_jp2_header(self):
+ def test_only_one_cdef_in_jp2h(self):
"""There can only be one channel definition box in the jp2 header."""
j2k = Jp2k(self.j2kfile)
@@ -253,13 +272,14 @@ class TestChannelDefinition(unittest.TestCase):
boxes = [self.ihdr, cdef, self.colr_rgb, cdef]
self.jp2h.box = boxes
- boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
+ boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
- def test_not_in_jp2_header(self):
+ def test_not_in_jp2h(self):
+ """need cdef in jp2h"""
j2k = Jp2k(self.j2kfile)
boxes = [self.ihdr, self.colr_rgb]
self.jp2h.box = boxes
@@ -269,34 +289,37 @@ class TestChannelDefinition(unittest.TestCase):
cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
association=association)
- boxes = [self.jP, self.ftyp, self.jp2h, cdef, self.jp2c]
+ boxes = [self.jp2b, self.ftyp, self.jp2h, cdef, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
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
- # same length.
+ """Channel types are limited to 0, 1, 2, 65535
+ Should reject if not all of index, channel_type, association the
+ same length.
+ """
channel_type = (COLOR, COLOR, 3)
association = (RED, GREEN, BLUE)
with self.assertRaises(IOError):
- box = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
- association=association)
+ glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
+ association=association)
def test_wrong_lengths(self):
- # Should reject if not all of index, channel_type, association the
- # same length.
+ """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):
- box = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
- association=association)
+ glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
+ association=association)
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
class TestXML(unittest.TestCase):
+ """Test suite for XML boxes."""
def setUp(self):
self.jp2file = glymur.data.nemo()
@@ -331,12 +354,12 @@ class TestXML(unittest.TestCase):
self.xmlfile = tfile.name
j2k = Jp2k(self.j2kfile)
- c = j2k.get_codestream()
- height = c.segment[1].ysiz
- width = c.segment[1].xsiz
- num_components = len(c.segment[1].xrsiz)
+ codestream = j2k.get_codestream()
+ height = codestream.segment[1].ysiz
+ width = codestream.segment[1].xsiz
+ num_components = len(codestream.segment[1].xrsiz)
- self.jP = JPEG2000SignatureBox()
+ self.jp2b = JPEG2000SignatureBox()
self.ftyp = FileTypeBox()
self.jp2h = JP2HeaderBox()
self.jp2c = ContiguousCodestreamBox()
@@ -346,19 +369,17 @@ class TestXML(unittest.TestCase):
def tearDown(self):
os.unlink(self.xmlfile)
- pass
- def test_negative_both_file_and_xml_provided(self):
+ def test_negative_file_and_xml(self):
"""The XML should come from only one source."""
- j2k = Jp2k(self.j2kfile)
xml_object = ET.parse(self.xmlfile)
- with self.assertRaises((IOError, OSError)) as ce:
- xmlb = glymur.jp2box.XMLBox(filename=self.xmlfile, xml=xml_object)
+ with self.assertRaises((IOError, OSError)):
+ glymur.jp2box.XMLBox(filename=self.xmlfile, xml=xml_object)
@unittest.skipIf(os.name == "nt",
"Problems using NamedTemporaryFile on windows.")
def test_basic_xml(self):
- # Should be able to write an XMLBox.
+ """Should be able to write a basic XMLBox"""
j2k = Jp2k(self.j2kfile)
self.jp2h.box = [self.ihdr, self.colr]
@@ -368,7 +389,7 @@ class TestXML(unittest.TestCase):
self.assertEqual(ET.tostring(xmlb.xml),
b'0')
- boxes = [self.jP, self.ftyp, self.jp2h, xmlb, self.jp2c]
+ boxes = [self.jp2b, self.ftyp, self.jp2h, xmlb, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
@@ -380,12 +401,13 @@ class TestXML(unittest.TestCase):
@unittest.skipIf(os.name == "nt",
"Problems using NamedTemporaryFile on windows.")
def test_xml_from_file(self):
+ """Must be able to create an XML box from an XML file."""
j2k = Jp2k(self.j2kfile)
self.jp2h.box = [self.ihdr, self.colr]
xmlb = glymur.jp2box.XMLBox(filename=self.xmlfile)
- boxes = [self.jP, self.ftyp, self.jp2h, xmlb, self.jp2c]
+ boxes = [self.jp2b, self.ftyp, self.jp2h, xmlb, self.jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name, boxes=boxes)
jp2 = Jp2k(tfile.name)
@@ -403,17 +425,18 @@ class TestXML(unittest.TestCase):
class TestColourSpecificationBox(unittest.TestCase):
+ """Test suite for colr box instantiation."""
def setUp(self):
self.j2kfile = glymur.data.goodstuff()
j2k = Jp2k(self.j2kfile)
- c = j2k.get_codestream()
- height = c.segment[1].ysiz
- width = c.segment[1].xsiz
- num_components = len(c.segment[1].xrsiz)
+ codestream = j2k.get_codestream()
+ height = codestream.segment[1].ysiz
+ width = codestream.segment[1].xsiz
+ num_components = len(codestream.segment[1].xrsiz)
- self.jP = JPEG2000SignatureBox()
+ self.jp2b = JPEG2000SignatureBox()
self.ftyp = FileTypeBox()
self.jp2h = JP2HeaderBox()
self.jp2c = ContiguousCodestreamBox()
@@ -425,10 +448,11 @@ class TestColourSpecificationBox(unittest.TestCase):
@unittest.skipIf(os.name == "nt",
"Problems using NamedTemporaryFile on windows.")
- def test_color_specification_box_with_out_enumerated_colorspace(self):
+ def test_colr_with_out_enum_cspace(self):
+ """must supply an enumerated colorspace when writing"""
j2k = Jp2k(self.j2kfile)
- boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
+ boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
boxes[2].box = [self.ihdr, ColourSpecificationBox(colorspace=None)]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(NotImplementedError):
@@ -436,47 +460,52 @@ class TestColourSpecificationBox(unittest.TestCase):
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_missing_colr_box(self):
+ """jp2h must have a colr box"""
j2k = Jp2k(self.j2kfile)
- boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
+ boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c]
boxes[2].box = [self.ihdr]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
- def test_default_ColourSpecificationBox(self):
- b = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.SRGB)
- self.assertEqual(b.method, glymur.core.ENUMERATED_COLORSPACE)
- self.assertEqual(b.precedence, 0)
- self.assertEqual(b.approximation, 0)
- self.assertEqual(b.colorspace, glymur.core.SRGB)
- self.assertIsNone(b.icc_profile)
+ def test_default_colr(self):
+ """basic colr instantiation"""
+ colr = ColourSpecificationBox(colorspace=glymur.core.SRGB)
+ self.assertEqual(colr.method, glymur.core.ENUMERATED_COLORSPACE)
+ self.assertEqual(colr.precedence, 0)
+ self.assertEqual(colr.approximation, 0)
+ self.assertEqual(colr.colorspace, glymur.core.SRGB)
+ self.assertIsNone(colr.icc_profile)
- def test_ColourSpecificationBox_with_colorspace_and_icc(self):
- # Colour specification boxes can't have both.
+ def test_colr_with_cspace_and_icc(self):
+ """Colour specification boxes can't have both."""
with self.assertRaises((OSError, IOError)):
colorspace = glymur.core.SRGB
- icc_profile = b'\x01\x02\x03\x04'
- b = glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
- icc_profile=icc_profile)
+ rawb = b'\x01\x02\x03\x04'
+ glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
+ icc_profile=rawb)
- def test_ColourSpecificationBox_with_bad_method(self):
+ def test_colr_with_bad_method(self):
+ """colr must have a valid method field"""
colorspace = glymur.core.SRGB
method = -1
with self.assertRaises(IOError):
- b = glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
- method=method)
+ glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
+ method=method)
- def test_ColourSpecificationBox_with_bad_approximation(self):
+ def test_colr_with_bad_approx(self):
+ """colr must have a valid approximation field"""
colorspace = glymur.core.SRGB
approx = -1
with self.assertRaises(IOError):
- b = glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
- approximation=approx)
+ glymur.jp2box.ColourSpecificationBox(colorspace=colorspace,
+ approximation=approx)
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
"Missing openjp2 library.")
class TestWrap(unittest.TestCase):
+ """Tests for wrap method."""
def setUp(self):
self.j2kfile = glymur.data.goodstuff()
@@ -486,7 +515,7 @@ class TestWrap(unittest.TestCase):
pass
def verify_wrapped_raw(self, jp2file):
- # Shared method by at least two tests.
+ """Shared fixture"""
jp2 = Jp2k(jp2file)
self.assertEqual(len(jp2.box), 4)
@@ -536,6 +565,7 @@ class TestWrap(unittest.TestCase):
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_wrap(self):
+ """basic test for rewrapping a j2c file, no specified boxes"""
j2k = Jp2k(self.j2kfile)
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
j2k.wrap(tfile.name)
@@ -543,6 +573,7 @@ class TestWrap(unittest.TestCase):
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_wrap_jp2(self):
+ """basic test for rewrapping a jp2 file, no specified boxes"""
j2k = Jp2k(self.jp2file)
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
jp2 = j2k.wrap(tfile.name)
@@ -550,16 +581,17 @@ class TestWrap(unittest.TestCase):
self.assertEqual(boxes, ['jP ', 'ftyp', 'jp2h', 'jp2c'])
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
- def test_default_layout_but_with_specified_boxes(self):
+ def test_default_layout_with_boxes(self):
+ """basic test for rewrapping a jp2 file, boxes specified"""
j2k = Jp2k(self.j2kfile)
boxes = [JPEG2000SignatureBox(),
FileTypeBox(),
JP2HeaderBox(),
ContiguousCodestreamBox()]
- c = j2k.get_codestream()
- height = c.segment[1].ysiz
- width = c.segment[1].xsiz
- num_components = len(c.segment[1].xrsiz)
+ codestream = j2k.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),
@@ -569,17 +601,17 @@ class TestWrap(unittest.TestCase):
self.verify_wrapped_raw(tfile.name)
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
- def test_image_header_box_not_first_in_jp2_header(self):
- # The specification says that ihdr must be the first box in jp2h.
+ def test_ihdr_not_first_in_jp2h(self):
+ """The specification says that ihdr must be the first box in jp2h."""
j2k = Jp2k(self.j2kfile)
boxes = [JPEG2000SignatureBox(),
FileTypeBox(),
JP2HeaderBox(),
ContiguousCodestreamBox()]
- c = j2k.get_codestream()
- height = c.segment[1].ysiz
- width = c.segment[1].xsiz
- num_components = len(c.segment[1].xrsiz)
+ codestream = j2k.get_codestream()
+ height = codestream.segment[1].ysiz
+ width = codestream.segment[1].xsiz
+ num_components = len(codestream.segment[1].xrsiz)
boxes[2].box = [ColourSpecificationBox(colorspace=glymur.core.SRGB),
ImageHeaderBox(height=height,
width=width,
@@ -589,14 +621,15 @@ class TestWrap(unittest.TestCase):
j2k.wrap(tfile.name, boxes=boxes)
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
- def test_first_2_boxes_not_jP_and_ftyp(self):
+ def test_first_boxes_jp_and_ftyp(self):
+ """first two boxes must be jP followed by ftyp"""
j2k = Jp2k(self.j2kfile)
- c = j2k.get_codestream()
- height = c.segment[1].ysiz
- width = c.segment[1].xsiz
- num_components = len(c.segment[1].xrsiz)
+ codestream = j2k.get_codestream()
+ height = codestream.segment[1].ysiz
+ width = codestream.segment[1].xsiz
+ num_components = len(codestream.segment[1].xrsiz)
- jP = JPEG2000SignatureBox()
+ jp2b = JPEG2000SignatureBox()
ftyp = FileTypeBox()
jp2h = JP2HeaderBox()
jp2c = ContiguousCodestreamBox()
@@ -604,20 +637,21 @@ class TestWrap(unittest.TestCase):
ihdr = ImageHeaderBox(height=height, width=width,
num_components=num_components)
jp2h.box = [ihdr, colr]
- boxes = [ftyp, jP, jp2h, jp2c]
+ boxes = [ftyp, jp2b, jp2h, jp2c]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_jp2h_not_preceeding_jp2c(self):
+ """jp2h must precede jp2c"""
j2k = Jp2k(self.j2kfile)
- c = j2k.get_codestream()
- height = c.segment[1].ysiz
- width = c.segment[1].xsiz
- num_components = len(c.segment[1].xrsiz)
+ codestream = j2k.get_codestream()
+ height = codestream.segment[1].ysiz
+ width = codestream.segment[1].xsiz
+ num_components = len(codestream.segment[1].xrsiz)
- jP = JPEG2000SignatureBox()
+ jp2b = JPEG2000SignatureBox()
ftyp = FileTypeBox()
jp2h = JP2HeaderBox()
jp2c = ContiguousCodestreamBox()
@@ -625,68 +659,74 @@ class TestWrap(unittest.TestCase):
ihdr = ImageHeaderBox(height=height, width=width,
num_components=num_components)
jp2h.box = [ihdr, colr]
- boxes = [jP, ftyp, jp2c, jp2h]
+ boxes = [jp2b, ftyp, jp2c, jp2h]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_missing_codestream(self):
+ """Need a codestream box in order to call wrap method."""
j2k = Jp2k(self.j2kfile)
- c = j2k.get_codestream()
- height = c.segment[1].ysiz
- width = c.segment[1].xsiz
- num_components = len(c.segment[1].xrsiz)
+ codestream = j2k.get_codestream()
+ height = codestream.segment[1].ysiz
+ width = codestream.segment[1].xsiz
+ num_components = len(codestream.segment[1].xrsiz)
- jP = JPEG2000SignatureBox()
+ jp2k = JPEG2000SignatureBox()
ftyp = FileTypeBox()
jp2h = JP2HeaderBox()
ihdr = ImageHeaderBox(height=height, width=width,
num_components=num_components)
jp2h.box = [ihdr]
- boxes = [jP, ftyp, jp2h]
+ boxes = [jp2k, ftyp, jp2h]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
with self.assertRaises(IOError):
j2k.wrap(tfile.name, boxes=boxes)
class TestJp2Boxes(unittest.TestCase):
+ """Tests for canonical JP2 boxes."""
- def test_default_JPEG2000SignatureBox(self):
- # Should be able to instantiate a JPEG2000SignatureBox
- b = glymur.jp2box.JPEG2000SignatureBox()
- self.assertEqual(b.signature, (13, 10, 135, 10))
+ def test_default_jp2k(self):
+ """Should be able to instantiate a JPEG2000SignatureBox"""
+ jp2k = glymur.jp2box.JPEG2000SignatureBox()
+ self.assertEqual(jp2k.signature, (13, 10, 135, 10))
- def test_default_FileTypeBox(self):
- # Should be able to instantiate a FileTypeBox
- b = glymur.jp2box.FileTypeBox()
- self.assertEqual(b.brand, 'jp2 ')
- self.assertEqual(b.minor_version, 0)
- self.assertEqual(b.compatibility_list, ['jp2 '])
+ def test_default_ftyp(self):
+ """Should be able to instantiate a FileTypeBox"""
+ ftyp = glymur.jp2box.FileTypeBox()
+ self.assertEqual(ftyp.brand, 'jp2 ')
+ self.assertEqual(ftyp.minor_version, 0)
+ self.assertEqual(ftyp.compatibility_list, ['jp2 '])
- def test_default_ImageHeaderBox(self):
- # Should be able to instantiate an image header box.
- b = glymur.jp2box.ImageHeaderBox(height=512, width=256,
+ def test_default_ihdr(self):
+ """Should be able to instantiate an image header box."""
+ ihdr = glymur.jp2box.ImageHeaderBox(height=512, width=256,
num_components=3)
- self.assertEqual(b.height, 512)
- self.assertEqual(b.width, 256)
- self.assertEqual(b.num_components, 3)
- self.assertEqual(b.bits_per_component, 8)
- self.assertFalse(b.signed)
- self.assertFalse(b.colorspace_unknown)
+ self.assertEqual(ihdr.height, 512)
+ self.assertEqual(ihdr.width, 256)
+ self.assertEqual(ihdr.num_components, 3)
+ self.assertEqual(ihdr.bits_per_component, 8)
+ self.assertFalse(ihdr.signed)
+ self.assertFalse(ihdr.colorspace_unknown)
- def test_default_JP2HeaderBox(self):
- b1 = JP2HeaderBox()
- b1.box = [ImageHeaderBox(height=512, width=256),
+ def test_default_jp2headerbox(self):
+ """Should be able to set jp2h boxes."""
+ box = JP2HeaderBox()
+ box.box = [ImageHeaderBox(height=512, width=256),
ColourSpecificationBox(colorspace=glymur.core.GREYSCALE)]
+ self.assertTrue(True)
- def test_default_ContiguousCodestreamBox(self):
- b = ContiguousCodestreamBox()
- self.assertEqual(b.box_id, 'jp2c')
- self.assertIsNone(b.main_header)
+ def test_default_ccodestreambox(self):
+ """Raw instantiation should not produce a main_header."""
+ box = ContiguousCodestreamBox()
+ self.assertEqual(box.box_id, 'jp2c')
+ self.assertIsNone(box.main_header)
class TestJpxBoxes(unittest.TestCase):
+ """Tests for JPX boxes."""
def setUp(self):
pass
@@ -694,11 +734,11 @@ class TestJpxBoxes(unittest.TestCase):
def tearDown(self):
pass
- @unittest.skipIf(format_corpus_data_root is None,
+ @unittest.skipIf(FORMAT_CORPUS_DATA_ROOT is None,
"FORMAT_CORPUS_DATA_ROOT environment variable not set")
def test_codestream_header(self):
- # Should recognize codestream header box.
- jfile = os.path.join(format_corpus_data_root,
+ """Should recognize codestream header box."""
+ jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT,
'jp2k-formats/balloon.jpf')
jpx = Jp2k(jfile)
@@ -706,11 +746,11 @@ class TestJpxBoxes(unittest.TestCase):
self.assertEqual(jpx.box[4].box_id, 'jpch')
self.assertEqual(len(jpx.box[4].box), 0)
- @unittest.skipIf(format_corpus_data_root is None,
+ @unittest.skipIf(FORMAT_CORPUS_DATA_ROOT is None,
"FORMAT_CORPUS_DATA_ROOT environment variable not set")
def test_compositing_layer_header(self):
- # Should recognize compositing layer header box.
- jfile = os.path.join(format_corpus_data_root,
+ """Should recognize compositing layer header box."""
+ jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT,
'jp2k-formats/balloon.jpf')
jpx = Jp2k(jfile)
diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py
index 1371a05..747782f 100644
--- a/glymur/test/test_jp2k.py
+++ b/glymur/test/test_jp2k.py
@@ -1,4 +1,15 @@
-# pylint: disable-all
+"""
+Tests for general glymur functionality.
+"""
+# E1101: assertWarns introduced in python 3.2
+# pylint: disable=E1101
+
+# R0904: Not too many methods in unittest.
+# pylint: disable=R0904
+
+# E0611: unittest.mock is unknown to python2.7/pylint
+# pylint: disable=E0611,F0401
+
import doctest
import os
import re
@@ -25,20 +36,24 @@ import pkg_resources
import glymur
from glymur import Jp2k
-from glymur.lib import openjp2 as opj2
from .fixtures import OPENJP2_IS_V2_OFFICIAL
try:
- data_root = os.environ['OPJ_DATA_ROOT']
+ DATA_ROOT = os.environ['OPJ_DATA_ROOT']
except KeyError:
- data_root = None
+ DATA_ROOT = None
except:
raise
# Doc tests should be run as well.
def load_tests(loader, tests, ignore):
+ # W0613: "loader" and "ignore" are necessary for the protocol
+ # They are unused here, however.
+ # pylint: disable=W0613
+
+ """Should run doc tests as well"""
if os.name == "nt":
# Can't do it on windows, temporary file issue.
return tests
@@ -54,6 +69,7 @@ def load_tests(loader, tests, ignore):
glymur.lib.openjpeg.OPENJPEG is None,
"Missing openjp2 library.")
class TestConfig(unittest.TestCase):
+ """Test suite for reading without proper library in place."""
def setUp(self):
self.jp2file = glymur.data.nemo()
@@ -62,24 +78,24 @@ class TestConfig(unittest.TestCase):
def tearDown(self):
pass
- def test_read_without_library_backing_us_up(self):
+ def test_read_without_library(self):
"""Don't have either openjp2 or openjpeg libraries? Must error out.
"""
with patch('glymur.lib.openjp2.OPENJP2', new=None):
with patch('glymur.lib.openjpeg.OPENJPEG', new=None):
with self.assertRaises(glymur.jp2k.LibraryNotFoundError):
- d = glymur.Jp2k(self.jp2file).read()
+ glymur.Jp2k(self.jp2file).read()
- def test_read_bands_without_library_backing_us_up(self):
+ def test_read_bands_without_library(self):
"""Don't have openjp2 library? Must error out.
"""
with patch('glymur.lib.openjp2.OPENJP2', new=None):
with patch('glymur.lib.openjpeg.OPENJPEG', new=None):
with self.assertRaises(glymur.jp2k.LibraryNotFoundError):
- d = glymur.Jp2k(self.jp2file).read_bands()
+ glymur.Jp2k(self.jp2file).read_bands()
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
- def test_write_without_library_backing_us_up(self):
+ def test_write_without_library(self):
"""Don't have openjp2 library? Must error out.
"""
data = glymur.Jp2k(self.j2kfile).read()
@@ -95,32 +111,34 @@ class TestConfig(unittest.TestCase):
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
"Missing openjp2 library.")
class TestJp2kBadXmlFile(unittest.TestCase):
+ """Test suite for bad XML box situations"""
@classmethod
def setUpClass(cls):
- # Setup a JP2 file with a bad XML box. We only need to do this once
- # per class rather than once per test.
+ """Setup a JP2 file with a bad XML box. We only need to do this once
+ per class rather than once per test.
+ """
jp2file = pkg_resources.resource_filename(glymur.__name__,
"data/nemo.jp2")
with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile:
cls._bad_xml_file = tfile.name
with open(jp2file, 'rb') as ifile:
# Everything up until the jp2c box.
- buffer = ifile.read(77)
- tfile.write(buffer)
+ write_buffer = ifile.read(77)
+ tfile.write(write_buffer)
# Write the xml box with bad xml
# Length = 28, id is 'xml '.
- buffer = struct.pack('>I4s', int(28), b'xml ')
- tfile.write(buffer)
+ write_buffer = struct.pack('>I4s', int(28), b'xml ')
+ tfile.write(write_buffer)
- buffer = 'this is a test'
- buffer = buffer.encode()
- tfile.write(buffer)
+ write_buffer = 'this is a test'
+ write_buffer = write_buffer.encode()
+ tfile.write(write_buffer)
# Get the rest of the input file.
- buffer = ifile.read()
- tfile.write(buffer)
+ write_buffer = ifile.read()
+ tfile.write(write_buffer)
tfile.flush()
@classmethod
@@ -137,13 +155,12 @@ class TestJp2kBadXmlFile(unittest.TestCase):
@unittest.skipIf(sys.hexversion < 0x03020000,
"Uses features introduced in 3.2.")
def test_invalid_xml_box_warning(self):
- # Should be able to recover from xml box with bad xml.
- # Just verify that a warning is issued on 3.3+
- with self.assertWarns(UserWarning) as cw:
- jp2k = Jp2k(self._bad_xml_file)
+ """Should warn in case of bad XML"""
+ with self.assertWarns(UserWarning):
+ Jp2k(self._bad_xml_file)
def test_invalid_xml_box(self):
- # Should be able to recover from xml box with bad xml.
+ """Should be able to recover info from xml box with bad xml."""
with warnings.catch_warnings():
warnings.simplefilter("ignore")
jp2k = Jp2k(self._bad_xml_file)
@@ -157,6 +174,7 @@ class TestJp2kBadXmlFile(unittest.TestCase):
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
"Missing openjp2 library.")
class TestJp2k(unittest.TestCase):
+ """Test suite for version 2.0/2.0+ of openjpeg"""
def setUp(self):
self.jp2file = glymur.data.nemo()
@@ -166,7 +184,7 @@ class TestJp2k(unittest.TestCase):
pass
def test_rlevel_max(self):
- # Verify that rlevel=-1 gets us the lowest resolution image
+ """Verify that rlevel=-1 gets us the lowest resolution image"""
j = Jp2k(self.j2kfile)
thumbnail1 = j.read(rlevel=-1)
thumbnail2 = j.read(rlevel=5)
@@ -174,43 +192,52 @@ class TestJp2k(unittest.TestCase):
self.assertEqual(thumbnail1.shape, (25, 15, 3))
def test_bad_area_parameter(self):
- # Verify that we error out appropriately if given a bad area parameter.
+ """Should error out appropriately if given a bad area parameter."""
j = Jp2k(self.jp2file)
with self.assertRaises(IOError):
# Start corner must be >= 0
- d = j.read(area=(-1, -1, 1, 1))
+ j.read(area=(-1, -1, 1, 1))
with self.assertRaises(IOError):
# End corner must be > 0
- d = j.read(area=(10, 10, 0, 0))
+ j.read(area=(10, 10, 0, 0))
with self.assertRaises(IOError):
# End corner must be >= start corner
- d = j.read(area=(10, 10, 8, 8))
+ j.read(area=(10, 10, 8, 8))
def test_rlevel_too_high(self):
- # Verify that we error out appropriately if not given a JPEG 2000 file.
+ """Should error out appropriately if reduce level too high"""
j = Jp2k(self.jp2file)
with self.assertRaises(IOError):
- d = j.read(rlevel=6)
+ j.read(rlevel=6)
- def test_not_JPEG2000(self):
- # Verify that we error out appropriately if not given a JPEG 2000 file.
+ def test_not_jpeg2000(self):
+ """Should error out appropriately if not given a JPEG 2000 file."""
filename = pkg_resources.resource_filename(glymur.__name__, "jp2k.py")
with self.assertRaises(IOError):
- jp2k = Jp2k(filename)
+ Jp2k(filename)
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
# at all.
- if sys.hexversion < 0x03030000:
- error = OSError
- else:
- error = IOError
- with self.assertRaises(error):
+ with self.assertRaises(OSError):
filename = 'this file does not actually exist on the file system.'
- jp2k = Jp2k(filename)
+ Jp2k(filename)
+
+ @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
+ def test_write_with_jp2_in_caps(self):
+ """should be able to write with JP2 suffix."""
+ j2k = Jp2k(self.j2kfile)
+ expdata = j2k.read()
+ with tempfile.NamedTemporaryFile(suffix='.JP2') as tfile:
+ ofile = Jp2k(tfile.name, 'wb')
+ ofile.write(expdata)
+ actdata = ofile.read()
+ np.testing.assert_array_equal(actdata, expdata)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_write_srgb_without_mct(self):
+ """should be able to write RGB without specifying mct"""
j2k = Jp2k(self.j2kfile)
expdata = j2k.read()
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
@@ -219,12 +246,12 @@ class TestJp2k(unittest.TestCase):
actdata = ofile.read()
np.testing.assert_array_equal(actdata, expdata)
- c = ofile.get_codestream()
- self.assertEqual(c.segment[2].spcod[3], 0) # no mct
+ codestream = ofile.get_codestream()
+ self.assertEqual(codestream.segment[2].spcod[3], 0) # no mct
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_write_grayscale_with_mct(self):
- # MCT usage makes no sense for grayscale images.
+ """MCT usage makes no sense for grayscale images."""
j2k = Jp2k(self.j2kfile)
expdata = j2k.read()
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
@@ -234,6 +261,7 @@ class TestJp2k(unittest.TestCase):
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_write_cprl(self):
+ """Must be able to write a CPRL progression order file"""
# Issue 17
j = Jp2k(self.jp2file)
expdata = j.read(rlevel=1)
@@ -243,11 +271,11 @@ class TestJp2k(unittest.TestCase):
actdata = ofile.read()
np.testing.assert_array_equal(actdata, expdata)
- c = ofile.get_codestream()
- self.assertEqual(c.segment[2].spcod[0], glymur.core.CPRL)
+ codestream = ofile.get_codestream()
+ self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL)
def test_jp2_boxes(self):
- # Verify the boxes of a JP2 file.
+ """Verify the boxes of a JP2 file. Basic jp2 test."""
jp2k = Jp2k(self.jp2file)
# top-level boxes
@@ -305,39 +333,41 @@ class TestJp2k(unittest.TestCase):
self.assertEqual(jp2k.box[2].box[1].colorspace, glymur.core.SRGB)
self.assertIsNone(jp2k.box[2].box[1].icc_profile)
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_j2k_box(self):
+ """A J2K/J2C file must not have any boxes."""
# Verify that a J2K file has no boxes.
- filename = os.path.join(data_root, 'input/conformance/p0_01.j2k')
+ filename = os.path.join(DATA_ROOT, 'input/conformance/p0_01.j2k')
jp2k = Jp2k(filename)
self.assertEqual(len(jp2k.box), 0)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
- def test_64bit_XL_field(self):
+ def test_64bit_xl_field(self):
+ """XL field should be supported"""
# Verify that boxes with the XL field are properly read.
# Don't have such a file on hand, so we create one. Copy our example
# file, but making the codestream have a 64-bit XL field.
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
with open(self.jp2file, 'rb') as ifile:
# Everything up until the jp2c box.
- buffer = ifile.read(3127)
- tfile.write(buffer)
+ write_buffer = ifile.read(3127)
+ tfile.write(write_buffer)
# The L field must be 1 in order to signal the presence of the
# XL field. The actual length of the jp2c box increased by 8
# (8 bytes for the XL field).
- L = 1
- T = b'jp2c'
- XL = 1133427 + 8
- buffer = struct.pack('>I4sQ', int(L), T, XL)
- tfile.write(buffer)
+ length = 1
+ typ = b'jp2c'
+ xlen = 1133427 + 8
+ write_buffer = struct.pack('>I4sQ', int(length), typ, xlen)
+ tfile.write(write_buffer)
# Get the rest of the input file (minus the 8 bytes for L and
# T.
ifile.seek(8, 1)
- buffer = ifile.read()
- tfile.write(buffer)
+ write_buffer = ifile.read()
+ tfile.write(write_buffer)
tfile.flush()
jp2k = Jp2k(tfile.name)
@@ -347,7 +377,8 @@ class TestJp2k(unittest.TestCase):
self.assertEqual(jp2k.box[5].length, 1133427 + 8)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
- def test_L_is_zero(self):
+ def test_length_field_is_zero(self):
+ """L=0 (length field in box header) is allowed"""
# Verify that boxes with the L field as zero are correctly read.
# This should only happen in the last box of a JPEG 2000 file.
# Our example image has its last box at byte 588458.
@@ -355,19 +386,19 @@ class TestJp2k(unittest.TestCase):
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
with open(self.jp2file, 'rb') as ifile:
# Everything up until the jp2c box.
- buffer = ifile.read(588458)
- tfile.write(buffer)
+ write_buffer = ifile.read(588458)
+ tfile.write(write_buffer)
- L = 0
- T = b'uuid'
- buffer = struct.pack('>I4s', int(L), T)
- tfile.write(buffer)
+ length = 0
+ typ = b'uuid'
+ write_buffer = struct.pack('>I4s', int(length), typ)
+ tfile.write(write_buffer)
# Get the rest of the input file (minus the 8 bytes for L and
# T.
ifile.seek(8, 1)
- buffer = ifile.read()
- tfile.write(buffer)
+ write_buffer = ifile.read()
+ tfile.write(write_buffer)
tfile.flush()
new_jp2 = Jp2k(tfile.name)
@@ -381,31 +412,34 @@ class TestJp2k(unittest.TestCase):
self.assertEqual(new_jp2.box[j].length,
baseline_jp2.box[j].length)
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_read_differing_subsamples(self):
+ """should error out with read used on differently subsampled images"""
# Verify that we error out appropriately if we use the read method
# on an image with differing subsamples
#
# Issue 86.
- filename = os.path.join(data_root, 'input/conformance/p0_05.j2k')
+ filename = os.path.join(DATA_ROOT, 'input/conformance/p0_05.j2k')
j = Jp2k(filename)
with self.assertRaises(RuntimeError):
j.read()
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_empty_box_with_j2k(self):
- # Verify that the list of boxes in a J2C/J2K file is present, but
- # empty.
- filename = os.path.join(data_root, 'input/conformance/p0_05.j2k')
+ """Verify that the list of boxes in a J2C/J2K file is present, but
+ empty.
+ """
+ filename = os.path.join(DATA_ROOT, 'input/conformance/p0_05.j2k')
j = Jp2k(filename)
self.assertEqual(j.box, [])
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
- def test_code_block_height_different_than_width(self):
- # Verify that we can set a code block size where height does not equal
- # width.
+ def test_cblkh_different_than_width(self):
+ """Verify that we can set a code block size where height does not equal
+ width.
+ """
data = np.zeros((128, 128), dtype=np.uint8)
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
@@ -413,49 +447,50 @@ class TestJp2k(unittest.TestCase):
# The code block dimensions are given as rows x columns.
j.write(data, cbsize=(16, 32))
- c = j.get_codestream()
+ codestream = j.get_codestream()
# Code block size is reported as XY in the codestream.
- self.assertEqual(tuple(c.segment[2].spcod[5:7]), (3, 2))
+ self.assertEqual(tuple(codestream.segment[2].spcod[5:7]), (3, 2))
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
- def test_negative_too_many_dimensions(self):
- # OpenJP2 only allows 2D or 3D images.
+ def test_too_many_dimensions(self):
+ """OpenJP2 only allows 2D or 3D images."""
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
- with self.assertRaises(IOError) as ce:
+ with self.assertRaises(IOError):
data = np.zeros((128, 128, 2, 2), dtype=np.uint8)
j.write(data)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
- def test_unrecognized_jp2_colorspace(self):
- # We only allow RGB and GRAYSCALE.
+ def test_unrecognized_jp2_clrspace(self):
+ """We only allow RGB and GRAYSCALE. Should error out with others"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
- with self.assertRaises(IOError) as ce:
+ with self.assertRaises(IOError):
data = np.zeros((128, 128, 3), dtype=np.uint8)
j.write(data, colorspace='cmyk')
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
- def test_2D_rgb(self):
- # RGB must have at least 3 components.
+ def test_2d_rgb(self):
+ """RGB must have at least 3 components."""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
- with self.assertRaises(IOError) as ce:
+ with self.assertRaises(IOError):
data = np.zeros((128, 128, 2), dtype=np.uint8)
j.write(data, colorspace='rgb')
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_colorspace_with_j2k(self):
- # Specifying a colorspace with J2K does not make sense.
+ """Specifying a colorspace with J2K does not make sense"""
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
j = Jp2k(tfile.name, 'wb')
- with self.assertRaises(IOError) as ce:
+ with self.assertRaises(IOError):
data = np.zeros((128, 128, 3), dtype=np.uint8)
j.write(data, colorspace='rgb')
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_specify_rgb(self):
+ """specify RGB explicitly"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
data = np.zeros((128, 128, 3), dtype=np.uint8)
@@ -464,6 +499,7 @@ class TestJp2k(unittest.TestCase):
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_specify_gray(self):
+ """test gray explicitly specified (that's GRAY, not GREY)"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
data = np.zeros((128, 128), dtype=np.uint8)
@@ -473,6 +509,7 @@ class TestJp2k(unittest.TestCase):
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_specify_grey(self):
+ """test grey explicitly specified"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
data = np.zeros((128, 128), dtype=np.uint8)
@@ -484,6 +521,7 @@ class TestJp2k(unittest.TestCase):
"Does not seem to work on official v2.0.0 release.")
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_grey_with_extra_component(self):
+ """version 2.0 cannot write gray + extra"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
data = np.zeros((128, 128, 2), dtype=np.uint8)
@@ -495,7 +533,8 @@ class TestJp2k(unittest.TestCase):
glymur.core.GREYSCALE)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
- def test_grey_with_two_extra_components(self):
+ def test_grey_with_two_extra_comps(self):
+ """should be able to write gray + two extra components"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
data = np.zeros((128, 128, 3), dtype=np.uint8)
@@ -510,6 +549,7 @@ class TestJp2k(unittest.TestCase):
"Does not seem to work on official v2.0.0 release.")
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_rgb_with_extra_component(self):
+ """v2.0+ should be able to write extra components"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
data = np.zeros((128, 128, 4), dtype=np.uint8)
@@ -522,7 +562,8 @@ class TestJp2k(unittest.TestCase):
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL is False,
"Test is specific for v2.0.0 release")
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
- def test_extra_components_on_v2_official(self):
+ def test_extra_components_on_v2(self):
+ """must error out in 1.x with extra components."""
# Extra components seems to require 2.0+. Verify that we error out.
with self.assertRaises(IOError):
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
@@ -531,48 +572,50 @@ class TestJp2k(unittest.TestCase):
j.write(data)
def test_specify_ycc(self):
- # We don't support writing YCC at the moment.
+ """Should reject YCC"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
j = Jp2k(tfile.name, 'wb')
- with self.assertRaises(IOError) as ce:
+ with self.assertRaises(IOError):
data = np.zeros((128, 128, 3), dtype=np.uint8)
j.write(data, colorspace='ycc')
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_uinf_ulst_url_boxes(self):
+ """Verify that we can read UINF, ULST, and URL boxes"""
# Verify that we can read UINF, ULST, and URL boxes. I don't have
# easy access to such a file, and there's no such file in the
# openjpeg repository, so I'll fake one.
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
with open(self.jp2file, 'rb') as ifile:
# Everything up until the jp2c box.
- buffer = ifile.read(77)
- tfile.write(buffer)
+ write_buffer = ifile.read(77)
+ tfile.write(write_buffer)
# Write the UINF superbox
# Length = 50, id is uinf.
- buffer = struct.pack('>I4s', int(50), b'uinf')
- tfile.write(buffer)
+ write_buffer = struct.pack('>I4s', int(50), b'uinf')
+ tfile.write(write_buffer)
# Write the ULST box.
# Length is 26, 1 UUID, hard code that UUID as zeros.
- buffer = struct.pack('>I4sHIIII', int(26), b'ulst', int(1),
- int(0), int(0), int(0), int(0))
- tfile.write(buffer)
+ write_buffer = struct.pack('>I4sHIIII', int(26), b'ulst',
+ int(1), int(0), int(0), int(0),
+ int(0))
+ tfile.write(write_buffer)
# Write the URL box.
# Length is 16, version is one byte, flag is 3 bytes, url
# is the rest.
- buffer = struct.pack('>I4sBBBB',
- int(16), b'url ',
- int(0), int(0), int(0), int(0))
- tfile.write(buffer)
- buffer = struct.pack('>ssss', b'a', b'b', b'c', b'd')
- tfile.write(buffer)
+ write_buffer = struct.pack('>I4sBBBB',
+ int(16), b'url ',
+ int(0), int(0), int(0), int(0))
+ tfile.write(write_buffer)
+ write_buffer = struct.pack('>ssss', b'a', b'b', b'c', b'd')
+ tfile.write(write_buffer)
# Get the rest of the input file.
- buffer = ifile.read()
- tfile.write(buffer)
+ write_buffer = ifile.read()
+ tfile.write(write_buffer)
tfile.flush()
jp2k = Jp2k(tfile.name)
@@ -596,27 +639,26 @@ class TestJp2k(unittest.TestCase):
self.assertEqual(jp2k.box[3].box[1].url, 'abcd')
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
- def test_xml_box_with_trailing_nulls(self):
- # ElementTree does not like trailing null chars after valid XML
- # text.
+ def test_xml_with_trailing_nulls(self):
+ """ElementTree doesn't like trailing null chars after valid XML text"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
with open(self.jp2file, 'rb') as ifile:
# Everything up until the jp2c box.
- buffer = ifile.read(77)
- tfile.write(buffer)
+ write_buffer = ifile.read(77)
+ tfile.write(write_buffer)
# Write the xml box
# Length = 36, id is 'xml '.
- buffer = struct.pack('>I4s', int(36), b'xml ')
- tfile.write(buffer)
+ write_buffer = struct.pack('>I4s', int(36), b'xml ')
+ tfile.write(write_buffer)
- buffer = 'this is a test' + chr(0)
- buffer = buffer.encode()
- tfile.write(buffer)
+ write_buffer = 'this is a test' + chr(0)
+ write_buffer = write_buffer.encode()
+ tfile.write(write_buffer)
# Get the rest of the input file.
- buffer = ifile.read()
- tfile.write(buffer)
+ write_buffer = ifile.read()
+ tfile.write(write_buffer)
tfile.flush()
jp2k = Jp2k(tfile.name)
@@ -629,6 +671,7 @@ class TestJp2k(unittest.TestCase):
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_asoc_label_box(self):
+ """Test asoc and label box"""
# Construct a fake file with an asoc and a label box, as
# OpenJPEG doesn't have such a file.
data = Jp2k(self.jp2file).read(rlevel=1)
@@ -639,30 +682,30 @@ class TestJp2k(unittest.TestCase):
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2:
# Offset of the codestream is where we start.
- buffer = tfile.read(77)
- tfile2.write(buffer)
+ read_buffer = tfile.read(77)
+ tfile2.write(read_buffer)
# read the rest of the file, it's the codestream.
codestream = tfile.read()
# Write the asoc superbox.
# Length = 36, id is 'asoc'.
- buffer = struct.pack('>I4s', int(56), b'asoc')
- tfile2.write(buffer)
+ write_buffer = struct.pack('>I4s', int(56), b'asoc')
+ tfile2.write(write_buffer)
# Write the contained label box
- buffer = struct.pack('>I4s', int(13), b'lbl ')
- tfile2.write(buffer)
+ write_buffer = struct.pack('>I4s', int(13), b'lbl ')
+ tfile2.write(write_buffer)
tfile2.write('label'.encode())
# Write the xml box
# Length = 36, id is 'xml '.
- buffer = struct.pack('>I4s', int(35), b'xml ')
- tfile2.write(buffer)
+ write_buffer = struct.pack('>I4s', int(35), b'xml ')
+ tfile2.write(write_buffer)
- buffer = 'this is a test'
- buffer = buffer.encode()
- tfile2.write(buffer)
+ write_buffer = 'this is a test'
+ write_buffer = write_buffer.encode()
+ tfile2.write(write_buffer)
# Now append the codestream.
tfile2.write(codestream)
@@ -678,10 +721,10 @@ class TestJp2k(unittest.TestCase):
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL,
"Segfault on official v2.0.0 release.")
def test_openjpeg_library_message(self):
- # Verify the error message produced by the openjpeg library.
+ """Verify the error message produced by the openjpeg library"""
# This will confirm that the error callback mechanism is working.
- with open(self.jp2file, 'rb') as fp:
- data = fp.read()
+ with open(self.jp2file, 'rb') as fptr:
+ data = fptr.read()
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
# Codestream starts at byte 3127. SIZ marker at 3137.
# COD marker at 3186. Subsampling at 3180.
@@ -705,13 +748,13 @@ class TestJp2k(unittest.TestCase):
:\sdx=1\sdy=0''', re.VERBOSE)
if sys.hexversion < 0x03020000:
with self.assertRaisesRegexp((IOError, OSError), regexp):
- d = j.read(rlevel=1)
+ j.read(rlevel=1)
else:
with self.assertRaisesRegex((IOError, OSError), regexp):
- d = j.read(rlevel=1)
+ j.read(rlevel=1)
def test_xmp_attribute(self):
- # Verify that we can read the XMP packet in our shipping example file.
+ """Verify the XMP packet in the shipping example file can be read."""
j = Jp2k(self.jp2file)
xmp = j.box[4].data
ns0 = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}'
@@ -723,7 +766,7 @@ class TestJp2k(unittest.TestCase):
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_unrecognized_exif_tag(self):
- # An unrecognized exif tag should be handled gracefully.
+ """An unrecognized exif tag should be handled gracefully."""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
shutil.copyfile(self.jp2file, tfile.name)
@@ -733,10 +776,10 @@ class TestJp2k(unittest.TestCase):
# the the Image IFD number of tags, where we finally find the first
# tag, "Make" (271). We'll corrupt it by changing it into 171,
# which does not correspond to any known Exif Image tag.
- with open(tfile.name, 'r+b') as fp:
- fp.seek(117)
- buffer = struct.pack('I4s', int(56), b'asoc')
- tfile2.write(buffer)
+ wbuffer = struct.pack('>I4s', int(56), b'asoc')
+ tfile2.write(wbuffer)
# Write the contained label box
- buffer = struct.pack('>I4s', int(13), b'lbl ')
- tfile2.write(buffer)
+ wbuffer = struct.pack('>I4s', int(13), b'lbl ')
+ tfile2.write(wbuffer)
tfile2.write('label'.encode())
# Write the xml box
# Length = 36, id is 'xml '.
- buffer = struct.pack('>I4s', int(35), b'xml ')
- tfile2.write(buffer)
+ wbuffer = struct.pack('>I4s', int(35), b'xml ')
+ tfile2.write(wbuffer)
- buffer = 'this is a test'
- buffer = buffer.encode()
- tfile2.write(buffer)
+ wbuffer = 'this is a test'
+ wbuffer = wbuffer.encode()
+ tfile2.write(wbuffer)
# Now append the codestream.
tfile2.write(codestream)
@@ -180,6 +189,7 @@ class TestPrintingNeedsLib(unittest.TestCase):
self.assertEqual(actual, expected)
def test_jp2dump(self):
+ """basic jp2dump test"""
with patch('sys.stdout', new=StringIO()) as fake_out:
glymur.jp2dump(self._plain_nemo_file)
actual = fake_out.getvalue().strip()
@@ -188,9 +198,10 @@ class TestPrintingNeedsLib(unittest.TestCase):
lst = actual.split('\n')
lst = lst[1:]
actual = '\n'.join(lst)
- self.assertEqual(actual, self.expectedPlain)
+ self.assertEqual(actual, self.expected_plain)
def test_entire_file(self):
+ """verify output from printing entire file"""
j = glymur.Jp2k(self._plain_nemo_file)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j)
@@ -201,10 +212,11 @@ class TestPrintingNeedsLib(unittest.TestCase):
lst = lst[1:]
actual = '\n'.join(lst)
- self.assertEqual(actual, self.expectedPlain)
+ self.assertEqual(actual, self.expected_plain)
class TestPrinting(unittest.TestCase):
+ """Test suite for printing where the libraries are not needed"""
def setUp(self):
# Save sys.stdout.
@@ -213,7 +225,8 @@ class TestPrinting(unittest.TestCase):
def tearDown(self):
pass
- def test_COC_segment(self):
+ def test_coc_segment(self):
+ """verify printing of COC segment"""
j = glymur.Jp2k(self.jp2file)
codestream = j.get_codestream(header_only=False)
with patch('sys.stdout', new=StringIO()) as fake_out:
@@ -240,7 +253,8 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- def test_COD_segment(self):
+ def test_cod_segment(self):
+ """verify printing of COD segment"""
j = glymur.Jp2k(self.jp2file)
codestream = j.get_codestream()
with patch('sys.stdout', new=StringIO()) as fake_out:
@@ -271,14 +285,13 @@ class TestPrinting(unittest.TestCase):
' Segmentation symbols: False']
expected = '\n'.join(lines)
- self.actual = actual
- self.expected = expected
self.assertEqual(actual, expected)
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_icc_profile(self):
- filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2')
+ """verify printing of colr box with ICC profile"""
+ filename = os.path.join(DATA_ROOT, 'input/nonregression/text_GBR.jp2')
with warnings.catch_warnings():
# brand is 'jp2 ', but has any icc profile.
warnings.simplefilter("ignore")
@@ -343,24 +356,26 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
- def test_CRG(self):
- filename = os.path.join(data_root, 'input/conformance/p0_03.j2k')
+ def test_crg(self):
+ """verify printing of CRG segment"""
+ filename = os.path.join(DATA_ROOT, 'input/conformance/p0_03.j2k')
j = glymur.Jp2k(filename)
codestream = j.get_codestream()
with patch('sys.stdout', new=StringIO()) as fake_out:
print(codestream.segment[-5])
actual = fake_out.getvalue().strip()
- lines = ['CRG marker segment at (87, 6)',
+ lines = ['CRG marker segment @ (87, 6)',
' Vertical, Horizontal offset: (0.50, 1.00)']
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
- def test_RGN(self):
- filename = os.path.join(data_root, 'input/conformance/p0_03.j2k')
+ def test_rgn(self):
+ """verify printing of RGN segment"""
+ filename = os.path.join(DATA_ROOT, 'input/conformance/p0_03.j2k')
j = glymur.Jp2k(filename)
codestream = j.get_codestream(header_only=False)
with patch('sys.stdout', new=StringIO()) as fake_out:
@@ -373,10 +388,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
- def test_SOP(self):
- filename = os.path.join(data_root, 'input/conformance/p0_03.j2k')
+ def test_sop(self):
+ """verify printing of SOP segment"""
+ filename = os.path.join(DATA_ROOT, 'input/conformance/p0_03.j2k')
j = glymur.Jp2k(filename)
codestream = j.get_codestream(header_only=False)
with patch('sys.stdout', new=StringIO()) as fake_out:
@@ -387,11 +403,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
- def test_CME(self):
- # Test printing a CME or comment marker segment.
- filename = os.path.join(data_root, 'input/conformance/p0_02.j2k')
+ def test_cme(self):
+ """Test printing a CME or comment marker segment."""
+ filename = os.path.join(DATA_ROOT, 'input/conformance/p0_02.j2k')
j = glymur.Jp2k(filename)
codestream = j.get_codestream()
# 2nd to last segment in the main header
@@ -403,7 +419,8 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- def test_EOC_segment(self):
+ def test_eoc_segment(self):
+ """verify printing of eoc segment"""
j = glymur.Jp2k(self.jp2file)
codestream = j.get_codestream(header_only=False)
with patch('sys.stdout', new=StringIO()) as fake_out:
@@ -414,10 +431,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
- def test_PLT_segment(self):
- filename = os.path.join(data_root, 'input/conformance/p0_07.j2k')
+ def test_plt_segment(self):
+ """verify printing of PLT segment"""
+ filename = os.path.join(DATA_ROOT, 'input/conformance/p0_07.j2k')
j = glymur.Jp2k(filename)
codestream = j.get_codestream(header_only=False)
with patch('sys.stdout', new=StringIO()) as fake_out:
@@ -432,10 +450,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
- def test_POD_segment(self):
- filename = os.path.join(data_root, 'input/conformance/p0_13.j2k')
+ def test_pod_segment(self):
+ """verify printing of POD segment"""
+ filename = os.path.join(DATA_ROOT, 'input/conformance/p0_13.j2k')
j = glymur.Jp2k(filename)
codestream = j.get_codestream()
with patch('sys.stdout', new=StringIO()) as fake_out:
@@ -461,10 +480,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
- def test_PPM_segment(self):
- filename = os.path.join(data_root, 'input/conformance/p1_03.j2k')
+ def test_ppm_segment(self):
+ """verify printing of PPM segment"""
+ filename = os.path.join(DATA_ROOT, 'input/conformance/p1_03.j2k')
j = glymur.Jp2k(filename)
codestream = j.get_codestream()
with patch('sys.stdout', new=StringIO()) as fake_out:
@@ -478,10 +498,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
- def test_PPT_segment(self):
- filename = os.path.join(data_root, 'input/conformance/p1_06.j2k')
+ def test_ppt_segment(self):
+ """verify printing of ppt segment"""
+ filename = os.path.join(DATA_ROOT, 'input/conformance/p1_06.j2k')
j = glymur.Jp2k(filename)
codestream = j.get_codestream(header_only=False)
with patch('sys.stdout', new=StringIO()) as fake_out:
@@ -495,7 +516,8 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- def test_QCC_segment(self):
+ def test_qcc_segment(self):
+ """verify printing of qcc segment"""
j = glymur.Jp2k(self.jp2file)
codestream = j.get_codestream(header_only=False)
with patch('sys.stdout', new=StringIO()) as fake_out:
@@ -510,7 +532,8 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- def test_QCD_segment_5x3_transform(self):
+ def test_qcd_segment_5x3_transform(self):
+ """verify printing of qcd segment"""
j = glymur.Jp2k(self.jp2file)
codestream = j.get_codestream()
with patch('sys.stdout', new=StringIO()) as fake_out:
@@ -524,7 +547,8 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- def test_SIZ_segment(self):
+ def test_siz_segment(self):
+ """verify printing of SIZ segment"""
j = glymur.Jp2k(self.jp2file)
codestream = j.get_codestream()
with patch('sys.stdout', new=StringIO()) as fake_out:
@@ -545,7 +569,8 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- def test_SOC_segment(self):
+ def test_soc_segment(self):
+ """verify printing of SOC segment"""
j = glymur.Jp2k(self.jp2file)
codestream = j.get_codestream()
with patch('sys.stdout', new=StringIO()) as fake_out:
@@ -556,7 +581,8 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- def test_SOD_segment(self):
+ def test_sod_segment(self):
+ """verify printing of SOD segment"""
j = glymur.Jp2k(self.jp2file)
codestream = j.get_codestream(header_only=False)
with patch('sys.stdout', new=StringIO()) as fake_out:
@@ -567,7 +593,8 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- def test_SOT_segment(self):
+ def test_sot_segment(self):
+ """verify printing of SOT segment"""
j = glymur.Jp2k(self.jp2file)
codestream = j.get_codestream(header_only=False)
with patch('sys.stdout', new=StringIO()) as fake_out:
@@ -581,13 +608,13 @@ class TestPrinting(unittest.TestCase):
' Number of tile parts: 1']
expected = '\n'.join(lines)
- self.maxDiff = None
self.assertEqual(actual, expected)
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
- def test_TLM_segment(self):
- filename = os.path.join(data_root, 'input/conformance/p0_15.j2k')
+ def test_tlm_segment(self):
+ """verify printing of TLM segment"""
+ filename = os.path.join(DATA_ROOT, 'input/conformance/p0_15.j2k')
j = glymur.Jp2k(filename)
codestream = j.get_codestream()
with patch('sys.stdout', new=StringIO()) as fake_out:
@@ -605,7 +632,7 @@ class TestPrinting(unittest.TestCase):
@unittest.skipIf(sys.hexversion < 0x02070000,
"Differences in XML printing between 2.6 and 2.7")
def test_xmp(self):
- # Verify the printing of a UUID/XMP box.
+ """Verify the printing of a UUID/XMP box."""
j = glymur.Jp2k(self.jp2file)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.box[4])
@@ -627,6 +654,7 @@ class TestPrinting(unittest.TestCase):
self.assertEqual(actual, expected)
def test_codestream(self):
+ """verify printing of entire codestream"""
j = glymur.Jp2k(self.jp2file)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.get_codestream())
@@ -672,15 +700,15 @@ class TestPrinting(unittest.TestCase):
' CME marker segment @ (3209, 37)',
' "Created by OpenJPEG version 2.0.0"']
expected = '\n'.join(lst)
- self.maxDiff = None
self.assertEqual(actual, expected)
@unittest.skipIf(sys.hexversion < 0x02070000,
"Differences in XML printing between 2.6 and 2.7")
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_xml(self):
- filename = os.path.join(data_root, 'input/conformance/file1.jp2')
+ """verify printing of XML box"""
+ filename = os.path.join(DATA_ROOT, 'input/conformance/file1.jp2')
j = glymur.Jp2k(filename)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.box[2])
@@ -707,10 +735,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_channel_definition(self):
- filename = os.path.join(data_root, 'input/conformance/file2.jp2')
+ """verify printing of cdef box"""
+ filename = os.path.join(DATA_ROOT, 'input/conformance/file2.jp2')
j = glymur.Jp2k(filename)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.box[2].box[2])
@@ -722,10 +751,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_component_mapping(self):
- filename = os.path.join(data_root, 'input/conformance/file9.jp2')
+ """verify printing of cmap box"""
+ filename = os.path.join(DATA_ROOT, 'input/conformance/file9.jp2')
j = glymur.Jp2k(filename)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.box[2].box[2])
@@ -737,10 +767,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
- def test_palette(self):
- filename = os.path.join(data_root, 'input/conformance/file9.jp2')
+ def test_palette7(self):
+ """verify printing of pclr box"""
+ filename = os.path.join(DATA_ROOT, 'input/conformance/file9.jp2')
j = glymur.Jp2k(filename)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.box[2].box[1])
@@ -750,10 +781,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
- def test_palette(self):
- filename = os.path.join(data_root, 'input/conformance/file7.jp2')
+ def test_rreq(self):
+ """verify printing of reader requirements box"""
+ filename = os.path.join(DATA_ROOT, 'input/conformance/file7.jp2')
j = glymur.Jp2k(filename)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.box[2])
@@ -773,25 +805,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- @unittest.skipIf(data_root is None,
- "OPJ_DATA_ROOT environment variable not set")
- def test_CRG(self):
- filename = os.path.join(data_root, 'input/conformance/p0_03.j2k')
- j = glymur.Jp2k(filename)
- codestream = j.get_codestream()
- with patch('sys.stdout', new=StringIO()) as fake_out:
- print(codestream.segment[6])
- actual = fake_out.getvalue().strip()
- lines = ['CRG marker segment @ (87, 6)',
- ' Vertical, Horizontal offset: (0.50, 1.00)']
- expected = '\n'.join(lines)
- self.assertEqual(actual, expected)
-
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_differing_subsamples(self):
- # Issue 86.
- filename = os.path.join(data_root, 'input/conformance/p0_05.j2k')
+ """verify printing of SIZ with different subsampling... Issue 86."""
+ filename = os.path.join(DATA_ROOT, 'input/conformance/p0_05.j2k')
j = glymur.Jp2k(filename)
codestream = j.get_codestream()
with patch('sys.stdout', new=StringIO()) as fake_out:
@@ -810,11 +828,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_palette_box(self):
- # Verify that palette (pclr) boxes are printed without error.
- filename = os.path.join(data_root, 'input/conformance/file9.jp2')
+ """Verify that palette (pclr) boxes are printed without error."""
+ filename = os.path.join(DATA_ROOT, 'input/conformance/file9.jp2')
j = glymur.Jp2k(filename)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.box[2].box[1])
@@ -826,54 +844,56 @@ class TestPrinting(unittest.TestCase):
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_less_common_boxes(self):
+ """verify uinf, ulst, url, res, resd, resc box printing"""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
with open(self.jp2file, 'rb') as ifile:
# Everything up until the jp2c box.
- buffer = ifile.read(77)
- tfile.write(buffer)
+ wbuffer = ifile.read(77)
+ tfile.write(wbuffer)
# Write the UINF superbox
# Length = 50, id is uinf.
- buffer = struct.pack('>I4s', int(50), b'uinf')
- tfile.write(buffer)
+ wbuffer = struct.pack('>I4s', int(50), b'uinf')
+ tfile.write(wbuffer)
# Write the ULST box.
# Length is 26, 1 UUID, hard code that UUID as zeros.
- buffer = struct.pack('>I4sHIIII', int(26), b'ulst', int(1),
- int(0), int(0), int(0), int(0))
- tfile.write(buffer)
+ wbuffer = struct.pack('>I4sHIIII', int(26), b'ulst', int(1),
+ int(0), int(0), int(0), int(0))
+ tfile.write(wbuffer)
# Write the URL box.
# Length is 16, version is one byte, flag is 3 bytes, url
# is the rest.
- buffer = struct.pack('>I4sBBBB',
- int(16), b'url ',
- int(0), int(0), int(0), int(0))
- tfile.write(buffer)
- buffer = struct.pack('>ssss', b'a', b'b', b'c', b'd')
- tfile.write(buffer)
+ wbuffer = struct.pack('>I4sBBBB',
+ int(16), b'url ',
+ int(0), int(0), int(0), int(0))
+ tfile.write(wbuffer)
+
+ wbuffer = struct.pack('>ssss', b'a', b'b', b'c', b'd')
+ tfile.write(wbuffer)
# Start the resolution superbox.
- buffer = struct.pack('>I4s', int(44), b'res ')
- tfile.write(buffer)
+ wbuffer = struct.pack('>I4s', int(44), b'res ')
+ tfile.write(wbuffer)
# Write the capture resolution box.
- buffer = struct.pack('>I4sHHHHBB',
- int(18), b'resc',
- int(1), int(1), int(1), int(1),
- int(0), int(1))
- tfile.write(buffer)
+ wbuffer = struct.pack('>I4sHHHHBB',
+ int(18), b'resc',
+ int(1), int(1), int(1), int(1),
+ int(0), int(1))
+ tfile.write(wbuffer)
# Write the display resolution box.
- buffer = struct.pack('>I4sHHHHBB',
- int(18), b'resd',
- int(1), int(1), int(1), int(1),
- int(1), int(0))
- tfile.write(buffer)
+ wbuffer = struct.pack('>I4sHHHHBB',
+ int(18), b'resd',
+ int(1), int(1), int(1), int(1),
+ int(1), int(0))
+ tfile.write(wbuffer)
# Get the rest of the input file.
- buffer = ifile.read()
- tfile.write(buffer)
+ wbuffer = ifile.read()
+ tfile.write(wbuffer)
tfile.flush()
jp2k = glymur.Jp2k(tfile.name)
@@ -901,12 +921,13 @@ class TestPrinting(unittest.TestCase):
@unittest.skipIf(sys.hexversion < 0x03000000,
"Ordered dicts not printing well in 2.7")
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
- def test_jpx_approximation_with_icc_profile(self):
+ def test_jpx_approx_icc_profile(self):
+ """verify jpx with approx field equal to zero"""
# ICC profiles may be used in JP2, but the approximation field should
# be zero unless we have jpx. This file does both.
- filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2')
+ filename = os.path.join(DATA_ROOT, 'input/nonregression/text_GBR.jp2')
with warnings.catch_warnings():
# brand is 'jp2 ', but has any icc profile.
warnings.simplefilter("ignore")
@@ -945,11 +966,11 @@ class TestPrinting(unittest.TestCase):
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
- @unittest.skipIf(data_root is None,
+ @unittest.skipIf(DATA_ROOT is None,
"OPJ_DATA_ROOT environment variable not set")
def test_uuid(self):
- # UUID box
- filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2')
+ """verify printing of UUID box"""
+ filename = os.path.join(DATA_ROOT, 'input/nonregression/text_GBR.jp2')
with warnings.catch_warnings():
# brand is 'jp2 ', but has any icc profile.
warnings.simplefilter("ignore")
@@ -968,6 +989,7 @@ class TestPrinting(unittest.TestCase):
@unittest.skipIf(sys.hexversion < 0x03000000,
"Ordered dicts not printing well in 2.7")
def test_exif_uuid(self):
+ """Verify printing of exif information"""
j = glymur.Jp2k(self.jp2file)
with patch('sys.stdout', new=StringIO()) as fake_out: