From 368c355560127afc0edc8797a838a06614f7695f Mon Sep 17 00:00:00 2001 From: John Evans Date: Sun, 20 Oct 2013 07:52:28 -0400 Subject: [PATCH 001/326] Added __repr__ for JPEG2000SignatureBox, FileTypeBox. --- glymur/jp2box.py | 10 ++++++++++ glymur/test/test_jp2box.py | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index bf88e4d..c3f2b63 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -801,6 +801,13 @@ class FileTypeBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.FileTypeBox(brand='{0}', minor_version={1}, " + msg += "compatibility_list={2})" + msg = msg.format(self.brand, self.minor_version, + self.compatibility_list) + return msg + def __str__(self): lst = [Jp2kBox.__str__(self), ' Brand: {0}', @@ -1141,6 +1148,9 @@ class JPEG2000SignatureBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + return 'glymur.jp2box.JPEG2000SignatureBox()' + def __str__(self): msg = Jp2kBox.__str__(self) msg += '\n Signature: {0:02x}{1:02x}{2:02x}{3:02x}' diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index ef62460..617a93a 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -677,6 +677,10 @@ class TestJp2Boxes(unittest.TestCase): jp2k = glymur.jp2box.JPEG2000SignatureBox() self.assertEqual(jp2k.signature, (13, 10, 135, 10)) + # Test the representation instantiation. + newbox = eval(repr(jp2k)) + self.assertTrue(isinstance(newbox, glymur.jp2box.JPEG2000SignatureBox)) + def test_default_ftyp(self): """Should be able to instantiate a FileTypeBox""" ftyp = glymur.jp2box.FileTypeBox() @@ -684,6 +688,13 @@ class TestJp2Boxes(unittest.TestCase): self.assertEqual(ftyp.minor_version, 0) self.assertEqual(ftyp.compatibility_list, ['jp2 ']) + # Test the representation instantiation. + newbox = eval(repr(ftyp)) + self.assertTrue(isinstance(newbox, glymur.jp2box.FileTypeBox)) + self.assertEqual(newbox.brand, 'jp2 ') + self.assertEqual(newbox.minor_version, 0) + self.assertEqual(newbox.compatibility_list, ['jp2 ']) + def test_default_ihdr(self): """Should be able to instantiate an image header box.""" ihdr = glymur.jp2box.ImageHeaderBox(height=512, width=256, From 0e302a571000a465d34f7d3632e1c1de03bcbdae Mon Sep 17 00:00:00 2001 From: John Evans Date: Sun, 20 Oct 2013 11:57:51 -0400 Subject: [PATCH 002/326] Adding XMP UUID write support. Still needs tests. #104 --- glymur/jp2box.py | 19 +++++++++++++++++++ glymur/jp2k.py | 9 ++++++--- glymur/test/test_jp2box.py | 16 ++++++++-------- setup.py | 2 +- 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index bf88e4d..6f628df 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2122,6 +2122,7 @@ class UUIDBox(Jp2kBox): text = raw_data.decode('utf-8') elt = ET.fromstring(text) self.data = ET.ElementTree(elt) + self._type = 'XMP' elif the_uuid.bytes == b'JpgTiffExif->JP2': exif_obj = Exif(raw_data) ifds = OrderedDict() @@ -2130,8 +2131,16 @@ class UUIDBox(Jp2kBox): ifds['GPSInfo'] = exif_obj.exif_gpsinfo ifds['Iop'] = exif_obj.exif_iop self.data = ifds + self._type = 'Exif' else: self.data = raw_data + self._type = 'unknown' + + if length == 0: + # Need to compute the length. + # The length is 8 (L and T fields) + 16 (length of UUID identifier) + # + length of uuid data. + length = 24 + len(self.data) self.length = length self.offset = offset @@ -2164,6 +2173,16 @@ class UUIDBox(Jp2kBox): return msg + def write(self, fptr): + """Write a UUID box box to file. + """ + if self._type != 'XMP': + msg = "Only XMP UUID boxes can currently be written." + raise NotImplementedError(msg) + read_buffer = struct.pack('>I4s', self.length, 'uuid') + fptr.write(read_buffer) + fptr.write(self.data) + @staticmethod def parse(fptr, offset, length): """Parse UUID box. diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 1bd36a4..45eaf39 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -514,14 +514,17 @@ class Jp2k(Jp2kBox): Parameters ---------- box : Jp2Box - Instance of a JP2 box. Currently only XML boxes are allowed. + Instance of a JP2 box. Only UUID and XML boxes can currently be + appended. """ if self._codec_format == opj2.CODEC_J2K: msg = "Only JP2 files can currently have boxes appended to them." raise IOError(msg) - if box.box_id != 'xml ': - raise IOError("Only XML boxes can currently be appended.") + if not ((box.box_id == 'xml ') or + (box.box_id == 'uuid' and box._type == 'XMP')): + msg = "Only XML boxes and XMP UUID boxes can currently be appended." + raise IOError(msg) # Check the last box. If the length field is zero, then rewrite # the length field to reflect the true length of the box. diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index ef62460..ff61bc5 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -429,14 +429,14 @@ class TestAppend(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: shutil.copyfile(self.j2kfile, tfile.name) - jp2 = Jp2k(tfile.name) + j2k = Jp2k(tfile.name) - # Make a UUID box. - uuid_instance = uuid.UUID('00000000-0000-0000-0000-000000000000') - data = b'0123456789' - uuidbox = glymur.jp2box.UUIDBox(uuid_instance, data) + # Make an XML box. XML boxes should always be appendable to jp2 + # files. + the_xml = ET.fromstring('0') + xmlbox = glymur.jp2box.XMLBox(xml=the_xml) with self.assertRaises(IOError): - jp2.append(uuidbox) + j2k.append(xmlbox) def test_length_field_is_zero(self): """L=0 (length field in box header) is handled. @@ -473,14 +473,14 @@ class TestAppend(unittest.TestCase): self.assertEqual(ET.tostring(jp2.box[-1].xml.getroot()), b'0') - def test_only_xml_allowed_to_append(self): + def test_append_allowable_boxes(self): """Only XML boxes are allowed to be appended.""" with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: shutil.copyfile(self.jp2file, tfile.name) jp2 = Jp2k(tfile.name) - # Make a UUID box. + # Make a UUID box. Only XMP UUID boxes can currently be appended. uuid_instance = uuid.UUID('00000000-0000-0000-0000-000000000000') data = b'0123456789' uuidbox = glymur.jp2box.UUIDBox(uuid_instance, data) diff --git a/setup.py b/setup.py index 89e69f7..b780e4d 100644 --- a/setup.py +++ b/setup.py @@ -42,7 +42,7 @@ kwargs['classifiers'] = clssfrs # Get the version string. Cannot do this by importing glymur! version_file = os.path.join('glymur', 'version.py') -with open('glymur/version.py', 'rt') as fptr: +with open(version_file, 'rt') as fptr: contents = fptr.read() match = re.search('version\s*=\s*"(?P\d*.\d*.\d*.*)"\n', contents) kwargs['version'] = match.group('version') From cdd00d2c2d5936dd7e0e5bc5596fe63e0b19f8f7 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 23 Oct 2013 06:01:48 -0400 Subject: [PATCH 003/326] added __repr__ for colr, cdef, ihdr boxes. #133 --- glymur/jp2box.py | 45 ++++++++++++++++++++---- glymur/test/test_jp2box.py | 71 ++++++++++++++++++++++++++++++++------ 2 files changed, 99 insertions(+), 17 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index c3f2b63..b7f385f 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -206,6 +206,17 @@ class ColourSpecificationBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.ColourSpecificationBox(" + msg += "method={0}, precedence={1}, approximation={2}, colorspace={3}, " + msg += "icc_profile={4})" + msg = msg.format(self.method, + self.precedence, + self.approximation, + self.colorspace, + self.icc_profile) + return msg + def __str__(self): msg = Jp2kBox.__str__(self) @@ -426,12 +437,12 @@ class ChannelDefinitionBox(Jp2kBox): offset of the box from the start of the file. longname : str more verbose description of the box. - index : int + index : list number of the channel. Defaults to monotonically increasing sequence, i.e. [0, 1, 2, ...] - channel_type : int + channel_type : list type of the channel - association : int + association : list index of the associated color """ def __init__(self, index=None, channel_type=None, association=None, @@ -458,9 +469,9 @@ class ChannelDefinitionBox(Jp2kBox): msg += " 65535 - unspecified" raise IOError(msg) - self.index = index - self.channel_type = channel_type - self.association = association + self.index = tuple(index) + self.channel_type = tuple(channel_type) + self.association = tuple(association) self.__dict__.update(**kwargs) def __str__(self): @@ -475,6 +486,12 @@ class ChannelDefinitionBox(Jp2kBox): msg = msg.format(self.index[j], color_type_string, assn) return msg + def __repr__(self): + msg = "glymur.jp2box.ChannelDefinitionBox(" + msg += "index={0}, channel_type={1}, association={2})" + msg = msg.format(self.index, self.channel_type, self.association) + return msg + def write(self, fptr): """Write a channel definition box to file. """ @@ -921,6 +938,22 @@ class ImageHeaderBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.ImageHeaderBox(" + msg += "{height}, {width}, num_components={num_components}, " + msg += "signed={signed}, bits_per_component={bits_per_component}, " + msg += "compression={compression}, " + msg += "colorspace_unknown={colorspace_unknown}, " + msg += "ip_provided={ip_provided})" + msg = msg.format(height=self.height, width=self.width, + num_components=self.num_components, + signed=self.signed, + bits_per_component=self.bits_per_component, + compression=self.compression, + colorspace_unknown=self.colorspace_unknown, + ip_provided=self.ip_provided) + return msg + def __str__(self): msg = Jp2kBox.__str__(self) msg = "{0}" diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 617a93a..c6de126 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -677,10 +677,6 @@ class TestJp2Boxes(unittest.TestCase): jp2k = glymur.jp2box.JPEG2000SignatureBox() self.assertEqual(jp2k.signature, (13, 10, 135, 10)) - # Test the representation instantiation. - newbox = eval(repr(jp2k)) - self.assertTrue(isinstance(newbox, glymur.jp2box.JPEG2000SignatureBox)) - def test_default_ftyp(self): """Should be able to instantiate a FileTypeBox""" ftyp = glymur.jp2box.FileTypeBox() @@ -688,13 +684,6 @@ class TestJp2Boxes(unittest.TestCase): self.assertEqual(ftyp.minor_version, 0) self.assertEqual(ftyp.compatibility_list, ['jp2 ']) - # Test the representation instantiation. - newbox = eval(repr(ftyp)) - self.assertTrue(isinstance(newbox, glymur.jp2box.FileTypeBox)) - self.assertEqual(newbox.brand, 'jp2 ') - self.assertEqual(newbox.minor_version, 0) - self.assertEqual(newbox.compatibility_list, ['jp2 ']) - def test_default_ihdr(self): """Should be able to instantiate an image header box.""" ihdr = glymur.jp2box.ImageHeaderBox(height=512, width=256, @@ -720,6 +709,66 @@ class TestJp2Boxes(unittest.TestCase): self.assertIsNone(box.main_header) +class TestRepr(unittest.TestCase): + """Tests for __repr__ methods.""" + def test_default_jp2k(self): + """Should be able to eval a JPEG2000SignatureBox""" + jp2k = glymur.jp2box.JPEG2000SignatureBox() + + # Test the representation instantiation. + newbox = eval(repr(jp2k)) + self.assertTrue(isinstance(newbox, glymur.jp2box.JPEG2000SignatureBox)) + self.assertEqual(newbox.signature, (13, 10, 135, 10)) + + def test_default_ftyp(self): + """Should be able to instantiate a FileTypeBox""" + ftyp = glymur.jp2box.FileTypeBox() + + # Test the representation instantiation. + newbox = eval(repr(ftyp)) + self.assertTrue(isinstance(newbox, glymur.jp2box.FileTypeBox)) + self.assertEqual(newbox.brand, 'jp2 ') + self.assertEqual(newbox.minor_version, 0) + self.assertEqual(newbox.compatibility_list, ['jp2 ']) + + def test_colourspecification_box(self): + """Verify __repr__ method on colr box.""" + # TODO: add icc_profile + box = ColourSpecificationBox(colorspace=glymur.core.SRGB) + + newbox = eval(repr(box)) + self.assertEqual(newbox.method, glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(newbox.precedence, 0) + self.assertEqual(newbox.approximation, 0) + self.assertEqual(newbox.colorspace, glymur.core.SRGB) + self.assertIsNone(newbox.icc_profile) + + def test_channeldefinition_box(self): + """Verify __repr__ method on cdef box.""" + channel_type = [COLOR, COLOR, COLOR] + association = [RED, GREEN, BLUE] + cdef = glymur.jp2box.ChannelDefinitionBox(index=[0, 1, 2], + channel_type=channel_type, + association=association) + newbox = eval(repr(cdef)) + self.assertEqual(newbox.index, (0, 1, 2)) + self.assertEqual(newbox.channel_type, (COLOR, COLOR, COLOR)) + self.assertEqual(newbox.association, (RED, GREEN, BLUE)) + + def test_imageheader_box(self): + """Verify __repr__ method on ihdr box.""" + ihdr = ImageHeaderBox(100, 200, num_components=3) + newbox = eval(repr(ihdr)) + self.assertEqual(newbox.height, 100) + self.assertEqual(newbox.width, 200) + self.assertEqual(newbox.num_components, 3) + self.assertFalse(newbox.signed) + self.assertEqual(newbox.bits_per_component, 8) + self.assertEqual(newbox.compression, 7) + self.assertFalse(newbox.colorspace_unknown) + self.assertFalse(newbox.ip_provided) + + class TestJpxBoxes(unittest.TestCase): """Tests for JPX boxes.""" From 18818495d24643806fbd3bd3db3471cc5a1b3ae9 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 23 Oct 2013 06:41:31 -0400 Subject: [PATCH 004/326] Added jp2h __repr__ method. #133 --- glymur/jp2box.py | 8 ++++++-- glymur/test/test_jp2box.py | 13 ++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index b7f385f..7f75f60 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1102,11 +1102,15 @@ class JP2HeaderBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - def __init__(self, length=0, offset=-1): + def __init__(self, box=[], length=0, offset=-1): Jp2kBox.__init__(self, box_id='jp2h', longname='JP2 Header') self.length = length self.offset = offset - self.box = [] + self.box = box + + def __repr__(self): + msg = "glymur.jp2box.JP2HeaderBox(box={0})".format(self.box) + return msg def __str__(self): msg = Jp2kBox.__str__(self) diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index c6de126..fb748f7 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -755,9 +755,20 @@ class TestRepr(unittest.TestCase): self.assertEqual(newbox.channel_type, (COLOR, COLOR, COLOR)) self.assertEqual(newbox.association, (RED, GREEN, BLUE)) - def test_imageheader_box(self): + def test_jp2header_box(self): """Verify __repr__ method on ihdr box.""" ihdr = ImageHeaderBox(100, 200, num_components=3) + colr = ColourSpecificationBox(colorspace=glymur.core.SRGB) + jp2h = JP2HeaderBox(box=[ihdr, colr]) + newbox = eval(repr(jp2h)) + self.assertEqual(newbox.box_id, 'jp2h') + self.assertEqual(newbox.box[0].box_id, 'ihdr') + self.assertEqual(newbox.box[1].box_id, 'colr') + + def test_imageheader_box(self): + """Verify __repr__ method on jhdr box.""" + ihdr = ImageHeaderBox(100, 200, num_components=3) + newbox = eval(repr(ihdr)) self.assertEqual(newbox.height, 100) self.assertEqual(newbox.width, 200) From 3ad31c91b84d28244f7c8aee2fcfafe87f4219f6 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 23 Oct 2013 08:07:15 -0400 Subject: [PATCH 005/326] Added support for jplh, jpch, cmap, res , resd, resc #133 --- glymur/jp2box.py | 48 +++++++++++++++++++++++++++++++------- glymur/test/test_jp2box.py | 43 +++++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 7f75f60..3c775c0 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -554,11 +554,15 @@ class CodestreamHeaderBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - def __init__(self, length=0, offset=-1): + def __init__(self, box=[], length=0, offset=-1): Jp2kBox.__init__(self, box_id='jpch', longname='Codestream Header') self.length = length self.offset = offset - self.box = [] + self.box = box + + def __repr__(self): + msg = "glymur.jp2box.CodestreamHeaderBox(box={0})".format(self.box) + return msg def __str__(self): msg = Jp2kBox.__str__(self) @@ -612,13 +616,18 @@ class CompositingLayerHeaderBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - def __init__(self, length=0, offset=-1): + def __init__(self, box=[], length=0, offset=-1): Jp2kBox.__init__(self, box_id='jplh', longname='Compositing Layer Header') self.length = length self.offset = offset self.box = [] + def __repr__(self): + msg = "glymur.jp2box.CompositingLayerHeaderBox(box={0})" + msg = msg.format(self.box) + return msg + def __str__(self): msg = Jp2kBox.__str__(self) for box in self.box: @@ -667,11 +676,11 @@ class ComponentMappingBox(Jp2kBox): Offset of the box from the start of the file. longname : str Verbose description of the box. - component_index : int + component_index : tuple Index of component in codestream that is mapped to this channel. - mapping_type : int + mapping_type : tuple mapping type, either direct use (0) or palette (1) - palette_index : int + palette_index : tuple Index component from palette """ def __init__(self, component_index, mapping_type, palette_index, @@ -683,6 +692,14 @@ class ComponentMappingBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.ComponentMappingBox(" + msg += "component_index={0}, mapping_type={1}, palette_index={2})" + msg = msg.format(self.component_index, + self.mapping_type, + self.palette_index) + return msg + def __str__(self): msg = Jp2kBox.__str__(self) @@ -1610,11 +1627,16 @@ class ResolutionBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - def __init__(self, length=0, offset=-1): + def __init__(self, box=[], length=0, offset=-1): Jp2kBox.__init__(self, box_id='res ', longname='Resolution') self.length = length self.offset = offset - self.box = [] + self.box = box + + def __repr__(self): + msg = "glymur.jp2box.ResolutionBox(box={0})" + msg = msg.format(self.box) + return msg def __str__(self): msg = Jp2kBox.__str__(self) @@ -1676,6 +1698,11 @@ class CaptureResolutionBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.CaptureResolutionBox({0}, {1})" + msg = msg.format(self.vertical_resolution, self.horizontal_resolution) + return msg + def __str__(self): msg = Jp2kBox.__str__(self) msg += '\n VCR: {0}'.format(self.vertical_resolution) @@ -1733,6 +1760,11 @@ class DisplayResolutionBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.DisplayResolutionBox({0}, {1})" + msg = msg.format(self.vertical_resolution, self.horizontal_resolution) + return msg + def __str__(self): msg = Jp2kBox.__str__(self) msg += '\n VDR: {0}'.format(self.vertical_resolution) diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index fb748f7..0373b01 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -40,7 +40,7 @@ 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 +from .fixtures import OPENJP2_IS_V2_OFFICIAL, opj_data_file try: FORMAT_CORPUS_DATA_ROOT = os.environ['FORMAT_CORPUS_DATA_ROOT'] @@ -779,6 +779,47 @@ class TestRepr(unittest.TestCase): self.assertFalse(newbox.colorspace_unknown) self.assertFalse(newbox.ip_provided) + def test_codestreamheader_box(self): + """Verify __repr__ method on jpch box.""" + jpch = glymur.jp2box.CodestreamHeaderBox() + newbox = eval(repr(jpch)) + self.assertEqual(newbox.box_id, 'jpch') + self.assertEqual(len(newbox.box), 0) + + def test_compositinglayerheader_box(self): + """Verify __repr__ method on jplh box.""" + jplh = glymur.jp2box.CompositingLayerHeaderBox() + newbox = eval(repr(jplh)) + self.assertEqual(newbox.box_id, 'jplh') + self.assertEqual(len(newbox.box), 0) + + def test_componentmapping_box(self): + """Verify __repr__ method on cmap box.""" + cmap = glymur.jp2box.ComponentMappingBox(component_index=(0, 0, 0), + mapping_type=(1, 1, 1), + palette_index=(0, 1, 2)) + newbox = eval(repr(cmap)) + self.assertEqual(newbox.box_id, 'cmap') + self.assertEqual(newbox.component_index, (0, 0, 0)) + self.assertEqual(newbox.mapping_type, (1, 1, 1)) + self.assertEqual(newbox.palette_index, (0, 1, 2)) + + def test_resolution_boxes(self): + """Verify __repr__ method on resolution boxes.""" + resc = glymur.jp2box.CaptureResolutionBox(0.5, 2.5) + resd = glymur.jp2box.DisplayResolutionBox(2.5, 0.5) + res_super_box = glymur.jp2box.ResolutionBox(box=[resc, resd]) + + newbox = eval(repr(res_super_box)) + + self.assertEqual(newbox.box_id, 'res ') + self.assertEqual(newbox.box[0].box_id, 'resc') + self.assertEqual(newbox.box[0].vertical_resolution, 0.5) + self.assertEqual(newbox.box[0].horizontal_resolution, 2.5) + self.assertEqual(newbox.box[1].box_id, 'resd') + self.assertEqual(newbox.box[1].vertical_resolution, 2.5) + self.assertEqual(newbox.box[1].horizontal_resolution, 0.5) + class TestJpxBoxes(unittest.TestCase): """Tests for JPX boxes.""" From 74420e587ccbb592ec97ee2e8641a83c928feebd Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 23 Oct 2013 08:17:17 -0400 Subject: [PATCH 006/326] Added 'url ', 'lbl ' box support. #133 --- glymur/jp2box.py | 9 +++++++++ glymur/test/test_jp2box.py | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 3c775c0..52ad263 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1826,6 +1826,10 @@ class LabelBox(Jp2kBox): msg += '\n Label: {0}'.format(self.label) return msg + def __repr__(self): + msg = 'glymur.jp2box.LabelBox("{0}")'.format(self.label) + return msg + @staticmethod def parse(fptr, offset, length): """Parse Label box. @@ -2109,6 +2113,11 @@ class DataEntryURLBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.DataEntryURLBox({0}, {1}, '{2}')" + msg = msg.format(self.version, self.flag, self.url) + return msg + def __str__(self): msg = Jp2kBox.__str__(self) msg += '\n ' diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 0373b01..55a7b02 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -820,6 +820,25 @@ class TestRepr(unittest.TestCase): self.assertEqual(newbox.box[1].vertical_resolution, 2.5) self.assertEqual(newbox.box[1].horizontal_resolution, 0.5) + def test_label_box(self): + """Verify __repr__ method on label box.""" + lbl = glymur.jp2box.LabelBox("this is a test") + newbox = eval(repr(lbl)) + self.assertEqual(newbox.box_id, 'lbl ') + self.assertEqual(newbox.label, "this is a test") + + def test_data_entry_url_box(self): + """Verify __repr__ method on data entry url box.""" + version = 0 + flag = (0, 0, 0) + url = "http://readthedocs.glymur.org" + box = glymur.jp2box.DataEntryURLBox(version, flag, url) + newbox = eval(repr(box)) + self.assertEqual(newbox.box_id, 'url ') + self.assertEqual(newbox.version, version) + self.assertEqual(newbox.flag, flag) + self.assertEqual(newbox.url, url) + class TestJpxBoxes(unittest.TestCase): """Tests for JPX boxes.""" From 7ac1134872952ef999ad8cde07460807bf7397dd Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 23 Oct 2013 18:58:09 -0400 Subject: [PATCH 007/326] Added ulst and uinf repr support. #133 --- CHANGES.txt | 3 +++ glymur/jp2box.py | 12 ++++++++++-- glymur/test/test_jp2box.py | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 40846e4..7e7c53c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +Oct 22, 2013 - v0.5.7 Super boxes constructors now take optional box list + argument. + Oct 13, 2013 - v0.5.6 Fixed handling of non-ascii chars in XML boxes. Fixed some docstring errors in jp2box module. diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 52ad263..8303981 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1989,6 +1989,10 @@ class UUIDListBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.UUIDListBox({0})".format(self.ulst) + return msg + def __str__(self): msg = Jp2kBox.__str__(self) for j, uuid_item in enumerate(self.ulst): @@ -2040,11 +2044,15 @@ class UUIDInfoBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - def __init__(self, length=0, offset=-1): + def __init__(self, box=[], length=0, offset=-1): Jp2kBox.__init__(self, box_id='uinf', longname='UUIDInfo') self.length = length self.offset = offset - self.box = [] + self.box = box + + def __repr__(self): + msg = "glymur.jp2box.UUIDInfoBox(box={0})".format(self.box) + return msg def __str__(self): msg = Jp2kBox.__str__(self) diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 55a7b02..b82af47 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -23,6 +23,7 @@ import struct import sys import tempfile import uuid +from uuid import UUID import xml.etree.cElementTree as ET if sys.hexversion < 0x02070000: @@ -839,6 +840,24 @@ class TestRepr(unittest.TestCase): self.assertEqual(newbox.flag, flag) self.assertEqual(newbox.url, url) + def test_uuidinfo_box(self): + """Verify __repr__ method on uinf box.""" + uinf = glymur.jp2box.UUIDInfoBox() + newbox = eval(repr(uinf)) + self.assertEqual(newbox.box_id, 'uinf') + self.assertEqual(len(newbox.box), 0) + + def test_uuidlist_box(self): + """Verify __repr__ method on ulst box.""" + uuid1 = uuid.UUID('00000000-0000-0000-0000-000000000001') + uuid2 = uuid.UUID('00000000-0000-0000-0000-000000000002') + uuids = [uuid1, uuid2] + ulst = glymur.jp2box.UUIDListBox(ulst=uuids) + newbox = eval(repr(ulst)) + self.assertEqual(newbox.box_id, 'ulst') + self.assertEqual(newbox.ulst[0], uuid1) + self.assertEqual(newbox.ulst[1], uuid2) + class TestJpxBoxes(unittest.TestCase): """Tests for JPX boxes.""" From d3dd654c17aa4924f2ba27f3089594f6949d2b56 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 24 Oct 2013 08:21:14 -0400 Subject: [PATCH 008/326] Proper repr support for SIZ segment. SIZ segment constructor change. The SIZ segment was taking raw buffer arguments, which are difficult for eval(repr()) to deal with, so the constructor was changed to make the arguments more explicit. #133 --- CHANGES.txt | 4 +- glymur/codestream.py | 113 +++++++++++++++++++++------------ glymur/test/test_codestream.py | 44 +++++++++++++ 3 files changed, 119 insertions(+), 42 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 7e7c53c..0d23501 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,5 @@ -Oct 22, 2013 - v0.5.7 Super boxes constructors now take optional box list - argument. +Oct 22, 2013 - v0.5.7 Super box constructors now take optional box list + argument. Removed ssiz attribute from SIZsegment class. Oct 13, 2013 - v0.5.6 Fixed handling of non-ascii chars in XML boxes. Fixed some docstring errors in jp2box module. diff --git a/glymur/codestream.py b/glymur/codestream.py index e83c5cc..d01795f 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -646,16 +646,50 @@ class Codestream(object): length, = struct.unpack('>H', read_buffer) xy_buffer = fptr.read(36) + data = struct.unpack('>HIIIIIIIIH', xy_buffer) - num_components, = struct.unpack('>H', xy_buffer[-2:]) + rsiz = data[0] + xysiz = (data[1], data[2]) + xyosiz = (data[3], data[4]) + xytsiz = (data[5], data[6]) + xytosiz = (data[7], data[8]) - component_buffer = fptr.read(num_components * 3) + # Csiz is the number of components + Csiz = data[9] - segment = SIZsegment(xy_buffer, component_buffer, length, offset) + component_buffer = fptr.read(Csiz * 3) + data = struct.unpack('>' + 'B' * len(component_buffer), + component_buffer) + + bitdepth = tuple(((x & 0x7f) + 1) for x in data[0::3]) + signed = tuple(((x & 0xb0) > 0) for x in data[0::3]) + + xrsiz = data[1::3] + yrsiz = data[2::3] + + for j, subsampling in enumerate(zip(xrsiz, yrsiz)): + if 0 in subsampling: + msg = "Invalid subsampling value for component {0}: " + msg += "dx={1}, dy={2}." + msg = msg.format(j, subsampling[0], subsampling[1]) + warnings.warn(msg) + + kwargs = {'rsiz': rsiz, + 'xysiz': xysiz, + 'xyosiz': xyosiz, + 'xytsiz': xytsiz, + 'xytosiz': xytosiz, + 'Csiz': Csiz, + 'bitdepth': bitdepth, + 'signed': signed, + 'xyrsiz': (xrsiz, yrsiz), + 'length': length, + 'offset': offset} + segment = SIZsegment(**kwargs) # Need to keep track of the number of components from SIZ for - # other markers - self._csiz = len(segment.ssiz) + # other markers. + self._csiz = Csiz return segment @@ -1441,8 +1475,8 @@ class SIZsegment(Segment): Width and height of reference tile with respect to the reference grid. xtosiz, ytosiz : int Horizontal and vertical offsets of tile from origin of reference grid. - ssiz : iterable bytes - Encoded precision (depth) in bits and sign of each component. + Csiz : int + Number of components in image. bitdepth : iterable bytes Precision (depth) in bits of each component. signed : iterable bool @@ -1457,21 +1491,20 @@ class SIZsegment(Segment): 15444-1:2004 - Information technology -- JPEG 2000 image coding system: Core coding system """ - def __init__(self, xy_buffer, component_buffer, length, offset): - Segment.__init__(self, marker_id='SIZ') + def __init__(self, rsiz=-1, xysiz=None, xyosiz=-1, xytsiz=-1, xytosiz=-1, + Csiz=-1, bitdepth=None, signed=None, xyrsiz=-1, length=-1, + offset=-1): + Segment.__init__(self, marker_id='SIZ', length=length, offset=offset) - data = struct.unpack('>HIIIIIIIIH', xy_buffer) - - self.rsiz = data[0] - self.xsiz = data[1] - self.ysiz = data[2] - self.xosiz = data[3] - self.yosiz = data[4] - self.xtsiz = data[5] - self.ytsiz = data[6] - self.xtosiz = data[7] - self.ytosiz = data[8] - # disregarding the last element in data + self.rsiz = rsiz + self.xsiz, self.ysiz = xysiz + self.xosiz, self.yosiz = xyosiz + self.xtsiz, self.ytsiz = xytsiz + self.xtosiz, self.ytosiz = xytosiz + self.Csiz = Csiz + self.bitdepth = bitdepth + self.signed = signed + self.xrsiz, self.yrsiz = xyrsiz num_tiles_x = (self.xsiz - self.xosiz) / (self.xtsiz - self.xtosiz) num_tiles_y = (self.ysiz - self.yosiz) / (self.ytsiz - self.ytosiz) @@ -1480,26 +1513,23 @@ class SIZsegment(Segment): msg = "Invalid number of tiles ({0}).".format(numtiles) warnings.warn(msg) - data = struct.unpack('>' + 'B' * len(component_buffer), - component_buffer) - - self.ssiz = data[0::3] - - for j, subsampling in enumerate(list(zip(data[1::3], data[2::3]))): - if 0 in subsampling: - msg = "Invalid subsampling value for component {0}: " - msg += "dx={1}, dy={2}." - msg = msg.format(j, subsampling[0], subsampling[1]) - warnings.warn(msg) - 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.length = length - self.offset = offset + def __repr__(self): + msg = "glymur.codestream.SIZsegment(rsiz={rsiz}, xysiz={xysiz}, " + msg += "xyosiz={xyosiz}, xytsiz={xytsiz}, xytosiz={xytosiz}, " + msg += "Csiz={Csiz}, bitdepth={bitdepth}, signed={signed}, " + msg += "xyrsiz={xyrsiz})" + msg = msg.format(rsiz=self.rsiz, + xysiz=(self.xsiz, self.ysiz), + xyosiz=(self.xosiz, self.yosiz), + xytsiz=(self.xtsiz, self.ytsiz), + xytosiz=(self.xtosiz, self.ytosiz), + Csiz=self.Csiz, + bitdepth=self.bitdepth, + signed=self.signed, + xyrsiz=(self.xrsiz, self.yrsiz)) + return msg + def __str__(self): msg = Segment.__str__(self) msg += '\n ' @@ -1549,6 +1579,9 @@ class SOCsegment(Segment): Segment.__init__(self, marker_id='SOC') self.__dict__.update(**kwargs) + def __repr__(self): + msg = "glymur.codestream.SOCsegment()" + return msg class SODsegment(Segment): """Container for Start of Data (SOD) segment information. diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py index 8db5039..76042b9 100644 --- a/glymur/test/test_codestream.py +++ b/glymur/test/test_codestream.py @@ -116,5 +116,49 @@ class TestCodestream(unittest.TestCase): # codestream, so the last one is EOC. self.assertEqual(codestream.segment[-1].marker_id, 'EOC') + +class TestCodestreamRepr(unittest.TestCase): + + def setUp(self): + self.jp2file = glymur.data.nemo() + + def tearDown(self): + pass + + def test_soc(self): + """Test SOC segment repr""" + segment = glymur.codestream.SOCsegment() + newseg = eval(repr(segment)) + self.assertEqual(newseg.marker_id, 'SOC') + + def test_siz(self): + """Test SIZ segment repr""" + kwargs = {'rsiz': 0, + 'xysiz': (2592, 1456), + 'xyosiz': (0, 0), + 'xytsiz': (2592, 1456), + 'xytosiz': (0, 0), + 'Csiz': 3, + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': ((1, 1, 1), (1, 1, 1))} + segment = glymur.codestream.SIZsegment(**kwargs) + newseg = eval(repr(segment)) + self.assertEqual(newseg.marker_id, 'SIZ') + self.assertEqual(newseg.xsiz, 2592) + self.assertEqual(newseg.ysiz, 1456) + self.assertEqual(newseg.xosiz, 0) + self.assertEqual(newseg.yosiz, 0) + self.assertEqual(newseg.xtsiz, 2592) + self.assertEqual(newseg.ytsiz, 1456) + self.assertEqual(newseg.xtosiz, 0) + self.assertEqual(newseg.ytosiz, 0) + + self.assertEqual(newseg.xrsiz, (1, 1, 1)) + self.assertEqual(newseg.yrsiz, (1, 1, 1)) + self.assertEqual(newseg.bitdepth, (8, 8, 8)) + self.assertEqual(newseg.signed, (False, False, False)) + + if __name__ == "__main__": unittest.main() From 9799762fb5ae56a63659ce5a62ed6524cb72c385 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 24 Oct 2013 08:48:14 -0400 Subject: [PATCH 009/326] Added repr support for Jp2k class. #133 --- glymur/jp2k.py | 4 ++++ glymur/test/test_jp2k.py | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 1bd36a4..a9927da 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -72,6 +72,10 @@ class Jp2k(Jp2kBox): if mode == 'rb': self.parse() + def __repr__(self): + msg = "glymur.Jp2k('{0}')".format(self.filename) + return msg + def __str__(self): metadata = ['File: ' + os.path.basename(self.filename)] if len(self.box) > 0: diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 636ea9a..15da73d 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -67,6 +67,15 @@ class TestJp2k(unittest.TestCase): def tearDown(self): pass + def test_repr(self): + """Verify that results of __repr__ are eval-able.""" + j = Jp2k(self.j2kfile) + newjp2 = eval(repr(j)) + + self.assertEqual(newjp2.filename, self.j2kfile) + self.assertEqual(newjp2.mode, 'rb') + self.assertEqual(len(newjp2.box), 0) + def test_rlevel_max(self): """Verify that rlevel=-1 gets us the lowest resolution image""" j = Jp2k(self.j2kfile) From c8fe9ed7763ed46e90e3feca56a2688a3dc2269f Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 24 Oct 2013 09:29:45 -0400 Subject: [PATCH 010/326] Added proper repr support for association box. #133 --- glymur/jp2box.py | 8 ++++++-- glymur/test/test_jp2box.py | 7 +++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 8303981..6466a63 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1061,11 +1061,15 @@ class AssociationBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - def __init__(self, length=0, offset=-1): + def __init__(self, box=[], length=0, offset=-1): Jp2kBox.__init__(self, box_id='asoc', longname='Association') self.length = length self.offset = offset - self.box = [] + self.box = box + + def __repr__(self): + msg = "glymur.jp2box.AssociationBox(box={0})".format(self.box) + return msg def __str__(self): msg = Jp2kBox.__str__(self) diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index b82af47..bf14789 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -780,6 +780,13 @@ class TestRepr(unittest.TestCase): self.assertFalse(newbox.colorspace_unknown) self.assertFalse(newbox.ip_provided) + def test_association_box(self): + """Verify __repr__ method on asoc box.""" + asoc = glymur.jp2box.AssociationBox() + newbox = eval(repr(asoc)) + self.assertEqual(newbox.box_id, 'asoc') + self.assertEqual(len(newbox.box), 0) + def test_codestreamheader_box(self): """Verify __repr__ method on jpch box.""" jpch = glymur.jp2box.CodestreamHeaderBox() From 6ffe1a08a4423425e928b020626f0c3b80d26f59 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 24 Oct 2013 16:54:39 -0400 Subject: [PATCH 011/326] The palette box now returns a regular numpy array. Closes #135 Simplifies the code quite a bit. Palette boxes seem to be quite rare. --- glymur/jp2box.py | 72 ++++++++--------------------------- glymur/test/test_opj_suite.py | 39 +++++++------------ 2 files changed, 29 insertions(+), 82 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index bf88e4d..bd527c1 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1193,8 +1193,8 @@ class PaletteBox(Jp2kBox): offset of the box from the start of the file. longname : str more verbose description of the box. - palette : list - Colormap represented as list of 1D arrays, one per color component. + palette : ndarray + Colormap array. """ def __init__(self, palette, bits_per_component, signed, length=0, offset=-1): @@ -1207,8 +1207,7 @@ class PaletteBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - msg += '\n Size: ({0} x {1})'.format(len(self.palette[0]), - len(self.palette)) + msg += '\n Size: ({0} x {1})'.format(*self.palette.shape) return msg @staticmethod @@ -1238,68 +1237,29 @@ class PaletteBox(Jp2kBox): bps = [((x & 0x07f) + 1) for x in data] signed = [((x & 0x80) > 1) for x in data] + fmt = '>' + for bits in bps: + if bits <= 8: + fmt += 'B' + elif bits <= 16: + fmt += 'H' + elif bits <= 32: + fmt += 'I' + # 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. read_buffer = fptr.read(num_entries * row_nbytes) - palette = _buffer2palette(read_buffer, num_entries, num_columns, bps) + palette = np.zeros((num_entries, num_columns), dtype=np.int32) + for j in range(num_entries): + palette[j] = struct.unpack_from(fmt, read_buffer, + offset=j * row_nbytes) 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', diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 456ff9b..37d6b73 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -3797,19 +3797,16 @@ class TestSuiteDump(unittest.TestCase): self.assertEqual(jp2.box[2].box[0].ip_provided, False) # Palette box. - self.assertEqual(len(jp2.box[2].box[1].palette), 3) - self.assertEqual(len(jp2.box[2].box[1].palette[0]), 256) - self.assertEqual(len(jp2.box[2].box[1].palette[1]), 256) - self.assertEqual(len(jp2.box[2].box[1].palette[2]), 256) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[0][0], 0) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[1][0], 0) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[2][0], 0) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[0][128], 73) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[1][128], 92) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[2][128], 53) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[0][-1], 245) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[1][-1], 245) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[2][-1], 245) + self.assertEqual(jp2.box[2].box[1].palette.shape, (256, 3)) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[0, 0], 0) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[0, 1], 0) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[0, 2], 0) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[128, 0], 73) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[128, 1], 92) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[128, 2], 53) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[255, 0], 245) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[255, 1], 245) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[255, 2], 245) # Component mapping box self.assertEqual(jp2.box[2].box[2].component_index, (0, 0, 0)) @@ -5699,10 +5696,7 @@ class TestSuiteDump(unittest.TestCase): # Jp2 Header # Palette box. - self.assertEqual(len(jp2.box[3].box[2].palette), 3) - self.assertEqual(len(jp2.box[3].box[2].palette[0]), 256) - self.assertEqual(len(jp2.box[3].box[2].palette[1]), 256) - self.assertEqual(len(jp2.box[3].box[2].palette[2]), 256) + self.assertEqual(jp2.box[3].box[2].palette.shape, (256, 3)) # Jp2 Header # Component mapping box @@ -6127,11 +6121,7 @@ class TestSuiteDump(unittest.TestCase): # Jp2 Header # Palette box. - self.assertEqual(len(jp2.box[3].box[2].palette), 4) - self.assertEqual(len(jp2.box[3].box[2].palette[0]), 1) - self.assertEqual(len(jp2.box[3].box[2].palette[1]), 1) - self.assertEqual(len(jp2.box[3].box[2].palette[2]), 1) - self.assertEqual(len(jp2.box[3].box[2].palette[3]), 1) + self.assertEqual(jp2.box[3].box[2].palette.shape, (1, 4)) # Jp2 Header # Component mapping box @@ -6242,10 +6232,7 @@ class TestSuiteDump(unittest.TestCase): # Jp2 Header # Palette box. # 3 columns with 16 entries. - self.assertEqual(len(jp2.box[3].box[2].palette), 3) - self.assertEqual(len(jp2.box[3].box[2].palette[0]), 16) - self.assertEqual(len(jp2.box[3].box[2].palette[1]), 16) - self.assertEqual(len(jp2.box[3].box[2].palette[2]), 16) + self.assertEqual(jp2.box[3].box[2].palette.shape, (16, 3)) # Jp2 Header # Component mapping box From 08aaa25fdd03dcad140c1bd6b82b31840d633961 Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 24 Oct 2013 19:05:11 -0400 Subject: [PATCH 012/326] Changed nemo.jp2 to have a single XMP UUID. #104 --- glymur/data/nemo.jp2 | Bin 1135423 -> 1135519 bytes glymur/jp2box.py | 16 ++--- glymur/jp2k.py | 2 +- glymur/test/fixtures.py | 87 ++++++++++++++++++++++++ glymur/test/test_jp2box.py | 4 +- glymur/test/test_jp2k.py | 78 ++++++--------------- glymur/test/test_printing.py | 127 ++++++++++++++--------------------- 7 files changed, 169 insertions(+), 145 deletions(-) diff --git a/glymur/data/nemo.jp2 b/glymur/data/nemo.jp2 index 55d199cfe6ebc1630bd87a238c6f4cb4b7333d9d..838583d2f3e22a49d877299178a764436b808e74 100644 GIT binary patch delta 2966 zcma)8&2QsG6c^3%Rmzt*f<(3|C$_OY9w)KYT7t9q!gl{X$|VkgdO4^Oh+`@J{sYu>!Se?R~AkMp0e_YS^Z{=4_t z^1r>u-=BnuYAu2&AgR+_1b&!Uo_l&YOVh|wlxQCNEf!A|&sB&|0urW)qO~;Tu)u=$ z>7nDX7sLWhZloka%YPc5W)i7OvtiQ8by_Y9l$1^;U{ftA;(8=m&`qRKkBnw4Wyy?1 ziuQW8`^}Tsn^?oMi{&qk#~J)L zn~)=9^e~`BgAEb5dd>Rnw1EbgY_V4O_Tl8G1=kU_kFg;m4kl z#Eof`QWo-)PnN$mK9N4?)?M#KFCMUjasUmdw#H4>@C!$D>vl*I=FfA39i$ZaGFL+H zYN2<-Y;8ee*U)c6n%YRXvB8z-E9airUyXYLmH(|}%p*P*Hv7KgQW6)SoSmaG9^qx+ zOvw%P(wU76vpBEBl=gDoQxZ-AsbQln%$2zk`Xx=10f|RW5cwo8w5a`v{2>0sZ{}0G zE6M|3TpJ3*i}mV!p}n#zT-rVz+Dx|wymR3m#ninFQxZpU2A3+YUlH{-Vg-)z{gM#SxI6;Ns9a&$e42=Rt+9m46$??MNLU@@<_ ze(}_lrxCX5?B$`I!<+k@WN}_a6`>+&f-ShxsSDppW)WRHCk1?6jtz-a>9HVUAzv$c zES%8kJQlM~ILf`w{oJ^N)p*wTZ3iF5Upan>hgiyd@mI}D;a^tk;Hvgd!Drr7Hbzg7 z+A`YDL=!^!rAS81nJkbXd&}vjhCXb!Z?F|8Kv-0Rhq>V>%@0bo41vKm9+;k zb4ijwpIni2#ytDk`S|8~_>ENC0)Vzi#%d@u;12VB=z7)jM&6wHPDH$lQ>7iJ3feMS z*eqs?=dM<)mk##6{}9Y!NhlXi7Ig3c@rC(L$M>{*npT0E70%CW=X)JLv>G!XTd)o0 zC&4;s@AG`gUD^Ke=-{WLgV%uefEqxLfL;fB1L#elw}9RT+6Q_Eh~wV{dJpJ*pb!2! JI{5J8{{NWigc<+< delta 2973 zcmcJN&rTCj6vpq&v=pX=q7WBWlVKu}kkFZy63e7CF>NhHE&rO}w%C?5wzO6t)rFHN z3B*;A#I3CK5nQ-nqHV4yH`lCSqW=bn3h=ic<|_ssFPOlMK&-*0bM zOAF17LbX!Kw5ye={3rPFeRv&ao$la6b17RZzO0s8WF9e1|6l#&;lA1C87=MjjB(Ux zR24cr8AWOot_rEsxmQh^lf&`sr-^kvZG%0YU`kJ6=M(%%|yL zd{H$rt*KE>os}XnE%r#8nLamHM12YMJT79s-pXto?i7zro%r#~5rG!swP-1Qv^2Zi)rY%bDgM1G)4=qgDi{j7)JhApP5H!#3O7~*gC`3we0FtJVwal)l@ErTs~7+TU>c+c`Zyy z$UyC2d0hDW^Y~zAEH5V7TD#t?m$!;iyI$LTqqXPcVyUrS*5GMcktL&K>(!k6;`x%4 zYP8ByEF24~Q~2BNMCGI?C0eD5wvv8w_=SrfCjDbWqJ)q2t2Yl{b9eN1v8hDDsv^@p zAhlGhT-<833XMi>PTr`!t#7wvg*H=Ix1^X*PNu_#t55Ilv`dOJ#K!L##mcusY%PCkjT|Tr)krTpZ#nN=L=ivk o2KIIX9K=HcI4s', self.length, 'uuid') + serialized_buffer = b'' + serialized_buffer += ET.tostring(self.data.getroot(), encoding='utf-8') + serialized_buffer += b'' + if self.length == 0: + self.length = 24 + len(serialized_buffer) + read_buffer = struct.pack('>I4s', self.length, b'uuid') fptr.write(read_buffer) - fptr.write(self.data) + fptr.write(self.uuid.bytes) + fptr.write(serialized_buffer) @staticmethod def parse(fptr, offset, length): diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 45eaf39..42eaab5 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -1013,7 +1013,7 @@ class Jp2k(Jp2kBox): >>> jp2 = glymur.Jp2k(jfile) >>> codestream = jp2.get_codestream() >>> print(codestream.segment[1]) - SIZ marker segment @ (3137, 47) + SIZ marker segment @ (3233, 47) Profile: 2 Reference Grid Height, Width: (1456 x 2592) Vertical, Horizontal Reference Grid Offset: (0 x 0) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index b872f1d..68c0706 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -167,3 +167,90 @@ def read_pgx_header(pgx_file): header = header.rstrip() return header, pos + +nemo_xmp_box = """UUID Box (uuid) @ (77, 3146) + UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) + UUID Data: + + + + Google + 2013-02-09T14:47:53 + + + 1 + 72/1 + 72/1 + 2 + HTC + HTC Glacier + 2592 + 1456 + + + 8 + 8 + 8 + + + 2 + 3 + + + 1343036288/4294967295 + 1413044224/4294967295 + + + + + 2748779008/4294967295 + 1417339264/4294967295 + 1288490240/4294967295 + 2576980480/4294967295 + 644245120/4294967295 + 257698032/4294967295 + + + + + 1 + 2528 + 1424 + 353/100 + 0 + 0/1 + WGS-84 + 2013-02-09T14:47:53 + + + 76 + + + 0220 + 0100 + + + 1 + 2 + 3 + 0 + + + 42,20.56N + 71,5.29W + 2013-02-09T19:47:53Z + NETWORK + + + 2013-02-09T14:47:53 + + + + + Glymur + Python XMP Toolkit + + + + + """ diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index ff61bc5..2f6241c 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -419,7 +419,7 @@ class TestAppend(unittest.TestCase): # The sequence of box IDs should be the same as before, but with an # xml box at the end. box_ids = [box.box_id for box in jp2.box] - expected = ['jP ', 'ftyp', 'jp2h', 'uuid', 'uuid', 'jp2c', 'xml '] + expected = ['jP ', 'ftyp', 'jp2h', 'uuid', 'jp2c', 'xml '] self.assertEqual(box_ids, expected) self.assertEqual(ET.tostring(jp2.box[-1].xml.getroot()), b'0') @@ -468,7 +468,7 @@ class TestAppend(unittest.TestCase): # The sequence of box IDs should be the same as before, but with an # xml box at the end. box_ids = [box.box_id for box in jp2.box] - expected = ['jP ', 'ftyp', 'jp2h', 'uuid', 'uuid', 'jp2c', 'xml '] + expected = ['jP ', 'ftyp', 'jp2h', 'uuid', 'jp2c', 'xml '] self.assertEqual(box_ids, expected) self.assertEqual(ET.tostring(jp2.box[-1].xml.getroot()), b'0') diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 636ea9a..c9e750a 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -100,7 +100,7 @@ class TestJp2k(unittest.TestCase): jp2k = Jp2k(self.jp2file) # top-level boxes - self.assertEqual(len(jp2k.box), 6) + self.assertEqual(len(jp2k.box), 5) self.assertEqual(jp2k.box[0].box_id, 'jP ') self.assertEqual(jp2k.box[0].offset, 0) @@ -119,15 +119,11 @@ class TestJp2k(unittest.TestCase): self.assertEqual(jp2k.box[3].box_id, 'uuid') self.assertEqual(jp2k.box[3].offset, 77) - self.assertEqual(jp2k.box[3].length, 638) + self.assertEqual(jp2k.box[3].length, 3146) - self.assertEqual(jp2k.box[4].box_id, 'uuid') - self.assertEqual(jp2k.box[4].offset, 715) - self.assertEqual(jp2k.box[4].length, 2412) - - self.assertEqual(jp2k.box[5].box_id, 'jp2c') - self.assertEqual(jp2k.box[5].offset, 3127) - self.assertEqual(jp2k.box[5].length, 1132296) + self.assertEqual(jp2k.box[4].box_id, 'jp2c') + self.assertEqual(jp2k.box[4].offset, 3223) + self.assertEqual(jp2k.box[4].length, 1132296) # jp2h super box self.assertEqual(len(jp2k.box[2].box), 2) @@ -169,7 +165,7 @@ class TestJp2k(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with open(self.jp2file, 'rb') as ifile: # Everything up until the jp2c box. - write_buffer = ifile.read(3127) + write_buffer = ifile.read(3223) tfile.write(write_buffer) # The L field must be 1 in order to signal the presence of the @@ -190,9 +186,9 @@ class TestJp2k(unittest.TestCase): jp2k = Jp2k(tfile.name) - self.assertEqual(jp2k.box[5].box_id, 'jp2c') - self.assertEqual(jp2k.box[5].offset, 3127) - self.assertEqual(jp2k.box[5].length, 1133427 + 8) + self.assertEqual(jp2k.box[4].box_id, 'jp2c') + self.assertEqual(jp2k.box[4].offset, 3223) + self.assertEqual(jp2k.box[4].length, 1133427 + 8) @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_length_field_is_zero(self): @@ -357,45 +353,12 @@ class TestJp2k(unittest.TestCase): def test_xmp_attribute(self): """Verify the XMP packet in the shipping example file can be read.""" j = Jp2k(self.jp2file) - xmp = j.box[4].data + xmp = j.box[3].data ns0 = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}' - ns1 = '{http://ns.adobe.com/xap/1.0/}' - name = '{0}RDF/{0}Description'.format(ns0) + ns2 = '{http://ns.adobe.com/xap/1.0/}' + name = '{0}RDF/{0}Description/{1}CreatorTool'.format(ns0, ns2) elt = xmp.find(name) - attr_value = elt.attrib['{0}CreatorTool'.format(ns1)] - self.assertEqual(attr_value, 'glymur') - - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") - def test_unrecognized_exif_tag(self): - """An unrecognized exif tag should be handled gracefully.""" - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - shutil.copyfile(self.jp2file, tfile.name) - - # The Exif UUID starts at byte 77. There are 8 bytes for the L and - # T fields, then 16 bytes for the UUID identifier, then 6 exif - # header bytes, then 8 bytes for the TIFF header, then 2 bytes - # 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 fptr: - fptr.seek(117) - write_buffer = struct.pack('', - ' ', - ' ', - ' ', - ' '] - expected = '\n'.join(lst) + expected = nemo_xmp_box self.assertEqual(actual, expected) def test_codestream(self): @@ -657,8 +645,8 @@ class TestPrinting(unittest.TestCase): print(j.get_codestream()) actual = fake_out.getvalue().strip() lst = ['Codestream:', - ' SOC marker segment @ (3135, 0)', - ' SIZ marker segment @ (3137, 47)', + ' SOC marker segment @ (3231, 0)', + ' SIZ marker segment @ (3233, 47)', ' Profile: 2', ' Reference Grid Height, Width: (1456 x 2592)', ' Vertical, Horizontal Reference Grid Offset: (0 x 0)', @@ -668,7 +656,7 @@ class TestPrinting(unittest.TestCase): ' Signed: (False, False, False)', ' Vertical, Horizontal Subsampling: ' + '((1, 1), (1, 1), (1, 1))', - ' COD marker segment @ (3186, 12)', + ' COD marker segment @ (3282, 12)', ' Coding style:', ' Entropy coder, without partitions', ' SOP marker segments: False', @@ -690,11 +678,11 @@ class TestPrinting(unittest.TestCase): ' Vertically stripe causal context: False', ' Predictable termination: False', ' Segmentation symbols: False', - ' QCD marker segment @ (3200, 7)', + ' QCD marker segment @ (3296, 7)', ' Quantization style: no quantization, ' + '2 guard bits', ' Step size: [(0, 8), (0, 9), (0, 9), (0, 10)]', - ' CME marker segment @ (3209, 37)', + ' CME marker segment @ (3305, 37)', ' "Created by OpenJPEG version 2.0.0"'] expected = '\n'.join(lst) self.assertEqual(actual, expected) @@ -1046,59 +1034,42 @@ class TestPrinting(unittest.TestCase): "Ordered dicts not printing well in 2.7") def test_exif_uuid(self): """Verify printing of exif information""" - j = glymur.Jp2k(self.jp2file) + with tempfile.NamedTemporaryFile(suffix='.jp2', mode='wb') as tfile: - with patch('sys.stdout', new=StringIO()) as fake_out: - print(j.box[3]) - actual = fake_out.getvalue().strip() + with open(self.jp2file, 'rb') as ifptr: + tfile.write(ifptr.read()) - lines = ["UUID Box (uuid) @ (77, 638)", + # Write L, T, UUID identifier. + tfile.write(struct.pack('>I4s', 76, b'uuid')) + tfile.write(b'JpgTiffExif->JP2') + + tfile.write(b'Exif\x00\x00') + xbuffer = struct.pack(' Date: Thu, 24 Oct 2013 20:38:29 -0400 Subject: [PATCH 013/326] Refactored Exif uuid code to separate subpackage. #104 --- CHANGES.txt | 4 + glymur/_uuid_io/Exif.py | 520 ++++++++++++++++++++++++++++++++ glymur/_uuid_io/__init__.py | 1 + glymur/jp2box.py | 509 +------------------------------ glymur/test/test_jp2box_uuid.py | 78 +++++ 5 files changed, 606 insertions(+), 506 deletions(-) create mode 100644 glymur/_uuid_io/Exif.py create mode 100644 glymur/_uuid_io/__init__.py create mode 100644 glymur/test/test_jp2box_uuid.py diff --git a/CHANGES.txt b/CHANGES.txt index 40846e4..65270bf 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,7 @@ +Oct 24, 2013 - v0.6.0 Palette box palette changed to 2D numpy array. Removed + nemo Exif and simple XMP UUIDs in favor of larger XMP UUID. Refactored + Exif UUID code into _uuid_io sub package. + Oct 13, 2013 - v0.5.6 Fixed handling of non-ascii chars in XML boxes. Fixed some docstring errors in jp2box module. diff --git a/glymur/_uuid_io/Exif.py b/glymur/_uuid_io/Exif.py new file mode 100644 index 0000000..f70a090 --- /dev/null +++ b/glymur/_uuid_io/Exif.py @@ -0,0 +1,520 @@ +# -*- coding: utf-8 -*- +""" +Handlers for various UUID types. +""" +import struct +import sys +import warnings + +if sys.hexversion < 0x02070000: + # pylint: disable=F0401,E0611 + from ordereddict import OrderedDict +else: + from collections import OrderedDict + +class _Exif(object): + """ + Attributes + ---------- + read_buffer : bytes + Raw byte stream consisting of the UUID data. + endian : str + Either '<' for big-endian, or '>' for little-endian. + """ + + def __init__(self, read_buffer): + """Interpret raw buffer consisting of Exif IFD. + """ + self.exif_image = None + self.exif_photo = None + self.exif_gpsinfo = None + self.exif_iop = None + + self.read_buffer = read_buffer + + # Ignore the first six bytes. + # Next 8 should be (73, 73, 42, 8) + data = struct.unpack('' for little-endian. + num_tags : int + Number of tags in the IFD. + raw_ifd : dictionary + Maps tag number to "mildly-interpreted" tag value. + processed_ifd : dictionary + Maps tag name to "mildly-interpreted" tag value. + """ + datatype2fmt = {1: ('B', 1), + 2: ('B', 1), + 3: ('H', 2), + 4: ('I', 4), + 5: ('II', 8), + 7: ('B', 1), + 9: ('i', 4), + 10: ('ii', 8)} + + def __init__(self, endian, read_buffer, offset): + self.endian = endian + self.read_buffer = read_buffer + self.processed_ifd = OrderedDict() + + self.num_tags, = struct.unpack(endian + 'H', + read_buffer[offset:offset + 2]) + + fmt = self.endian + 'HHII' * self.num_tags + ifd_buffer = read_buffer[offset + 2:offset + 2 + self.num_tags * 12] + data = struct.unpack(fmt, ifd_buffer) + self.raw_ifd = OrderedDict() + for j, tag in enumerate(data[0::4]): + # The offset to the tag offset/payload is the offset to the IFD + # plus 2 bytes for the number of tags plus 12 bytes for each + # tag entry plus 8 bytes to the offset/payload itself. + toffp = read_buffer[offset + 10 + j * 12:offset + 10 + j * 12 + 4] + tag_data = self.parse_tag(data[j * 4 + 1], + data[j * 4 + 2], + toffp) + self.raw_ifd[tag] = tag_data + + def parse_tag(self, dtype, count, offset_buf): + """Interpret an Exif image tag data payload. + """ + fmt = self.datatype2fmt[dtype][0] * count + payload_size = self.datatype2fmt[dtype][1] * count + + if payload_size <= 4: + # Interpret the payload from the 4 bytes in the tag entry. + target_buffer = offset_buf[:payload_size] + else: + # Interpret the payload at the offset specified by the 4 bytes in + # the tag entry. + offset, = struct.unpack(self.endian + 'I', offset_buf) + target_buffer = self.read_buffer[offset:offset + payload_size] + + if dtype == 2: + # ASCII + if sys.hexversion < 0x03000000: + payload = target_buffer.rstrip('\x00') + else: + payload = target_buffer.decode('utf-8').rstrip('\x00') + + else: + payload = struct.unpack(self.endian + fmt, target_buffer) + if dtype == 5 or dtype == 10: + # Rational or Signed Rational. Construct the list of values. + rational_payload = [] + for j in range(count): + value = float(payload[j * 2]) / float(payload[j * 2 + 1]) + rational_payload.append(value) + payload = rational_payload + if count == 1: + # If just a single value, then return a scalar instead of a + # tuple. + payload = payload[0] + + return payload + + def post_process(self, tagnum2name): + """Map the tag name instead of tag number to the tag value. + """ + for tag, value in self.raw_ifd.items(): + try: + tag_name = tagnum2name[tag] + except KeyError: + # Ok, we don't recognize this tag. Just use the numeric id. + msg = 'Unrecognized Exif tag "{0}".'.format(tag) + warnings.warn(msg, UserWarning) + tag_name = tag + self.processed_ifd[tag_name] = value + + +class _ExifImageIfd(_Ifd): + """ + Attributes + ---------- + tagnum2name : dict + Maps Exif image tag numbers to the tag names. + ifd : dict + Maps tag names to tag values. + """ + tagnum2name = {11: 'ProcessingSoftware', + 254: 'NewSubfileType', + 255: 'SubfileType', + 256: 'ImageWidth', + 257: 'ImageLength', + 258: 'BitsPerSample', + 259: 'Compression', + 262: 'PhotometricInterpretation', + 263: 'Threshholding', + 264: 'CellWidth', + 265: 'CellLength', + 266: 'FillOrder', + 269: 'DocumentName', + 270: 'ImageDescription', + 271: 'Make', + 272: 'Model', + 273: 'StripOffsets', + 274: 'Orientation', + 277: 'SamplesPerPixel', + 278: 'RowsPerStrip', + 279: 'StripByteCounts', + 282: 'XResolution', + 283: 'YResolution', + 284: 'PlanarConfiguration', + 290: 'GrayResponseUnit', + 291: 'GrayResponseCurve', + 292: 'T4Options', + 293: 'T6Options', + 296: 'ResolutionUnit', + 301: 'TransferFunction', + 305: 'Software', + 306: 'DateTime', + 315: 'Artist', + 316: 'HostComputer', + 317: 'Predictor', + 318: 'WhitePoint', + 319: 'PrimaryChromaticities', + 320: 'ColorMap', + 321: 'HalftoneHints', + 322: 'TileWidth', + 323: 'TileLength', + 324: 'TileOffsets', + 325: 'TileByteCounts', + 330: 'SubIFDs', + 332: 'InkSet', + 333: 'InkNames', + 334: 'NumberOfInks', + 336: 'DotRange', + 337: 'TargetPrinter', + 338: 'ExtraSamples', + 339: 'SampleFormat', + 340: 'SMinSampleValue', + 341: 'SMaxSampleValue', + 342: 'TransferRange', + 343: 'ClipPath', + 344: 'XClipPathUnits', + 345: 'YClipPathUnits', + 346: 'Indexed', + 347: 'JPEGTables', + 351: 'OPIProxy', + 512: 'JPEGProc', + 513: 'JPEGInterchangeFormat', + 514: 'JPEGInterchangeFormatLength', + 515: 'JPEGRestartInterval', + 517: 'JPEGLosslessPredictors', + 518: 'JPEGPointTransforms', + 519: 'JPEGQTables', + 520: 'JPEGDCTables', + 521: 'JPEGACTables', + 529: 'YCbCrCoefficients', + 530: 'YCbCrSubSampling', + 531: 'YCbCrPositioning', + 532: 'ReferenceBlackWhite', + 700: 'XMLPacket', + 18246: 'Rating', + 18249: 'RatingPercent', + 32781: 'ImageID', + 33421: 'CFARepeatPatternDim', + 33422: 'CFAPattern', + 33423: 'BatteryLevel', + 33432: 'Copyright', + 33434: 'ExposureTime', + 33437: 'FNumber', + 33723: 'IPTCNAA', + 34377: 'ImageResources', + 34665: 'ExifTag', + 34675: 'InterColorProfile', + 34850: 'ExposureProgram', + 34852: 'SpectralSensitivity', + 34853: 'GPSTag', + 34855: 'ISOSpeedRatings', + 34856: 'OECF', + 34857: 'Interlace', + 34858: 'TimeZoneOffset', + 34859: 'SelfTimerMode', + 36867: 'DateTimeOriginal', + 37122: 'CompressedBitsPerPixel', + 37377: 'ShutterSpeedValue', + 37378: 'ApertureValue', + 37379: 'BrightnessValue', + 37380: 'ExposureBiasValue', + 37381: 'MaxApertureValue', + 37382: 'SubjectDistance', + 37383: 'MeteringMode', + 37384: 'LightSource', + 37385: 'Flash', + 37386: 'FocalLength', + 37387: 'FlashEnergy', + 37388: 'SpatialFrequencyResponse', + 37389: 'Noise', + 37390: 'FocalPlaneXResolution', + 37391: 'FocalPlaneYResolution', + 37392: 'FocalPlaneResolutionUnit', + 37393: 'ImageNumber', + 37394: 'SecurityClassification', + 37395: 'ImageHistory', + 37396: 'SubjectLocation', + 37397: 'ExposureIndex', + 37398: 'TIFFEPStandardID', + 37399: 'SensingMethod', + 40091: 'XPTitle', + 40092: 'XPComment', + 40093: 'XPAuthor', + 40094: 'XPKeywords', + 40095: 'XPSubject', + 50341: 'PrintImageMatching', + 50706: 'DNGVersion', + 50707: 'DNGBackwardVersion', + 50708: 'UniqueCameraModel', + 50709: 'LocalizedCameraModel', + 50710: 'CFAPlaneColor', + 50711: 'CFALayout', + 50712: 'LinearizationTable', + 50713: 'BlackLevelRepeatDim', + 50714: 'BlackLevel', + 50715: 'BlackLevelDeltaH', + 50716: 'BlackLevelDeltaV', + 50717: 'WhiteLevel', + 50718: 'DefaultScale', + 50719: 'DefaultCropOrigin', + 50720: 'DefaultCropSize', + 50721: 'ColorMatrix1', + 50722: 'ColorMatrix2', + 50723: 'CameraCalibration1', + 50724: 'CameraCalibration2', + 50725: 'ReductionMatrix1', + 50726: 'ReductionMatrix2', + 50727: 'AnalogBalance', + 50728: 'AsShotNeutral', + 50729: 'AsShotWhiteXY', + 50730: 'BaselineExposure', + 50731: 'BaselineNoise', + 50732: 'BaselineSharpness', + 50733: 'BayerGreenSplit', + 50734: 'LinearResponseLimit', + 50735: 'CameraSerialNumber', + 50736: 'LensInfo', + 50737: 'ChromaBlurRadius', + 50738: 'AntiAliasStrength', + 50739: 'ShadowScale', + 50740: 'DNGPrivateData', + 50741: 'MakerNoteSafety', + 50778: 'CalibrationIlluminant1', + 50779: 'CalibrationIlluminant2', + 50780: 'BestQualityScale', + 50781: 'RawDataUniqueID', + 50827: 'OriginalRawFileName', + 50828: 'OriginalRawFileData', + 50829: 'ActiveArea', + 50830: 'MaskedAreas', + 50831: 'AsShotICCProfile', + 50832: 'AsShotPreProfileMatrix', + 50833: 'CurrentICCProfile', + 50834: 'CurrentPreProfileMatrix', + 50879: 'ColorimetricReference', + 50931: 'CameraCalibrationSignature', + 50932: 'ProfileCalibrationSignature', + 50934: 'AsShotProfileName', + 50935: 'NoiseReductionApplied', + 50936: 'ProfileName', + 50937: 'ProfileHueSatMapDims', + 50938: 'ProfileHueSatMapData1', + 50939: 'ProfileHueSatMapData2', + 50940: 'ProfileToneCurve', + 50941: 'ProfileEmbedPolicy', + 50942: 'ProfileCopyright', + 50964: 'ForwardMatrix1', + 50965: 'ForwardMatrix2', + 50966: 'PreviewApplicationName', + 50967: 'PreviewApplicationVersion', + 50968: 'PreviewSettingsName', + 50969: 'PreviewSettingsDigest', + 50970: 'PreviewColorSpace', + 50971: 'PreviewDateTime', + 50972: 'RawImageDigest', + 50973: 'OriginalRawFileDigest', + 50974: 'SubTileBlockSize', + 50975: 'RowInterleaveFactor', + 50981: 'ProfileLookTableDims', + 50982: 'ProfileLookTableData', + 51008: 'OpcodeList1', + 51009: 'OpcodeList2', + 51022: 'OpcodeList3', + 51041: 'NoiseProfile'} + + def __init__(self, endian, read_buffer, offset): + _Ifd.__init__(self, endian, read_buffer, offset) + self.post_process(self.tagnum2name) + + +class _ExifPhotoIfd(_Ifd): + """Represents tags found in the Exif sub ifd. + """ + tagnum2name = {33434: 'ExposureTime', + 33437: 'FNumber', + 34850: 'ExposureProgram', + 34852: 'SpectralSensitivity', + 34855: 'ISOSpeedRatings', + 34856: 'OECF', + 34864: 'SensitivityType', + 34865: 'StandardOutputSensitivity', + 34866: 'RecommendedExposureIndex', + 34867: 'ISOSpeed', + 34868: 'ISOSpeedLatitudeyyy', + 34869: 'ISOSpeedLatitudezzz', + 36864: 'ExifVersion', + 36867: 'DateTimeOriginal', + 36868: 'DateTimeDigitized', + 37121: 'ComponentsConfiguration', + 37122: 'CompressedBitsPerPixel', + 37377: 'ShutterSpeedValue', + 37378: 'ApertureValue', + 37379: 'BrightnessValue', + 37380: 'ExposureBiasValue', + 37381: 'MaxApertureValue', + 37382: 'SubjectDistance', + 37383: 'MeteringMode', + 37384: 'LightSource', + 37385: 'Flash', + 37386: 'FocalLength', + 37396: 'SubjectArea', + 37500: 'MakerNote', + 37510: 'UserComment', + 37520: 'SubSecTime', + 37521: 'SubSecTimeOriginal', + 37522: 'SubSecTimeDigitized', + 40960: 'FlashpixVersion', + 40961: 'ColorSpace', + 40962: 'PixelXDimension', + 40963: 'PixelYDimension', + 40964: 'RelatedSoundFile', + 40965: 'InteroperabilityTag', + 41483: 'FlashEnergy', + 41484: 'SpatialFrequencyResponse', + 41486: 'FocalPlaneXResolution', + 41487: 'FocalPlaneYResolution', + 41488: 'FocalPlaneResolutionUnit', + 41492: 'SubjectLocation', + 41493: 'ExposureIndex', + 41495: 'SensingMethod', + 41728: 'FileSource', + 41729: 'SceneType', + 41730: 'CFAPattern', + 41985: 'CustomRendered', + 41986: 'ExposureMode', + 41987: 'WhiteBalance', + 41988: 'DigitalZoomRatio', + 41989: 'FocalLengthIn35mmFilm', + 41990: 'SceneCaptureType', + 41991: 'GainControl', + 41992: 'Contrast', + 41993: 'Saturation', + 41994: 'Sharpness', + 41995: 'DeviceSettingDescription', + 41996: 'SubjectDistanceRange', + 42016: 'ImageUniqueID', + 42032: 'CameraOwnerName', + 42033: 'BodySerialNumber', + 42034: 'LensSpecification', + 42035: 'LensMake', + 42036: 'LensModel', + 42037: 'LensSerialNumber'} + + def __init__(self, endian, read_buffer, offset): + _Ifd.__init__(self, endian, read_buffer, offset) + self.post_process(self.tagnum2name) + + +class _ExifGPSInfoIfd(_Ifd): + """Represents information found in the GPSInfo sub IFD. + """ + tagnum2name = {0: 'GPSVersionID', + 1: 'GPSLatitudeRef', + 2: 'GPSLatitude', + 3: 'GPSLongitudeRef', + 4: 'GPSLongitude', + 5: 'GPSAltitudeRef', + 6: 'GPSAltitude', + 7: 'GPSTimeStamp', + 8: 'GPSSatellites', + 9: 'GPSStatus', + 10: 'GPSMeasureMode', + 11: 'GPSDOP', + 12: 'GPSSpeedRef', + 13: 'GPSSpeed', + 14: 'GPSTrackRef', + 15: 'GPSTrack', + 16: 'GPSImgDirectionRef', + 17: 'GPSImgDirection', + 18: 'GPSMapDatum', + 19: 'GPSDestLatitudeRef', + 20: 'GPSDestLatitude', + 21: 'GPSDestLongitudeRef', + 22: 'GPSDestLongitude', + 23: 'GPSDestBearingRef', + 24: 'GPSDestBearing', + 25: 'GPSDestDistanceRef', + 26: 'GPSDestDistance', + 27: 'GPSProcessingMethod', + 28: 'GPSAreaInformation', + 29: 'GPSDateStamp', + 30: 'GPSDifferential'} + + def __init__(self, endian, read_buffer, offset): + _Ifd.__init__(self, endian, read_buffer, offset) + self.post_process(self.tagnum2name) + + +class _ExifInteroperabilityIfd(_Ifd): + """Represents tags found in the Interoperability sub IFD. + """ + tagnum2name = {1: 'InteroperabilityIndex', + 2: 'InteroperabilityVersion', + 4096: 'RelatedImageFileFormat', + 4097: 'RelatedImageWidth', + 4098: 'RelatedImageLength'} + + def __init__(self, endian, read_buffer, offset): + _Ifd.__init__(self, endian, read_buffer, offset) + self.post_process(self.tagnum2name) + + + diff --git a/glymur/_uuid_io/__init__.py b/glymur/_uuid_io/__init__.py new file mode 100644 index 0000000..721ee36 --- /dev/null +++ b/glymur/_uuid_io/__init__.py @@ -0,0 +1 @@ +from .Exif import _Exif diff --git a/glymur/jp2box.py b/glymur/jp2box.py index a270424..3ea911e 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -39,6 +39,8 @@ from .core import _COLOR_TYPE_MAP_DISPLAY from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE from .core import ANY_ICC_PROFILE, VENDOR_COLOR_METHOD +from ._uuid_io import _Exif + _METHOD_DISPLAY = { ENUMERATED_COLORSPACE: 'enumerated colorspace', RESTRICTED_ICC_PROFILE: 'restricted ICC profile', @@ -2084,7 +2086,7 @@ class UUIDBox(Jp2kBox): self.data = ET.ElementTree(elt) self._type = 'XMP' elif the_uuid.bytes == b'JpgTiffExif->JP2': - exif_obj = Exif(raw_data) + exif_obj = _Exif(raw_data) ifds = OrderedDict() ifds['Image'] = exif_obj.exif_image ifds['Photo'] = exif_obj.exif_photo @@ -2170,511 +2172,6 @@ class UUIDBox(Jp2kBox): return box -class Exif(object): - """ - Attributes - ---------- - read_buffer : bytes - Raw byte stream consisting of the UUID data. - endian : str - Either '<' for big-endian, or '>' for little-endian. - """ - - def __init__(self, read_buffer): - """Interpret raw buffer consisting of Exif IFD. - """ - self.exif_image = None - self.exif_photo = None - self.exif_gpsinfo = None - self.exif_iop = None - - self.read_buffer = read_buffer - - # Ignore the first six bytes. - # Next 8 should be (73, 73, 42, 8) - data = struct.unpack('' for little-endian. - num_tags : int - Number of tags in the IFD. - raw_ifd : dictionary - Maps tag number to "mildly-interpreted" tag value. - processed_ifd : dictionary - Maps tag name to "mildly-interpreted" tag value. - """ - datatype2fmt = {1: ('B', 1), - 2: ('B', 1), - 3: ('H', 2), - 4: ('I', 4), - 5: ('II', 8), - 7: ('B', 1), - 9: ('i', 4), - 10: ('ii', 8)} - - def __init__(self, endian, read_buffer, offset): - self.endian = endian - self.read_buffer = read_buffer - self.processed_ifd = OrderedDict() - - self.num_tags, = struct.unpack(endian + 'H', - read_buffer[offset:offset + 2]) - - fmt = self.endian + 'HHII' * self.num_tags - ifd_buffer = read_buffer[offset + 2:offset + 2 + self.num_tags * 12] - data = struct.unpack(fmt, ifd_buffer) - self.raw_ifd = OrderedDict() - for j, tag in enumerate(data[0::4]): - # The offset to the tag offset/payload is the offset to the IFD - # plus 2 bytes for the number of tags plus 12 bytes for each - # tag entry plus 8 bytes to the offset/payload itself. - toffp = read_buffer[offset + 10 + j * 12:offset + 10 + j * 12 + 4] - tag_data = self.parse_tag(data[j * 4 + 1], - data[j * 4 + 2], - toffp) - self.raw_ifd[tag] = tag_data - - def parse_tag(self, dtype, count, offset_buf): - """Interpret an Exif image tag data payload. - """ - fmt = self.datatype2fmt[dtype][0] * count - payload_size = self.datatype2fmt[dtype][1] * count - - if payload_size <= 4: - # Interpret the payload from the 4 bytes in the tag entry. - target_buffer = offset_buf[:payload_size] - else: - # Interpret the payload at the offset specified by the 4 bytes in - # the tag entry. - offset, = struct.unpack(self.endian + 'I', offset_buf) - target_buffer = self.read_buffer[offset:offset + payload_size] - - if dtype == 2: - # ASCII - if sys.hexversion < 0x03000000: - payload = target_buffer.rstrip('\x00') - else: - payload = target_buffer.decode('utf-8').rstrip('\x00') - - else: - payload = struct.unpack(self.endian + fmt, target_buffer) - if dtype == 5 or dtype == 10: - # Rational or Signed Rational. Construct the list of values. - rational_payload = [] - for j in range(count): - value = float(payload[j * 2]) / float(payload[j * 2 + 1]) - rational_payload.append(value) - payload = rational_payload - if count == 1: - # If just a single value, then return a scalar instead of a - # tuple. - payload = payload[0] - - return payload - - def post_process(self, tagnum2name): - """Map the tag name instead of tag number to the tag value. - """ - for tag, value in self.raw_ifd.items(): - try: - tag_name = tagnum2name[tag] - except KeyError: - # Ok, we don't recognize this tag. Just use the numeric id. - msg = 'Unrecognized Exif tag "{0}".'.format(tag) - warnings.warn(msg, UserWarning) - tag_name = tag - self.processed_ifd[tag_name] = value - - -class _ExifImageIfd(_Ifd): - """ - Attributes - ---------- - tagnum2name : dict - Maps Exif image tag numbers to the tag names. - ifd : dict - Maps tag names to tag values. - """ - tagnum2name = {11: 'ProcessingSoftware', - 254: 'NewSubfileType', - 255: 'SubfileType', - 256: 'ImageWidth', - 257: 'ImageLength', - 258: 'BitsPerSample', - 259: 'Compression', - 262: 'PhotometricInterpretation', - 263: 'Threshholding', - 264: 'CellWidth', - 265: 'CellLength', - 266: 'FillOrder', - 269: 'DocumentName', - 270: 'ImageDescription', - 271: 'Make', - 272: 'Model', - 273: 'StripOffsets', - 274: 'Orientation', - 277: 'SamplesPerPixel', - 278: 'RowsPerStrip', - 279: 'StripByteCounts', - 282: 'XResolution', - 283: 'YResolution', - 284: 'PlanarConfiguration', - 290: 'GrayResponseUnit', - 291: 'GrayResponseCurve', - 292: 'T4Options', - 293: 'T6Options', - 296: 'ResolutionUnit', - 301: 'TransferFunction', - 305: 'Software', - 306: 'DateTime', - 315: 'Artist', - 316: 'HostComputer', - 317: 'Predictor', - 318: 'WhitePoint', - 319: 'PrimaryChromaticities', - 320: 'ColorMap', - 321: 'HalftoneHints', - 322: 'TileWidth', - 323: 'TileLength', - 324: 'TileOffsets', - 325: 'TileByteCounts', - 330: 'SubIFDs', - 332: 'InkSet', - 333: 'InkNames', - 334: 'NumberOfInks', - 336: 'DotRange', - 337: 'TargetPrinter', - 338: 'ExtraSamples', - 339: 'SampleFormat', - 340: 'SMinSampleValue', - 341: 'SMaxSampleValue', - 342: 'TransferRange', - 343: 'ClipPath', - 344: 'XClipPathUnits', - 345: 'YClipPathUnits', - 346: 'Indexed', - 347: 'JPEGTables', - 351: 'OPIProxy', - 512: 'JPEGProc', - 513: 'JPEGInterchangeFormat', - 514: 'JPEGInterchangeFormatLength', - 515: 'JPEGRestartInterval', - 517: 'JPEGLosslessPredictors', - 518: 'JPEGPointTransforms', - 519: 'JPEGQTables', - 520: 'JPEGDCTables', - 521: 'JPEGACTables', - 529: 'YCbCrCoefficients', - 530: 'YCbCrSubSampling', - 531: 'YCbCrPositioning', - 532: 'ReferenceBlackWhite', - 700: 'XMLPacket', - 18246: 'Rating', - 18249: 'RatingPercent', - 32781: 'ImageID', - 33421: 'CFARepeatPatternDim', - 33422: 'CFAPattern', - 33423: 'BatteryLevel', - 33432: 'Copyright', - 33434: 'ExposureTime', - 33437: 'FNumber', - 33723: 'IPTCNAA', - 34377: 'ImageResources', - 34665: 'ExifTag', - 34675: 'InterColorProfile', - 34850: 'ExposureProgram', - 34852: 'SpectralSensitivity', - 34853: 'GPSTag', - 34855: 'ISOSpeedRatings', - 34856: 'OECF', - 34857: 'Interlace', - 34858: 'TimeZoneOffset', - 34859: 'SelfTimerMode', - 36867: 'DateTimeOriginal', - 37122: 'CompressedBitsPerPixel', - 37377: 'ShutterSpeedValue', - 37378: 'ApertureValue', - 37379: 'BrightnessValue', - 37380: 'ExposureBiasValue', - 37381: 'MaxApertureValue', - 37382: 'SubjectDistance', - 37383: 'MeteringMode', - 37384: 'LightSource', - 37385: 'Flash', - 37386: 'FocalLength', - 37387: 'FlashEnergy', - 37388: 'SpatialFrequencyResponse', - 37389: 'Noise', - 37390: 'FocalPlaneXResolution', - 37391: 'FocalPlaneYResolution', - 37392: 'FocalPlaneResolutionUnit', - 37393: 'ImageNumber', - 37394: 'SecurityClassification', - 37395: 'ImageHistory', - 37396: 'SubjectLocation', - 37397: 'ExposureIndex', - 37398: 'TIFFEPStandardID', - 37399: 'SensingMethod', - 40091: 'XPTitle', - 40092: 'XPComment', - 40093: 'XPAuthor', - 40094: 'XPKeywords', - 40095: 'XPSubject', - 50341: 'PrintImageMatching', - 50706: 'DNGVersion', - 50707: 'DNGBackwardVersion', - 50708: 'UniqueCameraModel', - 50709: 'LocalizedCameraModel', - 50710: 'CFAPlaneColor', - 50711: 'CFALayout', - 50712: 'LinearizationTable', - 50713: 'BlackLevelRepeatDim', - 50714: 'BlackLevel', - 50715: 'BlackLevelDeltaH', - 50716: 'BlackLevelDeltaV', - 50717: 'WhiteLevel', - 50718: 'DefaultScale', - 50719: 'DefaultCropOrigin', - 50720: 'DefaultCropSize', - 50721: 'ColorMatrix1', - 50722: 'ColorMatrix2', - 50723: 'CameraCalibration1', - 50724: 'CameraCalibration2', - 50725: 'ReductionMatrix1', - 50726: 'ReductionMatrix2', - 50727: 'AnalogBalance', - 50728: 'AsShotNeutral', - 50729: 'AsShotWhiteXY', - 50730: 'BaselineExposure', - 50731: 'BaselineNoise', - 50732: 'BaselineSharpness', - 50733: 'BayerGreenSplit', - 50734: 'LinearResponseLimit', - 50735: 'CameraSerialNumber', - 50736: 'LensInfo', - 50737: 'ChromaBlurRadius', - 50738: 'AntiAliasStrength', - 50739: 'ShadowScale', - 50740: 'DNGPrivateData', - 50741: 'MakerNoteSafety', - 50778: 'CalibrationIlluminant1', - 50779: 'CalibrationIlluminant2', - 50780: 'BestQualityScale', - 50781: 'RawDataUniqueID', - 50827: 'OriginalRawFileName', - 50828: 'OriginalRawFileData', - 50829: 'ActiveArea', - 50830: 'MaskedAreas', - 50831: 'AsShotICCProfile', - 50832: 'AsShotPreProfileMatrix', - 50833: 'CurrentICCProfile', - 50834: 'CurrentPreProfileMatrix', - 50879: 'ColorimetricReference', - 50931: 'CameraCalibrationSignature', - 50932: 'ProfileCalibrationSignature', - 50934: 'AsShotProfileName', - 50935: 'NoiseReductionApplied', - 50936: 'ProfileName', - 50937: 'ProfileHueSatMapDims', - 50938: 'ProfileHueSatMapData1', - 50939: 'ProfileHueSatMapData2', - 50940: 'ProfileToneCurve', - 50941: 'ProfileEmbedPolicy', - 50942: 'ProfileCopyright', - 50964: 'ForwardMatrix1', - 50965: 'ForwardMatrix2', - 50966: 'PreviewApplicationName', - 50967: 'PreviewApplicationVersion', - 50968: 'PreviewSettingsName', - 50969: 'PreviewSettingsDigest', - 50970: 'PreviewColorSpace', - 50971: 'PreviewDateTime', - 50972: 'RawImageDigest', - 50973: 'OriginalRawFileDigest', - 50974: 'SubTileBlockSize', - 50975: 'RowInterleaveFactor', - 50981: 'ProfileLookTableDims', - 50982: 'ProfileLookTableData', - 51008: 'OpcodeList1', - 51009: 'OpcodeList2', - 51022: 'OpcodeList3', - 51041: 'NoiseProfile'} - - def __init__(self, endian, read_buffer, offset): - _Ifd.__init__(self, endian, read_buffer, offset) - self.post_process(self.tagnum2name) - - -class _ExifPhotoIfd(_Ifd): - """Represents tags found in the Exif sub ifd. - """ - tagnum2name = {33434: 'ExposureTime', - 33437: 'FNumber', - 34850: 'ExposureProgram', - 34852: 'SpectralSensitivity', - 34855: 'ISOSpeedRatings', - 34856: 'OECF', - 34864: 'SensitivityType', - 34865: 'StandardOutputSensitivity', - 34866: 'RecommendedExposureIndex', - 34867: 'ISOSpeed', - 34868: 'ISOSpeedLatitudeyyy', - 34869: 'ISOSpeedLatitudezzz', - 36864: 'ExifVersion', - 36867: 'DateTimeOriginal', - 36868: 'DateTimeDigitized', - 37121: 'ComponentsConfiguration', - 37122: 'CompressedBitsPerPixel', - 37377: 'ShutterSpeedValue', - 37378: 'ApertureValue', - 37379: 'BrightnessValue', - 37380: 'ExposureBiasValue', - 37381: 'MaxApertureValue', - 37382: 'SubjectDistance', - 37383: 'MeteringMode', - 37384: 'LightSource', - 37385: 'Flash', - 37386: 'FocalLength', - 37396: 'SubjectArea', - 37500: 'MakerNote', - 37510: 'UserComment', - 37520: 'SubSecTime', - 37521: 'SubSecTimeOriginal', - 37522: 'SubSecTimeDigitized', - 40960: 'FlashpixVersion', - 40961: 'ColorSpace', - 40962: 'PixelXDimension', - 40963: 'PixelYDimension', - 40964: 'RelatedSoundFile', - 40965: 'InteroperabilityTag', - 41483: 'FlashEnergy', - 41484: 'SpatialFrequencyResponse', - 41486: 'FocalPlaneXResolution', - 41487: 'FocalPlaneYResolution', - 41488: 'FocalPlaneResolutionUnit', - 41492: 'SubjectLocation', - 41493: 'ExposureIndex', - 41495: 'SensingMethod', - 41728: 'FileSource', - 41729: 'SceneType', - 41730: 'CFAPattern', - 41985: 'CustomRendered', - 41986: 'ExposureMode', - 41987: 'WhiteBalance', - 41988: 'DigitalZoomRatio', - 41989: 'FocalLengthIn35mmFilm', - 41990: 'SceneCaptureType', - 41991: 'GainControl', - 41992: 'Contrast', - 41993: 'Saturation', - 41994: 'Sharpness', - 41995: 'DeviceSettingDescription', - 41996: 'SubjectDistanceRange', - 42016: 'ImageUniqueID', - 42032: 'CameraOwnerName', - 42033: 'BodySerialNumber', - 42034: 'LensSpecification', - 42035: 'LensMake', - 42036: 'LensModel', - 42037: 'LensSerialNumber'} - - def __init__(self, endian, read_buffer, offset): - _Ifd.__init__(self, endian, read_buffer, offset) - self.post_process(self.tagnum2name) - - -class _ExifGPSInfoIfd(_Ifd): - """Represents information found in the GPSInfo sub IFD. - """ - tagnum2name = {0: 'GPSVersionID', - 1: 'GPSLatitudeRef', - 2: 'GPSLatitude', - 3: 'GPSLongitudeRef', - 4: 'GPSLongitude', - 5: 'GPSAltitudeRef', - 6: 'GPSAltitude', - 7: 'GPSTimeStamp', - 8: 'GPSSatellites', - 9: 'GPSStatus', - 10: 'GPSMeasureMode', - 11: 'GPSDOP', - 12: 'GPSSpeedRef', - 13: 'GPSSpeed', - 14: 'GPSTrackRef', - 15: 'GPSTrack', - 16: 'GPSImgDirectionRef', - 17: 'GPSImgDirection', - 18: 'GPSMapDatum', - 19: 'GPSDestLatitudeRef', - 20: 'GPSDestLatitude', - 21: 'GPSDestLongitudeRef', - 22: 'GPSDestLongitude', - 23: 'GPSDestBearingRef', - 24: 'GPSDestBearing', - 25: 'GPSDestDistanceRef', - 26: 'GPSDestDistance', - 27: 'GPSProcessingMethod', - 28: 'GPSAreaInformation', - 29: 'GPSDateStamp', - 30: 'GPSDifferential'} - - def __init__(self, endian, read_buffer, offset): - _Ifd.__init__(self, endian, read_buffer, offset) - self.post_process(self.tagnum2name) - - -class _ExifInteroperabilityIfd(_Ifd): - """Represents tags found in the Interoperability sub IFD. - """ - tagnum2name = {1: 'InteroperabilityIndex', - 2: 'InteroperabilityVersion', - 4096: 'RelatedImageFileFormat', - 4097: 'RelatedImageWidth', - 4098: 'RelatedImageLength'} - - def __init__(self, endian, read_buffer, offset): - _Ifd.__init__(self, endian, read_buffer, offset) - self.post_process(self.tagnum2name) - - # Map each box ID to the corresponding class. _BOX_WITH_ID = { 'asoc': AssociationBox, diff --git a/glymur/test/test_jp2box_uuid.py b/glymur/test/test_jp2box_uuid.py new file mode 100644 index 0000000..6278f57 --- /dev/null +++ b/glymur/test/test_jp2box_uuid.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +"""Test suite for printing. +""" +# C0302: don't care too much about having too many lines in a test module +# pylint: disable=C0302 + +# E061: unittest.mock introduced in 3.3 (python-2.7/pylint issue) +# pylint: disable=E0611,F0401 + +# R0904: Not too many methods in unittest. +# pylint: disable=R0904 + +import os +import re +import struct +import sys +import tempfile +import warnings +from xml.etree import cElementTree as ET + +if sys.hexversion < 0x02070000: + import unittest2 as unittest +else: + import unittest + +if sys.hexversion < 0x03000000: + from StringIO import StringIO +else: + from io import StringIO + +if sys.hexversion <= 0x03030000: + from mock import patch +else: + from unittest.mock import patch + +import glymur +from glymur import Jp2k +from .fixtures import OPJ_DATA_ROOT, opj_data_file, nemo_xmp_box + + +class TestUUIDExif(unittest.TestCase): + """Tests for UUIDs of Exif type.""" + + def setUp(self): + self.jp2file = glymur.data.nemo() + + def tearDown(self): + pass + + @unittest.skipIf(sys.hexversion < 0x03000000, "Requires assertWarns, 3.2+") + def test_unrecognized_exif_tag(self): + """Verify warning in case of unrecognized tag.""" + with tempfile.NamedTemporaryFile(suffix='.jp2', mode='wb') as tfile: + + with open(self.jp2file, 'rb') as ifptr: + tfile.write(ifptr.read()) + + # Write L, T, UUID identifier. + tfile.write(struct.pack('>I4s', 52, b'uuid')) + tfile.write(b'JpgTiffExif->JP2') + + tfile.write(b'Exif\x00\x00') + xbuffer = struct.pack(' Date: Sat, 26 Oct 2013 16:51:07 -0400 Subject: [PATCH 014/326] Refactored UUID handling. #104 New classes for each type in _uuid_io sub package. --- glymur/_uuid_io/Exif.py | 50 ++++++++++++------- glymur/_uuid_io/XMP.py | 46 +++++++++++++++++ glymur/_uuid_io/__init__.py | 5 +- glymur/_uuid_io/generic.py | 27 ++++++++++ glymur/core.py | 45 +++++++++++++++++ glymur/jp2box.py | 95 ++++++------------------------------ glymur/test/test_jp2k.py | 2 +- glymur/test/test_printing.py | 3 +- 8 files changed, 173 insertions(+), 100 deletions(-) create mode 100644 glymur/_uuid_io/XMP.py create mode 100644 glymur/_uuid_io/generic.py diff --git a/glymur/_uuid_io/Exif.py b/glymur/_uuid_io/Exif.py index f70a090..3fb24b1 100644 --- a/glymur/_uuid_io/Exif.py +++ b/glymur/_uuid_io/Exif.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- """ -Handlers for various UUID types. +Handlers for Exif UUIDs. Be nice if we would find a standard for this. """ +import pprint import struct import sys import warnings @@ -12,7 +13,7 @@ if sys.hexversion < 0x02070000: else: from collections import OrderedDict -class _Exif(object): +class UUIDExif(object): """ Attributes ---------- @@ -25,10 +26,10 @@ class _Exif(object): def __init__(self, read_buffer): """Interpret raw buffer consisting of Exif IFD. """ - self.exif_image = None - self.exif_photo = None - self.exif_gpsinfo = None - self.exif_iop = None + exif_image = None + exif_photo = None + exif_gpsinfo = None + exif_iop = None self.read_buffer = read_buffer @@ -45,24 +46,39 @@ class _Exif(object): # This is the 'Exif Image' portion. exif = _ExifImageIfd(self.endian, read_buffer[6:], offset) - self.exif_image = exif.processed_ifd + exif_image = exif.processed_ifd - if 'ExifTag' in self.exif_image.keys(): - offset = self.exif_image['ExifTag'] - photo = _ExifPhotoIfd(self.endian, read_buffer[6:], offset) - self.exif_photo = photo.processed_ifd + if 'ExifTag' in exif_image.keys(): + offset = exif_image['ExifTag'] + photo_ifd = _ExifPhotoIfd(self.endian, read_buffer[6:], offset) + exif_photo = photo_ifd.processed_ifd - if 'InteroperabilityTag' in self.exif_photo.keys(): - offset = self.exif_photo['InteroperabilityTag'] + if 'InteroperabilityTag' in exif_photo.keys(): + offset = exif_photo['InteroperabilityTag'] interop = _ExifInteroperabilityIfd(self.endian, read_buffer[6:], offset) - self.iop = interop.processed_ifd + iop = interop.processed_ifd - if 'GPSTag' in self.exif_image.keys(): - offset = self.exif_image['GPSTag'] + if 'GPSTag' in exif_image.keys(): + offset = exif_image['GPSTag'] gps = _ExifGPSInfoIfd(self.endian, read_buffer[6:], offset) - self.exif_gpsinfo = gps.processed_ifd + exif_gpsinfo = gps.processed_ifd + + self.ifds = OrderedDict() + self.ifds['Image'] = exif_image + self.ifds['Photo'] = exif_photo + self.ifds['GPSInfo'] = exif_gpsinfo + self.ifds['Iop'] = exif_iop + + def __str__(self): + # 2.7 has trouble pretty-printing ordered dicts, so print them + # as regular dicts. Not ideal, but at least it's good on 3.3+. + if sys.hexversion < 0x03000000: + data = dict(self.ifds) + else: + data = self.ifds + return '\n' + pprint.pformat(data) class _Ifd(object): diff --git a/glymur/_uuid_io/XMP.py b/glymur/_uuid_io/XMP.py new file mode 100644 index 0000000..0451a92 --- /dev/null +++ b/glymur/_uuid_io/XMP.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- + +""" +Handler for a UUID for XMP. +""" + +import sys +from xml.etree import cElementTree as ET + +from ..core import _pretty_print_xml + +class UUIDXMP(object): + """ + Handler for a UUID for XMP. + + Attributes + ---------- + packet : ElementTree + XML conforming to the XMP specifications. + + References + ---------- + .. [XMP] International Organization for Standardication. ISO/IEC + 16684-1:2012 - Graphic technology -- Extensible metadata platform (XMP) + specification -- Part 1: Data model, serialization and core properties + """ + def __init__(self, read_buffer): + """ + Parameters + ---------- + read_buffer : byte array + sequence of bytes that can be decoded into an XMP packet. + """ + + # XMP data. Parse as XML. + if sys.hexversion < 0x03000000: + # 2.x strings same as bytes + elt = ET.fromstring(read_buffer) + else: + # 3.x takes strings, not bytes. + text = read_buffer.decode('utf-8') + elt = ET.fromstring(text) + self.packet = ET.ElementTree(elt) + + def __str__(self): + return _pretty_print_xml(self.packet) diff --git a/glymur/_uuid_io/__init__.py b/glymur/_uuid_io/__init__.py index 721ee36..5545351 100644 --- a/glymur/_uuid_io/__init__.py +++ b/glymur/_uuid_io/__init__.py @@ -1 +1,4 @@ -from .Exif import _Exif +from .Exif import UUIDExif +from .XMP import UUIDXMP +from .generic import UUIDGeneric + diff --git a/glymur/_uuid_io/generic.py b/glymur/_uuid_io/generic.py new file mode 100644 index 0000000..bad68a2 --- /dev/null +++ b/glymur/_uuid_io/generic.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + +""" +Handler for a generic UUID. +""" + +class UUIDGeneric(object): + """ + Handler for a generic UUID that is not currently recognized. + + Attributes + ---------- + data : byte array + Sequence of uninterpreted bytes as read from the file. + """ + def __init__(self, read_buffer): + """ + Parameters + ---------- + read_buffer : byte array + sequence of bytes as read from the file. + """ + self.data = read_buffer + + def __str__(self): + return '{0} bytes'.format(len(self.data)) + diff --git a/glymur/core.py b/glymur/core.py index 22b5a19..620d0e3 100644 --- a/glymur/core.py +++ b/glymur/core.py @@ -1,5 +1,8 @@ """Core definitions to be shared amongst the modules. """ +import copy +import xml.etree.cElementTree as ET + # Progression order LRCP = 0 RLCP = 1 @@ -73,3 +76,45 @@ _CAPABILITIES_DISPLAY = { 1: '0', 2: '1', 3: '3'} + + +def _pretty_print_xml(xml, level=0): + """Pretty print XML data. + """ + xml = copy.deepcopy(xml) + _indent(xml.getroot(), level=level) + xmltext = ET.tostring(xml.getroot(), encoding='utf-8').decode('utf-8') + + # Indent it a bit. + lst = [(' ' + x) for x in xmltext.split('\n')] + try: + xml = '\n'.join(lst) + return '\n{0}'.format(xml) + except UnicodeEncodeError: + # This can happen on python 2.x if the character set contains certain + # non-ascii characters. Just print out the corresponding xml char + # entities instead. + xml = u'\n'.join(lst) + text = u'\n{0}'.format(xml) + text = text.encode('ascii', 'xmlcharrefreplace') + return text + + +def _indent(elem, level=0): + """Recipe for pretty printing XML. Please see + + http://effbot.org/zone/element-lib.htm#prettyprint + """ + i = "\n" + level * " " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + _indent(elem, level + 1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 3ea911e..681cf51 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -13,7 +13,6 @@ References # pylint: disable=C0302,R0903,R0913 -import copy import datetime import math import os @@ -38,8 +37,9 @@ from .core import _COLORSPACE_MAP_DISPLAY from .core import _COLOR_TYPE_MAP_DISPLAY from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE from .core import ANY_ICC_PROFILE, VENDOR_COLOR_METHOD +from .core import _pretty_print_xml -from ._uuid_io import _Exif +from . import _uuid_io _METHOD_DISPLAY = { ENUMERATED_COLORSPACE: 'enumerated colorspace', @@ -2065,8 +2065,11 @@ class UUIDBox(Jp2kBox): ---------- the_uuid : uuid.UUID Identifies the type of UUID box. + data : object + Specific to each type of UUID. There are handlers for XMP, Exif, + and unknown UUIDs. raw_data : byte array - This is the "payload" of data for the specified UUID. + Sequence of uninterpreted bytes as read from the file. length : int length of the box in bytes. offset : int @@ -2076,59 +2079,33 @@ class UUIDBox(Jp2kBox): self.uuid = the_uuid if the_uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): - # XMP data. Parse as XML. Seems to be a difference between - # ElementTree in version 2.7 and 3.3. - if sys.hexversion < 0x03000000: - elt = ET.fromstring(raw_data) - else: - text = raw_data.decode('utf-8') - elt = ET.fromstring(text) - self.data = ET.ElementTree(elt) + self.data = _uuid_io.UUIDXMP(raw_data) self._type = 'XMP' elif the_uuid.bytes == b'JpgTiffExif->JP2': - exif_obj = _Exif(raw_data) - ifds = OrderedDict() - ifds['Image'] = exif_obj.exif_image - ifds['Photo'] = exif_obj.exif_photo - ifds['GPSInfo'] = exif_obj.exif_gpsinfo - ifds['Iop'] = exif_obj.exif_iop - self.data = ifds + self.data = _uuid_io.UUIDExif(raw_data) self._type = 'Exif' else: - self.data = raw_data + self.data = _uuid_io.UUIDGeneric(raw_data) self._type = 'unknown' + + self.raw_data = raw_data self.length = length self.offset = offset def __str__(self): msg = '{0}\n' - msg += ' UUID: {1}{2}\n' + msg += ' UUID: {1} ({2})\n' msg += ' UUID Data: {3}' - if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): - uuid_type = ' (XMP)' - uuid_data = _pretty_print_xml(self.data) - elif self.uuid.bytes == b'JpgTiffExif->JP2': - uuid_type = ' (Exif)' - # 2.7 has trouble pretty-printing ordered dicts, so print them - # as regular dicts. Not ideal, but at least it's good on 3.3+. - if sys.hexversion < 0x03000000: - data = dict(self.data) - else: - data = self.data - uuid_data = '\n' + pprint.pformat(data) - else: - uuid_type = '' - uuid_data = '{0} bytes'.format(len(self.data)) - msg = msg.format(Jp2kBox.__str__(self), self.uuid, - uuid_type, - uuid_data) + self._type, + str(self.data)) return msg + def write(self, fptr): """Write a UUID box box to file. """ @@ -2196,45 +2173,3 @@ _BOX_WITH_ID = { 'url ': DataEntryURLBox, 'uuid': UUIDBox, 'xml ': XMLBox} - - -def _indent(elem, level=0): - """Recipe for pretty printing XML. Please see - - http://effbot.org/zone/element-lib.htm#prettyprint - """ - i = "\n" + level * " " - if len(elem): - if not elem.text or not elem.text.strip(): - elem.text = i + " " - if not elem.tail or not elem.tail.strip(): - elem.tail = i - for elem in elem: - _indent(elem, level + 1) - if not elem.tail or not elem.tail.strip(): - elem.tail = i - else: - if level and (not elem.tail or not elem.tail.strip()): - elem.tail = i - - -def _pretty_print_xml(xml, level=0): - """Pretty print XML data. - """ - xml = copy.deepcopy(xml) - _indent(xml.getroot(), level=level) - xmltext = ET.tostring(xml.getroot(), encoding='utf-8').decode('utf-8') - - # Indent it a bit. - lst = [(' ' + x) for x in xmltext.split('\n')] - try: - xml = '\n'.join(lst) - return '\n{0}'.format(xml) - except UnicodeEncodeError: - # This can happen on python 2.x if the character set contains certain - # non-ascii characters. Just print out the corresponding xml char - # entities instead. - xml = u'\n'.join(lst) - text = u'\n{0}'.format(xml) - text = text.encode('ascii', 'xmlcharrefreplace') - return text diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index c9e750a..026b129 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -353,7 +353,7 @@ class TestJp2k(unittest.TestCase): def test_xmp_attribute(self): """Verify the XMP packet in the shipping example file can be read.""" j = Jp2k(self.jp2file) - xmp = j.box[3].data + xmp = j.box[3].data.packet ns0 = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}' ns2 = '{http://ns.adobe.com/xap/1.0/}' name = '{0}RDF/{0}Description/{1}CreatorTool'.format(ns0, ns2) diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 5824e0e..a1f0c55 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -636,6 +636,7 @@ class TestPrinting(unittest.TestCase): actual = fake_out.getvalue().strip() expected = nemo_xmp_box + self.maxDiff = None self.assertEqual(actual, expected) def test_codestream(self): @@ -1024,7 +1025,7 @@ class TestPrinting(unittest.TestCase): print(jp2.box[4]) actual = fake_out.getvalue().strip() lines = ['UUID Box (uuid) @ (1544, 25)', - ' UUID: 3a0d0218-0ae9-4115-b376-4bca41ce0e71', + ' UUID: 3a0d0218-0ae9-4115-b376-4bca41ce0e71 (unknown)', ' UUID Data: 1 bytes'] expected = '\n'.join(lines) From 7e717b50370f56dd0895922196098ef6c72c347c Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 26 Oct 2013 18:05:44 -0400 Subject: [PATCH 015/326] Made Exif handling more resilient. #104 --- glymur/_uuid_io/Exif.py | 6 +++- glymur/jp2box.py | 23 +++++++++---- glymur/test/test_jp2box_uuid.py | 57 ++++++++++++++++++++++++++++++++- 3 files changed, 77 insertions(+), 9 deletions(-) diff --git a/glymur/_uuid_io/Exif.py b/glymur/_uuid_io/Exif.py index 3fb24b1..0a86c18 100644 --- a/glymur/_uuid_io/Exif.py +++ b/glymur/_uuid_io/Exif.py @@ -39,9 +39,13 @@ class UUIDExif(object): if data[0] == 73 and data[1] == 73: # little endian self.endian = '<' - else: + elif data[0] == 77 and data[1] == 77: # big endian self.endian = '>' + else: + msg = "Bad byte order indication: {0}".format(read_buffer[6:8]) + raise RuntimeError(msg) + offset = data[3] # This is the 'Exif Image' portion. diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 681cf51..074d18e 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -19,6 +19,7 @@ import os import pprint import struct import sys +import traceback import uuid import warnings import xml.etree.cElementTree as ET @@ -2078,15 +2079,23 @@ class UUIDBox(Jp2kBox): Jp2kBox.__init__(self, box_id='uuid', longname='UUID') self.uuid = the_uuid - if the_uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): - self.data = _uuid_io.UUIDXMP(raw_data) - self._type = 'XMP' - elif the_uuid.bytes == b'JpgTiffExif->JP2': - self.data = _uuid_io.UUIDExif(raw_data) - self._type = 'Exif' - else: + try: + if the_uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): + self.data = _uuid_io.UUIDXMP(raw_data) + self._type = 'XMP' + elif the_uuid.bytes == b'JpgTiffExif->JP2': + self.data = _uuid_io.UUIDExif(raw_data) + self._type = 'Exif' + else: + self.data = _uuid_io.UUIDGeneric(raw_data) + self._type = 'unknown' + except Exception as err: + # In case of any exception, create the generic UUID. self.data = _uuid_io.UUIDGeneric(raw_data) self._type = 'unknown' + msg = "Error encountered during UUID processing, " + msg += "the UUID will be treated as generic.\n\n{0}" + warnings.warn(msg.format(traceback.format_exc())) self.raw_data = raw_data diff --git a/glymur/test/test_jp2box_uuid.py b/glymur/test/test_jp2box_uuid.py index 6278f57..a1d93a4 100644 --- a/glymur/test/test_jp2box_uuid.py +++ b/glymur/test/test_jp2box_uuid.py @@ -63,7 +63,7 @@ class TestUUIDExif(unittest.TestCase): xbuffer = struct.pack('I4s', 52, b'uuid')) + tfile.write(b'JpgTiffExif->JP2') + + tfile.write(b'Exif\x00\x00') + xbuffer = struct.pack('I4s', 52, b'uuid')) + tfile.write(b'JpgTiffExif->JP2') + + tfile.write(b'Exif\x00\x00') + xbuffer = struct.pack(' Date: Sat, 26 Oct 2013 18:21:24 -0400 Subject: [PATCH 016/326] Pylint issues, #104 --- glymur/_uuid_io/Exif.py | 2 +- glymur/_uuid_io/__init__.py | 4 +++- glymur/jp2box.py | 12 ++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/glymur/_uuid_io/Exif.py b/glymur/_uuid_io/Exif.py index 0a86c18..ccc03dd 100644 --- a/glymur/_uuid_io/Exif.py +++ b/glymur/_uuid_io/Exif.py @@ -62,7 +62,7 @@ class UUIDExif(object): interop = _ExifInteroperabilityIfd(self.endian, read_buffer[6:], offset) - iop = interop.processed_ifd + exif_iop = interop.processed_ifd if 'GPSTag' in exif_image.keys(): offset = exif_image['GPSTag'] diff --git a/glymur/_uuid_io/__init__.py b/glymur/_uuid_io/__init__.py index 5545351..a23c2ce 100644 --- a/glymur/_uuid_io/__init__.py +++ b/glymur/_uuid_io/__init__.py @@ -1,4 +1,6 @@ +""" +Sub package for handling various UUIDs. +""" from .Exif import UUIDExif from .XMP import UUIDXMP from .generic import UUIDGeneric - diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 074d18e..34eeeb8 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2089,7 +2089,7 @@ class UUIDBox(Jp2kBox): else: self.data = _uuid_io.UUIDGeneric(raw_data) self._type = 'unknown' - except Exception as err: + except Exception: # In case of any exception, create the generic UUID. self.data = _uuid_io.UUIDGeneric(raw_data) self._type = 'unknown' @@ -2121,15 +2121,15 @@ class UUIDBox(Jp2kBox): if self._type != 'XMP': msg = "Only XMP UUID boxes can currently be written." raise NotImplementedError(msg) - serialized_buffer = b'' - serialized_buffer += ET.tostring(self.data.getroot(), encoding='utf-8') - serialized_buffer += b'' + serialized = b'' + serialized += ET.tostring(self.data.packet.getroot(), encoding='utf-8') + serialized += b'' if self.length == 0: - self.length = 24 + len(serialized_buffer) + self.length = 24 + len(serialized) read_buffer = struct.pack('>I4s', self.length, b'uuid') fptr.write(read_buffer) fptr.write(self.uuid.bytes) - fptr.write(serialized_buffer) + fptr.write(serialized) @staticmethod def parse(fptr, offset, length): From c4060f23c88ece87a5e97daccf1c7c37cbe80b88 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 26 Oct 2013 19:23:33 -0400 Subject: [PATCH 017/326] Added big endian Exif support. #104 --- glymur/_uuid_io/Exif.py | 6 +++--- glymur/test/test_jp2box_uuid.py | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/glymur/_uuid_io/Exif.py b/glymur/_uuid_io/Exif.py index ccc03dd..fb5e2a1 100644 --- a/glymur/_uuid_io/Exif.py +++ b/glymur/_uuid_io/Exif.py @@ -34,8 +34,8 @@ class UUIDExif(object): self.read_buffer = read_buffer # Ignore the first six bytes. - # Next 8 should be (73, 73, 42, 8) - data = struct.unpack('I4s', 52, b'uuid')) + tfile.write(b'JpgTiffExif->JP2') + + tfile.write(b'Exif\x00\x00') + xbuffer = struct.pack('>BBHI', 77, 77, 42, 8) + tfile.write(xbuffer) + + # We will write just a single tag. + tfile.write(struct.pack('>H', 1)) + + # The "Make" tag is tag no. 271. + tfile.write(struct.pack('>HHI4s', 271, 2, 3, b'HTC\x00')) + tfile.flush() + + jp2 = glymur.Jp2k(tfile.name) + self.assertEqual(jp2.box[-1].data.ifds['Image']['Make'], "HTC") + if __name__ == "__main__": unittest.main() From dc994377e1bf2c425ddee2072e94c01771ccd9f6 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 26 Oct 2013 21:43:32 -0400 Subject: [PATCH 018/326] Added palette and xml box repr support. #133 --- glymur/jp2box.py | 16 +++++++++++++++- glymur/test/test_jp2box.py | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 6466a63..c7d69d5 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -75,6 +75,12 @@ class Jp2kBox(object): self.offset = offset self.longname = longname + def __repr__(self): + msg = "glymur.jp2box.Jp2kBox(box_id='{0}', offset={1}, length={2}, " + msg += "longname='{3}')" + msg = msg.format(self.box_id, self.offset, self.length, self.longname) + return msg + def __str__(self): msg = "{0} Box ({1})".format(self.longname, self.box_id) msg += " @ ({0}, {1})".format(self.offset, self.length) @@ -1273,6 +1279,12 @@ class PaletteBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.PaletteBox({0}, bits_per_component={1}, " + msg += "signed={2})" + msg = msg.format(self.palette, self.bits_per_component, self.signed) + return msg + def __str__(self): msg = Jp2kBox.__str__(self) msg += '\n Size: ({0} x {1})'.format(len(self.palette[0]), @@ -1895,6 +1907,9 @@ class XMLBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + return "glymur.jp2box.XMLBox(xml={0})".format(self.xml) + def __str__(self): msg = Jp2kBox.__str__(self) xml = self.xml @@ -1953,7 +1968,6 @@ class XMLBox(Jp2kBox): msg = msg.format(offset, ude.reason) warnings.warn(msg, UserWarning) - # Strip out any trailing nulls, as they can foul up XML parsing. text = text.rstrip(chr(0)) diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index bf14789..117d52b 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -18,6 +18,7 @@ Test suite specifically targeting JP2 box layout. import doctest import os +import re import shutil import struct import sys @@ -414,7 +415,7 @@ class TestAppend(unittest.TestCase): jp2 = Jp2k(tfile.name) the_xml = ET.fromstring('0') - xmlbox = glymur.jp2box.XMLBox(xml=the_xml) + xmlbox = glymur.jp2box.XMLBox(xml=ET.ElementTree(the_xml)) jp2.append(xmlbox) # The sequence of box IDs should be the same as before, but with an @@ -865,6 +866,36 @@ class TestRepr(unittest.TestCase): self.assertEqual(newbox.ulst[0], uuid1) self.assertEqual(newbox.ulst[1], uuid2) + def test_jp2k_box(self): + """Verify Superclass repr.""" + box = glymur.jp2box.Jp2kBox(box_id='one', offset=2, length=3, + longname='four') + newbox = eval(repr(box)) + self.assertEqual(newbox.box_id, 'one') + self.assertEqual(newbox.offset, 2) + self.assertEqual(newbox.length, 3) + self.assertEqual(newbox.longname, 'four') + + def test_palette_box(self): + """Verify Palette box repr.""" + palette = np.array([[255, 0, 1000], [0, 255, 0]], dtype=np.int32) + bps = (8, 8, 16) + signed = (True, False, True) + box = glymur.jp2box.PaletteBox(palette=palette, bits_per_component=bps, + signed=(True, False, True)) + # The palette can't be reinstantiated thru eval/repr. + s = repr(box) + self.assertTrue(True) + + def test_xml_box(self): + """Verify xml box repr.""" + elt = ET.fromstring('0') + tree = ET.ElementTree(elt) + box = glymur.jp2box.XMLBox(xml=tree) + + regexp = "glymur.jp2box.XMLBox\(xml= Date: Sat, 26 Oct 2013 22:16:01 -0400 Subject: [PATCH 019/326] Regexp tests can only run on 2.7 and 3.3. #133 --- glymur/test/test_jp2box.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 117d52b..94c3f73 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -887,15 +887,21 @@ class TestRepr(unittest.TestCase): s = repr(box) self.assertTrue(True) + @unittest.skipIf(sys.hexversion < 0x02070000, "Requires 2.7+") def test_xml_box(self): """Verify xml box repr.""" elt = ET.fromstring('0') tree = ET.ElementTree(elt) box = glymur.jp2box.XMLBox(xml=tree) - regexp = "glymur.jp2box.XMLBox\(xml= Date: Sun, 27 Oct 2013 06:09:39 -0400 Subject: [PATCH 020/326] Added rreg repr support. #133 --- glymur/jp2box.py | 12 ++++++++++++ glymur/test/test_jp2box.py | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index c7d69d5..00e2a1e 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1506,6 +1506,18 @@ class ReaderRequirementsBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.ReaderRequirementsBox(fuam={fuam}, dcm={dcm}, " + msg += "standard_flag={standard_flag}, standard_mask={standard_mask}, " + msg += "vendor_feature={vendor_feature}, vendor_mask={vendor_mask})" + msg = msg.format(fuam=self.fuam, + dcm=self.dcm, + standard_flag=self.standard_flag, + standard_mask=self.standard_mask, + vendor_feature=self.vendor_feature, + vendor_mask=self.vendor_mask) + return msg + def __str__(self): msg = Jp2kBox.__str__(self) diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 94c3f73..648e37c 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -903,6 +903,22 @@ class TestRepr(unittest.TestCase): else: self.assertRegex(repr(box), regexp) + def test_readerrequirements_box(self): + """Verify rreq repr method.""" + box = glymur.jp2box.ReaderRequirementsBox(fuam=160, dcm=192, + standard_flag=(5, 61, 43), + standard_mask=(128, 96, 64), + vendor_feature=[], + vendor_mask=[]) + newbox = eval(repr(box)) + self.assertEqual(box.fuam, newbox.fuam) + self.assertEqual(box.dcm, newbox.dcm) + self.assertEqual(box.standard_flag, newbox.standard_flag) + self.assertEqual(box.standard_mask, newbox.standard_mask) + self.assertEqual(box.vendor_feature, newbox.vendor_feature) + self.assertEqual(box.vendor_mask, newbox.vendor_mask) + + class TestJpxBoxes(unittest.TestCase): """Tests for JPX boxes.""" From 5e5bff39c3bd3e22dea650d18b2fb60140e776fa Mon Sep 17 00:00:00 2001 From: John Evans Date: Sun, 27 Oct 2013 14:00:40 -0400 Subject: [PATCH 021/326] Added UUIDBox and ContiguousCodeStreamBox repr support. Closes #133 --- glymur/jp2box.py | 12 +++++++++++- glymur/test/test_jp2box.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 00e2a1e..b363c76 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -773,6 +773,10 @@ class ContiguousCodestreamBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.ContiguousCodeStreamBox(main_header={0})" + return msg.format(repr(self.main_header)) + def __str__(self): msg = Jp2kBox.__str__(self) msg += '\n Main header:' @@ -978,7 +982,6 @@ class ImageHeaderBox(Jp2kBox): return msg def __str__(self): - msg = Jp2kBox.__str__(self) msg = "{0}" msg += '\n Size: [{1} {2} {3}]' msg += '\n Bitdepth: {4}' @@ -2238,6 +2241,7 @@ class UUIDBox(Jp2kBox): """ Jp2kBox.__init__(self, box_id='uuid', longname='UUID') self.uuid = the_uuid + self.raw_data = raw_data if the_uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): # XMP data. Parse as XML. Seems to be a difference between @@ -2262,6 +2266,12 @@ class UUIDBox(Jp2kBox): self.length = length self.offset = offset + def __repr__(self): + msg = "glymur.jp2box.UUIDBox(the_uuid={0}, " + msg += "raw_data=)" + return msg.format(repr(self.uuid), len(self.raw_data)) + + def __str__(self): msg = '{0}\n' msg += ' UUID: {1}{2}\n' diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 648e37c..05c0a83 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -918,6 +918,42 @@ class TestRepr(unittest.TestCase): self.assertEqual(box.vendor_feature, newbox.vendor_feature) self.assertEqual(box.vendor_mask, newbox.vendor_mask) + @unittest.skipIf(sys.hexversion < 0x02070000, "Requires 2.7+") + def test_uuid_box(self): + """Verify uuid repr method.""" + uuid_instance = uuid.UUID('00000000-0000-0000-0000-000000000000') + data = b'0123456789' + box = glymur.jp2box.UUIDBox(the_uuid=uuid_instance, raw_data=data) + + # Since the raw_data parameter is a sequence of bytes which could be + # quite long, don't bother trying to make it conform to eval(repr()). + regexp = "glymur.jp2box.UUIDBox\(" + regexp += "the_uuid=UUID\('00000000-0000-0000-0000-000000000000'\),\s" + regexp += "raw_data=\)" + + if sys.hexversion < 0x03000000: + self.assertRegexpMatches(repr(box), regexp) + else: + self.assertRegex(repr(box), regexp) + + @unittest.skipIf(sys.hexversion < 0x02070000, "Requires 2.7+") + def test_contiguous_codestream_box(self): + """Verify contiguous codestream box repr method.""" + jp2file = glymur.data.nemo() + jp2 = Jp2k(jp2file) + box = jp2.box[-1] + + # Difficult to eval(repr()) this, so just match the general pattern. + regexp = "glymur.jp2box.ContiguousCodeStreamBox" + regexp += "\(main_header= Date: Sun, 27 Oct 2013 14:28:22 -0400 Subject: [PATCH 022/326] minor documentation tweaks. #104 --- glymur/jp2box.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index c02f497..ec1b349 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1209,7 +1209,7 @@ class JPEG2000SignatureBox(Jp2kBox): offset of the box from the start of the file. longname : str more verbose description of the box. - signature : byte + signature : tuple Four-byte tuple identifying the file as JPEG 2000. """ def __init__(self, signature=(13, 10, 135, 10), length=0, offset=-1): @@ -1807,7 +1807,7 @@ class LabelBox(Jp2kBox): longname : str more verbose description of the box. label : str - Label + Textual label. """ def __init__(self, label, length=0, offset=-1): Jp2kBox.__init__(self, box_id='lbl ', longname='Label') @@ -2179,9 +2179,12 @@ class UUIDBox(Jp2kBox): more verbose description of the box. uuid : uuid.UUID 16-byte UUID - data : bytes or dict or ElementTree.Element - Vendor-specific data. Exif UUIDs are interpreted as dictionaries. - XMP UUIDs are interpreted as standard XML. + raw_data : byte array + Sequence of uninterpreted bytes as read from the file. + data : object + Specific to each type of UUID. There are handlers for XMP, Exif, and + generic (unknown) UUIDs. In the case of XMP and Exif UUIDs, this is + the interpreted version of raw_data. References ---------- @@ -2195,9 +2198,6 @@ class UUIDBox(Jp2kBox): ---------- the_uuid : uuid.UUID Identifies the type of UUID box. - data : object - Specific to each type of UUID. There are handlers for XMP, Exif, - and unknown UUIDs. raw_data : byte array Sequence of uninterpreted bytes as read from the file. length : int From 727c4dd2ea357a5dc57d551a318cf08f0dc60ee7 Mon Sep 17 00:00:00 2001 From: jevans Date: Sun, 27 Oct 2013 18:13:42 -0400 Subject: [PATCH 023/326] Verifying that we can write XMP UUIDs. --- glymur/test/fixtures.py | 16 ++++++++++++++++ glymur/test/test_jp2box_uuid.py | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 68c0706..9c543bf 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -254,3 +254,19 @@ nemo_xmp_box = """UUID Box (uuid) @ (77, 3146) """ + +SimpleRDF = """ + + + Simple value + + + + Suse + Fedora + + + + +""" diff --git a/glymur/test/test_jp2box_uuid.py b/glymur/test/test_jp2box_uuid.py index e8cb7be..c17a6ab 100644 --- a/glymur/test/test_jp2box_uuid.py +++ b/glymur/test/test_jp2box_uuid.py @@ -12,9 +12,11 @@ import os import re +import shutil import struct import sys import tempfile +import uuid import warnings from xml.etree import cElementTree as ET @@ -35,9 +37,38 @@ else: import glymur from glymur import Jp2k -from .fixtures import OPJ_DATA_ROOT, opj_data_file, nemo_xmp_box +from .fixtures import OPJ_DATA_ROOT, opj_data_file, SimpleRDF +class TestUUIDXMP(unittest.TestCase): + """Tests for UUIDs of XMP type.""" + + def setUp(self): + self.jp2file = glymur.data.nemo() + + def tearDown(self): + pass + + def test_append(self): + """Should be able to append an XMP UUID box.""" + the_uuid = uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac') + raw_data = SimpleRDF.encode('utf-8') + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + shutil.copyfile(self.jp2file, tfile.name) + jp2 = Jp2k(tfile.name) + ubox = glymur.jp2box.UUIDBox(the_uuid=the_uuid, raw_data=raw_data) + jp2.append(ubox) + + # Should be two UUID boxes now. + expected_ids = ['jP ', 'ftyp', 'jp2h', 'uuid', 'jp2c', 'uuid'] + actual_ids = [b.box_id for b in jp2.box] + self.assertEqual(actual_ids, expected_ids) + + # The data should be an XMP packet, which gets interpreted as + # an ElementTree. + self.assertTrue(isinstance(jp2.box[-1].data.packet, + ET.ElementTree)) + class TestUUIDExif(unittest.TestCase): """Tests for UUIDs of Exif type.""" From 32760a6ecc6322ecd746a15b5adbbad73f8608aa Mon Sep 17 00:00:00 2001 From: jevans Date: Sun, 27 Oct 2013 22:09:15 -0400 Subject: [PATCH 024/326] Doc updates for writing XMP UUIDs. #104 --- docs/source/how_do_i.rst | 84 ++++++++++++++++++++++-------------- docs/source/introduction.rst | 12 +++--- 2 files changed, 56 insertions(+), 40 deletions(-) diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index 212862d..443e8db 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -4,7 +4,7 @@ How do I...? ... read the lowest resolution thumbnail? -===================================== +========================================= Printing the Jp2k object should reveal the number of resolutions (look in the COD segment section), but you can take a shortcut by supplying -1 as the resolution level. :: @@ -15,7 +15,7 @@ resolution level. :: >>> thumbnail = j.read(rlevel=-1) ... display metadata? -================= +===================== There are two ways. From the unix command line, the script *jp2dump* is available. :: @@ -35,7 +35,7 @@ codestream box, only the main header is printed. It is possible to print >>> print(j.get_codestream()) ... add XML metadata? -================= +===================== You can append any number of XML boxes to a JP2 file (not to a raw codestream). Consider the following XML file `data.xml` : :: @@ -66,12 +66,12 @@ The **append** method can add an XML box as shown below:: >>> jp2.append(xmlbox) >>> print(jp2) -... add metadata in a more general fashion? -======================================= +... add even more metadata? +=========================== An existing raw codestream (or JP2 file) can be wrapped (re-wrapped) in a user-defined set of JP2 boxes. To get just a minimal JP2 jacket on the codestream provided by `goodstuff.j2k` (a file consisting of a raw codestream), -you can use the **wrap** method with no box argument: :: +you can use the :py:meth:`wrap` method with no box argument: :: >>> import glymur >>> jfile = glymur.data.goodstuff() @@ -108,8 +108,9 @@ JP2 header superbox. XML boxes are not in the minimal set of box requirements for the JP2 format, so in order to add an XML box into the mix before the codestream box, we'll need to -re-specify all of the boxes. If you already have a JP2 jacket in place, you can just reuse that, -though. Take the following example content in an XML file `favorites.xml` : :: +re-specify all of the boxes. If you already have a JP2 jacket in place, +you can just reuse that, though. Take the following example content in +an XML file `favorites.xml` : :: @@ -152,13 +153,13 @@ the following will work. :: . (truncated) . -As to the question of which method you should use, **append** or **wrap**, -to add metadata, you should keep in mind that **wrap** produces a new JP2 file, -while **append** modifies an existing file and is currently limited to XML -boxes. +As to the question of which method you should use, :py:meth:`append` or +:py:meth:`wrap`, to add metadata, you should keep in mind that :py:meth:`wrap` +produces a new JP2 file, while :py:meth:`append` modifies an existing file and +is currently limited to XML and UUID boxes. ... create an image with an alpha layer? -==================================== +======================================== OpenJPEG can create JP2 files with more than 3 components (requires the development version of OpenJPEG), but by default, any extra components are @@ -219,32 +220,49 @@ Here's how the Preview application on the mac shows the RGBA image. .. image:: goodstuff_alpha.png -work with XMP UUIDs? -==================== +... work with XMP UUIDs? +======================== The example JP2 file shipped with glymur has an XMP UUID. :: >>> import glymur >>> j = glymur.Jp2k(glymur.data.nemo()) - >>> print(j.box[4]) - UUID Box (uuid) @ (715, 2412) - UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) - UUID Data: - + >>> print(j.box[3]) # formatting added to the XML below + - - - + . + . + . + -Since the UUID data in this case is returned as an ElementTree instance, one can -use ElementTree to access the data. For example, to extract the -**CreatorTool** attribute value, the following would work:: +Since the UUID data in this case is returned as an ElementTree instance, +one can use ElementTree from the standard library to access the data. +For example, to extract the **CreatorTool** attribute value, the following +would work:: - >>> xmp = j.box[4].data - >>> ns0 = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}' - >>> ns1 = '{http://ns.adobe.com/xap/1.0/}' - >>> name = '{0}RDF/{0}Description'.format(ns0) + >>> xmp = j.box[3].data.packet + >>> rdf = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}' + >>> ns2 = '{http://ns.adobe.com/xap/1.0/}' + >>> name = '{0}RDF/{0}Description/{1}CreatorTool'.format(rdf, ns2) >>> elt = xmp.find(name) >>> elt - - >>> elt.attrib['{0}CreatorTool'.format(ns1)] - 'glymur' + + >>> elt.text + 'Google' + +Yes, that's painful. A better solution is to install the Python XMP Toolkit +(developer branch):: + + >>> from libxmp import XMPMeta + >>> from libxmp.consts import XMP_NS_XMP as NS_XAP + >>> meta = XMPMeta() + >>> meta.parse_from_str(j.box[3].raw_data.decode('utf-8')) + >>> meta.get_property(NS_XAP, 'CreatorTool') + 'Google' + diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index e1a3b3b..0a88902 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -7,11 +7,9 @@ which allows one to read and write JPEG 2000 files from within Python. Glymur supports both reading and writing of JPEG 2000 images, but writing JPEG 2000 images is currently limited to images that can fit in memory -Of particular focus is retrieval of metadata. Reading Exif UUIDs is supported, -as is reading XMP UUIDs as the XMP data packet is just XML. There is -some very limited support for reading JPX metadata. For instance, -**asoc** and **labl** boxes are recognized, so GMLJP2 metadata can -be retrieved from such JPX files. +In regards to metadata, most JP2 boxes are properly interpreted. +Certain optional JP2 boxes can also be written, including XML boxes and +XMP UUIDs. There is some very limited support for reading JPX metadata. Glymur works on Python 2.6, 2.7, and 3.3. @@ -20,8 +18,8 @@ OpenJPEG Installation Glymur will read JPEG 2000 images with versions 1.3, 1.4, 1.5, 2.0, and the trunk/development version of OpenJPEG. Writing images is only supported with the 1.5 or better, however, and the trunk/development -version is strongly recommended. For more information about OpenJPEG, -please consult http://www.openjpeg.org. +version of OpenJPEG is strongly recommended. For more information about +OpenJPEG, please consult http://www.openjpeg.org. If you use MacPorts or if you have a sufficiently recent version of Linux, your package manager should already provide you with a version of From d41f019c590ab21857f553429cea8a24beb7c577 Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 29 Oct 2013 19:41:59 -0400 Subject: [PATCH 025/326] Removed class-0 conformance tests. Closes #139 Keeps us in sync with upstream openjpeg. See r2350. --- glymur/test/test_opj_suite.py | 299 +--------------------------------- 1 file changed, 4 insertions(+), 295 deletions(-) diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 37d6b73..97b68e1 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -62,266 +62,6 @@ class TestSuite(unittest.TestCase): def tearDown(self): pass - def test_ETS_C0P0_p0_01_j2k(self): - jfile = opj_data_file('input/conformance/p0_01.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c0p0_01.pgx') - pgxdata = read_pgx(pgxfile) - np.testing.assert_array_equal(jpdata, pgxdata) - - def test_ETS_C0P0_p0_02_j2k(self): - jfile = opj_data_file('input/conformance/p0_02.j2k') - jp2k = Jp2k(jfile) - with warnings.catch_warnings(): - # Invalid marker ID. - warnings.simplefilter("ignore") - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c0p0_02.pgx') - pgxdata = read_pgx(pgxfile) - np.testing.assert_array_equal(jpdata, pgxdata) - - @unittest.skip("Known failure in OPENJPEG test suite.") - def test_ETS_C0P0_p0_03_j2k(self): - jfile = opj_data_file('input/conformance/p0_03.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c0p0_03r0.pgx') - pgxdata = read_pgx(pgxfile) - np.testing.assert_array_equal(jpdata, pgxdata) - - def test_ETS_C0P0_p0_03_j2k_r1(self): - jfile = opj_data_file('input/conformance/p0_03.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=1) - - pgxfile = opj_data_file('baseline/conformance/c0p0_03r1.pgx') - pgxdata = read_pgx(pgxfile) - np.testing.assert_array_equal(jpdata, pgxdata) - - @unittest.skip("Known failure in OPENJPEG test suite.") - def test_ETS_C0P0_p0_04_j2k(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=3) - - pgxfile = opj_data_file('baseline/conformance/c0p0_04.pgx') - pgxdata = read_pgx(pgxfile) - - self.assertTrue(peak_tolerance(jpdata[:, :, 2], pgxdata) < 33) - self.assertTrue(mse(jpdata[:, :, 2], pgxdata) < 55.8) - - @unittest.skip("Known failure in OPENJPEG test suite.") - def test_ETS_C0P0_p0_07_j2k(self): - jfile = opj_data_file('input/conformance/p0_07.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read() - - pgxfile = opj_data_file('baseline/conformance/c0p0_07.pgx') - pgxdata = read_pgx(pgxfile) - - self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 10) - self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 0.34) - - @unittest.skip("8-bit pgx data vs 12-bit j2k data") - def test_ETS_C0P0_p0_08_j2k(self): - jfile = opj_data_file('input/conformance/p0_08.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=5) - - pgxfile = opj_data_file('baseline/conformance/c0p0_08.pgx') - pgxdata = read_pgx(pgxfile) - - self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 7) - self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 6.72) - - def test_ETS_C0P0_p0_09_j2k(self): - jfile = opj_data_file('input/conformance/p0_09.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=2) - - pgxfile = opj_data_file('baseline/conformance/c0p0_09.pgx') - pgxdata = read_pgx(pgxfile) - - self.assertTrue(peak_tolerance(jpdata, pgxdata) < 4) - self.assertTrue(mse(jpdata, pgxdata) < 1.47) - - @unittest.skip("Known failure in OPENJPEG test suite.") - def test_ETS_C0P0_p0_10_j2k(self): - jfile = opj_data_file('input/conformance/p0_10.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c0p0_10.pgx') - pgxdata = read_pgx(pgxfile) - - self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 10) - self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 2.84) - - def test_ETS_C0P0_p0_11_j2k(self): - jfile = opj_data_file('input/conformance/p0_11.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c0p0_11.pgx') - pgxdata = read_pgx(pgxfile) - - np.testing.assert_array_equal(jpdata, pgxdata) - - @unittest.skip("fprintf stderr output in r2343.") - def test_ETS_C0P0_p0_12_j2k(self): - jfile = opj_data_file('input/conformance/p0_12.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c0p0_12.pgx') - pgxdata = read_pgx(pgxfile) - - np.testing.assert_array_equal(jpdata, pgxdata) - - @unittest.skip("Known failure in OPENJPEG test suite.") - def test_ETS_C0P0_p0_13_j2k(self): - jfile = opj_data_file('input/conformance/p0_13.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c0p0_13.pgx') - pgxdata = read_pgx(pgxfile) - - np.testing.assert_array_equal(jpdata[:, :, 0], pgxdata) - - @unittest.skip("Known failure in OPENJPEG test suite.") - def test_ETS_C0P0_p0_14_j2k(self): - jfile = opj_data_file('input/conformance/p0_14.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=2) - - pgxfile = opj_data_file('baseline/conformance/c0p0_14.pgx') - pgxdata = read_pgx(pgxfile) - - np.testing.assert_array_equal(jpdata[:, :, 0], pgxdata) - - @unittest.skip("Known failure in OPENJPEG test suite.") - def test_ETS_C0P0_p0_15_j2k(self): - jfile = opj_data_file('input/conformance/p0_15.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c0p0_15r0.pgx') - pgxdata = read_pgx(pgxfile) - - np.testing.assert_array_equal(jpdata, pgxdata) - - def test_ETS_C0P0_p0_15_j2k_r1(self): - jfile = opj_data_file('input/conformance/p0_15.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=1) - - pgxfile = opj_data_file('baseline/conformance/c0p0_15r1.pgx') - pgxdata = read_pgx(pgxfile) - - np.testing.assert_array_equal(jpdata, pgxdata) - - def test_ETS_C0P0_p0_16_j2k(self): - jfile = opj_data_file('input/conformance/p0_16.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c0p0_16.pgx') - pgxdata = read_pgx(pgxfile) - - np.testing.assert_array_equal(jpdata, pgxdata) - - def test_ETS_C0P1_p1_01_j2k(self): - jfile = opj_data_file('input/conformance/p1_01.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c0p1_01.pgx') - pgxdata = read_pgx(pgxfile) - - np.testing.assert_array_equal(jpdata, pgxdata) - - @unittest.skip("Known failure in OPENJPEG test suite operation.") - def test_ETS_C0P1_p1_02_j2k(self): - jfile = opj_data_file('input/conformance/p1_02.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=3) - - pgxfile = opj_data_file('baseline/conformance/c0p1_02.pgx') - pgxdata = read_pgx(pgxfile) - - self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 35) - self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 74) - - @unittest.skip("Known failure in OPENJPEG test suite operation.") - def test_ETS_C0P1_p1_04_j2k(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c0p1_04r0.pgx') - pgxdata = read_pgx(pgxfile) - - print(peak_tolerance(jpdata, pgxdata)) - self.assertTrue(peak_tolerance(jpdata, pgxdata) < 2) - self.assertTrue(mse(jpdata, pgxdata) < 0.55) - - @unittest.skip("Known failure in OPENJPEG test suite, precision issue.") - def test_ETS_C0P1_p1_04_j2k_r3(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=3) - - pgxfile = opj_data_file('baseline/conformance/c0p1_04r3.pgx') - pgxdata = read_pgx(pgxfile) - - print(peak_tolerance(jpdata, pgxdata)) - self.assertTrue(peak_tolerance(jpdata, pgxdata) < 2) - self.assertTrue(mse(jpdata, pgxdata) < 0.55) - - @unittest.skip("Known failure in OPENJPEG test suite operation.") - def test_ETS_C0P1_p1_05_j2k(self): - jfile = opj_data_file('input/conformance/p1_05.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=4) - - pgxfile = opj_data_file('baseline/conformance/c0p1_05.pgx') - pgxdata = read_pgx(pgxfile) - - print(peak_tolerance(jpdata[:, :, 0], pgxdata)) - print(peak_tolerance(jpdata[:, :, 1], pgxdata)) - self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 128) - self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 16384) - - @unittest.skip("Known failure in OPENJPEG test suite operation.") - def test_ETS_C0P1_p1_06_j2k(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=1) - - pgxfile = opj_data_file('baseline/conformance/c0p1_06.pgx') - pgxdata = read_pgx(pgxfile) - - print(peak_tolerance(jpdata[:, :, 0], pgxdata)) - print(peak_tolerance(jpdata[:, :, 1], pgxdata)) - self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 128) - self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 16384) - - @unittest.skip("fprintf stderr output in r2345.") - def test_ETS_C0P1_p1_07_j2k(self): - jfile = opj_data_file('input/conformance/p1_07.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read_bands(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c0p1_07.pgx') - pgxdata = read_pgx(pgxfile) - - np.testing.assert_array_equal(jpdata[0], pgxdata) - def test_ETS_C1P0_p0_01_j2k(self): jfile = opj_data_file('input/conformance/p0_01.j2k') jp2k = Jp2k(jfile) @@ -332,10 +72,13 @@ class TestSuite(unittest.TestCase): np.testing.assert_array_equal(jpdata, pgxdata) + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") def test_ETS_C1P0_p0_02_j2k(self): jfile = opj_data_file('input/conformance/p0_02.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) + with self.assertWarns(UserWarning): + jpdata = jp2k.read(rlevel=0) pgxfile = opj_data_file('baseline/conformance/c1p0_02_0.pgx') pgxdata = read_pgx(pgxfile) @@ -6728,40 +6471,6 @@ class TestSuite_bands(unittest.TestCase): def tearDown(self): pass - def test_ETS_C0P0_p0_05_j2k(self): - jfile = opj_data_file('input/conformance/p0_05.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read_bands(rlevel=3) - - pgxfile = opj_data_file('baseline/conformance/c0p0_05.pgx') - pgxdata = read_pgx(pgxfile) - self.assertTrue(peak_tolerance(jpdata[0], pgxdata) < 54) - self.assertTrue(mse(jpdata[0], pgxdata) < 68) - - @unittest.skip("8-bit pgx data vs 12-bit j2k data") - def test_ETS_C0P0_p0_06_j2k(self): - jfile = opj_data_file('input/conformance/p0_06.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read_bands(rlevel=3) - - pgxfile = opj_data_file('baseline/conformance/c0p0_06.pgx') - pgxdata = read_pgx(pgxfile) - tol = peak_tolerance(jpdata[0], pgxdata) - self.assertTrue(tol < 109) - m = mse(jpdata[0], pgxdata) - self.assertTrue(m < 743) - - def test_ETS_C0P1_p1_03_j2k(self): - jfile = opj_data_file('input/conformance/p1_03.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read_bands(rlevel=3) - - pgxfile = opj_data_file('baseline/conformance/c0p1_03.pgx') - pgxdata = read_pgx(pgxfile) - - self.assertTrue(peak_tolerance(jpdata[0], pgxdata) < 28) - self.assertTrue(mse(jpdata[0], pgxdata) < 18.8) - def test_ETS_C1P1_p1_03_j2k(self): jfile = opj_data_file('input/conformance/p1_03.j2k') jp2k = Jp2k(jfile) From fb63b8956ee48e74b50f64e4116dc02efe08545e Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 29 Oct 2013 19:52:00 -0400 Subject: [PATCH 026/326] Changelog update --- CHANGES.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 5e62c1c..8441cae 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +Oct 29, 2013 - v0.5.10 Palette box now a 2D numpy array instead of a list of + 1D arrays. + Oct 29, 2013 - v0.5.9 Fixed bad library load on linux as a result of 0.5.8 Oct 29, 2013 - v0.5.8 Fixed unnecessary warnings when default locations for From 2465a212a138e29a3eb470afbf1363e576c82f9e Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 6 Nov 2013 20:33:44 -0500 Subject: [PATCH 027/326] Fixed too-short underlines. --- docs/source/how_do_i.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index 212862d..f34264e 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -4,7 +4,7 @@ How do I...? ... read the lowest resolution thumbnail? -===================================== +========================================= Printing the Jp2k object should reveal the number of resolutions (look in the COD segment section), but you can take a shortcut by supplying -1 as the resolution level. :: @@ -15,7 +15,7 @@ resolution level. :: >>> thumbnail = j.read(rlevel=-1) ... display metadata? -================= +===================== There are two ways. From the unix command line, the script *jp2dump* is available. :: @@ -35,7 +35,7 @@ codestream box, only the main header is printed. It is possible to print >>> print(j.get_codestream()) ... add XML metadata? -================= +===================== You can append any number of XML boxes to a JP2 file (not to a raw codestream). Consider the following XML file `data.xml` : :: @@ -67,7 +67,7 @@ The **append** method can add an XML box as shown below:: >>> print(jp2) ... add metadata in a more general fashion? -======================================= +=========================================== An existing raw codestream (or JP2 file) can be wrapped (re-wrapped) in a user-defined set of JP2 boxes. To get just a minimal JP2 jacket on the codestream provided by `goodstuff.j2k` (a file consisting of a raw codestream), @@ -158,7 +158,7 @@ while **append** modifies an existing file and is currently limited to XML boxes. ... create an image with an alpha layer? -==================================== +======================================== OpenJPEG can create JP2 files with more than 3 components (requires the development version of OpenJPEG), but by default, any extra components are From e471f25a6e0f84aee41534b08f55de11cbbd4ab9 Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 6 Nov 2013 20:34:03 -0500 Subject: [PATCH 028/326] Updated for upstream openjpeg r2354. #139 --- docs/source/detailed_installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index 3e4d9dc..92810b7 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -13,7 +13,7 @@ both read and write JPEG 2000 files, but you may wish to install version 2.0 or the 2.0+ version from OpenJPEG's development trunk for better performance. If you do that, you should compile it as a shared library (named *openjp2* instead of *openjpeg*) from the developmental source that you can retrieve -via subversion. As of this time of writing, svn revision 2347 works. +via subversion. As of this time of writing, svn revision r2354 works. You should also download the test data for the purpose of configuring and running OpenJPEG's test suite, check their instructions for all this. You should set the **OPJ_DATA_ROOT** environment variable for the purpose From 9e87780ed3b2b1e6802c7fcfae60207c697717df Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 14 Nov 2013 20:07:15 -0500 Subject: [PATCH 029/326] Restored ssiz attribute to SIZsegment. #140 --- CHANGES.txt | 3 +-- glymur/codestream.py | 11 ++++++++++- glymur/test/test_codestream.py | 11 +++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2dff6f3..dd11c27 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,5 @@ -Oct 29, 2013 - Palette box now a 2D numpy array instead of a list of +Nov 11, 2013 - Palette box now a 2D numpy array instead of a list of 1D arrays. Super box constructors now take optional box list argument. - Removed ssiz attribute from SIZsegment class. Oct 29, 2013 - v0.5.9 Fixed bad library load on linux as a result of 0.5.8 diff --git a/glymur/codestream.py b/glymur/codestream.py index d01795f..4274b61 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -662,7 +662,7 @@ class Codestream(object): component_buffer) bitdepth = tuple(((x & 0x7f) + 1) for x in data[0::3]) - signed = tuple(((x & 0xb0) > 0) for x in data[0::3]) + signed = tuple(((x & 0x80) > 0) for x in data[0::3]) xrsiz = data[1::3] yrsiz = data[2::3] @@ -1506,6 +1506,15 @@ class SIZsegment(Segment): self.signed = signed self.xrsiz, self.yrsiz = xyrsiz + # ssiz attribute to be removed in 1.0.0 + lst = [] + for bitdepth, signed in zip(self.bitdepth, self.signed): + if signed: + lst.append((bitdepth - 1) & 0x80) + else: + lst.append(bitdepth - 1) + self.ssiz = tuple(lst) + num_tiles_x = (self.xsiz - self.xosiz) / (self.xtsiz - self.xtosiz) num_tiles_y = (self.ysiz - self.yosiz) / (self.ytsiz - self.ytosiz) numtiles = math.ceil(num_tiles_x) * math.ceil(num_tiles_y) diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py index 76042b9..39e3b16 100644 --- a/glymur/test/test_codestream.py +++ b/glymur/test/test_codestream.py @@ -117,6 +117,17 @@ class TestCodestream(unittest.TestCase): self.assertEqual(codestream.segment[-1].marker_id, 'EOC') + def test_siz_segment_ssiz(self): + """ssiz attribute to be removed in future release""" + j = Jp2k(self.jp2file) + codestream = j.get_codestream() + + # The ssiz attribute was simply a tuple of raw bytes. + # The first 7 bits are interpreted as the bitdepth, the MSB determines + # whether or not it is signed. + self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7)) + + class TestCodestreamRepr(unittest.TestCase): def setUp(self): From 09d96c17693048b0ec8b6610cd5f4f4247cb3075 Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 14 Nov 2013 20:53:29 -0500 Subject: [PATCH 030/326] Handling the case of signed data as well. #140. --- glymur/codestream.py | 2 +- glymur/test/test_codestream.py | 24 +++++++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/glymur/codestream.py b/glymur/codestream.py index 4274b61..fc94d32 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -1510,7 +1510,7 @@ class SIZsegment(Segment): lst = [] for bitdepth, signed in zip(self.bitdepth, self.signed): if signed: - lst.append((bitdepth - 1) & 0x80) + lst.append((bitdepth - 1) | 0x80) else: lst.append(bitdepth - 1) self.ssiz = tuple(lst) diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py index 39e3b16..768bf7f 100644 --- a/glymur/test/test_codestream.py +++ b/glymur/test/test_codestream.py @@ -32,8 +32,6 @@ except: raise -@unittest.skipIf(DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") class TestCodestream(unittest.TestCase): """Test suite for unusual codestream cases.""" @@ -43,6 +41,8 @@ class TestCodestream(unittest.TestCase): def tearDown(self): pass + @unittest.skipIf(DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_reserved_marker_segment(self): """Reserved marker segments are ok.""" @@ -74,6 +74,8 @@ class TestCodestream(unittest.TestCase): self.assertEqual(codestream.segment[2].length, 3) self.assertEqual(codestream.segment[2].data, b'\x00') + @unittest.skipIf(DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(sys.hexversion < 0x03020000, "Uses features introduced in 3.2.") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @@ -105,6 +107,8 @@ class TestCodestream(unittest.TestCase): self.assertEqual(codestream.segment[2].length, 3) self.assertEqual(codestream.segment[2].data, b'\x00') + @unittest.skipIf(DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") def test_psot_is_zero(self): """Psot=0 in SOT is perfectly legal. Issue #78.""" filename = os.path.join(DATA_ROOT, @@ -117,7 +121,7 @@ class TestCodestream(unittest.TestCase): self.assertEqual(codestream.segment[-1].marker_id, 'EOC') - def test_siz_segment_ssiz(self): + def test_siz_segment_ssiz_unsigned(self): """ssiz attribute to be removed in future release""" j = Jp2k(self.jp2file) codestream = j.get_codestream() @@ -128,6 +132,20 @@ class TestCodestream(unittest.TestCase): self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7)) + @unittest.skipIf(DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_siz_segment_ssiz_signed(self): + """ssiz attribute to be removed in future release""" + filename = os.path.join(DATA_ROOT, 'input/conformance/p0_03.j2k') + j = Jp2k(filename) + codestream = j.get_codestream() + + # The ssiz attribute was simply a tuple of raw bytes. + # The first 7 bits are interpreted as the bitdepth, the MSB determines + # whether or not it is signed. + self.assertEqual(codestream.segment[1].ssiz, (131,)) + + class TestCodestreamRepr(unittest.TestCase): def setUp(self): From ffe17f12cbb9545e0d5abcc4f57ffe48dd6ebdba Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 23 Jan 2014 21:50:48 -0500 Subject: [PATCH 031/326] Introducing python-xmp-toolkit requirement. #104 Down to 3 failures and 1 error. --- glymur/_uuid_io/Exif.py | 83 +++++++-------------------------- glymur/_uuid_io/XMP.py | 46 ------------------ glymur/_uuid_io/__init__.py | 6 +-- glymur/_uuid_io/generic.py | 27 ----------- glymur/jp2box.py | 77 ++++++++++++++---------------- glymur/jp2k.py | 4 +- glymur/test/test_jp2box_uuid.py | 5 +- glymur/test/test_jp2k.py | 12 ++--- setup.py | 2 +- 9 files changed, 67 insertions(+), 195 deletions(-) delete mode 100644 glymur/_uuid_io/XMP.py delete mode 100644 glymur/_uuid_io/generic.py diff --git a/glymur/_uuid_io/Exif.py b/glymur/_uuid_io/Exif.py index fb5e2a1..c1b30f0 100644 --- a/glymur/_uuid_io/Exif.py +++ b/glymur/_uuid_io/Exif.py @@ -13,76 +13,27 @@ if sys.hexversion < 0x02070000: else: from collections import OrderedDict -class UUIDExif(object): +def tiff_header(read_buffer): """ - Attributes - ---------- - read_buffer : bytes - Raw byte stream consisting of the UUID data. - endian : str - Either '<' for big-endian, or '>' for little-endian. """ + # Ignore the first six bytes. + # Next 8 should be (73, 73, 42, 8) or (77, 77, 42, 8) + data = struct.unpack('JP2': - self.data = _uuid_io.UUIDExif(raw_data) - self._type = 'Exif' - else: - self.data = _uuid_io.UUIDGeneric(raw_data) - self._type = 'unknown' - except Exception: - # In case of any exception, create the generic UUID. - self.data = _uuid_io.UUIDGeneric(raw_data) - self._type = 'unknown' - msg = "Error encountered during UUID processing, " - msg += "the UUID will be treated as generic.\n\n{0}" - warnings.warn(msg.format(traceback.format_exc())) - - self.raw_data = raw_data - self.length = length self.offset = offset + self.data = None + + try: + self._parse_raw_data() + except Exception as e: + warnings.warn(str(e)) + + def _parse_raw_data(self): + """ + Private function for parsing UUID payloads if possible. + """ + if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): + xmp = XMPMeta() + xmp.parse_from_str(self.raw_data.decode('utf-8'), + xmpmeta_wrap=False) + self.data = xmp + elif self.uuid.bytes == b'JpgTiffExif->JP2': + self.data = _uuid_io.tiff_header(self.raw_data) + else: + self.data = self.raw_data def __repr__(self): msg = "glymur.jp2box.UUIDBox(the_uuid={0}, " msg += "raw_data=)" - return msg.format(repr(self.uuid), len(self.raw_data)) - + return msg.format(repr(self.uuid), len(self.data)) def __str__(self): msg = '{0}\n' - msg += ' UUID: {1} ({2})\n' - msg += ' UUID Data: {3}' + msg += ' UUID: {1}\n' + msg += ' UUID Data: {2}' - msg = msg.format(Jp2kBox.__str__(self), - self.uuid, - self._type, - str(self.data)) + msg = msg.format(Jp2kBox.__str__(self), self.uuid, str(self.data)) return msg - def write(self, fptr): - """Write a UUID box box to file. + """Write a UUID box to file. """ - if self._type != 'XMP': + if self.uuid != uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): msg = "Only XMP UUID boxes can currently be written." raise NotImplementedError(msg) - serialized = b'' - serialized += ET.tostring(self.data.packet.getroot(), encoding='utf-8') - serialized += b'' - if self.length == 0: - self.length = 24 + len(serialized) - read_buffer = struct.pack('>I4s', self.length, b'uuid') - fptr.write(read_buffer) + write_buffer = struct.pack('>I4s', self.length, b'uuid') + fptr.write(write_buffer) fptr.write(self.uuid.bytes) - fptr.write(serialized) + fptr.write(self.raw_data) @staticmethod def parse(fptr, offset, length): diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 4d7071e..a337ae7 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -20,6 +20,7 @@ import ctypes import math import os import struct +from uuid import UUID import warnings import numpy as np @@ -526,7 +527,8 @@ class Jp2k(Jp2kBox): raise IOError(msg) if not ((box.box_id == 'xml ') or - (box.box_id == 'uuid' and box._type == 'XMP')): + (box.box_id == 'uuid' and + box.uuid == UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'))): msg = "Only XML boxes and XMP UUID boxes can currently be appended." raise IOError(msg) diff --git a/glymur/test/test_jp2box_uuid.py b/glymur/test/test_jp2box_uuid.py index c17a6ab..8f24f74 100644 --- a/glymur/test/test_jp2box_uuid.py +++ b/glymur/test/test_jp2box_uuid.py @@ -35,6 +35,8 @@ if sys.hexversion <= 0x03030000: else: from unittest.mock import patch +from libxmp import XMPMeta + import glymur from glymur import Jp2k from .fixtures import OPJ_DATA_ROOT, opj_data_file, SimpleRDF @@ -66,8 +68,7 @@ class TestUUIDXMP(unittest.TestCase): # The data should be an XMP packet, which gets interpreted as # an ElementTree. - self.assertTrue(isinstance(jp2.box[-1].data.packet, - ET.ElementTree)) + self.assertTrue(isinstance(jp2.box[-1].data, XMPMeta)) class TestUUIDExif(unittest.TestCase): """Tests for UUIDs of Exif type.""" diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index c38cacd..c055aba 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -30,6 +30,8 @@ import warnings import numpy as np import pkg_resources +import libxmp + import glymur from glymur import Jp2k @@ -362,13 +364,9 @@ class TestJp2k(unittest.TestCase): def test_xmp_attribute(self): """Verify the XMP packet in the shipping example file can be read.""" j = Jp2k(self.jp2file) - xmp = j.box[3].data.packet - ns0 = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}' - ns2 = '{http://ns.adobe.com/xap/1.0/}' - name = '{0}RDF/{0}Description/{1}CreatorTool'.format(ns0, ns2) - elt = xmp.find(name) - self.assertEqual(elt.text, 'Google') - + xmp = j.box[3].data + creator_tool = xmp.get_property(libxmp.consts.XMP_NS_XMP, 'CreatorTool') + self.assertEqual(creator_tool, 'Google') @unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version), "Requires at least version 1.5") diff --git a/setup.py b/setup.py index b780e4d..b4fdc2f 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ kwargs = {'name': 'Glymur', 'license': 'MIT', 'test_suite': 'glymur.test'} -instllrqrs = ['numpy>=1.4.1'] +instllrqrs = ['numpy>=1.4.1', 'python-xmp-toolkit>=2.0.0'] if sys.hexversion < 0x03030000: instllrqrs.append('contextlib2>=0.4') instllrqrs.append('mock>=1.0.1') From 553b36d40ee1bf3a043db39bdc4f68fa902c7565 Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 24 Jan 2014 11:08:23 -0500 Subject: [PATCH 032/326] Passing on Anaconda platform with python-xmp-toolkit 2.0. #104 --- glymur/_uuid_io/Exif.py | 32 ++++++ glymur/_uuid_io/__init__.py | 2 +- glymur/jp2box.py | 29 +++--- glymur/test/fixtures.py | 174 +++++++++++++++++--------------- glymur/test/test_jp2box_uuid.py | 2 +- 5 files changed, 138 insertions(+), 101 deletions(-) diff --git a/glymur/_uuid_io/Exif.py b/glymur/_uuid_io/Exif.py index c1b30f0..a7c04aa 100644 --- a/glymur/_uuid_io/Exif.py +++ b/glymur/_uuid_io/Exif.py @@ -3,6 +3,7 @@ Handlers for Exif UUIDs. Be nice if we would find a standard for this. """ import pprint +import re import struct import sys import warnings @@ -13,8 +14,39 @@ if sys.hexversion < 0x02070000: else: from collections import OrderedDict +# The Python XMP Toolkit may be used for XMP UUIDs, but only if available and +# if the version is at least 2.0.0. +try: + import libxmp + if hasattr(libxmp, 'version') and re.match('[2-9].\d*.\d*', libxmp.version.VERSION): + from libxmp import XMPMeta + _HAS_PYTHON_XMP_TOOLKIT = True + else: + _HAS_PYTHON_XMP_TOOLKIT = False +except ImportError: + _HAS_PYTHON_XMP_TOOLKIT = False + +def xmp(read_buffer): + """ + If libxmp 2.0+ is installed, use it to describe the XMP data. + """ + if not _HAS_PYTHON_XMP_TOOLKIT: + # If the python xmp toolkit is not available or is not advanced enough, + # then issue a warning and just make available the raw data. + msg = "An XMP UUID was detected, but the Python XMP Toolkit package " + msg += "is either not available or is too old (must be at least 2.0). " + msg += "The UUID data field will consist only of the raw uninterpreted " + msg += "bytes." + warnings.warn(msg, UserWarning) + return read_buffer + + xmp = XMPMeta() + xmp.parse_from_str(read_buffer.decode('utf-8'), xmpmeta_wrap=False) + return xmp + def tiff_header(read_buffer): """ + Interpret the UUID data as a TIFF header. """ # Ignore the first six bytes. # Next 8 should be (73, 73, 42, 8) or (77, 77, 42, 8) diff --git a/glymur/_uuid_io/__init__.py b/glymur/_uuid_io/__init__.py index d12d20c..89bbf21 100644 --- a/glymur/_uuid_io/__init__.py +++ b/glymur/_uuid_io/__init__.py @@ -1,4 +1,4 @@ """ Sub package for handling various types of UUIDs. """ -from .Exif import tiff_header +from .Exif import tiff_header, xmp diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 300ac12..8b0f6f3 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -33,12 +33,6 @@ else: import numpy as np -try: - from libxmp import XMPMeta - _HAS_PYTHON_XMP_TOOLKIT = True -except ImportError: - _HAS_PYTHON_XMP_TOOLKIT = False - from .codestream import Codestream from .core import _COLORSPACE_MAP_DISPLAY from .core import _COLOR_TYPE_MAP_DISPLAY @@ -2228,10 +2222,7 @@ class UUIDBox(Jp2kBox): Private function for parsing UUID payloads if possible. """ if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): - xmp = XMPMeta() - xmp.parse_from_str(self.raw_data.decode('utf-8'), - xmpmeta_wrap=False) - self.data = xmp + self.data = _uuid_io.xmp(self.raw_data) elif self.uuid.bytes == b'JpgTiffExif->JP2': self.data = _uuid_io.tiff_header(self.raw_data) else: @@ -2243,11 +2234,19 @@ class UUIDBox(Jp2kBox): return msg.format(repr(self.uuid), len(self.data)) def __str__(self): - msg = '{0}\n' - msg += ' UUID: {1}\n' - msg += ' UUID Data: {2}' - - msg = msg.format(Jp2kBox.__str__(self), self.uuid, str(self.data)) + if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): + utype = "XMP" + elif self.uuid.bytes == b'JpgTiffExif->JP2': + utype = "EXIF" + else: + utype = "unknown" + msg = '{0}\n UUID: {1} ({2})\n'.format(Jp2kBox.__str__(self), + self.uuid, + utype) + if utype == 'unknown': + msg += ' UUID Data: {0} bytes'.format(len(self.raw_data)) + else: + msg += ' UUID Data: {0}'.format(str(self.data)) return msg diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 9c543bf..a33d1d6 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -170,90 +170,96 @@ def read_pgx_header(pgx_file): nemo_xmp_box = """UUID Box (uuid) @ (77, 3146) UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) - UUID Data: - - - - Google - 2013-02-09T14:47:53 - - - 1 - 72/1 - 72/1 - 2 - HTC - HTC Glacier - 2592 - 1456 - - - 8 - 8 - 8 - - - 2 - 3 - - - 1343036288/4294967295 - 1413044224/4294967295 - - - - - 2748779008/4294967295 - 1417339264/4294967295 - 1288490240/4294967295 - 2576980480/4294967295 - 644245120/4294967295 - 257698032/4294967295 - - - - - 1 - 2528 - 1424 - 353/100 - 0 - 0/1 - WGS-84 - 2013-02-09T14:47:53 - - - 76 - - - 0220 - 0100 - - - 1 - 2 - 3 - 0 - - - 42,20.56N - 71,5.29W - 2013-02-09T19:47:53Z - NETWORK - - - 2013-02-09T14:47:53 - - - - - Glymur - Python XMP Toolkit - - - - - """ + UUID Data: + + + + Google + 2013-02-09T14:47:53 + + + 1 + 72/1 + 72/1 + 2 + HTC + HTC Glacier + 2592 + 1456 + + + 8 + 8 + 8 + + + 2 + 3 + + + 1343036288/4294967295 + 1413044224/4294967295 + + + + + 2748779008/4294967295 + 1417339264/4294967295 + 1288490240/4294967295 + 2576980480/4294967295 + 644245120/4294967295 + 257698032/4294967295 + + + + + 1 + 2528 + 1424 + 353/100 + 0 + 0/1 + WGS-84 + 2013-02-09T14:47:53 + + + 76 + + + 0220 + 0100 + + + 1 + 2 + 3 + 0 + + + 42,20.56N + 71,5.29W + 2013-02-09T19:47:53Z + NETWORK + + + 2013-02-09T14:47:53 + + + + + Glymur + Python XMP Toolkit + + + + + +""" SimpleRDF = """ Date: Fri, 24 Jan 2014 15:35:23 -0500 Subject: [PATCH 033/326] Tests passing on bare 2.6.6 python. #104 --- glymur/test/fixtures.py | 12 ++++++++++++ glymur/test/test_jp2box_uuid.py | 14 ++++++++++---- glymur/test/test_jp2k.py | 9 ++++++--- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index a33d1d6..164f93d 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -11,6 +11,18 @@ import numpy as np import glymur +# The Python XMP Toolkit may be used for XMP UUIDs, but only if available and +# if the version is at least 2.0.0. +try: + import libxmp + if hasattr(libxmp, 'version') and re.match('[2-9].\d*.\d*', libxmp.version.VERSION): + from libxmp import XMPMeta + HAS_PYTHON_XMP_TOOLKIT = True + else: + HAS_PYTHON_XMP_TOOLKIT = False +except ImportError: + HAS_PYTHON_XMP_TOOLKIT = False + # Need to know of the libopenjp2 version is the official 2.0.0 release and NOT # the 2.0+ development version. OPENJP2_IS_V2_OFFICIAL = False diff --git a/glymur/test/test_jp2box_uuid.py b/glymur/test/test_jp2box_uuid.py index 3e7f196..1415777 100644 --- a/glymur/test/test_jp2box_uuid.py +++ b/glymur/test/test_jp2box_uuid.py @@ -35,7 +35,9 @@ if sys.hexversion <= 0x03030000: else: from unittest.mock import patch -from libxmp import XMPMeta +from .fixtures import HAS_PYTHON_XMP_TOOLKIT, OPJ_DATA_ROOT +if HAS_PYTHON_XMP_TOOLKIT: + from libxmp import XMPMeta import glymur from glymur import Jp2k @@ -66,9 +68,13 @@ class TestUUIDXMP(unittest.TestCase): actual_ids = [b.box_id for b in jp2.box] self.assertEqual(actual_ids, expected_ids) - # The data should be an XMP packet, which gets interpreted as - # an ElementTree. - self.assertTrue(isinstance(jp2.box[-1].data, XMPMeta)) + # The data should be an XMP packet + if HAS_PYTHON_XMP_TOOLKIT: + # when python xmp toolkit is available. + self.assertTrue(isinstance(jp2.box[-1].data, XMPMeta)) + else: + # when python xmp toolkit is not available. + self.assertTrue(isinstance(jp2.box[-1].data, str)) class TestUUIDExif(unittest.TestCase): """Tests for UUIDs of Exif type.""" diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index c055aba..cf5596e 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -30,12 +30,13 @@ import warnings import numpy as np import pkg_resources -import libxmp - import glymur from glymur import Jp2k -from .fixtures import OPENJP2_IS_V2_OFFICIAL +from .fixtures import HAS_PYTHON_XMP_TOOLKIT, OPENJP2_IS_V2_OFFICIAL +if HAS_PYTHON_XMP_TOOLKIT: + import libxmp + from .fixtures import OPJ_DATA_ROOT, opj_data_file @@ -361,6 +362,8 @@ class TestJp2k(unittest.TestCase): self.assertEqual(ET.tostring(jp2k.box[3].xml.getroot()), b'this is a test') + @unittest.skipIf(not HAS_PYTHON_XMP_TOOLKIT, + "Requires Python XMP Toolkit >= 2.0") def test_xmp_attribute(self): """Verify the XMP packet in the shipping example file can be read.""" j = Jp2k(self.jp2file) From c8f6aff4d82c8782af745c8f4b5182aae597387c Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 24 Jan 2014 15:56:50 -0500 Subject: [PATCH 034/326] pylint work. #104 --- glymur/_uuid_io/Exif.py | 3 +- glymur/codestream.py | 4 +- glymur/jp2box.py | 29 ++++---- glymur/jp2k.py | 12 ++-- glymur/lib/openjpeg.py | 124 ++++++++++++++++---------------- glymur/lib/test/test_openjp2.py | 16 ++--- glymur/test/fixtures.py | 3 +- glymur/test/test_jp2box.py | 12 ++-- glymur/version.py | 16 +++-- 9 files changed, 111 insertions(+), 108 deletions(-) diff --git a/glymur/_uuid_io/Exif.py b/glymur/_uuid_io/Exif.py index a7c04aa..cc8254e 100644 --- a/glymur/_uuid_io/Exif.py +++ b/glymur/_uuid_io/Exif.py @@ -18,7 +18,8 @@ else: # if the version is at least 2.0.0. try: import libxmp - if hasattr(libxmp, 'version') and re.match('[2-9].\d*.\d*', libxmp.version.VERSION): + if hasattr(libxmp, 'version') and re.match(r"""[2-9].\d*.\d*""", + libxmp.version.VERSION): from libxmp import XMPMeta _HAS_PYTHON_XMP_TOOLKIT = True else: diff --git a/glymur/codestream.py b/glymur/codestream.py index d01795f..67b2d56 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -663,7 +663,7 @@ class Codestream(object): bitdepth = tuple(((x & 0x7f) + 1) for x in data[0::3]) signed = tuple(((x & 0xb0) > 0) for x in data[0::3]) - + xrsiz = data[1::3] yrsiz = data[2::3] @@ -1529,7 +1529,7 @@ class SIZsegment(Segment): signed=self.signed, xyrsiz=(self.xrsiz, self.yrsiz)) return msg - + def __str__(self): msg = Segment.__str__(self) msg += '\n ' diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 8b0f6f3..85555e4 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -19,7 +19,6 @@ import os import pprint import struct import sys -import traceback import uuid import warnings import xml.etree.cElementTree as ET @@ -563,11 +562,11 @@ class CodestreamHeaderBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - def __init__(self, box=[], length=0, offset=-1): + def __init__(self, box=None, length=0, offset=-1): Jp2kBox.__init__(self, box_id='jpch', longname='Codestream Header') self.length = length self.offset = offset - self.box = box + self.box = box if box is not None else [] def __repr__(self): msg = "glymur.jp2box.CodestreamHeaderBox(box={0})".format(self.box) @@ -625,12 +624,12 @@ class CompositingLayerHeaderBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - def __init__(self, box=[], length=0, offset=-1): + def __init__(self, box=None, length=0, offset=-1): Jp2kBox.__init__(self, box_id='jplh', longname='Compositing Layer Header') self.length = length self.offset = offset - self.box = [] + self.box = box if box is not None else [] def __repr__(self): msg = "glymur.jp2box.CompositingLayerHeaderBox(box={0})" @@ -1073,11 +1072,11 @@ class AssociationBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - def __init__(self, box=[], length=0, offset=-1): + def __init__(self, box=None, length=0, offset=-1): Jp2kBox.__init__(self, box_id='asoc', longname='Association') self.length = length self.offset = offset - self.box = box + self.box = box if box is not None else [] def __repr__(self): msg = "glymur.jp2box.AssociationBox(box={0})".format(self.box) @@ -1135,11 +1134,11 @@ class JP2HeaderBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - def __init__(self, box=[], length=0, offset=-1): + def __init__(self, box=None, length=0, offset=-1): Jp2kBox.__init__(self, box_id='jp2h', longname='JP2 Header') self.length = length self.offset = offset - self.box = box + self.box = box if box is not None else [] def __repr__(self): msg = "glymur.jp2box.JP2HeaderBox(box={0})".format(self.box) @@ -1621,11 +1620,11 @@ class ResolutionBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - def __init__(self, box=[], length=0, offset=-1): + def __init__(self, box=None, length=0, offset=-1): Jp2kBox.__init__(self, box_id='res ', longname='Resolution') self.length = length self.offset = offset - self.box = box + self.box = box if box is not None else [] def __repr__(self): msg = "glymur.jp2box.ResolutionBox(box={0})" @@ -2040,11 +2039,11 @@ class UUIDInfoBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - def __init__(self, box=[], length=0, offset=-1): + def __init__(self, box=None, length=0, offset=-1): Jp2kBox.__init__(self, box_id='uinf', longname='UUIDInfo') self.length = length self.offset = offset - self.box = box + self.box = box if box is not None else [] def __repr__(self): msg = "glymur.jp2box.UUIDInfoBox(box={0})".format(self.box) @@ -2214,8 +2213,8 @@ class UUIDBox(Jp2kBox): try: self._parse_raw_data() - except Exception as e: - warnings.warn(str(e)) + except RuntimeError as error: + warnings.warn(str(error)) def _parse_raw_data(self): """ diff --git a/glymur/jp2k.py b/glymur/jp2k.py index a337ae7..4eb8b2a 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -484,17 +484,17 @@ class Jp2k(Jp2kBox): stack.callback(opj2.image_destroy, image) _populate_image_struct(cparams, image, img_array) - + codec = opj2.create_compress(cparams.codec_fmt) stack.callback(opj2.destroy_codec, codec) - + 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) - + 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) @@ -505,11 +505,11 @@ class Jp2k(Jp2kBox): strm = opj2.stream_create_default_file_stream_v3(self.filename, False) stack.callback(opj2.stream_destroy_v3, strm) - + opj2.start_compress(codec, image, strm) opj2.encode(codec, strm) opj2.end_compress(codec, strm) - + # Refresh the metadata. self.parse() diff --git a/glymur/lib/openjpeg.py b/glymur/lib/openjpeg.py index 5b1183f..418e9df 100644 --- a/glymur/lib/openjpeg.py +++ b/glymur/lib/openjpeg.py @@ -11,8 +11,8 @@ import numpy as np from .config import glymur_config _, OPENJPEG = glymur_config() -# Maximum number of tile parts expected by JPWL: increase at your will -JPWL_MAX_NO_TILESPECS = 16 +# Maximum number of tile parts expected by JPWL: increase at your will +JPWL_MAX_NO_TILESPECS = 16 J2K_MAXRLVLS = 33 # Number of maximum resolution level authorized PATH_LEN = 4096 # maximum allowed size for filenames @@ -58,7 +58,7 @@ class CommonStructType(ctypes.Structure): ("mj2_handle", ctypes.c_void_p)] -STREAM_READ = 0x0001 # The stream was opened for reading. +STREAM_READ = 0x0001 # The stream was opened for reading. STREAM_WRITE = 0x0002 # The stream was opened for writing. class CioType(ctypes.Structure): """Byte input-output stream (CIO) @@ -81,7 +81,7 @@ class CioType(ctypes.Structure): class CompressionInfoType(CommonStructType): - """Common fields between JPEG-2000 compression and decompression contexts. + """Common fields between JPEG-2000 compression and decompression contexts. This is for compression contexts. Corresponds to common_struct_t. """ pass @@ -91,68 +91,68 @@ class PocType(ctypes.Structure): """Progression order changes.""" _fields_ = [("resno", ctypes.c_int), # Resolution num start, Component num start, given by POC - ("compno0", ctypes.c_int), + ("compno0", ctypes.c_int), # Layer num end,Resolution num end, Component num end, given by POC - ("layno1", ctypes.c_int), - ("resno1", ctypes.c_int), - ("compno1", ctypes.c_int), + ("layno1", ctypes.c_int), + ("resno1", ctypes.c_int), + ("compno1", ctypes.c_int), - # Layer num start,Precinct num start, Precinct num end - ("layno0", ctypes.c_int), - ("precno0", ctypes.c_int), - ("precno1", ctypes.c_int), + # Layer num start,Precinct num start, Precinct num end + ("layno0", ctypes.c_int), + ("precno0", ctypes.c_int), + ("precno1", ctypes.c_int), # Progression order enum # OPJ_PROG_ORDER prg1,prg; - ("prg1", ctypes.c_int), - ("prg", ctypes.c_int), + ("prg1", ctypes.c_int), + ("prg", ctypes.c_int), - # Progression order string + # Progression order string # char progorder[5]; ("progorder", ctypes.c_char * 5), - # Tile number + # Tile number # int tile; - ("tile", ctypes.c_int), + ("tile", ctypes.c_int), # /** Start and end values for Tile width and height*/ # int tx0,tx1,ty0,ty1; - ("tx0", ctypes.c_int), - ("tx1", ctypes.c_int), - ("ty0", ctypes.c_int), - ("ty1", ctypes.c_int), + ("tx0", ctypes.c_int), + ("tx1", ctypes.c_int), + ("ty0", ctypes.c_int), + ("ty1", ctypes.c_int), # /** Start value, initialised in pi_initialise_encode*/ # int layS, resS, compS, prcS; - ("layS", ctypes.c_int), - ("resS", ctypes.c_int), - ("compS", ctypes.c_int), + ("layS", ctypes.c_int), + ("resS", ctypes.c_int), + ("compS", ctypes.c_int), ("prcS", ctypes.c_int), # /** End value, initialised in pi_initialise_encode */ # int layE, resE, compE, prcE; - ("layE", ctypes.c_int), - ("resE", ctypes.c_int), - ("compE", ctypes.c_int), - ("prcE", ctypes.c_int), + ("layE", ctypes.c_int), + ("resE", ctypes.c_int), + ("compE", ctypes.c_int), + ("prcE", ctypes.c_int), # Start and end values of Tile width and height, initialised in # pi_initialise_encode int txS,txE,tyS,tyE,dx,dy; - ("txS", ctypes.c_int), - ("txE", ctypes.c_int), - ("tyS", ctypes.c_int), - ("tyE", ctypes.c_int), - ("dx", ctypes.c_int), - ("dy", ctypes.c_int), + ("txS", ctypes.c_int), + ("txE", ctypes.c_int), + ("tyS", ctypes.c_int), + ("tyE", ctypes.c_int), + ("dx", ctypes.c_int), + ("dy", ctypes.c_int), - # Temporary values for Tile parts, initialised in pi_create_encode + # Temporary values for Tile parts, initialised in pi_create_encode # int lay_t, res_t, comp_t, prc_t,tx0_t,ty0_t; - ("lay_t", ctypes.c_int), - ("res_t", ctypes.c_int), - ("comp_t", ctypes.c_int), - ("prc_t", ctypes.c_int), - ("tx0_t", ctypes.c_int), + ("lay_t", ctypes.c_int), + ("res_t", ctypes.c_int), + ("comp_t", ctypes.c_int), + ("prc_t", ctypes.c_int), + ("tx0_t", ctypes.c_int), ("ty0_t", ctypes.c_int)] @@ -374,23 +374,23 @@ class DecompressionParametersType(ctypes.Structure): class ImageComptParmType(ctypes.Structure): """Component parameters structure used by the opj_image_create function. """ - _fields_ = [ - # XRsiz: horizontal separation of a sample of ith component with - # respect to the reference grid - ("dx", ctypes.c_int), + _fields_ = [ + # XRsiz: horizontal separation of a sample of ith component with + # respect to the reference grid + ("dx", ctypes.c_int), - # YRsiz: vertical separation of a sample of ith component with + # YRsiz: vertical separation of a sample of ith component with # respect to the reference grid */ - ("dy", ctypes.c_int), - - # data width, height - ("w", ctypes.c_int), - ("h", ctypes.c_int), + ("dy", ctypes.c_int), - # x component offset compared to the whole image - # y component offset compared to the whole image - ("x0", ctypes.c_int), - ("y0", ctypes.c_int), + # data width, height + ("w", ctypes.c_int), + ("h", ctypes.c_int), + + # x component offset compared to the whole image + # y component offset compared to the whole image + ("x0", ctypes.c_int), + ("y0", ctypes.c_int), # precision ('prec', ctypes.c_int), @@ -398,7 +398,7 @@ class ImageComptParmType(ctypes.Structure): # image depth in bits ('bpp', ctypes.c_int), - # signed (1) / unsigned (0) + # signed (1) / unsigned (0) ('sgnd', ctypes.c_int)] @@ -511,7 +511,7 @@ def destroy_compress(cinfo): def encode(cinfo, cio, image): """Wrapper for openjpeg library function opj_encode. - Encodes an image into a JPEG-2000 codestream. + Encodes an image into a JPEG-2000 codestream. Parameters ---------- @@ -540,7 +540,7 @@ def destroy_decompress(dinfo): def image_cmptparm_t_from_np(np_image): """Return appropriate image_cmptparm_t based on given numpy array. """ - try: + try: num_comps = np_image.shape[2] except IndexError: num_comps = 1 @@ -557,17 +557,17 @@ def image_cmptparm_t_from_np(np_image): bpp = 8 sgnd = 1 elif np_image.dtype == np.uint16: - prec = 16 - bpp = 16 + prec = 16 + bpp = 16 sgnd = 0 elif np_image.dtype == np.int16: - prec = 16 - bpp = 16 + prec = 16 + bpp = 16 sgnd = 1 else: raise(TypeError("unhandled")) - for j in range(0, num_comps): + for j in range(0, num_comps): tarr[j].dx = 1 tarr[j].dy = 1 tarr[j].w = np_image.shape[1] diff --git a/glymur/lib/test/test_openjp2.py b/glymur/lib/test/test_openjp2.py index 54d8254..8694507 100644 --- a/glymur/lib/test/test_openjp2.py +++ b/glymur/lib/test/test_openjp2.py @@ -1,5 +1,5 @@ """ -Tests for libopenjp2 wrapping functions. +Tests for libopenjp2 wrapping functions. """ # R0904: Seems like pylint is fooled in this situation # W0142: using kwargs is ok in this context @@ -212,7 +212,7 @@ class TestOpenJP2(unittest.TestCase): """Runs test designated tte3 in OpenJPEG test suite.""" with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: xtx3_setup(tfile.name) - self.assertTrue(True) + self.assertTrue(True) def test_rta3(self): """Runs test designated rta3 in OpenJPEG test suite.""" @@ -221,13 +221,13 @@ class TestOpenJP2(unittest.TestCase): codec_format = openjp2.CODEC_J2K self.j2k_random_tile_access(tfile.name, codec_format) - self.assertTrue(True) + self.assertTrue(True) def test_tte4(self): """Runs test designated tte4 in OpenJPEG test suite.""" with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: xtx4_setup(tfile.name) - self.assertTrue(True) + self.assertTrue(True) def test_rta4(self): """Runs test designated rta4 in OpenJPEG test suite.""" @@ -241,7 +241,7 @@ class TestOpenJP2(unittest.TestCase): """Runs test designated tte5 in OpenJPEG test suite.""" with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: xtx5_setup(tfile.name) - self.assertTrue(True) + self.assertTrue(True) def test_rta5(self): """Runs test designated rta5 in OpenJPEG test suite.""" @@ -332,8 +332,8 @@ def tile_encoder(**kwargs): def tile_decoder(**kwargs): """Fixture called with various configurations by many tests. - - Reads a tile. That's all it does. + + Reads a tile. That's all it does. """ stream = openjp2.stream_create_default_file_stream_v3(kwargs['filename'], True) @@ -355,7 +355,7 @@ def tile_decoder(**kwargs): openjp2.setup_decoder(codec, dparam) image = openjp2.read_header(stream, codec) - openjp2.set_decode_area(codec, image, + openjp2.set_decode_area(codec, image, kwargs['x0'], kwargs['y0'], kwargs['x1'], kwargs['y1']) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 164f93d..6626075 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -15,7 +15,8 @@ import glymur # if the version is at least 2.0.0. try: import libxmp - if hasattr(libxmp, 'version') and re.match('[2-9].\d*.\d*', libxmp.version.VERSION): + if hasattr(libxmp, 'version') and re.match(r'''[2-9].\d*.\d*''', + libxmp.version.VERSION): from libxmp import XMPMeta HAS_PYTHON_XMP_TOOLKIT = True else: diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index b83f86d..f731ff7 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -894,9 +894,9 @@ class TestRepr(unittest.TestCase): tree = ET.ElementTree(elt) box = glymur.jp2box.XMLBox(xml=tree) - regexp = "glymur.jp2box.XMLBox" - regexp += "\(xml=<(xml.etree.ElementTree.){0,1}ElementTree object " - regexp += "at 0x([a-f0-9]*)>\)" + regexp = r"""glymur.jp2box.XMLBox""" + regexp += r"""\(xml=<(xml.etree.ElementTree.){0,1}ElementTree object """ + regexp += """at 0x([a-f0-9]*)>\)""" if sys.hexversion < 0x03000000: self.assertRegexpMatches(repr(box), regexp) @@ -927,9 +927,9 @@ class TestRepr(unittest.TestCase): # Since the raw_data parameter is a sequence of bytes which could be # quite long, don't bother trying to make it conform to eval(repr()). - regexp = "glymur.jp2box.UUIDBox\(" - regexp += "the_uuid=UUID\('00000000-0000-0000-0000-000000000000'\),\s" - regexp += "raw_data=\)" + regexp = r"""glymur.jp2box.UUIDBox\(""" + regexp += """the_uuid=UUID\('00000000-0000-0000-0000-000000000000'\),\s""" + regexp += """raw_data=\)""" if sys.hexversion < 0x03000000: self.assertRegexpMatches(repr(box), regexp) diff --git a/glymur/version.py b/glymur/version.py index af7523e..260f880 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -1,10 +1,12 @@ -# This file is part of glymur, a Python interface for accessing JPEG 2000. -# -# http://glymur.readthedocs.org -# -# Copyright 2013 John Evans -# -# License: MIT +""" +This file is part of glymur, a Python interface for accessing JPEG 2000. + +http://glymur.readthedocs.org + +Copyright 2013 John Evans + +License: MIT +""" import sys import numpy as np From 77b58c1c5f956666816d9b40de746442c102c765 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 25 Jan 2014 15:36:55 -0500 Subject: [PATCH 035/326] No explicit dependence on libxmp anymore. Back to ElementTree. --- glymur/_uuid_io/Exif.py | 14 ++++++++++++++ glymur/_uuid_io/__init__.py | 2 +- glymur/jp2box.py | 28 ++++++++++++---------------- glymur/test/test_jp2box_uuid.py | 7 ++++--- glymur/test/test_jp2k.py | 5 ++++- glymur/test/test_printing.py | 12 ++++-------- 6 files changed, 39 insertions(+), 29 deletions(-) diff --git a/glymur/_uuid_io/Exif.py b/glymur/_uuid_io/Exif.py index c1b30f0..967468d 100644 --- a/glymur/_uuid_io/Exif.py +++ b/glymur/_uuid_io/Exif.py @@ -6,6 +6,7 @@ import pprint import struct import sys import warnings +import xml.etree.cElementTree as ET if sys.hexversion < 0x02070000: # pylint: disable=F0401,E0611 @@ -13,8 +14,21 @@ if sys.hexversion < 0x02070000: else: from collections import OrderedDict +def xml(raw_data): + """ + XMP data to be parsed as XML. + """ + if sys.hexversion < 0x03000000: + elt = ET.fromstring(raw_data) + else: + text = raw_data.decode('utf-8') + elt = ET.fromstring(text) + + return ET.ElementTree(elt) + def tiff_header(read_buffer): """ + Interpret the uuid raw data as a tiff header. """ # Ignore the first six bytes. # Next 8 should be (73, 73, 42, 8) or (77, 77, 42, 8) diff --git a/glymur/_uuid_io/__init__.py b/glymur/_uuid_io/__init__.py index d12d20c..778f912 100644 --- a/glymur/_uuid_io/__init__.py +++ b/glymur/_uuid_io/__init__.py @@ -1,4 +1,4 @@ """ Sub package for handling various types of UUIDs. """ -from .Exif import tiff_header +from .Exif import tiff_header, xml diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 300ac12..da8e1c9 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -33,12 +33,6 @@ else: import numpy as np -try: - from libxmp import XMPMeta - _HAS_PYTHON_XMP_TOOLKIT = True -except ImportError: - _HAS_PYTHON_XMP_TOOLKIT = False - from .codestream import Codestream from .core import _COLORSPACE_MAP_DISPLAY from .core import _COLOR_TYPE_MAP_DISPLAY @@ -2220,18 +2214,15 @@ class UUIDBox(Jp2kBox): try: self._parse_raw_data() - except Exception as e: - warnings.warn(str(e)) + except RuntimeError as error: + warnings.warn(str(error)) def _parse_raw_data(self): """ Private function for parsing UUID payloads if possible. """ if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): - xmp = XMPMeta() - xmp.parse_from_str(self.raw_data.decode('utf-8'), - xmpmeta_wrap=False) - self.data = xmp + self.data = _uuid_io.xml(self.raw_data) elif self.uuid.bytes == b'JpgTiffExif->JP2': self.data = _uuid_io.tiff_header(self.raw_data) else: @@ -2243,11 +2234,16 @@ class UUIDBox(Jp2kBox): return msg.format(repr(self.uuid), len(self.data)) def __str__(self): - msg = '{0}\n' - msg += ' UUID: {1}\n' - msg += ' UUID Data: {2}' + msg = '{0}\n UUID: {1}'.format(Jp2kBox.__str__(self), self.uuid) - msg = msg.format(Jp2kBox.__str__(self), self.uuid, str(self.data)) + if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): + line = ' (XMP)\n UUID Data: {0}' + msg += line.format(_pretty_print_xml(self.data)) + elif self.uuid.bytes == b'JpgTiffExif->JP2': + msg += ' (EXIF)\n UUID Data: {0}'.format(str(self.data)) + else: + line = ' (unknown)\n UUID Data: {0} bytes' + msg += line.format(len(self.raw_data)) return msg diff --git a/glymur/test/test_jp2box_uuid.py b/glymur/test/test_jp2box_uuid.py index 8f24f74..0d3684a 100644 --- a/glymur/test/test_jp2box_uuid.py +++ b/glymur/test/test_jp2box_uuid.py @@ -18,7 +18,7 @@ import sys import tempfile import uuid import warnings -from xml.etree import cElementTree as ET +import xml.etree if sys.hexversion < 0x02070000: import unittest2 as unittest @@ -68,7 +68,8 @@ class TestUUIDXMP(unittest.TestCase): # The data should be an XMP packet, which gets interpreted as # an ElementTree. - self.assertTrue(isinstance(jp2.box[-1].data, XMPMeta)) + self.assertTrue(isinstance(jp2.box[-1].data, + xml.etree.ElementTree.ElementTree)) class TestUUIDExif(unittest.TestCase): """Tests for UUIDs of Exif type.""" @@ -184,7 +185,7 @@ class TestUUIDExif(unittest.TestCase): tfile.flush() jp2 = glymur.Jp2k(tfile.name) - self.assertEqual(jp2.box[-1].data.ifds['Image']['Make'], "HTC") + self.assertEqual(jp2.box[-1].data['Make'], "HTC") if __name__ == "__main__": unittest.main() diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index c055aba..aeb0e41 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -31,6 +31,7 @@ import numpy as np import pkg_resources import libxmp +from libxmp import XMPMeta import glymur from glymur import Jp2k @@ -364,7 +365,9 @@ class TestJp2k(unittest.TestCase): def test_xmp_attribute(self): """Verify the XMP packet in the shipping example file can be read.""" j = Jp2k(self.jp2file) - xmp = j.box[3].data + xmp = XMPMeta() + xmp.parse_from_str(j.box[3].raw_data.decode('utf-8'), + xmpmeta_wrap=False) creator_tool = xmp.get_property(libxmp.consts.XMP_NS_XMP, 'CreatorTool') self.assertEqual(creator_tool, 'Google') diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index a1f0c55..382e28d 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -1057,6 +1057,8 @@ class TestPrinting(unittest.TestCase): tfile.write(struct.pack(' Date: Sat, 25 Jan 2014 15:55:36 -0500 Subject: [PATCH 036/326] Fixing tests broken by bad merge. --- glymur/test/fixtures.py | 174 +++++++++++++++++------------------ glymur/test/test_printing.py | 1 - 2 files changed, 84 insertions(+), 91 deletions(-) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 6626075..e11709b 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -183,96 +183,90 @@ def read_pgx_header(pgx_file): nemo_xmp_box = """UUID Box (uuid) @ (77, 3146) UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) - UUID Data: - - - - Google - 2013-02-09T14:47:53 - - - 1 - 72/1 - 72/1 - 2 - HTC - HTC Glacier - 2592 - 1456 - - - 8 - 8 - 8 - - - 2 - 3 - - - 1343036288/4294967295 - 1413044224/4294967295 - - - - - 2748779008/4294967295 - 1417339264/4294967295 - 1288490240/4294967295 - 2576980480/4294967295 - 644245120/4294967295 - 257698032/4294967295 - - - - - 1 - 2528 - 1424 - 353/100 - 0 - 0/1 - WGS-84 - 2013-02-09T14:47:53 - - - 76 - - - 0220 - 0100 - - - 1 - 2 - 3 - 0 - - - 42,20.56N - 71,5.29W - 2013-02-09T19:47:53Z - NETWORK - - - 2013-02-09T14:47:53 - - - - - Glymur - Python XMP Toolkit - - - - - -""" + UUID Data: + + + + Google + 2013-02-09T14:47:53 + + + 1 + 72/1 + 72/1 + 2 + HTC + HTC Glacier + 2592 + 1456 + + + 8 + 8 + 8 + + + 2 + 3 + + + 1343036288/4294967295 + 1413044224/4294967295 + + + + + 2748779008/4294967295 + 1417339264/4294967295 + 1288490240/4294967295 + 2576980480/4294967295 + 644245120/4294967295 + 257698032/4294967295 + + + + + 1 + 2528 + 1424 + 353/100 + 0 + 0/1 + WGS-84 + 2013-02-09T14:47:53 + + + 76 + + + 0220 + 0100 + + + 1 + 2 + 3 + 0 + + + 42,20.56N + 71,5.29W + 2013-02-09T19:47:53Z + NETWORK + + + 2013-02-09T14:47:53 + + + + + Glymur + Python XMP Toolkit + + + + + """ SimpleRDF = """ Date: Sat, 25 Jan 2014 16:04:57 -0500 Subject: [PATCH 037/326] Removed restriction on only writing XMP UUIDs. #104 --- glymur/jp2box.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index b3fcf89..6dbec1d 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2249,9 +2249,6 @@ class UUIDBox(Jp2kBox): def write(self, fptr): """Write a UUID box to file. """ - if self.uuid != uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): - msg = "Only XMP UUID boxes can currently be written." - raise NotImplementedError(msg) write_buffer = struct.pack('>I4s', self.length, b'uuid') fptr.write(write_buffer) fptr.write(self.uuid.bytes) From d6a8736aea1c8ed2469ba10235701fb5935f6f47 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 25 Jan 2014 17:07:33 -0500 Subject: [PATCH 038/326] Removed shutil debugging statement. --- glymur/test/test_printing.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 2828c96..b6e3484 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -1056,8 +1056,6 @@ class TestPrinting(unittest.TestCase): tfile.write(struct.pack(' Date: Sat, 25 Jan 2014 17:08:53 -0500 Subject: [PATCH 039/326] Removed python-xmp-toolkit requirement --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b4fdc2f..b780e4d 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ kwargs = {'name': 'Glymur', 'license': 'MIT', 'test_suite': 'glymur.test'} -instllrqrs = ['numpy>=1.4.1', 'python-xmp-toolkit>=2.0.0'] +instllrqrs = ['numpy>=1.4.1'] if sys.hexversion < 0x03030000: instllrqrs.append('contextlib2>=0.4') instllrqrs.append('mock>=1.0.1') From 3efd4169d947ec817a0a997d7fb06413012eed78 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 25 Jan 2014 18:14:31 -0500 Subject: [PATCH 040/326] Added fixtures for ICC profiles for 27, 33, and 34. --- glymur/test/fixtures.py | 67 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index e11709b..ff33237 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -283,3 +283,70 @@ SimpleRDF = """ """ + +text_gbr_27 = """Colour Specification Box (colr) @ (179, 1339) + Method: any ICC profile + Precedence: 2 + Approximation: accurately represents correct colorspace definition + ICC Profile: + {'Color Space': 'RGB', + 'Connection Space': 'XYZ', + 'Creator': u'appl', + 'Datetime': datetime.datetime(2009, 2, 25, 11, 26, 11), + 'Device Attributes': 'reflective, glossy, positive media polarity, color media', + 'Device Class': 'display device profile', + 'Device Manufacturer': u'appl', + 'Device Model': '', + 'File Signature': u'acsp', + 'Flags': 'not embedded, can be used independently', + 'Illuminant': array([ 0.96420288, 1. , 0.8249054 ]), + 'Platform': u'APPL', + 'Preferred CMM Type': 1634758764, + 'Rendering Intent': 'perceptual', + 'Size': 1328, + 'Version': '2.2.0'}""" + +text_gbr_33 = """Colour Specification Box (colr) @ (179, 1339) + Method: any ICC profile + Precedence: 2 + Approximation: accurately represents correct colorspace definition + ICC Profile: + {'Size': 1328, + 'Preferred CMM Type': 1634758764, + 'Version': '2.2.0', + 'Device Class': 'display device profile', + 'Color Space': 'RGB', + 'Connection Space': 'XYZ', + 'Datetime': datetime.datetime(2009, 2, 25, 11, 26, 11), + 'File Signature': 'acsp', + 'Platform': 'APPL', + 'Flags': 'not embedded, can be used independently', + 'Device Manufacturer': 'appl', + 'Device Model': '', + 'Device Attributes': 'reflective, glossy, positive media polarity, color media', + 'Rendering Intent': 'perceptual', + 'Illuminant': array([ 0.96420288, 1. , 0.8249054 ]), + 'Creator': 'appl'}""" + +text_gbr_34 = """Colour Specification Box (colr) @ (179, 1339) + Method: any ICC profile + Precedence: 2 + Approximation: accurately represents correct colorspace definition + ICC Profile: + {'Size': 1328, + 'Preferred CMM Type': 1634758764, + 'Version': '2.2.0', + 'Device Class': 'display device profile', + 'Color Space': 'RGB', + 'Connection Space': 'XYZ', + 'Datetime': datetime.datetime(2009, 2, 25, 11, 26, 11), + 'File Signature': 'acsp', + 'Platform': 'APPL', + 'Flags': 'not embedded, can be used independently', + 'Device Manufacturer': 'appl', + 'Device Model': '', + 'Device Attributes': 'reflective, glossy, positive media polarity, color ' + 'media', + 'Rendering Intent': 'perceptual', + 'Illuminant': array([ 0.96420288, 1. , 0.8249054 ]), + 'Creator': 'appl'}""" From 8617ce04421e5f44da83b3f43c2c97dea1af6b49 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 25 Jan 2014 18:16:54 -0500 Subject: [PATCH 041/326] Using fixtures for expected values. Removed one duplicated test. #104 --- glymur/test/test_printing.py | 109 +++-------------------------------- 1 file changed, 9 insertions(+), 100 deletions(-) diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index b6e3484..892e283 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -36,6 +36,7 @@ else: import glymur from glymur import Jp2k from .fixtures import OPJ_DATA_ROOT, opj_data_file, nemo_xmp_box +from .fixtures import text_gbr_27, text_gbr_33, text_gbr_34 @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @@ -284,75 +285,6 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") - def test_icc_profile(self): - """verify printing of colr box with ICC profile""" - filename = opj_data_file('input/nonregression/text_GBR.jp2') - with warnings.catch_warnings(): - # brand is 'jp2 ', but has any icc profile. - warnings.simplefilter("ignore") - jp2 = Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(jp2.box[3].box[1]) - actual = fake_out.getvalue().strip() - lin27 = ["Colour Specification Box (colr) @ (179, 1339)", - " Method: any ICC profile", - " Precedence: 2", - " Approximation: accurately represents correct " - + "colorspace definition", - " ICC Profile:", - " {'Color Space': 'RGB',", - " 'Connection Space': 'XYZ',", - " 'Creator': u'appl',", - " 'Datetime': " - + "datetime.datetime(2009, 2, 25, 11, 26, 11),", - " 'Device Attributes': 'reflective, glossy, " - + "positive media polarity, color media',", - " 'Device Class': 'display device profile',", - " 'Device Manufacturer': u'appl',", - " 'Device Model': '',", - " 'File Signature': u'acsp',", - " 'Flags': " - + "'not embedded, can be used independently',", - " 'Illuminant': " - + "array([ 0.96420288, 1. , 0.8249054 ]),", - " 'Platform': u'APPL',", - " 'Preferred CMM Type': 1634758764,", - " 'Rendering Intent': 'perceptual',", - " 'Size': 1328,", - " 'Version': '2.2.0'}"] - lin33 = ["Colour Specification Box (colr) @ (179, 1339)", - " Method: any ICC profile", - " Precedence: 2", - " Approximation: accurately represents correct " - + "colorspace definition", - " ICC Profile:", - " {'Size': 1328,", - " 'Preferred CMM Type': 1634758764,", - " 'Version': '2.2.0',", - " 'Device Class': 'display device profile',", - " 'Color Space': 'RGB',", - " 'Connection Space': 'XYZ',", - " 'Datetime': " - + "datetime.datetime(2009, 2, 25, 11, 26, 11),", - " 'File Signature': 'acsp',", - " 'Platform': 'APPL',", - " 'Flags': 'not embedded, can be used " - + "independently',", - " 'Device Manufacturer': 'appl',", - " 'Device Model': '',", - " 'Device Attributes': 'reflective, glossy, " - + "positive media polarity, color media',", - " 'Rendering Intent': 'perceptual',", - " 'Illuminant': " - + "array([ 0.96420288, 1. , 0.8249054 ]),", - " 'Creator': 'appl'}"] - - lines = lin27 if sys.hexversion < 0x03000000 else lin33 - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") def test_crg(self): @@ -963,12 +895,10 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) - @unittest.skipIf(sys.hexversion < 0x03000000, - "Ordered dicts not printing well in 2.7") @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") - def test_jpx_approx_icc_profile(self): - """verify jpx with approx field equal to zero""" + def test_icc_profile(self): + """verify icc profile printing with a jpx""" # ICC profiles may be used in JP2, but the approximation field should # be zero unless we have jpx. This file does both. filename = opj_data_file('input/nonregression/text_GBR.jp2') @@ -980,34 +910,13 @@ class TestPrinting(unittest.TestCase): with patch('sys.stdout', new=StringIO()) as fake_out: print(jp2.box[3].box[1]) actual = fake_out.getvalue().strip() - lines = ["Colour Specification Box (colr) @ (179, 1339)", - " Method: any ICC profile", - " Precedence: 2", - " Approximation: accurately represents " - + "correct colorspace definition", - " ICC Profile:", - " {'Size': 1328,", - " 'Preferred CMM Type': 1634758764,", - " 'Version': '2.2.0',", - " 'Device Class': 'display device profile',", - " 'Color Space': 'RGB',", - " 'Connection Space': 'XYZ',", - " 'Datetime': " - + "datetime.datetime(2009, 2, 25, 11, 26, 11),", - " 'File Signature': 'acsp',", - " 'Platform': 'APPL',", - " 'Flags': 'not embedded, " - + "can be used independently',", - " 'Device Manufacturer': 'appl',", - " 'Device Model': '',", - " 'Device Attributes': 'reflective, glossy, " - + "positive media polarity, color media',", - " 'Rendering Intent': 'perceptual',", - " 'Illuminant': array([ 0.96420288, 1. ," - + " 0.8249054 ]),", - " 'Creator': 'appl'}"] + if sys.hexversion < 0x03000000: + expected = text_gbr_27 + elif sys.hexversion < 0x03040000: + expected = text_gbr_33 + else: + expected = text_gbr_34 - expected = '\n'.join(lines) self.assertEqual(actual, expected) @unittest.skipIf(OPJ_DATA_ROOT is None, From 052cadbf8e1900009a322f4c6cab56e3f548fdcb Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 27 Jan 2014 16:48:40 -0500 Subject: [PATCH 042/326] No longer presenting ElementTree as viable for XMP in docs. #104 --- docs/source/how_do_i.rst | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index 443e8db..0fb4d9d 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -236,28 +236,21 @@ The example JP2 file shipped with glymur has an XMP UUID. :: xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" ns0:xmptk="Exempi + XMP Core 5.1.2"> + + Google + 2013-02-09T14:47:53 + + . . . Since the UUID data in this case is returned as an ElementTree instance, -one can use ElementTree from the standard library to access the data. -For example, to extract the **CreatorTool** attribute value, the following -would work:: - - >>> xmp = j.box[3].data.packet - >>> rdf = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}' - >>> ns2 = '{http://ns.adobe.com/xap/1.0/}' - >>> name = '{0}RDF/{0}Description/{1}CreatorTool'.format(rdf, ns2) - >>> elt = xmp.find(name) - >>> elt - - >>> elt.text - 'Google' - -Yes, that's painful. A better solution is to install the Python XMP Toolkit -(developer branch):: +one might first turn to ElementTree from the standard library. There's +a better solution though, particularly if you need to create XMP, and that +is to Use the Python XMP Toolkit instead (make sure you use version 2.0 and +not 1.0.2).:: >>> from libxmp import XMPMeta >>> from libxmp.consts import XMP_NS_XMP as NS_XAP From e843d887e181284945753c4ef293de8312d1288e Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 29 Jan 2014 20:19:08 -0500 Subject: [PATCH 043/326] Added freebox blurb. --- CHANGES.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 59ecd8f..818b7d1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,6 @@ -Nov 11, 2013 - Palette box now a 2D numpy array instead of a list of +Jan 29, 2014 - Palette box now a 2D numpy array instead of a list of 1D arrays. Super box constructors now take optional box list argument. + Added read support for JPX FreeBox. Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask length is unsupported. From f214459ef7fe0ab34686e0dd5467d2910c731cda Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 30 Jan 2014 18:48:56 -0500 Subject: [PATCH 044/326] Starting to fill out python xmp toolkit advocacy. #104 --- docs/source/how_do_i.rst | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index 443e8db..e620dbc 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -243,8 +243,8 @@ The example JP2 file shipped with glymur has an XMP UUID. :: Since the UUID data in this case is returned as an ElementTree instance, one can use ElementTree from the standard library to access the data. -For example, to extract the **CreatorTool** attribute value, the following -would work:: +For example, to extract the **CreatorTool** attribute value, one could do the +following >>> xmp = j.box[3].data.packet >>> rdf = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}' @@ -256,8 +256,8 @@ would work:: >>> elt.text 'Google' -Yes, that's painful. A better solution is to install the Python XMP Toolkit -(developer branch):: +But that would be painful. A better solution is to install the Python XMP +Toolkit:: >>> from libxmp import XMPMeta >>> from libxmp.consts import XMP_NS_XMP as NS_XAP @@ -266,3 +266,16 @@ Yes, that's painful. A better solution is to install the Python XMP Toolkit >>> meta.get_property(NS_XAP, 'CreatorTool') 'Google' +Where the Python XMP Toolkit can really shine, though, is when you are +converting an image from another format such as TIFF or JPEG into JPEG 2000. +For example:: + + >>> import requests + >>> r = requests.get('http://photojournal.jpl.nasa.gov/tiff/PIA17145.tif') + >>> with open('PIA17145.tif', 'wb') as fptr: fptr.write(r.content) + >>> from libxmp import XMPFiles + >>> xf = XMPFile() + >>> xf.open_file('PIA17145.tif') + >>> xmp = xf.get_xmp() + + From 6e9846ca9137e7625aaf09f09ae4f0dd45e32587 Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 30 Jan 2014 18:49:48 -0500 Subject: [PATCH 045/326] Fixed repr for XMP uuids. --- glymur/jp2box.py | 2 +- glymur/test/test_jp2box.py | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 6dbec1d..122d5c4 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2230,7 +2230,7 @@ class UUIDBox(Jp2kBox): def __repr__(self): msg = "glymur.jp2box.UUIDBox(the_uuid={0}, " msg += "raw_data=)" - return msg.format(repr(self.uuid), len(self.data)) + return msg.format(repr(self.uuid), len(self.raw_data)) def __str__(self): msg = '{0}\n UUID: {1}'.format(Jp2kBox.__str__(self), self.uuid) diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index f731ff7..a40a5c6 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -919,7 +919,7 @@ class TestRepr(unittest.TestCase): self.assertEqual(box.vendor_mask, newbox.vendor_mask) @unittest.skipIf(sys.hexversion < 0x02070000, "Requires 2.7+") - def test_uuid_box(self): + def test_uuid_box_generic(self): """Verify uuid repr method.""" uuid_instance = uuid.UUID('00000000-0000-0000-0000-000000000000') data = b'0123456789' @@ -936,6 +936,24 @@ class TestRepr(unittest.TestCase): else: self.assertRegex(repr(box), regexp) + @unittest.skipIf(sys.hexversion < 0x02070000, "Requires 2.7+") + def test_uuid_box_xmp(self): + """Verify uuid repr method for XMP UUID box.""" + jp2file = glymur.data.nemo() + j = Jp2k(jp2file) + box = j.box[3] + + # Since the raw_data parameter is a sequence of bytes which could be + # quite long, don't bother trying to make it conform to eval(repr()). + regexp = r"""glymur.jp2box.UUIDBox\(""" + regexp += """the_uuid=UUID\('be7acfcb-97a9-42e8-9c71-999491e3afac'\),\s""" + regexp += """raw_data=\)""" + + if sys.hexversion < 0x03000000: + self.assertRegexpMatches(repr(box), regexp) + else: + self.assertRegex(repr(box), regexp) + @unittest.skipIf(sys.hexversion < 0x02070000, "Requires 2.7+") def test_contiguous_codestream_box(self): """Verify contiguous codestream box repr method.""" From ef629e041cd944603c7a58915f838b1c32f99518 Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 30 Jan 2014 20:00:18 -0500 Subject: [PATCH 046/326] Added support for NumberListBox. closes #143 --- glymur/jp2box.py | 67 ++++++++++++++++++++++++++++++++ glymur/test/test_jp2box_jpx.py | 70 +++++++++++++++------------------- 2 files changed, 97 insertions(+), 40 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 6e62f88..51f8567 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1903,6 +1903,72 @@ class LabelBox(Jp2kBox): return box +class NumberListBox(Jp2kBox): + """Container for Number List box information. + + Attributes + ---------- + box_id : str + 4-character identifier for the box. + length : int + length of the box in bytes. + offset : int + offset of the box from the start of the file. + longname : str + more verbose description of the box. + AN : list + Descriptors of an entity with which the data contained within the same + Association box is associated. + """ + def __init__(self, associations, length=0, offset=-1): + Jp2kBox.__init__(self, box_id='nlst', longname='Number List') + self.associations = associations + self.length = length + self.offset = offset + + def __str__(self): + msg = Jp2kBox.__str__(self) + for j, association in enumerate(self.associations): + if association == 0: + msg += '\n Association[{0}]: the rendered result'.format(j) + elif (association >> 24) == 1: + idx = association & 0x00FFFFFF + msg += '\n Association[{0}]: Codestream {0} '.format(idx) + elif (association >> 24) == 2: + idx = association & 0x00FFFFFF + msg += '\n Association[{0}]: Compositing Layer {0}' + msg = msg.format(idx) + return msg + + def __repr__(self): + msg = 'glymur.jp2box.NumberListBox()' + return msg + + @staticmethod + def parse(fptr, offset, length): + """Parse Label box. + + Parameters + ---------- + fptr : file + Open file object. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + LabelBox instance + """ + num_bytes = offset + length - fptr.tell() + raw_data = fptr.read(num_bytes) + num_associations = int(len(raw_data) / 4) + lst = struct.unpack('>' + 'I' * num_associations, raw_data) + box = NumberListBox(lst, length=length, offset=offset) + return box + + class XMLBox(Jp2kBox): """Container for XML box information. @@ -2865,6 +2931,7 @@ _BOX_WITH_ID = { 'free': FreeBox, 'jp2h': JP2HeaderBox, 'lbl ': LabelBox, + 'nlst': NumberListBox, 'pclr': PaletteBox, 'res ': ResolutionBox, 'resc': CaptureResolutionBox, diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 57f5e6f..4a95528 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -20,46 +20,6 @@ from glymur import Jp2k from glymur.jp2box import ReaderRequirementsBox -@unittest.skipIf(sys.hexversion < 0x03000000, "Warning assert on 2.x.") -@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") -class TestReaderRequirements(unittest.TestCase): - """Test suite for XML boxes.""" - - def setUp(self): - self.jp2file = glymur.data.nemo() - pass - - def tearDown(self): - pass - - def test_mask_length_is_3(self): - """The standard says that the mask length should be 1, 2, 4, or 8.""" - # Rewrite nemo to include this kind of rreq box. - with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: - with open(self.jp2file, 'rb') as nemof: - # Read the jP and ftyp boxes as-is. - write_buffer = nemof.read(32) - tfile.write(write_buffer) - - # Fake a rreq box with ML = 3. - write_buffer = struct.pack('>I4sB', 74, b'rreq', 3) - tfile.write(write_buffer) - - # pad the rest with zeros - write_buffer = struct.pack('>65s', b'\x00' * 65) - tfile.write(write_buffer) - - # Write the rest of nemo. - tfile.write(nemof.read()) - tfile.flush() - - with self.assertWarns(UserWarning): - j = Jp2k(tfile.name) - self.assertEqual(j.box[2].box_id, 'rreq') - self.assertEqual(type(j.box[2]), - glymur.jp2box.ReaderRequirementsBox) - - @unittest.skipIf(sys.hexversion < 0x03000000, "Warning assert on 2.x.") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") class TestJPXOther(unittest.TestCase): @@ -72,6 +32,16 @@ class TestJPXOther(unittest.TestCase): def tearDown(self): pass + def test_rreq_box_strange_mask_length(self): + """The standard says that the mask length should be 1, 2, 4, or 8.""" + with warnings.catch_warnings(): + # This file has a rreq mask length that we do not recognize. + warnings.simplefilter("ignore") + j = Jp2k(self.jpxfile) + self.assertEqual(j.box[2].box_id, 'rreq') + self.assertEqual(type(j.box[2]), + glymur.jp2box.ReaderRequirementsBox) + def test_free_box(self): """Verify that we can handle a free box.""" with warnings.catch_warnings(): @@ -81,3 +51,23 @@ class TestJPXOther(unittest.TestCase): self.assertEqual(j.box[16].box[0].box_id, 'free') self.assertEqual(type(j.box[16].box[0]), glymur.jp2box.FreeBox) + def test_nlst(self): + """Verify that we can handle a free box.""" + with warnings.catch_warnings(): + # This file has a rreq mask length that we do not recognize. + warnings.simplefilter("ignore") + j = Jp2k(self.jpxfile) + self.assertEqual(j.box[16].box[1].box[0].box_id, 'nlst') + self.assertEqual(type(j.box[16].box[1].box[0]), + glymur.jp2box.NumberListBox) + + # Two associations. + self.assertEqual(len(j.box[16].box[1].box[0].associations), 2) + + # Codestream 0 + self.assertEqual(j.box[16].box[1].box[0].associations[0], 1 << 24) + + # Compositing Layer 0 + self.assertEqual(j.box[16].box[1].box[0].associations[1], 2 << 24) + + From dfa1fce07267a9fbb49d62016651dbd0a5d05526 Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 30 Jan 2014 20:05:09 -0500 Subject: [PATCH 047/326] NumberListBox --- CHANGES.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 818b7d1..bbf96bb 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,6 @@ -Jan 29, 2014 - Palette box now a 2D numpy array instead of a list of - 1D arrays. Super box constructors now take optional box list argument. - Added read support for JPX FreeBox. +Jan 29, 2014 - Added read support for JPX FreeBox, NumberListBox. Palette box + now a 2D numpy array instead of a list of 1D arrays. JP2 super box + constructors now take optional box list argument. Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask length is unsupported. From f3fca7d7d328d3e9e74911623ddd9de7872cdac0 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 1 Feb 2014 13:21:51 -0500 Subject: [PATCH 048/326] More xmp blurbs. --- docs/source/how_do_i.rst | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index e620dbc..3aaa300 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -268,14 +268,31 @@ Toolkit:: Where the Python XMP Toolkit can really shine, though, is when you are converting an image from another format such as TIFF or JPEG into JPEG 2000. -For example:: +For example, if you were to be converting the TIFF image found at +http://photojournal.jpl.nasa.gov/tiff/PIA17145.tif info JPEG 2000:: + + >>> import skimage.io + >>> image = skimage.io.imread('PIA17145.tif') + >>> from glymur import Jp2k + >>> jp2 = Jp2k('PIA17145.jp2', 'wb') + >>> jp2.write(image) + +Next you can extract the XMP metadata. - >>> import requests - >>> r = requests.get('http://photojournal.jpl.nasa.gov/tiff/PIA17145.tif') - >>> with open('PIA17145.tif', 'wb') as fptr: fptr.write(r.content) >>> from libxmp import XMPFiles >>> xf = XMPFile() >>> xf.open_file('PIA17145.tif') >>> xmp = xf.get_xmp() +If you are familiar with TIFF, you can verify that there's no XMP tag in the +TIFF file, but the Python XMP Toolkit takes advantage of the TIFF header +structure to populate an XMP packet for you. If you were working with a JPEG +file with Exif metadata, that information would be included in the XMP packet +as well. Now you can append the XMP packet in a UUIDBox:: + + >>> import uuid + >>> the_uuid = uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac') + >>> box = glymur.jp2box.UUIDBox(uuid, str(xmp).encode()) + >>> jp2.append(box) + From 1e43cb32cbc5cfecda88ac70b4d6cf088ebcb5b0 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 1 Feb 2014 15:42:13 -0500 Subject: [PATCH 049/326] Added support for Data Reference box. --- CHANGES.txt | 6 +-- glymur/data/12-v6.4.jpx | Bin 15091 -> 695609 bytes glymur/jp2box.py | 70 +++++++++++++++++++++++++++++++++ glymur/test/test_jp2box_jpx.py | 39 ++++++++++++++++-- 4 files changed, 108 insertions(+), 7 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index bbf96bb..e126c70 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,6 @@ -Jan 29, 2014 - Added read support for JPX FreeBox, NumberListBox. Palette box - now a 2D numpy array instead of a list of 1D arrays. JP2 super box - constructors now take optional box list argument. +Feb 01, 2014 - Added read support for JPX FreeBox, NumberListBox, Data Reference + Box. Palette box now a 2D numpy array instead of a list of 1D arrays. + JP2 super box constructors now take optional box list argument. Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask length is unsupported. diff --git a/glymur/data/12-v6.4.jpx b/glymur/data/12-v6.4.jpx index 63f5f13db7b1013bd4f1f043ae9aa4ede5f5bc65..a3e0c60e09d66f88581daabd4ae4c9f077b16455 100644 GIT binary patch literal 695609 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 z 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 W8vAc{F-ffK-_RruWE?Oe@Babm*R`4e delta 572 zcmdnFOY3v#hD*%#3=9fsB}q993=9%!ImIOmj6fC;2=p;9&1eMkII;?oGk`)sItN7K zR6k3AfeE6H2=%=TOf%USm<@>5Fa_wKP6n3kju~7(On}a2tV#j7n+0ee)=$b$Pf5(vH%KZ@(a$L})HgKJH!?6ZjyEuiH#CZmH!z4dFpM`g ziH{Em@^^AHiVqHV41f@Z<|cYs1x6qvv722_jN!)dCS(~-gxNI)rtzkH', read_buffer) + + # Read each data entry url box. + data_entry_url_box_list = [] + for _ in range(ndr): + start = fptr.tell() + read_buffer = fptr.read(8) + (box_length, box_id) = struct.unpack('>I4s', read_buffer) + if sys.hexversion >= 0x03000000: + box_id = box_id.decode('utf-8') + + box = DataEntryURLBox.parse(fptr, start, box_length) + data_entry_url_box_list.append(box) + + return DataReferenceBox(data_entry_url_box_list, + length=length, offset=offset) + + class FileTypeBox(Jp2kBox): """Container for JPEG 2000 file type box information. @@ -2922,6 +2991,7 @@ _BOX_WITH_ID = { 'cdef': ChannelDefinitionBox, 'cmap': ComponentMappingBox, 'colr': ColourSpecificationBox, + 'dtbl': DataReferenceBox, 'ftyp': FileTypeBox, 'ihdr': ImageHeaderBox, 'jP ': JPEG2000SignatureBox, diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 4a95528..01f6f1a 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -8,7 +8,6 @@ import struct import sys import tempfile import warnings -import xml.etree.cElementTree as ET if sys.hexversion < 0x02070000: import unittest2 as unittest @@ -17,7 +16,6 @@ else: import glymur from glymur import Jp2k -from glymur.jp2box import ReaderRequirementsBox @unittest.skipIf(sys.hexversion < 0x03000000, "Warning assert on 2.x.") @@ -27,7 +25,6 @@ class TestJPXOther(unittest.TestCase): def setUp(self): self.jpxfile = glymur.data.jpxfile() - pass def tearDown(self): pass @@ -51,6 +48,40 @@ class TestJPXOther(unittest.TestCase): self.assertEqual(j.box[16].box[0].box_id, 'free') self.assertEqual(type(j.box[16].box[0]), glymur.jp2box.FreeBox) + def test_dtbl(self): + """Verify that we can interpret Data Reference boxes.""" + # Copy the existing JPX file, add a data reference box onto the end. + with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile: + with open(self.jpxfile, 'rb') as ifile: + tfile.write(ifile.read()) + write_buffer = struct.pack('>I4s', 50, b'dtbl') + tfile.write(write_buffer) + + # Just two boxes. + write_buffer = struct.pack('>H', 2) + tfile.write(write_buffer) + + # First data entry url box. + write_buffer = struct.pack('>I4s', 20, b'url ') + tfile.write(write_buffer) + write_buffer = struct.pack('>BBBB8s', 0, 0, 0, 0, b'file:///') + tfile.write(write_buffer) + + # Second data entry url box. + write_buffer = struct.pack('>I4s', 20, b'url ') + tfile.write(write_buffer) + write_buffer = struct.pack('>BBBB8s', 0, 0, 0, 0, b'file:///') + tfile.write(write_buffer) + + tfile.flush() + + with self.assertWarns(UserWarning): + jpx = Jp2k(tfile.name) + + self.assertEqual(jpx.box[-1].box_id, 'dtbl') + self.assertEqual(len(jpx.box[-1].DR), 2) + + def test_nlst(self): """Verify that we can handle a free box.""" with warnings.catch_warnings(): @@ -60,7 +91,7 @@ class TestJPXOther(unittest.TestCase): self.assertEqual(j.box[16].box[1].box[0].box_id, 'nlst') self.assertEqual(type(j.box[16].box[1].box[0]), glymur.jp2box.NumberListBox) - + # Two associations. self.assertEqual(len(j.box[16].box[1].box[0].associations), 2) From 6740be931b07e75157cb5f43204677d6ee1d6068 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 1 Feb 2014 17:08:17 -0500 Subject: [PATCH 050/326] Added fragment list and fragment table boxes. #145 --- CHANGES.txt | 7 +-- glymur/jp2box.py | 132 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 135 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index e126c70..a65c20a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,7 @@ -Feb 01, 2014 - Added read support for JPX FreeBox, NumberListBox, Data Reference - Box. Palette box now a 2D numpy array instead of a list of 1D arrays. - JP2 super box constructors now take optional box list argument. +Feb 01, 2014 - Added read support for JPX free, number list, data reference, + fragment table, and fragment list boxes. Palette box now a 2D numpy array + instead of a list of 1D arrays. JP2 super box constructors now take + optional box list argument. Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask length is unsupported. diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 2d7fed7..1635aa0 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -835,7 +835,7 @@ class DataReferenceBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - for box in enumerate(self.DR): + for box in self.DR: msg += '\n ' + str(box) return msg @@ -984,6 +984,134 @@ class FileTypeBox(Jp2kBox): return box +class FragmentListBox(Jp2kBox): + """Container for JPX fragment list box information. + + Attributes + ---------- + box_id : str + 4-character identifier for the box. + length : int + length of the box in bytes. + offset : int + offset of the box from the start of the file. + longname : str + more verbose description of the box. + """ + def __init__(self, fragment_offset, fragment_length, data_reference, + length=0, offset=-1): + Jp2kBox.__init__(self, box_id='flst', longname='Fragment List') + self.fragment_offset = fragment_offset + self.fragment_length = fragment_length + self.data_reference = data_reference + self.length = length + self.offset = offset + + def __repr__(self): + msg = "glymur.jp2box.FragmentListBox()" + return msg + + def __str__(self): + msg = Jp2kBox.__str__(self) + for j in range(len(self.fragment_offset)): + msg += "\n Offset {0}: {1}" + msg += "\n Fragment Length {2}: {3}" + msg += "\n Data Reference {4}: {5}" + msg = msg.format(j, self.fragment_offset[j], + j, self.fragment_length[j], + j, self.data_reference[j]) + + return msg + + @staticmethod + def parse(fptr, offset, length): + """Parse JPX free box. + + Parameters + ---------- + f : file + Open file object. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + FreeBox instance + """ + read_buffer = fptr.read(2) + nf, = struct.unpack('>H', read_buffer) + + read_buffer = fptr.read(nf * 14) + lst = struct.unpack('>' + 'QIH' * nf, read_buffer) + frag_offset = lst[0::3] + frag_len = lst[1::3] + data_reference = lst[2::3] + return FragmentListBox(frag_offset, frag_len, data_reference, + length=length, offset=offset) + + +class FragmentTableBox(Jp2kBox): + """Container for JPX fragment table box information. + + Attributes + ---------- + box_id : str + 4-character identifier for the box. + length : int + length of the box in bytes. + offset : int + offset of the box from the start of the file. + longname : str + more verbose description of the box. + """ + def __init__(self, length=0, offset=-1): + Jp2kBox.__init__(self, box_id='ftbl', longname='Fragment Table') + self.length = length + self.offset = offset + + def __repr__(self): + msg = "glymur.jp2box.FragmentTableBox()" + return msg + + def __str__(self): + msg = Jp2kBox.__str__(self) + for box in self.box: + boxstr = str(box) + + # Add indentation. + strs = [('\n ' + x) for x in boxstr.split('\n')] + msg += ''.join(strs) + return msg + + @staticmethod + def parse(fptr, offset, length): + """Parse JPX fragment table superbox box. + + Parameters + ---------- + f : file + Open file object. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + FreeBox instance + """ + box = FragmentTableBox(length=length, offset=offset) + + # The FragmentTable box is a superbox, so go ahead and parse its child + # boxes. + box.box = box.parse_superbox(fptr) + + return box + + + class FreeBox(Jp2kBox): """Container for JPX free box information. @@ -2999,6 +3127,8 @@ _BOX_WITH_ID = { 'jplh': CompositingLayerHeaderBox, 'jp2c': ContiguousCodestreamBox, 'free': FreeBox, + 'flst': FragmentListBox, + 'ftbl': FragmentTableBox, 'jp2h': JP2HeaderBox, 'lbl ': LabelBox, 'nlst': NumberListBox, From 11fa080910281e5c18588e8d6e7deb485e8e103f Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 1 Feb 2014 17:58:11 -0500 Subject: [PATCH 051/326] Added test for flst, ftbl boxes. Closes #145 --- glymur/test/test_jp2box_jpx.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 01f6f1a..ae2c2b7 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -82,6 +82,35 @@ class TestJPXOther(unittest.TestCase): self.assertEqual(len(jpx.box[-1].DR), 2) + def test_ftbl(self): + """Verify that we can interpret Fragment Table boxes.""" + # Copy the existing JPX file, add a fragment table box onto the end. + with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile: + with open(self.jpxfile, 'rb') as ifile: + tfile.write(ifile.read()) + write_buffer = struct.pack('>I4s', 32, b'ftbl') + tfile.write(write_buffer) + + # Just one fragment list box + write_buffer = struct.pack('>I4s', 24, b'flst') + tfile.write(write_buffer) + + # Simple offset, length, reference + write_buffer = struct.pack('>HQIH', 1, 4237, 170246, 3) + tfile.write(write_buffer) + + tfile.flush() + + with self.assertWarns(UserWarning): + jpx = Jp2k(tfile.name) + + self.assertEqual(jpx.box[-1].box_id, 'ftbl') + self.assertEqual(jpx.box[-1].box[0].box_id, 'flst') + self.assertEqual(jpx.box[-1].box[0].fragment_offset, (4237,)) + self.assertEqual(jpx.box[-1].box[0].fragment_length, (170246,)) + self.assertEqual(jpx.box[-1].box[0].data_reference, (3,)) + + def test_nlst(self): """Verify that we can handle a free box.""" with warnings.catch_warnings(): From 92c77a1dffc2aebe50758050734eb80dfc26791d Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 3 Feb 2014 08:04:53 -0500 Subject: [PATCH 052/326] Can now read image from JPX/JP2 file with > 1 codestream. Closes #146. Now that we have a good JPX file with more than one image, that section of code could be worked thru a bit more intelligently. --- CHANGES.txt | 5 +++-- glymur/jp2k.py | 10 +++++++--- glymur/test/test_jp2k.py | 10 ++++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index a65c20a..894a103 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,7 +1,8 @@ -Feb 01, 2014 - Added read support for JPX free, number list, data reference, +Feb 03, 2014 - Added read support for JPX free, number list, data reference, fragment table, and fragment list boxes. Palette box now a 2D numpy array instead of a list of 1D arrays. JP2 super box constructors now take - optional box list argument. + optional box list argument. Fixed bug where JPX files with more than one + codestream but advertising jp2 compatibility were not being read. Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask length is unsupported. diff --git a/glymur/jp2k.py b/glymur/jp2k.py index a9927da..3fd9d21 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -1027,16 +1027,20 @@ class Jp2k(Jp2kBox): Raises ------ IOError - If the file is JPX with more than one codestream. + If the file is JPX with more than one codestream and no JP2 + compatibility is advertised. """ with open(self.filename, 'rb') as fptr: if self._codec_format == opj2.CODEC_J2K: codestream = Codestream(fptr, self.length, header_only=header_only) else: + ftyp = self.box[1] box = [x for x in self.box if x.box_id == 'jp2c'] - if len(box) != 1: - msg = "JP2 files must have a single codestream." + if len(box) > 1 and 'jp2 ' not in ftyp.compatibility_list: + msg = "If more than one codestream exists, JP2 " + msg += "compatibiltity must be advertised by the FileType " + msg += "box." raise RuntimeError(msg) fptr.seek(box[0].offset) read_buffer = fptr.read(8) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 15da73d..9ebe774 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -63,6 +63,7 @@ class TestJp2k(unittest.TestCase): def setUp(self): self.jp2file = glymur.data.nemo() self.j2kfile = glymur.data.goodstuff() + self.jpxfile = glymur.data.jpxfile() def tearDown(self): pass @@ -374,6 +375,15 @@ class TestJp2k(unittest.TestCase): attr_value = elt.attrib['{0}CreatorTool'.format(ns1)] self.assertEqual(attr_value, 'glymur') + def test_jpx_mult_codestreams_jp2_brand(self): + """Read JPX codestream when jp2-compatible.""" + # The file in question has multiple codestreams. + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + jpx = Jp2k(self.jpxfile) + data = jpx.read() + self.assertEqual(data.shape, (1024, 1024, 3)) + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_unrecognized_exif_tag(self): """An unrecognized exif tag should be handled gracefully.""" From a9d62da2c8a538684d0057daddc3a49e5c403812 Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 3 Feb 2014 08:36:18 -0500 Subject: [PATCH 053/326] openjpeg 1.3 does not apply the palette. Closes #147 --- glymur/test/test_jp2k.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 9ebe774..c664de9 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -382,7 +382,11 @@ class TestJp2k(unittest.TestCase): warnings.simplefilter("ignore") jpx = Jp2k(self.jpxfile) data = jpx.read() - self.assertEqual(data.shape, (1024, 1024, 3)) + if re.match(r"""1\.[0123]""", glymur.version.openjpeg_version): + # openjpeg 1.3 doesn't apply the palette, so it's a 2D image here + self.assertEqual(data.shape, (1024, 1024)) + else: + self.assertEqual(data.shape, (1024, 1024, 3)) @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_unrecognized_exif_tag(self): From 60656be66737853c786b20188c4965d9587957e1 Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 3 Feb 2014 19:51:34 -0500 Subject: [PATCH 054/326] closes #148 --- glymur/jp2box.py | 2 +- glymur/test/test_jp2box_jpx.py | 23 +++++++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 1635aa0..c1a6700 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2477,7 +2477,7 @@ class DataEntryURLBox(Jp2kBox): numbytes = offset + length - fptr.tell() read_buffer = fptr.read(numbytes) - url = read_buffer.decode('utf-8') + url = read_buffer.decode('utf-8').rstrip(chr(0)) box = DataEntryURLBox(version, flag, url, length=length, offset=offset) return box diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index ae2c2b7..0436c95 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -54,7 +54,10 @@ class TestJPXOther(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile: with open(self.jpxfile, 'rb') as ifile: tfile.write(ifile.read()) - write_buffer = struct.pack('>I4s', 50, b'dtbl') + # 8 + 2 + 20 + 36 + boxlen = 66 + + write_buffer = struct.pack('>I4s', boxlen, b'dtbl') tfile.write(write_buffer) # Just two boxes. @@ -62,15 +65,20 @@ class TestJPXOther(unittest.TestCase): tfile.write(write_buffer) # First data entry url box. - write_buffer = struct.pack('>I4s', 20, b'url ') + # This one will have a URL with 3 null chars at the end. + # They should be stripped off. + write_buffer = struct.pack('>I4s', 36, b'url ') tfile.write(write_buffer) - write_buffer = struct.pack('>BBBB8s', 0, 0, 0, 0, b'file:///') + url1 = 'file:////usr/local/bin' + write_buffer = struct.pack('>BBBB24s', 0, 0, 0, 0, + (url1 + chr(0) * 3).encode()) tfile.write(write_buffer) - # Second data entry url box. + # 2nd data entry url box. write_buffer = struct.pack('>I4s', 20, b'url ') tfile.write(write_buffer) - write_buffer = struct.pack('>BBBB8s', 0, 0, 0, 0, b'file:///') + url2 = 'file:///' + write_buffer = struct.pack('>BBBB8s', 0, 0, 0, 0, url2.encode()) tfile.write(write_buffer) tfile.flush() @@ -80,6 +88,8 @@ class TestJPXOther(unittest.TestCase): self.assertEqual(jpx.box[-1].box_id, 'dtbl') self.assertEqual(len(jpx.box[-1].DR), 2) + self.assertEqual(jpx.box[-1].DR[0].url, url1) + self.assertEqual(jpx.box[-1].DR[1].url, url2) def test_ftbl(self): @@ -110,7 +120,6 @@ class TestJPXOther(unittest.TestCase): self.assertEqual(jpx.box[-1].box[0].fragment_length, (170246,)) self.assertEqual(jpx.box[-1].box[0].data_reference, (3,)) - def test_nlst(self): """Verify that we can handle a free box.""" with warnings.catch_warnings(): @@ -129,5 +138,3 @@ class TestJPXOther(unittest.TestCase): # Compositing Layer 0 self.assertEqual(j.box[16].box[1].box[0].associations[1], 2 << 24) - - From a3fb3ec03727059304813d4cfdc161f4ead8f5b1 Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 4 Feb 2014 20:51:27 -0500 Subject: [PATCH 055/326] Write support for Palette, Component Mapping box. Closes #149 --- CHANGES.txt | 11 ++++--- glymur/jp2box.py | 67 +++++++++++++++++++++++++++++++++++--- glymur/test/test_jp2box.py | 26 +++++++++++++++ 3 files changed, 95 insertions(+), 9 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 894a103..324a530 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,8 +1,9 @@ -Feb 03, 2014 - Added read support for JPX free, number list, data reference, - fragment table, and fragment list boxes. Palette box now a 2D numpy array - instead of a list of 1D arrays. JP2 super box constructors now take - optional box list argument. Fixed bug where JPX files with more than one - codestream but advertising jp2 compatibility were not being read. +Feb 03, 2014 - Added write support for Palette and Component Mapping boxes. + Added read support for JPX free, number list, data reference, fragment + table, and fragment list boxes. Palette box now a 2D numpy array instead + of a list of 1D arrays. JP2 super box constructors now take optional box + list argument. Fixed bug where JPX files with more than one codestream + but advertising jp2 compatibility were not being read. Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask length is unsupported. diff --git a/glymur/jp2box.py b/glymur/jp2box.py index c1a6700..d3b4ed6 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -15,6 +15,7 @@ References import copy import datetime +import io import math import os import pprint @@ -23,6 +24,7 @@ import sys import uuid import warnings import xml.etree.cElementTree as ET + if sys.hexversion < 0x02070000: # pylint: disable=F0401,E0611 from ordereddict import OrderedDict @@ -719,6 +721,20 @@ class ComponentMappingBox(Jp2kBox): msg = msg.format(self.component_index[k], k) return msg + def write(self, fptr): + """Write a Component Mapping box to file. + """ + length = 8 + 4 * len(self.component_index) + write_buffer = struct.pack('>I4s', length, self.box_id.encode()) + fptr.write(write_buffer) + + for j in range(len(self.component_index)): + write_buffer = struct.pack('>HBB', + self.component_index[j], + self.mapping_type[j], + self.palette_index[j]) + fptr.write(write_buffer) + @staticmethod def parse(fptr, offset, length): """Parse component mapping box. @@ -1527,9 +1543,9 @@ class PaletteBox(Jp2kBox): self.offset = offset def __repr__(self): - msg = "glymur.jp2box.PaletteBox({0}, bits_per_component={1}, " - msg += "signed={2})" - msg = msg.format(self.palette, self.bits_per_component, self.signed) + msg = "glymur.jp2box.PaletteBox(ndarray, bits_per_component={0}, " + msg += "signed={1})" + msg = msg.format(self.bits_per_component, self.signed) return msg def __str__(self): @@ -1537,6 +1553,49 @@ class PaletteBox(Jp2kBox): msg += '\n Size: ({0} x {1})'.format(*self.palette.shape) return msg + def write(self, fptr): + """Write a Palette box to file. + """ + # Box length is usual header (8) + # + num entries NE (2) + num columns NC (1) + # + (bps/8, /signed) for each column (3) + bps * NC + # + + bytes_per_row = sum(self.bits_per_component) / 8 + bytes_per_palette = bytes_per_row * self.palette.shape[0] + box_length = 8 + 3 + self.palette.shape[1] + bytes_per_palette + + # Write the usual header. + write_buffer = struct.pack('>I4s', + int(box_length), self.box_id.encode()) + fptr.write(write_buffer) + + write_buffer = struct.pack('>HB', self.palette.shape[0], + self.palette.shape[1]) + fptr.write(write_buffer) + + bps_signed = [x - 1 for x in self.bits_per_component] + for j, item in enumerate(bps_signed): + if self.signed[j]: + bps_signed[j] |= 0x80 + write_buffer = struct.pack('>' + 'B' * self.palette.shape[1], + *bps_signed) + fptr.write(write_buffer) + + if self.bits_per_component[0] <= 8: + dtype = np.uint8 + code = 'B' + elif self.bits_per_component[0] <= 16: + dtype = np.uint16 + code = 'H' + elif self.bits_per_component[0] <= 32: + dtype = np.uint32 + code = 'I' + + fmt = '>' + code * self.palette.shape[1] + for row in self.palette: + write_buffer = struct.pack(fmt, *row) + fptr.write(write_buffer) + @staticmethod def parse(fptr, offset, length): """Parse palette box. @@ -1561,7 +1620,7 @@ class PaletteBox(Jp2kBox): # Need to determine bps and signed or not read_buffer = fptr.read(num_columns) data = struct.unpack('>' + 'B' * num_columns, read_buffer) - bps = [((x & 0x07f) + 1) for x in data] + bps = [((x & 0x7f) + 1) for x in data] signed = [((x & 0x80) > 1) for x in data] fmt = '>' diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 05c0a83..ff4d075 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -26,6 +26,7 @@ import tempfile import uuid from uuid import UUID import xml.etree.cElementTree as ET +import warnings if sys.hexversion < 0x02070000: import unittest2 as unittest @@ -496,6 +497,7 @@ class TestWrap(unittest.TestCase): def setUp(self): self.j2kfile = glymur.data.goodstuff() self.jp2file = glymur.data.nemo() + self.jpxfile = glymur.data.jpxfile() def tearDown(self): pass @@ -557,6 +559,30 @@ class TestWrap(unittest.TestCase): j2k.wrap(tfile.name) self.verify_wrapped_raw(tfile.name) + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") + def test_palette(self): + """basic test for rewrapping a jpx file""" + with warnings.catch_warnings(): + # This file has a rreq mask length that we do not recognize. + warnings.simplefilter("ignore") + jpx = Jp2k(self.jpxfile) + idx = [0, 1, 3, 6] + boxes = [jpx.box[idx] for idx in [0, 1, 3, 6]] + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + jp2 = jpx.wrap(tfile.name, boxes=boxes) + + # Verify the outer boxes. + boxes = [box.box_id for box in jp2.box] + self.assertEqual(boxes, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + # Verify the inside boxes. + boxes = [box.box_id for box in jp2.box[2].box] + self.assertEqual(boxes, ['ihdr', 'colr', 'pclr', 'cmap']) + + expected_offsets = [0, 12, 40, 887] + for j, offset in enumerate(expected_offsets): + self.assertEqual(jp2.box[j].offset, offset) + @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""" From 4487bb1f42f14b03a29a3d9dceffcc1cc24d886e Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 5 Feb 2014 11:35:05 -0500 Subject: [PATCH 056/326] Fixed repr on palette box. Closes #151 Had thought that numpy arrays could not be reconstituted through repr, but that is not the case. --- glymur/jp2box.py | 7 ++++--- glymur/test/test_jp2box.py | 10 +++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index d3b4ed6..7960c8d 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1543,9 +1543,10 @@ class PaletteBox(Jp2kBox): self.offset = offset def __repr__(self): - msg = "glymur.jp2box.PaletteBox(ndarray, bits_per_component={0}, " - msg += "signed={1})" - msg = msg.format(self.bits_per_component, self.signed) + msg = "glymur.jp2box.PaletteBox({0}, bits_per_component={1}, " + msg += "signed={2})" + msg = msg.format(repr(self.palette), self.bits_per_component, + self.signed) return msg def __str__(self): diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index ff4d075..83d656f 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -909,9 +909,13 @@ class TestRepr(unittest.TestCase): signed = (True, False, True) box = glymur.jp2box.PaletteBox(palette=palette, bits_per_component=bps, signed=(True, False, True)) - # The palette can't be reinstantiated thru eval/repr. - s = repr(box) - self.assertTrue(True) + + # Test will fail unless addition imports from numpy are done. + from numpy import array, int32 + newbox = eval(repr(box)) + np.testing.assert_array_equal(newbox.palette, palette) + self.assertEqual(newbox.bits_per_component, (8, 8, 16)) + self.assertEqual(newbox.signed, (True, False, True)) @unittest.skipIf(sys.hexversion < 0x02070000, "Requires 2.7+") def test_xml_box(self): From 423daf1dfef619f2cff61e4abbf8db9ff7114446 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 5 Feb 2014 15:20:33 -0500 Subject: [PATCH 057/326] Removed support for 2.6. Closes #150 --- .travis.yml | 2 -- docs/source/detailed_installation.rst | 12 +++++------- docs/source/introduction.rst | 3 ++- docs/source/roadmap.rst | 2 -- glymur/__init__.py | 8 +------- glymur/jp2box.py | 14 ++++---------- glymur/lib/test/test_openjp2.py | 9 +-------- glymur/lib/test/test_openjpeg.py | 9 +-------- glymur/test/test_callbacks.py | 5 +---- glymur/test/test_codestream.py | 9 +-------- glymur/test/test_config.py | 6 +----- glymur/test/test_conformance.py | 9 +-------- glymur/test/test_icc.py | 9 +-------- glymur/test/test_jp2box.py | 12 +----------- glymur/test/test_jp2box_jpx.py | 6 +----- glymur/test/test_jp2box_xml.py | 9 +-------- glymur/test/test_jp2k.py | 9 +-------- glymur/test/test_opj_suite.py | 9 +-------- glymur/test/test_opj_suite_neg.py | 9 +-------- glymur/test/test_opj_suite_write.py | 9 +-------- glymur/test/test_printing.py | 10 +--------- setup.py | 4 ---- 22 files changed, 27 insertions(+), 147 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9434d04..e55be64 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,13 +12,11 @@ before_install: # command to install dependencies install: - - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install --use-mirrors contextlib2 mock ordereddict unittest2; fi - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors contextlib2 mock; fi - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install --use-mirrors numpy; fi # command to run tests script: - - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then unit2 discover; fi - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then python -m unittest discover; fi - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then python -m unittest discover; fi diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index 92810b7..4e17c4f 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -95,18 +95,16 @@ documentation or use pip. * setuptools * matplotlib * pillow - * contextlib2 (python 2.6, 2.7 only) - * mock (python 2.6, 2.7 only) - * ordereddict (python 2.6 only) + * contextlib2 (2.7 only) + * mock (2.7 only) -Glymur's been tested on the following linux platforms without any unexpected +Glymur 0.6 been tested on the following linux platforms without any unexpected difficulties: - * OpenSUSE 12.3 - * Fedora 17, 18, 19 + * OpenSUSE 13.1 + * Fedora 19 * Raspian * Travis CI (currently Ubuntu 12.04?) - * CentOS 6.4 Windows ------- diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index e1a3b3b..75cdbfa 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -13,7 +13,8 @@ some very limited support for reading JPX metadata. For instance, **asoc** and **labl** boxes are recognized, so GMLJP2 metadata can be retrieved from such JPX files. -Glymur works on Python 2.6, 2.7, and 3.3. +Glymur 0.6 works on Python versions 2.7, 3.3 and 3.4. If you have 2.6, you should +use the 0.5 series. OpenJPEG Installation ===================== diff --git a/docs/source/roadmap.rst b/docs/source/roadmap.rst index 1e336f1..c29d4f4 100644 --- a/docs/source/roadmap.rst +++ b/docs/source/roadmap.rst @@ -8,5 +8,3 @@ Here's an incomplete list of what I'd like to focus on in the future. * investigate using CFFI or cython instead of ctypes to wrap openjp2 * investigate adding write support for UUID/XMP boxes (potentially a big project) * investigate JPIP (likely to be an even bigger project) - -Support for Python 2.6 will be dropped with the 0.6.0 release. diff --git a/glymur/__init__.py b/glymur/__init__.py index bf2f625..ba17727 100644 --- a/glymur/__init__.py +++ b/glymur/__init__.py @@ -1,6 +1,7 @@ """glymur - read, write, and interrogate JPEG 2000 files """ import sys +import unittest from glymur import version __version__ = version.version @@ -10,15 +11,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. """ - if sys.hexversion <= 0x02070000: - import unittest2 as unittest - else: - import unittest suite = unittest.defaultTestLoader.discover(__path__[0]) unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 7960c8d..c51dd1b 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -13,6 +13,7 @@ References # pylint: disable=C0302,R0903,R0913 +from collections import OrderedDict import copy import datetime import io @@ -23,16 +24,9 @@ import struct import sys import uuid import warnings +import xml 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: - from xml.etree.cElementTree import ParseError - from collections import OrderedDict - import numpy as np from .codestream import Codestream @@ -2330,10 +2324,10 @@ class XMLBox(Jp2kBox): try: elt = ET.fromstring(text.encode('utf-8')) xml = ET.ElementTree(elt) - except ParseError as parse_error: + except ET.ParseError as err: msg = 'A problem was encountered while parsing an XML box:' msg += '\n\n\t"{0}"\n\nNo XML was retrieved.' - msg = msg.format(str(parse_error)) + msg = msg.format(str(err)) warnings.warn(msg, UserWarning) xml = None diff --git a/glymur/lib/test/test_openjp2.py b/glymur/lib/test/test_openjp2.py index 54d8254..3ca6f55 100644 --- a/glymur/lib/test/test_openjp2.py +++ b/glymur/lib/test/test_openjp2.py @@ -5,17 +5,10 @@ Tests for libopenjp2 wrapping functions. # 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 sys import tempfile - -if sys.hexversion < 0x02070000: - import unittest2 as unittest -else: - import unittest +import unittest import numpy as np diff --git a/glymur/lib/test/test_openjpeg.py b/glymur/lib/test/test_openjpeg.py index 91e6d84..f28656c 100644 --- a/glymur/lib/test/test_openjpeg.py +++ b/glymur/lib/test/test_openjpeg.py @@ -1,19 +1,12 @@ """ 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 - -if sys.hexversion < 0x02070000: - import unittest2 as unittest -else: - import unittest +import unittest import glymur diff --git a/glymur/test/test_callbacks.py b/glymur/test/test_callbacks.py index 722c5da..5b6bd74 100644 --- a/glymur/test/test_callbacks.py +++ b/glymur/test/test_callbacks.py @@ -13,10 +13,7 @@ import sys import tempfile import warnings -if sys.hexversion < 0x02070000: - import unittest2 as unittest -else: - import unittest +import unittest if sys.hexversion <= 0x03030000: from mock import patch diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py index 768bf7f..5edd144 100644 --- a/glymur/test/test_codestream.py +++ b/glymur/test/test_codestream.py @@ -8,18 +8,11 @@ Test suite for codestream parsing. # 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 import tempfile - -if sys.hexversion < 0x02070000: - import unittest2 as unittest -else: - import unittest +import unittest from glymur import Jp2k import glymur diff --git a/glymur/test/test_config.py b/glymur/test/test_config.py index 32f067a..6884d2c 100644 --- a/glymur/test/test_config.py +++ b/glymur/test/test_config.py @@ -15,11 +15,7 @@ import imp import os import sys import tempfile - -if sys.hexversion < 0x02070000: - import unittest2 as unittest -else: - import unittest +import unittest if sys.hexversion <= 0x03030000: from mock import patch diff --git a/glymur/test/test_conformance.py b/glymur/test/test_conformance.py index 5f53b5f..8f4496d 100644 --- a/glymur/test/test_conformance.py +++ b/glymur/test/test_conformance.py @@ -7,18 +7,11 @@ These tests deal with JPX/JP2/J2K images in the format-corpus repository. # 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 re import sys - -if sys.hexversion < 0x02070000: - import unittest2 as unittest -else: - import unittest +import unittest import glymur from glymur import Jp2k diff --git a/glymur/test/test_icc.py b/glymur/test/test_icc.py index 8668999..46d4345 100644 --- a/glymur/test/test_icc.py +++ b/glymur/test/test_icc.py @@ -5,17 +5,10 @@ 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 sys - -if sys.hexversion < 0x02070000: - import unittest2 as unittest -else: - import unittest +import unittest import numpy as np diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 83d656f..d632de8 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -4,9 +4,6 @@ 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 @@ -26,13 +23,9 @@ import tempfile import uuid from uuid import UUID import xml.etree.cElementTree as ET +import unittest import warnings -if sys.hexversion < 0x02070000: - import unittest2 as unittest -else: - import unittest - import numpy as np import glymur @@ -917,7 +910,6 @@ class TestRepr(unittest.TestCase): self.assertEqual(newbox.bits_per_component, (8, 8, 16)) self.assertEqual(newbox.signed, (True, False, True)) - @unittest.skipIf(sys.hexversion < 0x02070000, "Requires 2.7+") def test_xml_box(self): """Verify xml box repr.""" elt = ET.fromstring('0') @@ -948,7 +940,6 @@ class TestRepr(unittest.TestCase): self.assertEqual(box.vendor_feature, newbox.vendor_feature) self.assertEqual(box.vendor_mask, newbox.vendor_mask) - @unittest.skipIf(sys.hexversion < 0x02070000, "Requires 2.7+") def test_uuid_box(self): """Verify uuid repr method.""" uuid_instance = uuid.UUID('00000000-0000-0000-0000-000000000000') @@ -966,7 +957,6 @@ class TestRepr(unittest.TestCase): else: self.assertRegex(repr(box), regexp) - @unittest.skipIf(sys.hexversion < 0x02070000, "Requires 2.7+") def test_contiguous_codestream_box(self): """Verify contiguous codestream box repr method.""" jp2file = glymur.data.nemo() diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 0436c95..8c33be6 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -7,13 +7,9 @@ import os import struct import sys import tempfile +import unittest import warnings -if sys.hexversion < 0x02070000: - import unittest2 as unittest -else: - import unittest - import glymur from glymur import Jp2k diff --git a/glymur/test/test_jp2box_xml.py b/glymur/test/test_jp2box_xml.py index b875188..4950da4 100644 --- a/glymur/test/test_jp2box_xml.py +++ b/glymur/test/test_jp2box_xml.py @@ -5,9 +5,6 @@ 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 @@ -21,6 +18,7 @@ import os import struct import sys import tempfile +import unittest import warnings import xml.etree.cElementTree as ET @@ -34,11 +32,6 @@ if sys.hexversion <= 0x03030000: else: from unittest.mock import patch -if sys.hexversion < 0x02070000: - import unittest2 as unittest -else: - import unittest - import glymur from glymur import Jp2k from glymur.jp2box import ColourSpecificationBox, ContiguousCodestreamBox diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index c664de9..b3704ed 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -17,14 +17,10 @@ import shutil import struct import sys import tempfile +import unittest import uuid from xml.etree import cElementTree as ET -if sys.hexversion < 0x02070000: - import unittest2 as unittest -else: - import unittest - import warnings import numpy as np @@ -47,9 +43,6 @@ def load_tests(loader, tests, ignore): if os.name == "nt": # Can't do it on windows, temporary file issue. return tests - if sys.hexversion < 0x02070000: - # Don't bother with doctests on 2.6 for the time being. - return tests if glymur.lib.openjp2.OPENJP2 is not None: tests.addTests(doctest.DocTestSuite('glymur.jp2k')) return tests diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 97b68e1..abe271a 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -27,16 +27,9 @@ suite. # asserWarns introduced in python 3.2 (python2.7/pylint issue) # pylint: disable=E1101 -# unittest2 is python2.6 only (pylint/python-2.7) -# pylint: disable=F0401 - import re import sys - -if sys.hexversion < 0x02070000: - import unittest2 as unittest -else: - import unittest +import unittest import warnings diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index 3785af9..4b14f4d 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -8,18 +8,11 @@ seem like logical negative tests to add. # R0904: Not too many methods in unittest. # pylint: disable=R0904 -# unittest2 is python2.6 only (pylint/python-2.7) -# pylint: disable=F0401 - import os import re import sys import tempfile - -if sys.hexversion < 0x02070000: - import unittest2 as unittest -else: - import unittest +import unittest import numpy as np diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 2f699e6..22a99e3 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -6,18 +6,11 @@ suite. # R0904: Seems like pylint is fooled in this situation # pylint: disable=R0904,C0103 -# unittest2 is python2.6 only (pylint/python-2.7) -# pylint: disable=F0401 - import os import re import sys import tempfile - -if sys.hexversion < 0x02070000: - import unittest2 as unittest -else: - import unittest +import unittest from .fixtures import read_image, NO_READ_BACKEND, NO_READ_BACKEND_MSG from .fixtures import OPJ_DATA_ROOT, opj_data_file diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index bb7da3e..77b5bb2 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -17,11 +17,7 @@ import sys import tempfile import warnings from xml.etree import cElementTree as ET - -if sys.hexversion < 0x02070000: - import unittest2 as unittest -else: - import unittest +import unittest if sys.hexversion < 0x03000000: from StringIO import StringIO @@ -626,8 +622,6 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) - @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.""" j = glymur.Jp2k(self.jp2file) @@ -699,8 +693,6 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lst) self.assertEqual(actual, expected) - @unittest.skipIf(sys.hexversion < 0x02070000, - "Differences in XML printing between 2.6 and 2.7") @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") def test_xml(self): diff --git a/setup.py b/setup.py index 2228943..92dcb27 100644 --- a/setup.py +++ b/setup.py @@ -20,13 +20,9 @@ instllrqrs = ['numpy>=1.4.1'] if sys.hexversion < 0x03030000: instllrqrs.append('contextlib2>=0.4') instllrqrs.append('mock>=1.0.1') -if sys.hexversion < 0x02070000: - instllrqrs.append('ordereddict>=1.1') - instllrqrs.append('unittest2>=0.5.1') kwargs['install_requires'] = instllrqrs clssfrs = ["Programming Language :: Python", - "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: Implementation :: CPython", From 4edaa7b9e6ebc0012d779a2d056e33b61aba7780 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 5 Feb 2014 15:55:28 -0500 Subject: [PATCH 058/326] Remove dangling 2.6 reference, simplify test script clause. Closes #150 Had left a dangling reference to 2.6 that showed up on travis-ci web page report. The test script section was only different for 2.6. Versions 2.7 and 3.3 are the same (and 3.4 should be the same when it arrives). That section can now be simplified. --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index e55be64..2f45864 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: python python: - - "2.6" - "2.7" - "3.3" @@ -17,8 +16,7 @@ install: # command to run tests script: - - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then python -m unittest discover; fi - - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then python -m unittest discover; fi + - python -m unittest discover notifications: email: "john.g.evans.ne@gmail.com" From f2245e0e05acdfeab4fb7c159c5931d7c606db16 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 5 Feb 2014 15:59:39 -0500 Subject: [PATCH 059/326] Removal of 2.6 --- CHANGES.txt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 324a530..de55628 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,9 +1,10 @@ -Feb 03, 2014 - Added write support for Palette and Component Mapping boxes. - Added read support for JPX free, number list, data reference, fragment - table, and fragment list boxes. Palette box now a 2D numpy array instead - of a list of 1D arrays. JP2 super box constructors now take optional box - list argument. Fixed bug where JPX files with more than one codestream - but advertising jp2 compatibility were not being read. +Feb 03, 2014 - Removed support for Python 2.6. Added write support for + Palette and Component Mapping boxes. Added read support for JPX free, + number list, data reference, fragment table, and fragment list boxes. + Palette box now a 2D numpy array instead of a list of 1D arrays. + JP2 super box constructors now take optional box list argument. + Fixed bug where JPX files with more than one codestream but + advertising jp2 compatibility were not being read. Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask length is unsupported. From 8ce1c72d6d9565589b7d0b63feabc2c8fdb89a3f Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 5 Feb 2014 20:58:32 -0500 Subject: [PATCH 060/326] Added write support for Association and NumberList boxes. Closes #152. --- CHANGES.txt | 9 ++-- glymur/jp2box.py | 37 ++++++++++++-- glymur/jp2k.py | 61 ++++++++++++++++++++++- glymur/test/test_jp2box.py | 2 +- glymur/test/test_jp2box_jpx.py | 88 +++++++++++++++++++++++++++++++++- 5 files changed, 186 insertions(+), 11 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index de55628..0a8b277 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,8 +1,9 @@ Feb 03, 2014 - Removed support for Python 2.6. Added write support for - Palette and Component Mapping boxes. Added read support for JPX free, - number list, data reference, fragment table, and fragment list boxes. - Palette box now a 2D numpy array instead of a list of 1D arrays. - JP2 super box constructors now take optional box list argument. + JP2 Palette and Component Mapping boxes, JPX Association and + NumberList boxes. Added read support for JPX free, number list, + data reference, fragment table, and fragment list boxes. Palette + box now a 2D numpy array instead of a list of 1D arrays. JP2 + super box constructors now take optional box list argument. Fixed bug where JPX files with more than one codestream but advertising jp2 compatibility were not being read. diff --git a/glymur/jp2box.py b/glymur/jp2box.py index c51dd1b..83d768d 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1369,6 +1369,21 @@ class AssociationBox(Jp2kBox): return box + def write(self, fptr): + """Write an association box to file. + """ + # Write the contained boxes, then come back and write the length. + orig_pos = fptr.tell() + fptr.write(struct.pack('>I', 0)) + fptr.write('asoc'.encode()) + for box in self.box: + box.write(fptr) + + end_pos = fptr.tell() + fptr.seek(orig_pos) + fptr.write(struct.pack('>I', end_pos - orig_pos)) + fptr.seek(end_pos) + class JP2HeaderBox(Jp2kBox): """Container for JP2 header box information. @@ -2180,15 +2195,19 @@ class NumberListBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) for j, association in enumerate(self.associations): + msg += '\n Association[{0}]: '.format(j) if association == 0: - msg += '\n Association[{0}]: the rendered result'.format(j) + msg += 'the rendered result' elif (association >> 24) == 1: idx = association & 0x00FFFFFF - msg += '\n Association[{0}]: Codestream {0} '.format(idx) + msg += 'Codestream {0}' + msg = msg.format(idx) elif (association >> 24) == 2: idx = association & 0x00FFFFFF - msg += '\n Association[{0}]: Compositing Layer {0}' - msg = msg.format(idx) + msg += 'Compositing Layer {0}' + msg = msg.format(j, idx) + else: + msg += 'unrecognized' return msg def __repr__(self): @@ -2219,6 +2238,16 @@ class NumberListBox(Jp2kBox): box = NumberListBox(lst, length=length, offset=offset) return box + def write(self, fptr): + """Write a NumberList box to file. + """ + fptr.write(struct.pack('>I', len(self.associations) * 4 + 8)) + fptr.write(self.box_id.encode()) + + fmt = '>' + 'I' * len(self.associations) + write_buffer = struct.pack(fmt, *self.associations) + fptr.write(write_buffer) + class XMLBox(Jp2kBox): """Container for XML box information. diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 3fd9d21..ea99a44 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -37,6 +37,10 @@ from .lib import openjp2 as opj2 from . import version from .lib import c as libc +JP2_IDS = ['colr', 'cdef', 'cmap', 'jp2c', 'ftyp', 'ihdr', 'jp2h', 'jP ', + 'pclr', 'res ', 'resc', 'resd', 'xml ', 'ulst', 'uinf', 'url ', + 'uuid'] +JPX_IDS = ['asoc', 'nlst'] class Jp2k(Jp2kBox): """JPEG 2000 file. @@ -610,7 +614,7 @@ class Jp2k(Jp2kBox): jp2c = [box for box in self.box if box.box_id == 'jp2c'] jp2c = jp2c[0] - ofile.write(struct.pack('>I', jp2c.length + 8)) + ofile.write(struct.pack('>I', jp2c.length)) ofile.write('jp2c'.encode()) with open(self.filename, 'rb') as ifile: # Seek 8 bytes past the L, T fields to get to the @@ -1170,6 +1174,61 @@ def _validate_jp2_box_sequence(boxes): msg = "All color channels must be defined in the " msg += "channel definition box." raise IOError(msg) + + # The compatibility list must contain at a minimum 'jp2 '. + if 'jp2 ' not in boxes[1].compatibility_list: + msg = "The ftyp box must contain 'jp2 ' in the compatibility list." + raise IOError(msg) + + # JPX checks. + _asoc_check(boxes) + _jpx_brand(boxes, boxes[1].brand) + _jpx_compatibility(boxes, boxes[1].compatibility_list) + +def _jpx_brand(boxes, brand): + """ + If there is a JPX box then the brand must be 'jpx '. + """ + for box in boxes: + if box.box_id in JPX_IDS: + if brand != 'jpx ': + msg = "A JPX box requires that the file type box brand be " + msg += "'jpx '." + raise RuntimeError(msg) + if hasattr(box, 'box') != 0: + # Same set of checks on any child boxes. + _jpx_brand(box.box, brand) + +def _jpx_compatibility(boxes, compatibility_list): + """ + If there is a JPX box then the compatibility list must also contain 'jpx '. + """ + for box in boxes: + if box.box_id in JPX_IDS: + if 'jpx ' not in compatibility_list: + msg = "A JPX box requires that 'jpx ' be present in the " + msg += "ftype compatibility list." + raise RuntimeError(msg) + if hasattr(box, 'box') != 0: + # Same set of checks on any child boxes. + _jpx_compatibility(box.box, compatibility_list) + + +def _asoc_check(boxes): + """ + Association boxes can only contain number list boxes and xml boxes, as far + as we know. + """ + for box in boxes: + if box.box_id == 'asoc': + if box.box[0].box_id != 'nlst' or box.box[1].box_id != 'xml ': + msg = "An Association box can only contain a NumberList box " + msg += "followed by an XML box." + raise RuntimeError(msg) + if hasattr(box, 'box') != 0: + # Same set of checks on any child boxes. + _asoc_check(box.box) + def extract_image_cube(image): diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index d632de8..0a3b24a 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -553,7 +553,7 @@ class TestWrap(unittest.TestCase): self.verify_wrapped_raw(tfile.name) @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") - def test_palette(self): + def test_jpx_to_jp2(self): """basic test for rewrapping a jpx file""" with warnings.catch_warnings(): # This file has a rreq mask length that we do not recognize. diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 8c33be6..dbbabb7 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -9,14 +9,100 @@ import sys import tempfile import unittest import warnings +import xml.etree.cElementTree as ET import glymur from glymur import Jp2k +@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") +class TestJPXWrap(unittest.TestCase): + """Test suite for wrapping JPX files.""" + + def setUp(self): + self.jp2file = glymur.data.nemo() + + raw_xml = b""" + + + 1 + 2008 + 141100 + + + + """ + with tempfile.NamedTemporaryFile(suffix=".xml", delete=False) as tfile: + tfile.write(raw_xml) + tfile.flush() + self.xmlfile = tfile.name + + def tearDown(self): + os.unlink(self.xmlfile) + + def test_association_box(self): + """Wrap JP2 to JPX with asoc(nlst, xml)""" + jp2 = Jp2k(self.jp2file) + boxes = [jp2.box[idx] for idx in [0, 1, 2, 5]] + + # The ftyp box must be modified to jpx with jp2 compatibility. + boxes[1].brand = 'jpx ' + boxes[1].compatibility_list = ['jp2 ', 'jpx '] + + numbers = (0, 1) + nlst = glymur.jp2box.NumberListBox(numbers) + the_xml = ET.fromstring('0') + xmlb = glymur.jp2box.XMLBox(xml=the_xml) + asoc = glymur.jp2box.AssociationBox([nlst, xmlb]) + boxes.append(asoc) + + with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: + jpx = jp2.wrap(tfile.name, boxes=boxes) + + self.assertEqual(jpx.box[1].compatibility_list, ['jp2 ', 'jpx ']) + self.assertEqual(jpx.box[-1].box_id, 'asoc') + self.assertEqual(jpx.box[-1].box[0].box_id, 'nlst') + self.assertEqual(jpx.box[-1].box[1].box_id, 'xml ') + self.assertEqual(jpx.box[-1].box[0].associations, numbers) + self.assertEqual(ET.tostring(jpx.box[-1].box[1].xml.getroot()), + b'0') + + def test_jp2_to_jpx_sans_jp2_compatibility(self): + """jp2 wrapped to jpx not including jp2 compatibility is wrong.""" + jp2 = Jp2k(self.jp2file) + boxes = [jp2.box[idx] for idx in [0, 1, 2, 5]] + boxes[1].compatibility_list.append('jp2 ') + numbers = [0, 1] + nlst = glymur.jp2box.NumberListBox(numbers) + the_xml = ET.fromstring('0') + xmlb = glymur.jp2box.XMLBox(xml=the_xml) + asoc = glymur.jp2box.AssociationBox([nlst, xmlb]) + boxes.append(asoc) + + with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: + with self.assertRaises(RuntimeError): + jpx = jp2.wrap(tfile.name, boxes=boxes) + + def test_jp2_to_jpx_sans_jpx_brand(self): + """Verify error when jp2 wrapped to jpx does not include jpx brand.""" + jp2 = Jp2k(self.jp2file) + boxes = [jp2.box[idx] for idx in [0, 1, 2, 5]] + boxes[1].brand = 'jpx ' + numbers = [0, 1] + nlst = glymur.jp2box.NumberListBox(numbers) + the_xml = ET.fromstring('0') + xmlb = glymur.jp2box.XMLBox(xml=the_xml) + asoc = glymur.jp2box.AssociationBox([nlst, xmlb]) + boxes.append(asoc) + + with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: + with self.assertRaises(RuntimeError): + jpx = jp2.wrap(tfile.name, boxes=boxes) + + @unittest.skipIf(sys.hexversion < 0x03000000, "Warning assert on 2.x.") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") -class TestJPXOther(unittest.TestCase): +class TestJPX(unittest.TestCase): """Test suite for other JPX boxes.""" def setUp(self): From 3fc782b6fd17226705c86688ed252eda9c396915 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 6 Feb 2014 10:14:10 -0500 Subject: [PATCH 061/326] Refactored superbox writing. Closes #153. At the moment this only affects jp2s and asoc superboxes for now. --- glymur/jp2box.py | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 83d768d..ca1d973 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -88,6 +88,26 @@ class Jp2kBox(object): msg = "Not supported for {0} box.".format(self.longname) raise NotImplementedError(msg) + def _write_superbox(self, fptr): + """Write a superbox. + + Parameters + ---------- + fptr : file or file object + Superbox (box of boxes) to be written to this file. + """ + # Write the contained boxes, then come back and write the length. + orig_pos = fptr.tell() + fptr.write(struct.pack('>I', 0)) + fptr.write(self.box_id.encode()) + for box in self.box: + box.write(fptr) + + end_pos = fptr.tell() + fptr.seek(orig_pos) + fptr.write(struct.pack('>I', end_pos - orig_pos)) + fptr.seek(end_pos) + def parse_superbox(self, fptr): """Parse a superbox (box consisting of nothing but other boxes. @@ -1372,17 +1392,7 @@ class AssociationBox(Jp2kBox): def write(self, fptr): """Write an association box to file. """ - # Write the contained boxes, then come back and write the length. - orig_pos = fptr.tell() - fptr.write(struct.pack('>I', 0)) - fptr.write('asoc'.encode()) - for box in self.box: - box.write(fptr) - - end_pos = fptr.tell() - fptr.seek(orig_pos) - fptr.write(struct.pack('>I', end_pos - orig_pos)) - fptr.seek(end_pos) + self._write_superbox(fptr) class JP2HeaderBox(Jp2kBox): @@ -1424,17 +1434,7 @@ class JP2HeaderBox(Jp2kBox): def write(self, fptr): """Write a JP2 Header box to file. """ - # Write the contained boxes, then come back and write the length. - orig_pos = fptr.tell() - fptr.write(struct.pack('>I', 0)) - fptr.write('jp2h'.encode()) - for box in self.box: - box.write(fptr) - - end_pos = fptr.tell() - fptr.seek(orig_pos) - fptr.write(struct.pack('>I', end_pos - orig_pos)) - fptr.seek(end_pos) + self._write_superbox(fptr) @staticmethod def parse(fptr, offset, length): From 1e3556e9017394d5b187ccad493185b8a6b7620a Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 6 Feb 2014 20:45:26 -0500 Subject: [PATCH 062/326] Added write support for DataEntryURL box. Closes #154 --- CHANGES.txt | 15 ++++++++------- glymur/jp2box.py | 12 ++++++++++++ glymur/test/test_jp2box.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 0a8b277..25dc20c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,11 +1,12 @@ Feb 03, 2014 - Removed support for Python 2.6. Added write support for - JP2 Palette and Component Mapping boxes, JPX Association and - NumberList boxes. Added read support for JPX free, number list, - data reference, fragment table, and fragment list boxes. Palette - box now a 2D numpy array instead of a list of 1D arrays. JP2 - super box constructors now take optional box list argument. - Fixed bug where JPX files with more than one codestream but - advertising jp2 compatibility were not being read. + JP2 DataEntryURL, Palette and Component Mapping boxes, JPX + Association and NumberList boxes. Added read support for JPX + free, number list, data reference, fragment table, and fragment + list boxes. Palette box now a 2D numpy array instead of a list + of 1D arrays. JP2 super box constructors now take optional box + list argument. Fixed bug where JPX files with more than one + codestream but advertising jp2 compatibility were not being + read. Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask length is unsupported. diff --git a/glymur/jp2box.py b/glymur/jp2box.py index ca1d973..47cfa30 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2518,6 +2518,18 @@ class DataEntryURLBox(Jp2kBox): self.length = length self.offset = offset + def write(self, fptr): + """Write a data entry url box to file. + """ + length = 8 + 1 + 3 + len(self.url.encode()) + write_buffer = struct.pack('>I4sBBBB', + length, self.box_id.encode(), + self.version, + self.flag[0], self.flag[1], self.flag[2]) + fptr.write(write_buffer) + fptr.write(self.url.encode()) + + def __repr__(self): msg = "glymur.jp2box.DataEntryURLBox({0}, {1}, '{2}')" msg = msg.format(self.version, self.flag, self.url) diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 0a3b24a..a4930a9 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -52,6 +52,34 @@ def load_tests(loader, tests, ignore): tests.addTests(doctest.DocTestSuite('glymur.jp2box')) return tests +@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") +class TestDataEntryURL(unittest.TestCase): + """Test suite for DataEntryURL boxes.""" + def setUp(self): + self.jp2file = glymur.data.nemo() + + def test_basic_url(self): + """Just your most basic URL box.""" + # Wrap our j2k file in a JP2 box along with an interior url box. + jp2 = Jp2k(self.jp2file) + + url = 'http://glymur.readthedocs.org' + deurl = glymur.jp2box.DataEntryURLBox(0, (0, 0, 0), url) + boxes = [box for box in jp2.box if box.box_id != 'uuid'] + boxes.append(deurl) + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + jp22 = jp2.wrap(tfile.name, boxes=boxes) + import shutil + shutil.copyfile(tfile.name, '/Users/jevans/a.jp2') + + actdata = [box.box_id for box in jp22.box] + expdata = ['jP ', 'ftyp', 'jp2h', 'jp2c', 'url '] + self.assertEqual(actdata, expdata) + self.assertEqual(jp22.box[4].version, 0) + self.assertEqual(jp22.box[4].flag, (0, 0, 0)) + self.assertEqual(jp22.box[4].url, url) + + @unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2 or OPENJP2_IS_V2_OFFICIAL, "Not supported until 2.0+.") From 03ea0f4b54ec77eb8db6a6089ab7a79ea693ff2f Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 6 Feb 2014 21:11:42 -0500 Subject: [PATCH 063/326] Removed test artifact. Closes #154 --- glymur/test/test_jp2box.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index a4930a9..c647bd2 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -69,8 +69,6 @@ class TestDataEntryURL(unittest.TestCase): boxes.append(deurl) with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: jp22 = jp2.wrap(tfile.name, boxes=boxes) - import shutil - shutil.copyfile(tfile.name, '/Users/jevans/a.jp2') actdata = [box.box_id for box in jp22.box] expdata = ['jP ', 'ftyp', 'jp2h', 'jp2c', 'url '] From fcbc8c52b4ab3ba9af197918b04b5425a19cecc5 Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 7 Feb 2014 16:52:28 -0500 Subject: [PATCH 064/326] Added new error message for case when wrapping jp2h box is empty. Closes #155 --- glymur/jp2k.py | 6 ++++++ glymur/test/test_jp2box.py | 20 ++++++++++++-------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index ea99a44..732a79f 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -1132,6 +1132,12 @@ def _validate_jp2_box_sequence(boxes): msg = "The codestream box must be preceeded by a jp2 header box." raise IOError(msg) + # 1st jp2 header box cannot be empty. + jp2h = boxes[jp2h_idx] + if len(jp2h.box) == 0: + msg = "The JP2 header superbox cannot be empty." + raise IOError(msg) + # 1st jp2 header box must be ihdr jp2h = boxes[jp2h_idx] if jp2h.box[0].box_id != 'ihdr': diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index c647bd2..831c4c6 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -510,6 +510,7 @@ class TestAppend(unittest.TestCase): jp2.append(uuidbox) +@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") class TestWrap(unittest.TestCase): """Tests for wrap method.""" @@ -570,7 +571,6 @@ class TestWrap(unittest.TestCase): self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) self.assertIsNone(jp2.box[2].box[1].icc_profile) - @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) @@ -578,7 +578,6 @@ class TestWrap(unittest.TestCase): j2k.wrap(tfile.name) self.verify_wrapped_raw(tfile.name) - @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_jpx_to_jp2(self): """basic test for rewrapping a jpx file""" with warnings.catch_warnings(): @@ -602,7 +601,6 @@ class TestWrap(unittest.TestCase): for j, offset in enumerate(expected_offsets): self.assertEqual(jp2.box[j].offset, offset) - @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) @@ -611,7 +609,17 @@ class TestWrap(unittest.TestCase): boxes = [box.box_id for box in jp2.box] self.assertEqual(boxes, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") + def test_empty_jp2h(self): + """JP2H box list cannot be empty.""" + jp2 = Jp2k(self.jp2file) + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + boxes = jp2.box + # Right here the jp2h superbox has two child boxes. Empty out that + # list to trigger the error. + boxes[2].box = [] + with self.assertRaises(IOError): + jp22 = jp2.wrap(tfile.name, boxes=boxes) + def test_default_layout_with_boxes(self): """basic test for rewrapping a jp2 file, boxes specified""" j2k = Jp2k(self.j2kfile) @@ -631,7 +639,6 @@ class TestWrap(unittest.TestCase): j2k.wrap(tfile.name, boxes=boxes) self.verify_wrapped_raw(tfile.name) - @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_ihdr_not_first_in_jp2h(self): """The specification says that ihdr must be the first box in jp2h.""" j2k = Jp2k(self.j2kfile) @@ -651,7 +658,6 @@ class TestWrap(unittest.TestCase): with self.assertRaises(IOError): j2k.wrap(tfile.name, boxes=boxes) - @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_first_boxes_jp_and_ftyp(self): """first two boxes must be jP followed by ftyp""" j2k = Jp2k(self.j2kfile) @@ -673,7 +679,6 @@ class TestWrap(unittest.TestCase): 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) @@ -695,7 +700,6 @@ class TestWrap(unittest.TestCase): 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) From 14743d6633d02c60384816981db6ced0828a72e3 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 8 Feb 2014 14:56:50 -0500 Subject: [PATCH 065/326] Added write support for dtbl boxes. --- CHANGES.txt | 16 ++++---- glymur/jp2box.py | 21 ++++++++++ glymur/jp2k.py | 52 +++++++++++++++++++++++ glymur/test/test_jp2box_jpx.py | 75 +++++++++++++++++++++------------- 4 files changed, 128 insertions(+), 36 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 25dc20c..bc823bd 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,12 +1,12 @@ -Feb 03, 2014 - Removed support for Python 2.6. Added write support for +Feb 08, 2014 - Removed support for Python 2.6. Added write support for JP2 DataEntryURL, Palette and Component Mapping boxes, JPX - Association and NumberList boxes. Added read support for JPX - free, number list, data reference, fragment table, and fragment - list boxes. Palette box now a 2D numpy array instead of a list - of 1D arrays. JP2 super box constructors now take optional box - list argument. Fixed bug where JPX files with more than one - codestream but advertising jp2 compatibility were not being - read. + Association, NumberList and DataReference boxes. Added read + support for JPX free, number list, data reference, fragment + table, and fragment list boxes. Palette box now a 2D numpy + array instead of a list of 1D arrays. JP2 super box constructors + now take optional box list argument. Fixed bug where JPX files + with more than one codestream but advertising jp2 compatibility + were not being read. Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask length is unsupported. diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 47cfa30..d535af9 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -863,6 +863,27 @@ class DataReferenceBox(Jp2kBox): self.length = length self.offset = offset + def write(self, fptr): + """Write a Data Reference box to file. + """ + + # Very similar to the say a superbox is written. + orig_pos = fptr.tell() + fptr.write(struct.pack('>I', 0)) + fptr.write(self.box_id.encode()) + + # Write the number of data entry url boxes. + write_buffer = struct.pack('>H', len(self.DR)) + fptr.write(write_buffer) + + for box in self.DR: + box.write(fptr) + + end_pos = fptr.tell() + fptr.seek(orig_pos) + fptr.write(struct.pack('>I', end_pos - orig_pos)) + fptr.seek(end_pos) + def __str__(self): msg = Jp2kBox.__str__(self) for box in self.DR: diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 732a79f..f9e1030 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -16,6 +16,7 @@ if sys.hexversion >= 0x03030000: else: from contextlib2 import ExitStack +from collections import Counter import ctypes import math import os @@ -1190,6 +1191,57 @@ def _validate_jp2_box_sequence(boxes): _asoc_check(boxes) _jpx_brand(boxes, boxes[1].brand) _jpx_compatibility(boxes, boxes[1].compatibility_list) + _check_for_singletons(boxes) + _check_top_level(boxes) + +def _collect_box_count(boxes): + """Count the occurences of each box type.""" + count = Counter([box.box_id for box in boxes]) + + # Add the counts in the superboxes. + for box in boxes: + if hasattr(box, 'box'): + count.update(_collect_box_count(box.box)) + + return count + +TOP_LEVEL_ONLY_BOXES = set(['dtbl']) + +def _check_superbox_for_top_levels(boxes): + """Several boxes can only occur at the top level.""" + # We are only looking at the boxes contained in a superbox, so if any of + # the blacklisted boxes show up here, it's an error. + box_ids = set([box.box_id for box in boxes]) + intersection = box_ids.intersection(TOP_LEVEL_ONLY_BOXES) + if len(intersection) > 0: + msg = "A '{0}' box cannot be nested in a superbox." + raise IOError(msg.format(list(intersection)[0])) + + # Recursively check any contained superboxes. + for box in boxes: + if hasattr(box, 'box'): + _check_superbox_for_top_levels(box.box) + +def _check_top_level(boxes): + """Several boxes can only occur at the top level.""" + # Add the counts in the superboxes. + for box in boxes: + if hasattr(box, 'box'): + _check_superbox_for_top_levels(box.box) + + count = _collect_box_count(boxes) + # Which boxes occur more than once? + multiples = [box_id for box_id, bcount in count.items() if bcount > 1] + if 'dtbl' in multiples: + raise IOError('There can only be one dtbl box in a file.') + +def _check_for_singletons(boxes): + """Several boxes can only occur once.""" + count = _collect_box_count(boxes) + # Which boxes occur more than once? + multiples = [box_id for box_id, bcount in count.items() if bcount > 1] + if 'dtbl' in multiples: + raise IOError('There can only be one dtbl box in a file.') def _jpx_brand(boxes, brand): """ diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index dbbabb7..39151ba 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -67,6 +67,41 @@ class TestJPXWrap(unittest.TestCase): self.assertEqual(ET.tostring(jpx.box[-1].box[1].xml.getroot()), b'0') + def test_only_one_data_reference(self): + """Data reference boxes cannot be inside a superbox .""" + jp2 = Jp2k(self.jp2file) + boxes = [jp2.box[idx] for idx in [0, 1, 2, 5]] + + flag = 0 + version = (0, 0, 0) + url = 'file:////usr/local/bin' + deurl = glymur.jp2box.DataEntryURLBox(flag, version, url) + dref = glymur.jp2box.DataReferenceBox([deurl]) + boxes.append(dref) + boxes.append(dref) + + with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: + with self.assertRaises(IOError): + jpx = jp2.wrap(tfile.name, boxes=boxes) + + def test_data_reference_not_at_top_level(self): + """Data reference boxes cannot be inside a superbox .""" + jp2 = Jp2k(self.jp2file) + boxes = [jp2.box[idx] for idx in [0, 1, 2, 5]] + + flag = 0 + version = (0, 0, 0) + url = 'file:////usr/local/bin' + deurl = glymur.jp2box.DataEntryURLBox(flag, version, url) + dref = glymur.jp2box.DataReferenceBox([deurl]) + + # Put it inside the jp2 header box. + boxes[2].box.append(dref) + + with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: + with self.assertRaises(IOError): + jpx = jp2.wrap(tfile.name, boxes=boxes) + def test_jp2_to_jpx_sans_jp2_compatibility(self): """jp2 wrapped to jpx not including jp2 compatibility is wrong.""" jp2 = Jp2k(self.jp2file) @@ -133,46 +168,30 @@ class TestJPX(unittest.TestCase): def test_dtbl(self): """Verify that we can interpret Data Reference boxes.""" # Copy the existing JPX file, add a data reference box onto the end. + flag = 0 + version = (0, 0, 0) + url1 = 'file:////usr/local/bin' + url2 = 'http://glymur.readthedocs.org' + chr(0) * 3 with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile: with open(self.jpxfile, 'rb') as ifile: tfile.write(ifile.read()) - # 8 + 2 + 20 + 36 - boxlen = 66 - write_buffer = struct.pack('>I4s', boxlen, b'dtbl') - tfile.write(write_buffer) - - # Just two boxes. - write_buffer = struct.pack('>H', 2) - tfile.write(write_buffer) - - # First data entry url box. - # This one will have a URL with 3 null chars at the end. - # They should be stripped off. - write_buffer = struct.pack('>I4s', 36, b'url ') - tfile.write(write_buffer) - url1 = 'file:////usr/local/bin' - write_buffer = struct.pack('>BBBB24s', 0, 0, 0, 0, - (url1 + chr(0) * 3).encode()) - tfile.write(write_buffer) - - # 2nd data entry url box. - write_buffer = struct.pack('>I4s', 20, b'url ') - tfile.write(write_buffer) - url2 = 'file:///' - write_buffer = struct.pack('>BBBB8s', 0, 0, 0, 0, url2.encode()) - tfile.write(write_buffer) + deurl1 = glymur.jp2box.DataEntryURLBox(flag, version, url1) + deurl2 = glymur.jp2box.DataEntryURLBox(flag, version, url2) + dref = glymur.jp2box.DataReferenceBox([deurl1, deurl2]) + dref.write(tfile) tfile.flush() - with self.assertWarns(UserWarning): + with warnings.catch_warnings(): + # This file has a rreq mask length that we do not recognize. + warnings.simplefilter("ignore") jpx = Jp2k(tfile.name) self.assertEqual(jpx.box[-1].box_id, 'dtbl') self.assertEqual(len(jpx.box[-1].DR), 2) self.assertEqual(jpx.box[-1].DR[0].url, url1) - self.assertEqual(jpx.box[-1].DR[1].url, url2) - + self.assertEqual(jpx.box[-1].DR[1].url, url2.rstrip('\0')) def test_ftbl(self): """Verify that we can interpret Fragment Table boxes.""" From 321a2544646495a159e45605f12769c6941c935d Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 8 Feb 2014 19:52:39 -0500 Subject: [PATCH 066/326] Completed XMP description. --- docs/source/how_do_i.rst | 72 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 7 deletions(-) diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index b0b476c..c1a904c 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -262,7 +262,7 @@ following 'Google' But that would be painful. A better solution is to install the Python XMP -Toolkit:: +Toolkit (make sure it is version 2.0):: >>> from libxmp import XMPMeta >>> from libxmp.consts import XMP_NS_XMP as NS_XAP @@ -285,19 +285,77 @@ http://photojournal.jpl.nasa.gov/tiff/PIA17145.tif info JPEG 2000:: Next you can extract the XMP metadata. >>> from libxmp import XMPFiles - >>> xf = XMPFile() + >>> xf = XMPFiles() >>> xf.open_file('PIA17145.tif') >>> xmp = xf.get_xmp() + >>> print(xmp) + + + + + 1016 + 1016 + + + 8 + + + 1 + 1 + 1 + 1 + 2 + + + + + converted PNM file + + + + + + If you are familiar with TIFF, you can verify that there's no XMP tag in the TIFF file, but the Python XMP Toolkit takes advantage of the TIFF header structure to populate an XMP packet for you. If you were working with a JPEG file with Exif metadata, that information would be included in the XMP packet -as well. Now you can append the XMP packet in a UUIDBox:: +as well. Now you can append the XMP packet in a UUIDBox. In order to do this, +though, you have to know the UUID that signifies XMP data.:: >>> import uuid - >>> the_uuid = uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac') - >>> box = glymur.jp2box.UUIDBox(uuid, str(xmp).encode()) + >>> xmp_uuid = uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac') + >>> box = glymur.jp2box.UUIDBox(xmp_uuid, str(xmp).encode()) >>> jp2.append(box) - - + >>> print(jp2.box[-1]) + UUID Box (uuid) @ (592316, 1053) + UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) + UUID Data: + + + + 1016 + 1016 + + + 8 + + + 1 + 1 + 1 + 1 + 2 + + + + + converted PNM file + + + + + From 4fafaa8661fe4352325a1577fbaf0dd4437e8e75 Mon Sep 17 00:00:00 2001 From: jevans Date: Sun, 9 Feb 2014 12:51:28 -0500 Subject: [PATCH 067/326] Added set_printoptions, get_printoptions functions. Closes #158 --- CHANGES.txt | 9 +- glymur/__init__.py | 1 + glymur/jp2box.py | 183 ++++++++++++++++--- glymur/test/fixtures.py | 332 +++++++++++++++++++++++++++++++++++ glymur/test/test_printing.py | 162 +++++++---------- 5 files changed, 557 insertions(+), 130 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index f70550e..f6c3728 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,10 +1,11 @@ -Feb 08, 2014 - Removed support for Python 2.6. Added write support for JP2 +Feb 09, 2014 - Removed support for Python 2.6. Added write support for JP2 UUID, DataEntryURL, Palette and Component Mapping boxes, JPX Association, NumberList and DataReference boxes. Added read support for JPX free, number list, data reference, fragment - table, and fragment list boxes. Palette box now a 2D numpy - array instead of a list of 1D arrays. JP2 super box constructors - now take optional box list argument. Fixed bug where JPX files + table, and fragment list boxes. Added get_printoptions, + set_printoptions functions. Palette box now a 2D numpy array + instead of a list of 1D arrays. JP2 super box constructors now + take optional box list argument. Fixed bug where JPX files with more than one codestream but advertising jp2 compatibility were not being read. diff --git a/glymur/__init__.py b/glymur/__init__.py index ba17727..5826f8c 100644 --- a/glymur/__init__.py +++ b/glymur/__init__.py @@ -8,6 +8,7 @@ __version__ = version.version from .jp2k import Jp2k from .jp2dump import jp2dump +from .jp2box import get_printoptions, set_printoptions from . import data diff --git a/glymur/jp2box.py b/glymur/jp2box.py index f8a0dc8..d0b6c07 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -14,9 +14,7 @@ References # pylint: disable=C0302,R0903,R0913 from collections import OrderedDict -import copy import datetime -import io import math import os import pprint @@ -24,7 +22,6 @@ import struct import sys import uuid import warnings -import xml import xml.etree.cElementTree as ET import numpy as np @@ -244,6 +241,8 @@ class ColourSpecificationBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg msg += '\n Method: {0}'.format(_METHOD_DISPLAY[self.method]) msg += '\n Precedence: {0}'.format(self.precedence) @@ -501,6 +500,9 @@ class ChannelDefinitionBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + for j in range(len(self.association)): color_type_string = _COLOR_TYPE_MAP_DISPLAY[self.channel_type[j]] if self.association[j] == 0: @@ -591,6 +593,9 @@ class CodestreamHeaderBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + for box in self.box: boxstr = str(box) @@ -655,6 +660,9 @@ class CompositingLayerHeaderBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + for box in self.box: boxstr = str(box) @@ -727,6 +735,9 @@ class ComponentMappingBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + for k in range(len(self.component_index)): if self.mapping_type[k] == 1: @@ -746,7 +757,7 @@ class ComponentMappingBox(Jp2kBox): fptr.write(write_buffer) for j in range(len(self.component_index)): - write_buffer = struct.pack('>HBB', + write_buffer = struct.pack('>HBB', self.component_index[j], self.mapping_type[j], self.palette_index[j]) @@ -812,6 +823,11 @@ class ContiguousCodestreamBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + if _printoptions['codestream'] == False: + return msg + msg += '\n Main header:' for segment in self.main_header.segment: segstr = str(segment) @@ -889,6 +905,9 @@ class DataReferenceBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + for box in self.DR: msg += '\n ' + str(box) return msg @@ -976,7 +995,11 @@ class FileTypeBox(Jp2kBox): return msg def __str__(self): - lst = [Jp2kBox.__str__(self), + msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + + lst = [msg, ' Brand: {0}', ' Compatibility: {1}'] msg = '\n'.join(lst) @@ -1067,6 +1090,9 @@ class FragmentListBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + for j in range(len(self.fragment_offset)): msg += "\n Offset {0}: {1}" msg += "\n Fragment Length {2}: {3}" @@ -1092,13 +1118,13 @@ class FragmentListBox(Jp2kBox): Returns ------- - FreeBox instance + FragmentListBox instance """ read_buffer = fptr.read(2) - nf, = struct.unpack('>H', read_buffer) + num_fragments, = struct.unpack('>H', read_buffer) - read_buffer = fptr.read(nf * 14) - lst = struct.unpack('>' + 'QIH' * nf, read_buffer) + read_buffer = fptr.read(num_fragments * 14) + lst = struct.unpack('>' + 'QIH' * num_fragments, read_buffer) frag_offset = lst[0::3] frag_len = lst[1::3] data_reference = lst[2::3] @@ -1131,6 +1157,9 @@ class FragmentTableBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + for box in self.box: boxstr = str(box) @@ -1191,6 +1220,9 @@ class FreeBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + return msg @staticmethod @@ -1280,6 +1312,10 @@ class ImageHeaderBox(Jp2kBox): return msg def __str__(self): + msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + msg = "{0}" msg += '\n Size: [{1} {2} {3}]' msg += '\n Bitdepth: {4}' @@ -1380,6 +1416,9 @@ class AssociationBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + for box in self.box: boxstr = str(box) @@ -1513,6 +1552,9 @@ class JPEG2000SignatureBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + msg += '\n Signature: {0:02x}{1:02x}{2:02x}{3:02x}' msg = msg.format(self.signature[0], self.signature[1], self.signature[2], self.signature[3]) @@ -1584,16 +1626,15 @@ class PaletteBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + msg += '\n Size: ({0} x {1})'.format(*self.palette.shape) return msg def write(self, fptr): """Write a Palette box to file. """ - # Box length is usual header (8) - # + num entries NE (2) + num columns NC (1) - # + (bps/8, /signed) for each column (3) + bps * NC - # + bytes_per_row = sum(self.bits_per_component) / 8 bytes_per_palette = bytes_per_row * self.palette.shape[0] box_length = 8 + 3 + self.palette.shape[1] + bytes_per_palette @@ -1608,7 +1649,7 @@ class PaletteBox(Jp2kBox): fptr.write(write_buffer) bps_signed = [x - 1 for x in self.bits_per_component] - for j, item in enumerate(bps_signed): + for j, _ in enumerate(bps_signed): if self.signed[j]: bps_signed[j] |= 0x80 write_buffer = struct.pack('>' + 'B' * self.palette.shape[1], @@ -1616,13 +1657,10 @@ class PaletteBox(Jp2kBox): fptr.write(write_buffer) if self.bits_per_component[0] <= 8: - dtype = np.uint8 code = 'B' elif self.bits_per_component[0] <= 16: - dtype = np.uint16 code = 'H' elif self.bits_per_component[0] <= 32: - dtype = np.uint32 code = 'I' fmt = '>' + code * self.palette.shape[1] @@ -1820,6 +1858,9 @@ class ReaderRequirementsBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + msg += '\n Standard Features:' for j in range(len(self.standard_flag)): @@ -2044,6 +2085,9 @@ class CaptureResolutionBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + msg += '\n VCR: {0}'.format(self.vertical_resolution) msg += '\n HCR: {0}'.format(self.horizontal_resolution) return msg @@ -2106,6 +2150,9 @@ class DisplayResolutionBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + msg += '\n VDR: {0}'.format(self.vertical_resolution) msg += '\n HDR: {0}'.format(self.horizontal_resolution) return msg @@ -2162,6 +2209,9 @@ class LabelBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + msg += '\n Label: {0}'.format(self.label) return msg @@ -2218,6 +2268,9 @@ class NumberListBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + for j, association in enumerate(self.associations): msg += '\n Association[{0}]: '.format(j) if association == 0: @@ -2315,6 +2368,11 @@ class XMLBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + if _printoptions['xml'] == False: + return msg + xml = self.xml if self.xml is not None: msg += _pretty_print_xml(self.xml) @@ -2416,9 +2474,12 @@ class UUIDListBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + for j, uuid_item in enumerate(self.ulst): msg += '\n UUID[{0}]: {1}'.format(j, uuid_item) - return(msg) + return msg @staticmethod def parse(fptr, offset, length): @@ -2446,7 +2507,7 @@ class UUIDListBox(Jp2kBox): ulst.append(uuid.UUID(bytes=read_buffer)) box = UUIDListBox(ulst, length=length, offset=offset) - return(box) + return box class UUIDInfoBox(Jp2kBox): @@ -2477,7 +2538,6 @@ class UUIDInfoBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - for box in self.box: box_str = str(box) @@ -2485,7 +2545,7 @@ class UUIDInfoBox(Jp2kBox): lst = [('\n ' + x) for x in box_str.split('\n')] msg += ''.join(lst) - return(msg) + return msg @staticmethod def parse(fptr, offset, length): @@ -2561,6 +2621,9 @@ class DataEntryURLBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + msg += '\n ' lines = ['Version: {0}', @@ -2671,15 +2734,30 @@ class UUIDBox(Jp2kBox): return msg.format(repr(self.uuid), len(self.raw_data)) def __str__(self): - msg = '{0}\n UUID: {1}'.format(Jp2kBox.__str__(self), self.uuid) + msg = Jp2kBox.__str__(self) + if _printoptions['short'] == True: + return msg + + msg = '{0}\n UUID: {1}'.format(msg, self.uuid) + if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): + msg += ' (XMP)' + elif self.uuid.bytes == b'JpgTiffExif->JP2': + msg += ' (EXIF)' + else: + msg += ' (unknown)' + + if (((_printoptions['xml'] == False) and + (self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac')))): + # If it's an XMP UUID, don't print the XML contents. + return msg if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): - line = ' (XMP)\n UUID Data: {0}' + line = '\n UUID Data: {0}' msg += line.format(_pretty_print_xml(self.data)) elif self.uuid.bytes == b'JpgTiffExif->JP2': - msg += ' (EXIF)\n UUID Data: {0}'.format(str(self.data)) + msg += '\n UUID Data: {0}'.format(str(self.data)) else: - line = ' (unknown)\n UUID Data: {0} bytes' + line = '\n UUID Data: {0} bytes' msg += line.format(len(self.raw_data)) return msg @@ -2748,3 +2826,58 @@ _BOX_WITH_ID = { 'url ': DataEntryURLBox, 'uuid': UUIDBox, 'xml ': XMLBox} + +_printoptions = {'short': False, 'xml': True, 'codestream': True} + +def set_printoptions(short=False, xml=True, codestream=True): + """Set printing options. + + These options determine the way JPEG 2000 boxes are displayed. + + Parameters + ---------- + short : bool, optional + When True, only the box ID, offset, and length are displayed. Useful + for displaying only the basic structure or skeleton of a JPEG 2000 file. + xml : bool, optional + When False, printing of the XML contents of any XML boxes or UUID XMP + boxes is suppressed. + codestream : bool, optional + When False, printing of the codestream contents is suppressed. + + See also + -------- + get_printoptions + + Examples + -------- + To put back the default options, you can use: + + >>> import glymur + >>> glymur.set_printoptions(short=False, xml=True, codestream=True) + """ + _printoptions['short'] = short + _printoptions['xml'] = xml + _printoptions['codestream'] = codestream + +def get_printoptions(): + """Return the current print options. + + Returns + ------- + print_opts : dict + Dictionary of current print options with keys + + - short : bool + - xml : bool + - codestream : bool + + For a full description of these options, see `set_printoptions`. + + See also + -------- + set_printoptions + """ + return _printoptions + + diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index ff33237..182a04f 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -350,3 +350,335 @@ text_gbr_34 = """Colour Specification Box (colr) @ (179, 1339) 'Rendering Intent': 'perceptual', 'Illuminant': array([ 0.96420288, 1. , 0.8249054 ]), 'Creator': 'appl'}""" + + +# Metadata dump of nemo. +nemo_dump_full = r'''JPEG 2000 Signature Box (jP ) @ (0, 12) + Signature: 0d0a870a +File Type Box (ftyp) @ (12, 20) + Brand: jp2 + Compatibility: ['jp2 '] +JP2 Header Box (jp2h) @ (32, 45) + Image Header Box (ihdr) @ (40, 22) + Size: [1456 2592 3] + Bitdepth: 8 + Signed: False + Compression: wavelet + Colorspace Unknown: False + Colour Specification Box (colr) @ (62, 15) + Method: enumerated colorspace + Precedence: 0 + Colorspace: sRGB +UUID Box (uuid) @ (77, 3146) + UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) + UUID Data: + + + + Google + 2013-02-09T14:47:53 + + + 1 + 72/1 + 72/1 + 2 + HTC + HTC Glacier + 2592 + 1456 + + + 8 + 8 + 8 + + + 2 + 3 + + + 1343036288/4294967295 + 1413044224/4294967295 + + + + + 2748779008/4294967295 + 1417339264/4294967295 + 1288490240/4294967295 + 2576980480/4294967295 + 644245120/4294967295 + 257698032/4294967295 + + + + + 1 + 2528 + 1424 + 353/100 + 0 + 0/1 + WGS-84 + 2013-02-09T14:47:53 + + + 76 + + + 0220 + 0100 + + + 1 + 2 + 3 + 0 + + + 42,20.56N + 71,5.29W + 2013-02-09T19:47:53Z + NETWORK + + + 2013-02-09T14:47:53 + + + + + Glymur + Python XMP Toolkit + + + + + + +Contiguous Codestream Box (jp2c) @ (3223, 1132296) + Main header: + SOC marker segment @ (3231, 0) + SIZ marker segment @ (3233, 47) + Profile: 2 + Reference Grid Height, Width: (1456 x 2592) + Vertical, Horizontal Reference Grid Offset: (0 x 0) + Reference Tile Height, Width: (1456 x 2592) + Vertical, Horizontal Reference Tile Offset: (0 x 0) + Bitdepth: (8, 8, 8) + Signed: (False, False, False) + Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1)) + COD marker segment @ (3282, 12) + Coding style: + Entropy coder, without partitions + SOP marker segments: False + EPH marker segments: False + Coding style parameters: + Progression order: LRCP + Number of layers: 2 + Multiple component transformation usage: reversible + Number of resolutions: 2 + Code block height, width: (64 x 64) + Wavelet transform: 5-3 reversible + Precinct size: default, 2^15 x 2^15 + Code block context: + Selective arithmetic coding bypass: False + Reset context probabilities on coding pass boundaries: False + Termination on each coding pass: False + Vertically stripe causal context: False + Predictable termination: False + Segmentation symbols: False + QCD marker segment @ (3296, 7) + Quantization style: no quantization, 2 guard bits + Step size: [(0, 8), (0, 9), (0, 9), (0, 10)] + CME marker segment @ (3305, 37) + "Created by OpenJPEG version 2.0.0"''' + +nemo_dump_short = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) +File Type Box (ftyp) @ (12, 20) +JP2 Header Box (jp2h) @ (32, 45) + Image Header Box (ihdr) @ (40, 22) + Colour Specification Box (colr) @ (62, 15) +UUID Box (uuid) @ (77, 3146) +Contiguous Codestream Box (jp2c) @ (3223, 1132296)""" + +nemo_dump_no_xml = '''JPEG 2000 Signature Box (jP ) @ (0, 12) + Signature: 0d0a870a +File Type Box (ftyp) @ (12, 20) + Brand: jp2 + Compatibility: ['jp2 '] +JP2 Header Box (jp2h) @ (32, 45) + Image Header Box (ihdr) @ (40, 22) + Size: [1456 2592 3] + Bitdepth: 8 + Signed: False + Compression: wavelet + Colorspace Unknown: False + Colour Specification Box (colr) @ (62, 15) + Method: enumerated colorspace + Precedence: 0 + Colorspace: sRGB +UUID Box (uuid) @ (77, 3146) + UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) +Contiguous Codestream Box (jp2c) @ (3223, 1132296) + Main header: + SOC marker segment @ (3231, 0) + SIZ marker segment @ (3233, 47) + Profile: 2 + Reference Grid Height, Width: (1456 x 2592) + Vertical, Horizontal Reference Grid Offset: (0 x 0) + Reference Tile Height, Width: (1456 x 2592) + Vertical, Horizontal Reference Tile Offset: (0 x 0) + Bitdepth: (8, 8, 8) + Signed: (False, False, False) + Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1)) + COD marker segment @ (3282, 12) + Coding style: + Entropy coder, without partitions + SOP marker segments: False + EPH marker segments: False + Coding style parameters: + Progression order: LRCP + Number of layers: 2 + Multiple component transformation usage: reversible + Number of resolutions: 2 + Code block height, width: (64 x 64) + Wavelet transform: 5-3 reversible + Precinct size: default, 2^15 x 2^15 + Code block context: + Selective arithmetic coding bypass: False + Reset context probabilities on coding pass boundaries: False + Termination on each coding pass: False + Vertically stripe causal context: False + Predictable termination: False + Segmentation symbols: False + QCD marker segment @ (3296, 7) + Quantization style: no quantization, 2 guard bits + Step size: [(0, 8), (0, 9), (0, 9), (0, 10)] + CME marker segment @ (3305, 37) + "Created by OpenJPEG version 2.0.0"''' + +nemo_dump_no_codestream = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) + Signature: 0d0a870a +File Type Box (ftyp) @ (12, 20) + Brand: jp2 + Compatibility: ['jp2 '] +JP2 Header Box (jp2h) @ (32, 45) + Image Header Box (ihdr) @ (40, 22) + Size: [1456 2592 3] + Bitdepth: 8 + Signed: False + Compression: wavelet + Colorspace Unknown: False + Colour Specification Box (colr) @ (62, 15) + Method: enumerated colorspace + Precedence: 0 + Colorspace: sRGB +UUID Box (uuid) @ (77, 3146) + UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) + UUID Data: + + + + Google + 2013-02-09T14:47:53 + + + 1 + 72/1 + 72/1 + 2 + HTC + HTC Glacier + 2592 + 1456 + + + 8 + 8 + 8 + + + 2 + 3 + + + 1343036288/4294967295 + 1413044224/4294967295 + + + + + 2748779008/4294967295 + 1417339264/4294967295 + 1288490240/4294967295 + 2576980480/4294967295 + 644245120/4294967295 + 257698032/4294967295 + + + + + 1 + 2528 + 1424 + 353/100 + 0 + 0/1 + WGS-84 + 2013-02-09T14:47:53 + + + 76 + + + 0220 + 0100 + + + 1 + 2 + 3 + 0 + + + 42,20.56N + 71,5.29W + 2013-02-09T19:47:53Z + NETWORK + + + 2013-02-09T14:47:53 + + + + + Glymur + Python XMP Toolkit + + + + + + +Contiguous Codestream Box (jp2c) @ (3223, 1132296)""" + +nemo_dump_no_codestream_no_xml = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) + Signature: 0d0a870a +File Type Box (ftyp) @ (12, 20) + Brand: jp2 + Compatibility: ['jp2 '] +JP2 Header Box (jp2h) @ (32, 45) + Image Header Box (ihdr) @ (40, 22) + Size: [1456 2592 3] + Bitdepth: 8 + Signed: False + Compression: wavelet + Colorspace Unknown: False + Colour Specification Box (colr) @ (62, 15) + Method: enumerated colorspace + Precedence: 0 + Colorspace: sRGB +UUID Box (uuid) @ (77, 3146) + UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) +Contiguous Codestream Box (jp2c) @ (3223, 1132296)""" diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 3e9d3ab..fa9ab0c 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -11,7 +11,6 @@ # pylint: disable=R0904 import os -import re import struct import sys import tempfile @@ -31,104 +30,76 @@ else: import glymur from glymur import Jp2k +from . import fixtures from .fixtures import OPJ_DATA_ROOT, opj_data_file, nemo_xmp_box from .fixtures import text_gbr_27, text_gbr_33, text_gbr_34 @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") -@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version), - "Need at least 1.5 in order to write jp2 files.") -class TestPrintingNeedsLib(unittest.TestCase): - """These tests require the library, mostly in order to just setup the test. - """ - - @classmethod - def setUpClass(cls): - # Setup a plain JP2 file without the two UUID boxes. - jp2file = glymur.data.nemo() - with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile: - cls._plain_nemo_file = tfile.name - ijfile = Jp2k(jp2file) - data = ijfile.read(rlevel=1) - ojfile = Jp2k(cls._plain_nemo_file, 'wb') - ojfile.write(data) - - @classmethod - def tearDownClass(cls): - os.unlink(cls._plain_nemo_file) - +class TestPrinting(unittest.TestCase): + """Tests for verifying how printing works.""" def setUp(self): self.jp2file = glymur.data.nemo() self.j2kfile = glymur.data.goodstuff() - # Save the output of dumping nemo.jp2 for more than one test. - lines = ['JPEG 2000 Signature Box (jP ) @ (0, 12)', - ' Signature: 0d0a870a', - 'File Type Box (ftyp) @ (12, 20)', - ' Brand: jp2 ', - " Compatibility: ['jp2 ']", - 'JP2 Header Box (jp2h) @ (32, 45)', - ' Image Header Box (ihdr) @ (40, 22)', - ' Size: [728 1296 3]', - ' Bitdepth: 8', - ' Signed: False', - ' Compression: wavelet', - ' Colorspace Unknown: False', - ' Colour Specification Box (colr) @ (62, 15)', - ' Method: enumerated colorspace', - ' Precedence: 0', - ' Colorspace: sRGB', - 'Contiguous Codestream Box (jp2c) @ (77, 1632355)', - ' Main header:', - ' SOC marker segment @ (85, 0)', - ' SIZ marker segment @ (87, 47)', - ' Profile: 2', - ' Reference Grid Height, Width: (728 x 1296)', - ' Vertical, Horizontal Reference Grid Offset: ' - + '(0 x 0)', - ' Reference Tile Height, Width: (728 x 1296)', - ' Vertical, Horizontal Reference Tile Offset: ' - + '(0 x 0)', - ' Bitdepth: (8, 8, 8)', - ' Signed: (False, False, False)', - ' Vertical, Horizontal Subsampling: ' - + '((1, 1), (1, 1), (1, 1))', - ' COD marker segment @ (136, 12)', - ' Coding style:', - ' Entropy coder, without partitions', - ' SOP marker segments: False', - ' EPH marker segments: False', - ' Coding style parameters:', - ' Progression order: LRCP', - ' Number of layers: 1', - ' Multiple component transformation usage: ' - + 'reversible', - ' Number of resolutions: 6', - ' Code block height, width: (64 x 64)', - ' Wavelet transform: 5-3 reversible', - ' Precinct size: default, 2^15 x 2^15', - ' Code block context:', - ' Selective arithmetic coding bypass: ' - + 'False', - ' Reset context probabilities on ' - + 'coding pass boundaries: False', - ' Termination on each coding pass: False', - ' Vertically stripe causal context: ' - + 'False', - ' Predictable termination: False', - ' Segmentation symbols: False', - ' QCD marker segment @ (150, 19)', - ' Quantization style: no quantization, ' - + '2 guard bits', - ' Step size: [(0, 8), (0, 9), (0, 9), ' - + '(0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), ' - + '(0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), ' - + '(0, 10)]'] - self.expected_plain = '\n'.join(lines) + # Reset printoptions for every test. + glymur.set_printoptions(short=False, xml=True, codestream=True) def tearDown(self): pass + def test_printopt_no_codestr_or_xml(self): + """Verify printed output when codestream=False and xml=False""" + glymur.set_printoptions(codestream=False, xml=False) + with patch('sys.stdout', new=StringIO()) as fake_out: + glymur.jp2dump(self.jp2file) + actual = fake_out.getvalue().strip() + + # Get rid of the filename line, as it is not set in stone. + lst = actual.split('\n') + lst = lst[1:] + actual = '\n'.join(lst) + self.assertEqual(actual, fixtures.nemo_dump_no_codestream_no_xml) + + def test_printoptions_no_codestream(self): + """Verify printed output when codestream=False""" + glymur.set_printoptions(codestream=False) + with patch('sys.stdout', new=StringIO()) as fake_out: + glymur.jp2dump(self.jp2file) + actual = fake_out.getvalue().strip() + + # Get rid of the filename line, as it is not set in stone. + lst = actual.split('\n') + lst = lst[1:] + actual = '\n'.join(lst) + self.assertEqual(actual, fixtures.nemo_dump_no_codestream) + + def test_printoptions_no_xml(self): + """Verify printed output when xml=False""" + glymur.set_printoptions(xml=False) + with patch('sys.stdout', new=StringIO()) as fake_out: + glymur.jp2dump(self.jp2file) + actual = fake_out.getvalue().strip() + + # Get rid of the filename line, as it is not set in stone. + lst = actual.split('\n') + lst = lst[1:] + actual = '\n'.join(lst) + self.assertEqual(actual, fixtures.nemo_dump_no_xml) + + def test_printoptions_short(self): + """Verify printed output when short=True""" + glymur.set_printoptions(short=True) + with patch('sys.stdout', new=StringIO()) as fake_out: + glymur.jp2dump(self.jp2file) + actual = fake_out.getvalue().strip() + + # Get rid of the filename line, as it is not set in stone. + lst = actual.split('\n') + lst = lst[1:] + actual = '\n'.join(lst) + self.assertEqual(actual, fixtures.nemo_dump_short) + def test_asoc_label_box(self): """verify printing of asoc, label boxes""" # Construct a fake file with an asoc and a label box, as @@ -185,18 +156,18 @@ class TestPrintingNeedsLib(unittest.TestCase): def test_jp2dump(self): """basic jp2dump test""" with patch('sys.stdout', new=StringIO()) as fake_out: - glymur.jp2dump(self._plain_nemo_file) + glymur.jp2dump(self.jp2file) actual = fake_out.getvalue().strip() # Get rid of the filename line, as it is not set in stone. lst = actual.split('\n') lst = lst[1:] actual = '\n'.join(lst) - self.assertEqual(actual, self.expected_plain) + self.assertEqual(actual, fixtures.nemo_dump_full) def test_entire_file(self): """verify output from printing entire file""" - j = glymur.Jp2k(self._plain_nemo_file) + j = glymur.Jp2k(self.jp2file) with patch('sys.stdout', new=StringIO()) as fake_out: print(j) actual = fake_out.getvalue().strip() @@ -206,18 +177,7 @@ class TestPrintingNeedsLib(unittest.TestCase): lst = lst[1:] actual = '\n'.join(lst) - 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. - self.jp2file = glymur.data.nemo() - - def tearDown(self): - pass + self.assertEqual(actual, fixtures.nemo_dump_full) def test_coc_segment(self): """verify printing of COC segment""" From 47d8e8470bd251bbc149ee21f79e5d84fba0dfee Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 10 Feb 2014 06:58:11 -0500 Subject: [PATCH 068/326] Print options remaining persistent across invocations. #162 --- glymur/jp2box.py | 9 +++++---- glymur/test/test_printing.py | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index d0b6c07..12613fa 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2829,7 +2829,7 @@ _BOX_WITH_ID = { _printoptions = {'short': False, 'xml': True, 'codestream': True} -def set_printoptions(short=False, xml=True, codestream=True): +def set_printoptions(**kwargs): """Set printing options. These options determine the way JPEG 2000 boxes are displayed. @@ -2856,9 +2856,10 @@ def set_printoptions(short=False, xml=True, codestream=True): >>> import glymur >>> glymur.set_printoptions(short=False, xml=True, codestream=True) """ - _printoptions['short'] = short - _printoptions['xml'] = xml - _printoptions['codestream'] = codestream + for key, value in kwargs.items(): + if key not in ['short', 'xml', 'codestream']: + raise TypeError('"{0}" not a valid keyword parameter.'.format(key)) + _printoptions[key] = value def get_printoptions(): """Return the current print options. diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index fa9ab0c..56fa4f0 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -48,6 +48,26 @@ class TestPrinting(unittest.TestCase): def tearDown(self): pass + def test_printoptions_bad_argument(self): + """Verify error when bad parameter to set_printoptions""" + with self.assertRaises(TypeError): + glymur.set_printoptions(hi='low') + + def test_printopt_no_codestr_then_no_xml(self): + """Verify printed output when codestream=False and xml=False, #162""" + # The print options should be persistent across invocations. + glymur.set_printoptions(codestream=False) + glymur.set_printoptions(xml=False) + with patch('sys.stdout', new=StringIO()) as fake_out: + glymur.jp2dump(self.jp2file) + actual = fake_out.getvalue().strip() + + # Get rid of the filename line, as it is not set in stone. + lst = actual.split('\n') + lst = lst[1:] + actual = '\n'.join(lst) + self.assertEqual(actual, fixtures.nemo_dump_no_codestream_no_xml) + def test_printopt_no_codestr_or_xml(self): """Verify printed output when codestream=False and xml=False""" glymur.set_printoptions(codestream=False, xml=False) From 29dbf594a5fc7354409bbef43635ba98d14b619e Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 10 Feb 2014 09:38:15 -0500 Subject: [PATCH 069/326] Now printing fuam, dcm, standard masks. #163 Improves visual decoding of the reader requirements box. --- CHANGES.txt | 12 ++++++------ glymur/jp2box.py | 8 ++++++++ glymur/test/fixtures.py | 14 ++++++++++++++ glymur/test/test_printing.py | 15 +-------------- 4 files changed, 29 insertions(+), 20 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index f6c3728..1febdfb 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,12 +2,12 @@ Feb 09, 2014 - Removed support for Python 2.6. Added write support for JP2 UUID, DataEntryURL, Palette and Component Mapping boxes, JPX Association, NumberList and DataReference boxes. Added read support for JPX free, number list, data reference, fragment - table, and fragment list boxes. Added get_printoptions, - set_printoptions functions. Palette box now a 2D numpy array - instead of a list of 1D arrays. JP2 super box constructors now - take optional box list argument. Fixed bug where JPX files - with more than one codestream but advertising jp2 compatibility - were not being read. + table, and fragment list boxes. Improved JPX Reader Requirements box + support. Added get_printoptions, set_printoptions functions. + Palette box now a 2D numpy array instead of a list of 1D arrays. + JP2 super box constructors now take optional box list argument. + Fixed bug where JPX files with more than one codestream but + advertising jp2 compatibility were not being read. Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask length is unsupported. diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 12613fa..53bad9f 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1861,6 +1861,14 @@ class ReaderRequirementsBox(Jp2kBox): if _printoptions['short'] == True: return msg + msg += '\n Fully Understands Aspect Mask: {0}'.format(self.fuam) + msg += '\n Display Completely Mask: {0}'.format(self.dcm) + + msg += '\n Standard Features and Masks:' + for j in range(len(self.standard_flag)): + sfl = self.standard_flag[j] + mask = self.standard_mask[j] + msg += '\n Feature {0:03d}: {1}'.format(sfl, mask) msg += '\n Standard Features:' for j in range(len(self.standard_flag)): diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 182a04f..0d5069b 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -682,3 +682,17 @@ JP2 Header Box (jp2h) @ (32, 45) UUID Box (uuid) @ (77, 3146) UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) Contiguous Codestream Box (jp2c) @ (3223, 1132296)""" + +# Output of reader requirement printing for file7.jp2 +file7_rreq = r"""Reader Requirements Box (rreq) @ (44, 24) + Fully Understands Aspect Mask: 160 + Display Completely Mask: 192 + Standard Features and Masks: + Feature 005: 128 + Feature 060: 96 + Feature 043: 64 + Standard Features: + Feature 005: Unrestricted JPEG 2000 Part 1 codestream, ITU-T Rec. T.800 | ISO/IEC 15444-1 + Feature 060: e-sRGB enumerated colorspace + Feature 043: (Deprecated) compositing layer uses restricted ICC profile + Vendor Features:""" diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 56fa4f0..26156dc 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -738,20 +738,7 @@ class TestPrinting(unittest.TestCase): with patch('sys.stdout', new=StringIO()) as fake_out: print(j.box[2]) actual = fake_out.getvalue().strip() - lines = ['Reader Requirements Box (rreq) @ (44, 24)', - ' Standard Features:', - ' Feature 005: ' - + 'Unrestricted JPEG 2000 Part 1 codestream, ' - + 'ITU-T Rec. T.800 | ISO/IEC 15444-1', - ' Feature 060: e-sRGB enumerated colorspace', - - ' Feature 043: ' - + '(Deprecated) ' - + 'compositing layer uses restricted ICC profile', - - ' Vendor Features:'] - expected = '\n'.join(lines) - self.assertEqual(actual, expected) + self.assertEqual(actual, fixtures.file7_rreq) @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") From 23e5f4db3fd0954a24e8ed19e5ce8a709f63f0bc Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 10 Feb 2014 11:38:54 -0500 Subject: [PATCH 070/326] Adding hooks into set_printoptions. --- bin/jp2dump | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/bin/jp2dump b/bin/jp2dump index cb05c44..f419787 100755 --- a/bin/jp2dump +++ b/bin/jp2dump @@ -5,11 +5,32 @@ import sys import glymur description='Print JPEG2000 metadata.' +epilog=r"""The default action is to print just the codestream headers. Supplying +--nocodestream will prevent any codestream information from printing, while +--fullcodestream will print the entire codestream (and just the codestream). + +JP2 file: default is to suppress codestream and XML. +JPX file: default is to suppress codestream and XML. +JPC file: default is to print codestream header. +""" parser = argparse.ArgumentParser(description=description) -parser.add_argument('-c', '--codestream', help='dump codestream', - action='store_true') +parser.add_argument('-x', '--noxml', help='suppress xml', + action='store_true') +parser.add_argument('--short', help='only box id, offset, and length', + action='store_true') +parser.add_argument('-c', '--nocodestream', help='suppress codestream', + action='store_false') +parser.add_argument('-f', '--fullcodestream', help='full codestream', + action='store_true') parser.add_argument('filename') args = parser.parse_args() +print(args) +if args.noxml: + glymur.set_printoptions(xml=False) +if args.short: + glymur.set_printoptions(short=True) +if args.nocodestream: + glymur.set_printoptions(codestream=False) filename = args.filename -glymur.jp2dump(args.filename, codestream=args.codestream) +glymur.jp2dump(args.filename, codestream=args.fullcodestream) From 116ef6e664ab35652fd9dc5da8fd5f24e3bccb66 Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 10 Feb 2014 11:48:16 -0500 Subject: [PATCH 071/326] _uuid_io no longer a sub package but rather a module of glymur proper. #166 --- glymur/{_uuid_io/Exif.py => _uuid_io.py} | 2 +- glymur/_uuid_io/__init__.py | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) rename glymur/{_uuid_io/Exif.py => _uuid_io.py} (99%) delete mode 100644 glymur/_uuid_io/__init__.py diff --git a/glymur/_uuid_io/Exif.py b/glymur/_uuid_io.py similarity index 99% rename from glymur/_uuid_io/Exif.py rename to glymur/_uuid_io.py index b07cd42..d4971a1 100644 --- a/glymur/_uuid_io/Exif.py +++ b/glymur/_uuid_io.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -Handlers for Exif UUIDs. Be nice if we would find a standard for this. +Part of glymur. """ import pprint import re diff --git a/glymur/_uuid_io/__init__.py b/glymur/_uuid_io/__init__.py deleted file mode 100644 index 778f912..0000000 --- a/glymur/_uuid_io/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -""" -Sub package for handling various types of UUIDs. -""" -from .Exif import tiff_header, xml From f2f05652e4a630665e1fbcd7398432371e4a5b00 Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 10 Feb 2014 14:52:53 -0500 Subject: [PATCH 072/326] Added printoption support to jp2dump. #164 --- bin/jp2dump | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/bin/jp2dump b/bin/jp2dump index f419787..08907e5 100755 --- a/bin/jp2dump +++ b/bin/jp2dump @@ -5,32 +5,30 @@ import sys import glymur description='Print JPEG2000 metadata.' -epilog=r"""The default action is to print just the codestream headers. Supplying ---nocodestream will prevent any codestream information from printing, while ---fullcodestream will print the entire codestream (and just the codestream). - -JP2 file: default is to suppress codestream and XML. -JPX file: default is to suppress codestream and XML. -JPC file: default is to print codestream header. -""" parser = argparse.ArgumentParser(description=description) -parser.add_argument('-x', '--noxml', help='suppress xml', +parser.add_argument('-x', '--noxml', help='Suppress XML.', action='store_true') -parser.add_argument('--short', help='only box id, offset, and length', - action='store_true') -parser.add_argument('-c', '--nocodestream', help='suppress codestream', - action='store_false') -parser.add_argument('-f', '--fullcodestream', help='full codestream', +parser.add_argument('-s', '--short', help='Only print box id, offset, and length.', action='store_true') +chelp='Level of codestream information. 0 suppressed all details, 1 prints headers, 2 prints the full codestream' +parser.add_argument('-c', '--codestream', + help=chelp, + nargs=1, + type=int, + default=0) parser.add_argument('filename') args = parser.parse_args() -print(args) if args.noxml: glymur.set_printoptions(xml=False) if args.short: glymur.set_printoptions(short=True) -if args.nocodestream: +if args.codestream[0] == 0: glymur.set_printoptions(codestream=False) + print_full_codestream = False +elif args.codestream[0] == 1: + print_full_codestream = False +else: + print_full_codestream = True filename = args.filename -glymur.jp2dump(args.filename, codestream=args.fullcodestream) +glymur.jp2dump(args.filename, codestream=print_full_codestream) From 3f56a08d2ced7a18b2d539daccd0222e4a1fa308 Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 10 Feb 2014 15:13:42 -0500 Subject: [PATCH 073/326] Added usage of set_printoptions method. #161 --- docs/source/how_do_i.rst | 197 +++++++++++++++++++++++++++++++++++---- 1 file changed, 178 insertions(+), 19 deletions(-) diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index 01dd24c..dde5b60 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -11,8 +11,8 @@ resolution level. :: >>> import glymur >>> file = glymur.data.nemo() - >>> j = glymur.Jp2k(file) - >>> thumbnail = j.read(rlevel=-1) + >>> jp2 = glymur.Jp2k(file) + >>> thumbnail = jp2.read(rlevel=-1) ... display metadata? ===================== @@ -23,16 +23,183 @@ available. :: From within Python, it is as simple as printing the Jp2k object, i.e. :: - >>> from glymur import Jp2k + >>> import glymur >>> file = glymur.data.nemo() - >>> j = Jp2k(file) - >>> print(j) + >>> jp2 = glymur.Jp2k(file) + >>> print(jp2) + File: nemo.jp2 + JPEG 2000 Signature Box (jP ) @ (0, 12) + Signature: 0d0a870a + File Type Box (ftyp) @ (12, 20) + Brand: jp2 + Compatibility: ['jp2 '] + JP2 Header Box (jp2h) @ (32, 45) + Image Header Box (ihdr) @ (40, 22) + Size: [1456 2592 3] + Bitdepth: 8 + Signed: False + Compression: wavelet + Colorspace Unknown: False + Colour Specification Box (colr) @ (62, 15) + Method: enumerated colorspace + Precedence: 0 + Colorspace: sRGB + UUID Box (uuid) @ (77, 3146) + UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) + UUID Data: + + + + Google + 2013-02-09T14:47:53 + + + 1 + 72/1 + 72/1 + 2 + HTC + HTC Glacier + 2592 + 1456 + + + 8 + 8 + 8 + + + 2 + 3 + + + 1343036288/4294967295 + 1413044224/4294967295 + + + + + 2748779008/4294967295 + 1417339264/4294967295 + 1288490240/4294967295 + 2576980480/4294967295 + 644245120/4294967295 + 257698032/4294967295 + + + + + 1 + 2528 + 1424 + 353/100 + 0 + 0/1 + WGS-84 + 2013-02-09T14:47:53 + + + 76 + + + 0220 + 0100 + + + 1 + 2 + 3 + 0 + + + 42,20.56N + 71,5.29W + 2013-02-09T19:47:53Z + NETWORK + + + 2013-02-09T14:47:53 + + + + + Glymur + Python XMP Toolkit + + + + + + + Contiguous Codestream Box (jp2c) @ (3223, 1132296) + Main header: + SOC marker segment @ (3231, 0) + SIZ marker segment @ (3233, 47) + Profile: 2 + Reference Grid Height, Width: (1456 x 2592) + Vertical, Horizontal Reference Grid Offset: (0 x 0) + Reference Tile Height, Width: (1456 x 2592) + Vertical, Horizontal Reference Tile Offset: (0 x 0) + Bitdepth: (8, 8, 8) + Signed: (False, False, False) + Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1)) + COD marker segment @ (3282, 12) + Coding style: + Entropy coder, without partitions + SOP marker segments: False + EPH marker segments: False + Coding style parameters: + Progression order: LRCP + Number of layers: 2 + Multiple component transformation usage: reversible + Number of resolutions: 2 + Code block height, width: (64 x 64) + Wavelet transform: 5-3 reversible + Precinct size: default, 2^15 x 2^15 + Code block context: + Selective arithmetic coding bypass: False + Reset context probabilities on coding pass boundaries: False + Termination on each coding pass: False + Vertically stripe causal context: False + Predictable termination: False + Segmentation symbols: False + QCD marker segment @ (3296, 7) + Quantization style: no quantization, 2 guard bits + Step size: [(0, 8), (0, 9), (0, 9), (0, 10)] + CME marker segment @ (3305, 37) + "Created by OpenJPEG version 2.0.0" + +That's fairly overwhelming, and perhaps lost in the flood of information +is the fact that the codestream metadata is limited to just what's in the +main codestream header. You can suppress the codestream and XML details by +making use of the :py:meth:`set_printoptions` function:: -This prints the metadata found in the JP2 boxes, but in the case of the -codestream box, only the main header is printed. It is possible to print -**only** the codestream information as well, i.e. :: + >>> glymur.set_printoptions(codestream=False, xml=False) + >>> print(jp2) + File: nemo.jp2 + JPEG 2000 Signature Box (jP ) @ (0, 12) + Signature: 0d0a870a + File Type Box (ftyp) @ (12, 20) + Brand: jp2 + Compatibility: ['jp2 '] + JP2 Header Box (jp2h) @ (32, 45) + Image Header Box (ihdr) @ (40, 22) + Size: [1456 2592 3] + Bitdepth: 8 + Signed: False + Compression: wavelet + Colorspace Unknown: False + Colour Specification Box (colr) @ (62, 15) + Method: enumerated colorspace + Precedence: 0 + Colorspace: sRGB + UUID Box (uuid) @ (77, 3146) + UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) + Contiguous Codestream Box (jp2c) @ (3223, 1132296) - >>> print(j.get_codestream()) +It is possible to print all the gory codestream details as well, i.e. :: + + >>> print(j.get_codestream()) # details not shown ... add XML metadata? ===================== @@ -55,7 +222,7 @@ Consider the following XML file `data.xml` : :: -The **append** method can add an XML box as shown below:: +The :py:meth:`append` method can add an XML box as shown below:: >>> import shutil >>> import glymur @@ -74,6 +241,7 @@ codestream provided by `goodstuff.j2k` (a file consisting of a raw codestream), you can use the :py:meth:`wrap` method with no box argument: :: >>> import glymur + >>> glymur.set_printoptions(codestream=False) >>> jfile = glymur.data.goodstuff() >>> j2k = glymur.Jp2k(jfile) >>> jp2 = j2k.wrap("newfile.jp2") @@ -96,10 +264,6 @@ you can use the :py:meth:`wrap` method with no box argument: :: Precedence: 0 Colorspace: sRGB Contiguous Codestream Box (jp2c) @ (77, 115228) - Main header: - . - . (truncated) - . The raw codestream was wrapped in a JP2 jacket with four boxes in the outer layer (the signature, file type, JP2 header, and contiguous codestream), with @@ -146,12 +310,7 @@ the following will work. :: Light Ale - Contiguous Codestream Box (jp2c) @ (153, 115236) - Main header: - . - . (truncated) - . As to the question of which method you should use, :py:meth:`append` or :py:meth:`wrap`, to add metadata, you should keep in mind that :py:meth:`wrap` From 1484fd974f0dc31cd9b7fe9abe5318be8e95748e Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 10 Feb 2014 15:43:18 -0500 Subject: [PATCH 074/326] Removed trailing newline from XML string output. #160 --- glymur/jp2box.py | 4 +++- glymur/test/fixtures.py | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 53bad9f..07ddb6e 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2761,7 +2761,9 @@ class UUIDBox(Jp2kBox): if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): line = '\n UUID Data: {0}' - msg += line.format(_pretty_print_xml(self.data)) + xmlstring = _pretty_print_xml(self.data) + xmlstring = xmlstring.rstrip() + msg += line.format(xmlstring) elif self.uuid.bytes == b'JpgTiffExif->JP2': msg += '\n UUID Data: {0}'.format(str(self.data)) else: diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 0d5069b..5f7d77a 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -455,7 +455,6 @@ UUID Box (uuid) @ (77, 3146) - Contiguous Codestream Box (jp2c) @ (3223, 1132296) Main header: SOC marker segment @ (3231, 0) @@ -660,7 +659,6 @@ UUID Box (uuid) @ (77, 3146) - Contiguous Codestream Box (jp2c) @ (3223, 1132296)""" nemo_dump_no_codestream_no_xml = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) From 87cbf87ed76f7d2000cc6c5381964dbd9f4fb765 Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 10 Feb 2014 16:47:09 -0500 Subject: [PATCH 075/326] Checking for proper location of jp2h children. #157 Certain boxes can only reside in jp2h or jpch (which is not currently supported for writing purposed). --- glymur/jp2k.py | 16 ++++++++++++++++ glymur/test/test_jp2box.py | 27 +++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 3ecc96f..55e3766 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -1198,6 +1198,22 @@ def _validate_jp2_box_sequence(boxes): _jpx_compatibility(boxes, boxes[1].compatibility_list) _check_for_singletons(boxes) _check_top_level(boxes) + _check_jp2h_child_boxes(boxes, 'top-level') + +JP2H_CHILDREN = set(['bpcc', 'cmap', 'ihdr', 'pclr']) +def _check_jp2h_child_boxes(boxes, parent_box_name): + """Certain boxes can only reside in the JP2 header.""" + box_ids = set([box.box_id for box in boxes]) + intersection = box_ids.intersection(JP2H_CHILDREN) + if len(intersection) > 0 and parent_box_name != 'jp2h': + msg = "A '{0}' box can only be nested in a JP2 header box." + raise IOError(msg.format(list(intersection)[0])) + + # Recursively check any contained superboxes. + for box in boxes: + if hasattr(box, 'box'): + _check_jp2h_child_boxes(box.box, box.box_id) + def _collect_box_count(boxes): """Count the occurences of each box type.""" diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 23c8e93..ac1b2a3 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -679,6 +679,33 @@ class TestWrap(unittest.TestCase): with self.assertRaises(IOError): j2k.wrap(tfile.name, boxes=boxes) + def test_pclr_not_in_jp2h(self): + """A palette box must reside in a JP2 header box.""" + palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.int32) + bps = (8, 8, 8) + signed = (True, False, True) + pclr = glymur.jp2box.PaletteBox(palette=palette, bits_per_component=bps, + signed=(True, False, True)) + + j2k = Jp2k(self.j2kfile) + codestream = j2k.get_codestream() + height = codestream.segment[1].ysiz + width = codestream.segment[1].xsiz + num_components = len(codestream.segment[1].xrsiz) + + jp2b = JPEG2000SignatureBox() + ftyp = FileTypeBox() + jp2h = JP2HeaderBox() + jp2c = ContiguousCodestreamBox() + colr = ColourSpecificationBox(colorspace=glymur.core.SRGB) + ihdr = ImageHeaderBox(height=height, width=width, + num_components=num_components) + jp2h.box = [ihdr, colr] + boxes = [jp2b, ftyp, jp2h, jp2c, pclr] + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + with self.assertRaises(IOError): + j2k.wrap(tfile.name, boxes=boxes) + def test_jp2h_not_preceeding_jp2c(self): """jp2h must precede jp2c""" j2k = Jp2k(self.j2kfile) From 09651c27b63901d1f1eb5419e458f6950935ee10 Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 10 Feb 2014 19:38:37 -0500 Subject: [PATCH 076/326] No longer claiming any windows support. #167 --- docs/source/detailed_installation.rst | 22 +++++----------------- docs/source/introduction.rst | 8 +++----- docs/source/roadmap.rst | 4 +--- 3 files changed, 9 insertions(+), 25 deletions(-) diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index 4e17c4f..d1b3084 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -45,13 +45,6 @@ the path will be :: $XDG_CONFIG_HOME/glymur/glymurrc -On windows, the path to the configuration file can be determined -by starting up Python and typing :: - - import os - os.path.join(os.path.expanduser('~'), 'glymur', 'glymurrc') - - You may also include a line for the version 1.x openjpeg library if you have it installed in a non-standard place, i.e. :: @@ -70,9 +63,8 @@ packages/RPMs/ports/whatever without going through pip. Mac OS X -------- -All the necessary packages are available to use glymur with Python 2.6, 2.7, -and 3.3 via MacPorts. For python 3.3, you should install the following set of -ports: +All the necessary packages are available to use glymur with MacPorts. +For python 3.3, you should install the following set of ports: * python33 * py33-numpy @@ -108,12 +100,8 @@ difficulties: Windows ------- -32-bit WinPython 2.7.5 seemed to work with OpenJPEG 1.X, 2.0, and the -development version, but still required contextlib2 and mock to be -installed via pip. WinPython 3.3.2, however, seems to have trouble -with OpenJPEG 2.0, so I would suggest using the development version with -that configuration. I no longer have any access to a windows machine, -so I cannot currently offer much guidance here. +The 0.6.x series of Glymur is untested on windows and I make no promises here. +I suggest that windows users check the 0.5.x series. ''''''' @@ -145,5 +133,5 @@ OpenJPEG counterparts are already failing, and others which do pass but still produce heaps of output on stderr. Rather than let this swamp the signal (that most of those tests are actually passing), they've been filtered out for now. There are also more skipped tests on Python 2.7 -than on Python 3.3. The important part is whether or not any test +than on Python3. The important part is whether or not any test errors are reported at the end. diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index 23698e3..38193eb 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -11,8 +11,8 @@ In regards to metadata, most JP2 boxes are properly interpreted. Certain optional JP2 boxes can also be written, including XML boxes and XMP UUIDs. There is some very limited support for reading JPX metadata. -Glymur 0.6 works on Python versions 2.7, 3.3 and 3.4. If you have 2.6, you should -use the 0.5 series. +Glymur 0.6 works on Python versions 2.7, 3.3 and 3.4. If you have Python 2.6, +you should use the 0.5 series of Glymur. OpenJPEG Installation ===================== @@ -24,9 +24,7 @@ OpenJPEG, please consult http://www.openjpeg.org. If you use MacPorts or if you have a sufficiently recent version of Linux, your package manager should already provide you with a version of -OpenJPEG 1.X which glymur can already use. If your platform is windows, -I suggest using the windows installers provided to you by the OpenJPEG -folks at https://code.google.com/p/openjpeg/downloads/list . +OpenJPEG 1.X which glymur can already use. Glymur Installation =================== diff --git a/docs/source/roadmap.rst b/docs/source/roadmap.rst index c29d4f4..6651587 100644 --- a/docs/source/roadmap.rst +++ b/docs/source/roadmap.rst @@ -5,6 +5,4 @@ Roadmap Here's an incomplete list of what I'd like to focus on in the future. * continue to monitor upstream changes in the openjp2 library - * investigate using CFFI or cython instead of ctypes to wrap openjp2 - * investigate adding write support for UUID/XMP boxes (potentially a big project) - * investigate JPIP (likely to be an even bigger project) + * investigate JPIP (likely to be a big project) From 0feca733992eacdb86cdd084984b78a044d705a1 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 11 Feb 2014 08:23:03 -0500 Subject: [PATCH 077/326] Added changelog section to docs. #171 --- docs/source/changelog.rst | 42 +++++++++++++++++++++++++++++++++++++++ docs/source/index.rst | 1 + 2 files changed, 43 insertions(+) create mode 100644 docs/source/changelog.rst diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst new file mode 100644 index 0000000..f7e5921 --- /dev/null +++ b/docs/source/changelog.rst @@ -0,0 +1,42 @@ +--------- +ChangeLog +--------- + +0.6.0 (pending) +=============== + + * added set_printoptions, get_printoptions function + * dropped support for Python 2.6, added support for Python 3.4 + * dropped windows support + * added write support for JP2 UUID, dataEntryURL, palette, and component mapping boxes + * added read/write support for JPX free, number list, and data reference boxes + * Added read support for JPX fragment list and fragment table boxes + * incompatible change to palette box constructor, it now takes a 2D numpy array instead of a list of 1D arrays + +0.5.0 (September 16, 2013) +========================== + + * added write support when using OpenJPEG version 1.5 + * added version module + +0.4.0 (August 18, 2013) +========================== + + * added append method + +0.3.0 (July 31, 2013) +========================== + + * added support for OpenJPEG library version 2.0.0 + +0.2.0 (July 11, 2013) +========================== + + * added Python 2.6, Python 2.7 on windows + * read/write using OpenJPEG library version 2.0.0 + * read using OpenJPEG 1.4 + +0.1.0 (May 27, 2013) +==================== + + * first release using development version (2.x) of OpenJPEG diff --git a/docs/source/index.rst b/docs/source/index.rst index 8536be8..792f75a 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -16,6 +16,7 @@ Contents: detailed_installation how_do_i roadmap + changelog api ------------------ From df5bf539eff559c27749d04649f04ca195761108 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 11 Feb 2014 13:39:56 -0500 Subject: [PATCH 078/326] Some refactoring to reduce complexity for box validity. #170 --- glymur/jp2k.py | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 55e3766..43e24b4 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -1122,6 +1122,11 @@ def _validate_jp2_box_sequence(boxes): msg += "must be the file type box." raise IOError(msg) + # The compatibility list must contain at a minimum 'jp2 '. + if 'jp2 ' not in boxes[1].compatibility_list: + msg = "The ftyp box must contain 'jp2 ' in the compatibility list." + raise IOError(msg) + # jp2c must be preceeded by jp2h jp2h_lst = [idx for (idx, box) in enumerate(boxes) if box.box_id == 'jp2h'] @@ -1138,14 +1143,22 @@ def _validate_jp2_box_sequence(boxes): msg = "The codestream box must be preceeded by a jp2 header box." raise IOError(msg) + _validate_jp2h(boxes[jp2h_idx]) + _check_jp2h_child_boxes(boxes, 'top-level') + _asoc_check(boxes) + _jpx_brand(boxes, boxes[1].brand) + _jpx_compatibility(boxes, boxes[1].compatibility_list) + _check_for_singletons(boxes) + _check_top_level(boxes) + +def _validate_jp2h(jp2h): + """Validate the JP2 Header box.""" # 1st jp2 header box cannot be empty. - jp2h = boxes[jp2h_idx] if len(jp2h.box) == 0: msg = "The JP2 header superbox cannot be empty." 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." @@ -1159,15 +1172,12 @@ def _validate_jp2_box_sequence(boxes): 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) + _validate_channel_definition(jp2h, colr) - cdef_lst = [j for (j, box) in enumerate(jp2h.box) - if box.box_id == 'cdef'] + +def _validate_channel_definition(jp2h, colr): + """Validate the channel definition box.""" + 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." @@ -1187,18 +1197,6 @@ def _validate_jp2_box_sequence(boxes): msg += "channel definition box." raise IOError(msg) - # The compatibility list must contain at a minimum 'jp2 '. - if 'jp2 ' not in boxes[1].compatibility_list: - msg = "The ftyp box must contain 'jp2 ' in the compatibility list." - raise IOError(msg) - - # JPX checks. - _asoc_check(boxes) - _jpx_brand(boxes, boxes[1].brand) - _jpx_compatibility(boxes, boxes[1].compatibility_list) - _check_for_singletons(boxes) - _check_top_level(boxes) - _check_jp2h_child_boxes(boxes, 'top-level') JP2H_CHILDREN = set(['bpcc', 'cmap', 'ihdr', 'pclr']) def _check_jp2h_child_boxes(boxes, parent_box_name): From 0205a3cbbaa32d919937cb89cd4a9fa98de9cef9 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 11 Feb 2014 13:57:30 -0500 Subject: [PATCH 079/326] More refactoring, think it's in good shape now. #170 --- glymur/jp2k.py | 47 ++++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 43e24b4..cd1a6eb 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -1115,6 +1115,18 @@ def _validate_jp2_box_sequence(boxes): This is non-exhaustive. """ + _validate_signature_compatibility(boxes) + _validate_jp2h(boxes) + _validate_jp2c(boxes) + _validate_association(boxes) + _validate_jpx_brand(boxes, boxes[1].brand) + _validate_jpx_compatibility(boxes, boxes[1].compatibility_list) + _validate_singletons(boxes) + _validate_top_level(boxes) + + +def _validate_signature_compatibility(boxes): + """Validate the file signature and compatibility status.""" # 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': @@ -1127,6 +1139,9 @@ def _validate_jp2_box_sequence(boxes): msg = "The ftyp box must contain 'jp2 ' in the compatibility list." raise IOError(msg) + +def _validate_jp2c(boxes): + """Validate the codestream box in relation to other boxes.""" # jp2c must be preceeded by jp2h jp2h_lst = [idx for (idx, box) in enumerate(boxes) if box.box_id == 'jp2h'] @@ -1143,16 +1158,14 @@ def _validate_jp2_box_sequence(boxes): msg = "The codestream box must be preceeded by a jp2 header box." raise IOError(msg) - _validate_jp2h(boxes[jp2h_idx]) - _check_jp2h_child_boxes(boxes, 'top-level') - _asoc_check(boxes) - _jpx_brand(boxes, boxes[1].brand) - _jpx_compatibility(boxes, boxes[1].compatibility_list) - _check_for_singletons(boxes) - _check_top_level(boxes) -def _validate_jp2h(jp2h): +def _validate_jp2h(boxes): """Validate the JP2 Header box.""" + _check_jp2h_child_boxes(boxes, 'top-level') + + jp2h_lst = [box for box in boxes if box.box_id == 'jp2h'] + jp2h = jp2h_lst[0] + # 1st jp2 header box cannot be empty. if len(jp2h.box) == 0: msg = "The JP2 header superbox cannot be empty." @@ -1196,7 +1209,7 @@ def _validate_channel_definition(jp2h, colr): msg = "All color channels must be defined in the " msg += "channel definition box." raise IOError(msg) - + JP2H_CHILDREN = set(['bpcc', 'cmap', 'ihdr', 'pclr']) def _check_jp2h_child_boxes(boxes, parent_box_name): @@ -1241,7 +1254,7 @@ def _check_superbox_for_top_levels(boxes): if hasattr(box, 'box'): _check_superbox_for_top_levels(box.box) -def _check_top_level(boxes): +def _validate_top_level(boxes): """Several boxes can only occur at the top level.""" # Add the counts in the superboxes. for box in boxes: @@ -1254,7 +1267,7 @@ def _check_top_level(boxes): if 'dtbl' in multiples: raise IOError('There can only be one dtbl box in a file.') -def _check_for_singletons(boxes): +def _validate_singletons(boxes): """Several boxes can only occur once.""" count = _collect_box_count(boxes) # Which boxes occur more than once? @@ -1262,7 +1275,7 @@ def _check_for_singletons(boxes): if 'dtbl' in multiples: raise IOError('There can only be one dtbl box in a file.') -def _jpx_brand(boxes, brand): +def _validate_jpx_brand(boxes, brand): """ If there is a JPX box then the brand must be 'jpx '. """ @@ -1274,9 +1287,9 @@ def _jpx_brand(boxes, brand): raise RuntimeError(msg) if hasattr(box, 'box') != 0: # Same set of checks on any child boxes. - _jpx_brand(box.box, brand) + _validate_jpx_brand(box.box, brand) -def _jpx_compatibility(boxes, compatibility_list): +def _validate_jpx_compatibility(boxes, compatibility_list): """ If there is a JPX box then the compatibility list must also contain 'jpx '. """ @@ -1288,10 +1301,10 @@ def _jpx_compatibility(boxes, compatibility_list): raise RuntimeError(msg) if hasattr(box, 'box') != 0: # Same set of checks on any child boxes. - _jpx_compatibility(box.box, compatibility_list) + _validate_jpx_compatibility(box.box, compatibility_list) -def _asoc_check(boxes): +def _validate_association(boxes): """ Association boxes can only contain number list boxes and xml boxes, as far as we know. @@ -1304,7 +1317,7 @@ def _asoc_check(boxes): raise RuntimeError(msg) if hasattr(box, 'box') != 0: # Same set of checks on any child boxes. - _asoc_check(box.box) + _validate_association(box.box) From b0719450b5bb8f3517956fbc285e5a93f841aacb Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 11 Feb 2014 16:16:22 -0500 Subject: [PATCH 080/326] Added support for rreq boxes with mask length of 3. #165 --- glymur/jp2box.py | 68 ++++++++++++++++++++++++++++++++-- glymur/test/test_jp2box.py | 5 +-- glymur/test/test_jp2box_jpx.py | 34 ++++++----------- glymur/test/test_jp2k.py | 4 +- 4 files changed, 77 insertions(+), 34 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 07ddb6e..a6ea821 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1837,10 +1837,10 @@ class ReaderRequirementsBox(Jp2kBox): Jp2kBox.__init__(self, box_id='rreq', longname='Reader Requirements') self.fuam = fuam self.dcm = dcm - self.standard_flag = standard_flag - self.standard_mask = standard_mask - self.vendor_feature = vendor_feature - self.vendor_mask = vendor_mask + self.standard_flag = tuple(standard_flag) + self.standard_mask = tuple(standard_mask) + self.vendor_feature = tuple(vendor_feature) + self.vendor_mask = tuple(vendor_mask) self.length = length self.offset = offset @@ -1902,6 +1902,9 @@ class ReaderRequirementsBox(Jp2kBox): read_buffer = fptr.read(1) mask_length, = struct.unpack('>B', read_buffer) + if mask_length == 3: + return _parse_rreq3(fptr, length, offset) + # Fully Understands Aspect Mask # Decodes Completely Mask read_buffer = fptr.read(2 * mask_length) @@ -1911,6 +1914,7 @@ class ReaderRequirementsBox(Jp2kBox): # The mask length tells us the format string to use when unpacking # from the buffer read from file. + try: mask_format = {1: 'B', 2: 'H', 4: 'I', 8: 'Q'}[mask_length] fuam, dcm = struct.unpack('>' + mask_format * 2, read_buffer) @@ -1931,6 +1935,62 @@ class ReaderRequirementsBox(Jp2kBox): return box +def _parse_rreq3(fptr, length, offset): + """Parse a reader requirements box. Special case when mask length is 3.""" + # Fully Understands Aspect Mask + # Decodes Completely Mask + read_buffer = fptr.read(2 * 3) + + fuam = dcm = standard_flag = standard_mask = [] + vendor_feature = vendor_mask = [] + + # The mask length tells us the format string to use when unpacking + # from the buffer read from file. + lst = struct.unpack('>BBBBBB', read_buffer) + fuam = lst[0] << 16 | lst[1] << 8 | lst[2] + dcm = lst[3] << 16 | lst[4] << 8 | lst[5] + + read_buffer = fptr.read(2) + num_standard_features, = struct.unpack('>H', read_buffer) + + fmt = '>' + 'HBBB' * num_standard_features + read_buffer = fptr.read(num_standard_features * 5) + lst = struct.unpack(fmt, read_buffer) + + standard_flag = lst[0::4] + standard_mask = [] + for j in range(num_standard_features): + items = lst[slice(j * 4 + 1, j * 4 + 4)] + mask = items[0] << 16 | items[1] << 8 | items[2] + standard_mask.append(mask) + + read_buffer = fptr.read(2) + num_vendor_features, = struct.unpack('>H', read_buffer) + + fmt = '>' + 'HBBB' * num_vendor_features + read_buffer = fptr.read(num_vendor_features * 5) + lst = struct.unpack(fmt, 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 + 3 + 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])) + + lst = struct.unpack('>BBB', ubuffer[16:]) + vmask = lst[0] << 16 | lst[1] << 8 | lst[2] + vendor_mask.append(vmask) + + box = ReaderRequirementsBox(fuam, dcm, standard_flag, standard_mask, + vendor_feature, vendor_mask, + length=length, offset=offset) + return box + + def _parse_standard_flag(fptr, mask_length): """Construct standard flag, standard mask data from the file. diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index ac1b2a3..ea1fddb 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -580,10 +580,7 @@ class TestWrap(unittest.TestCase): def test_jpx_to_jp2(self): """basic test for rewrapping a jpx file""" - with warnings.catch_warnings(): - # This file has a rreq mask length that we do not recognize. - warnings.simplefilter("ignore") - jpx = Jp2k(self.jpxfile) + jpx = Jp2k(self.jpxfile) idx = [0, 1, 3, 6] boxes = [jpx.box[idx] for idx in [0, 1, 3, 6]] with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 571afbc..93b821f 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -135,7 +135,6 @@ class TestJPXWrap(unittest.TestCase): jpx = jp2.wrap(tfile.name, boxes=boxes) -@unittest.skipIf(sys.hexversion < 0x03000000, "Warning assert on 2.x.") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") class TestJPX(unittest.TestCase): """Test suite for other JPX boxes.""" @@ -146,22 +145,18 @@ class TestJPX(unittest.TestCase): def tearDown(self): pass - def test_rreq_box_strange_mask_length(self): - """The standard says that the mask length should be 1, 2, 4, or 8.""" - with warnings.catch_warnings(): - # This file has a rreq mask length that we do not recognize. - warnings.simplefilter("ignore") - j = Jp2k(self.jpxfile) - self.assertEqual(j.box[2].box_id, 'rreq') - self.assertEqual(type(j.box[2]), + def test_jpx_rreq_mask_length_3(self): + """There are some JPX files with rreq mask length of 3.""" + jpx = Jp2k(self.jpxfile) + self.assertEqual(jpx.box[2].box_id, 'rreq') + self.assertEqual(type(jpx.box[2]), glymur.jp2box.ReaderRequirementsBox) + self.assertEqual(jpx.box[2].standard_flag, + (5, 42, 45, 2, 18, 19, 1, 8, 12, 31, 20)) def test_free_box(self): """Verify that we can handle a free box.""" - with warnings.catch_warnings(): - # This file has a rreq mask length that we do not recognize. - warnings.simplefilter("ignore") - j = Jp2k(self.jpxfile) + j = Jp2k(self.jpxfile) self.assertEqual(j.box[16].box[0].box_id, 'free') self.assertEqual(type(j.box[16].box[0]), glymur.jp2box.FreeBox) @@ -183,10 +178,7 @@ class TestJPX(unittest.TestCase): tfile.flush() - with warnings.catch_warnings(): - # This file has a rreq mask length that we do not recognize. - warnings.simplefilter("ignore") - jpx = Jp2k(tfile.name) + jpx = Jp2k(tfile.name) self.assertEqual(jpx.box[-1].box_id, 'dtbl') self.assertEqual(len(jpx.box[-1].DR), 2) @@ -212,8 +204,7 @@ class TestJPX(unittest.TestCase): tfile.flush() - with self.assertWarns(UserWarning): - jpx = Jp2k(tfile.name) + jpx = Jp2k(tfile.name) self.assertEqual(jpx.box[-1].box_id, 'ftbl') self.assertEqual(jpx.box[-1].box[0].box_id, 'flst') @@ -223,10 +214,7 @@ class TestJPX(unittest.TestCase): def test_nlst(self): """Verify that we can handle a free box.""" - with warnings.catch_warnings(): - # This file has a rreq mask length that we do not recognize. - warnings.simplefilter("ignore") - j = Jp2k(self.jpxfile) + j = Jp2k(self.jpxfile) self.assertEqual(j.box[16].box[1].box[0].box_id, 'nlst') self.assertEqual(type(j.box[16].box[1].box[0]), glymur.jp2box.NumberListBox) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index a36d42c..386f077 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -379,9 +379,7 @@ class TestJp2k(unittest.TestCase): def test_jpx_mult_codestreams_jp2_brand(self): """Read JPX codestream when jp2-compatible.""" # The file in question has multiple codestreams. - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - jpx = Jp2k(self.jpxfile) + jpx = Jp2k(self.jpxfile) data = jpx.read() if re.match(r"""1\.[0123]""", glymur.version.openjpeg_version): # openjpeg 1.3 doesn't apply the palette, so it's a 2D image here From e2539b04e12289cbfb2f9dbc44abd06ba139028c Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 11 Feb 2014 19:59:35 -0500 Subject: [PATCH 081/326] Added deprecated rreq descriptions. #173 --- glymur/jp2box.py | 49 +++++++++++++++++++++----------------- glymur/jp2k.py | 2 +- glymur/test/fixtures.py | 2 +- glymur/test/test_jp2box.py | 2 +- 4 files changed, 30 insertions(+), 25 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index a6ea821..03adce0 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1721,19 +1721,21 @@ class PaletteBox(Jp2kBox): # Map rreq codes to display text. _READER_REQUIREMENTS_DISPLAY = { 0: 'File not completely understood', - 1: 'Deprecated', + 1: 'Deprecated - contains no extensions', 2: 'Contains multiple composition layers', - 3: 'Deprecated', + 3: 'Deprecated - codestream is compressed using JPEG 2000 and requires ' + + 'at least a Profile 0 decoder as defind in ITU-T Rec. T.800 ' + + '| ISO/IEC 15444-1, A.10 Table A.45', 4: 'JPEG 2000 Part 1 Profile 1 codestream', 5: 'Unrestricted JPEG 2000 Part 1 codestream, ITU-T Rec. T.800 ' + '| ISO/IEC 15444-1', 6: 'Unrestricted JPEG 2000 Part 2 codestream', 7: 'JPEG codestream as defined in ISO/IEC 10918-1', - 8: 'Deprecated', + 8: 'Deprecated - does not contain opacity', 9: 'Non-premultiplied opacity channel', 10: 'Premultiplied opacity channel', 11: 'Chroma-key based opacity', - 12: 'Deprecated', + 12: 'Deprecated - codestream is contiguous', 13: 'Fragmented codestream where all fragments are in file and in order', 14: 'Fragmented codestream where all fragments are in file ' + 'but are out of order', @@ -1743,21 +1745,24 @@ _READER_REQUIREMENTS_DISPLAY = { + 'only through a URL specified network connection', 17: 'Compositing required to produce rendered result from multiple ' + 'compositing layers', - 18: 'Deprecated', - 19: 'Deprecated', - 20: 'Deprecated', + 18: 'Deprecated - support for compositing is not required', + 19: 'Deprecated - contains multiple, discrete layers that should not ' + + 'be combined through either animation or compositing', + 20: 'Deprecated - compositing layers each contain only a single ' + + 'codestream', 21: 'At least one compositing layer consists of multiple codestreams', - 22: 'Deprecated', + 22: 'Deprecated - all compositing layers are in the same colourspace', 23: 'Colourspace transformations are required to combine compositing ' + 'layers; not all compositing layers are in the same colourspace', - 24: 'Deprecated', - 25: 'Deprecated', + 24: 'Deprecated - rendered result created without using animation', + 25: 'Deprecated - animated, but first layer covers entire area and is ' + + 'opaque', 26: 'First animation layer does not cover entire rendered result', - 27: 'Deprecated', + 27: 'Deprecated - animated, and no layer is reused', 28: 'Reuse of animation layers', - 29: 'Deprecated', + 29: 'Deprecated - animated, but layers are reused', 30: 'Some animated frames are non-persistent', - 31: 'Deprecated', + 31: 'Deprecated - rendered result created without using scaling', 32: 'Rendered result involves scaling within a layer', 33: 'Rendered result involves scaling between layers', 34: 'ROI metadata', @@ -1768,11 +1773,11 @@ _READER_REQUIREMENTS_DISPLAY = { 39: 'JPX digital signatures', 40: 'JPX checksums', 41: 'Desires Graphics Arts Reproduction specified', - 42: 'Deprecated', - 43: '(Deprecated) compositing layer uses restricted ICC profile', + 42: 'Deprecated - compositing layer uses palettized colour', + 43: 'Deprecated - compositing layer uses restricted ICC profile', 44: 'Compositing layer uses Any ICC profile', - 45: 'Deprecated', - 46: 'Deprecated', + 45: 'Deprecated - compositing layer uses sRGB enumerated colourspace', + 46: 'Deprecated - compositing layer uses sRGB-grey enumerated colourspace', 47: 'BiLevel 1 enumerated colourspace', 48: 'BiLevel 2 enumerated colourspace', 49: 'YCbCr 1 enumerated colourspace', @@ -1789,14 +1794,14 @@ _READER_REQUIREMENTS_DISPLAY = { 60: 'e-sRGB enumerated colorspace', 61: 'ROMM_RGB enumerated colorspace', 62: 'Non-square samples', - 63: 'Deprecated', - 64: 'Deprecated', - 65: 'Deprecated', - 66: 'Deprecated', + 63: 'Deprecated - compositing layers have labels', + 64: 'Deprecated - codestreams have labels', + 65: 'Deprecated - compositing layers have different colour spaces', + 66: 'Deprecated - compositing layers have different metadata', 67: 'GIS metadata XML box', 68: 'JPSEC extensions in codestream as specified by ISO/IEC 15444-8', 69: 'JP3D extensions in codestream as specified by ISO/IEC 15444-10', - 70: 'Deprecated', + 70: 'Deprecated - compositing layer uses sYCC enumerated colour space', 71: 'e-sYCC enumerated colourspace', 72: 'JPEG 2000 Part 2 codestream as restricted by baseline conformance ' + 'requirements in M.9.2.3', diff --git a/glymur/jp2k.py b/glymur/jp2k.py index cd1a6eb..fbbf999 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -1211,7 +1211,7 @@ def _validate_channel_definition(jp2h, colr): raise IOError(msg) -JP2H_CHILDREN = set(['bpcc', 'cmap', 'ihdr', 'pclr']) +JP2H_CHILDREN = set(['bpcc', 'cdef', 'cmap', 'ihdr', 'pclr']) def _check_jp2h_child_boxes(boxes, parent_box_name): """Certain boxes can only reside in the JP2 header.""" box_ids = set([box.box_id for box in boxes]) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 5f7d77a..9b40a25 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -692,5 +692,5 @@ file7_rreq = r"""Reader Requirements Box (rreq) @ (44, 24) Standard Features: Feature 005: Unrestricted JPEG 2000 Part 1 codestream, ITU-T Rec. T.800 | ISO/IEC 15444-1 Feature 060: e-sRGB enumerated colorspace - Feature 043: (Deprecated) compositing layer uses restricted ICC profile + Feature 043: Deprecated - compositing layer uses restricted ICC profile Vendor Features:""" diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index ea1fddb..feffec9 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -315,7 +315,7 @@ class TestChannelDefinition(unittest.TestCase): boxes = [self.jp2b, self.ftyp, self.jp2h, cdef, self.jp2c] with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: - with self.assertRaises(IOError): + with self.assertRaises((IOError, OSError)): j2k.wrap(tfile.name, boxes=boxes) def test_bad_type(self): From db92f3fea774a425bc5f85402bdf4fc6ee81c177 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 12 Feb 2014 09:17:17 -0500 Subject: [PATCH 082/326] rreq masks and standard features now printed together. #174 Masks are printed in hex. --- glymur/jp2box.py | 18 ++++++------------ glymur/test/fixtures.py | 14 +++++--------- glymur/test/test_printing.py | 6 +++--- 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 03adce0..d5e79e1 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -11,7 +11,7 @@ References Extensions """ -# pylint: disable=C0302,R0903,R0913 +# pylint: disable=C0302,R0903,R0913,W0142 from collections import OrderedDict import datetime @@ -1866,20 +1866,14 @@ class ReaderRequirementsBox(Jp2kBox): if _printoptions['short'] == True: return msg - msg += '\n Fully Understands Aspect Mask: {0}'.format(self.fuam) - msg += '\n Display Completely Mask: {0}'.format(self.dcm) + msg += '\n Fully Understands Aspect Mask: 0x{0:x}'.format(self.fuam) + msg += '\n Display Completely Mask: 0x{0:x}'.format(self.dcm) msg += '\n Standard Features and Masks:' for j in range(len(self.standard_flag)): - sfl = self.standard_flag[j] - mask = self.standard_mask[j] - msg += '\n Feature {0:03d}: {1}'.format(sfl, mask) - - msg += '\n Standard Features:' - for j in range(len(self.standard_flag)): - sfl = self.standard_flag[j] - rrdisp = _READER_REQUIREMENTS_DISPLAY[self.standard_flag[j]] - msg += '\n Feature {0:03d}: {1}'.format(sfl, rrdisp) + args = (self.standard_flag[j], self.standard_mask[j], + _READER_REQUIREMENTS_DISPLAY[self.standard_flag[j]]) + msg += '\n Feature {0:03d}: 0x{1:x} {2}'.format(*args) msg += '\n Vendor Features:' for j in range(len(self.vendor_feature)): diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 9b40a25..6e0a76c 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -683,14 +683,10 @@ Contiguous Codestream Box (jp2c) @ (3223, 1132296)""" # Output of reader requirement printing for file7.jp2 file7_rreq = r"""Reader Requirements Box (rreq) @ (44, 24) - Fully Understands Aspect Mask: 160 - Display Completely Mask: 192 + Fully Understands Aspect Mask: 0xa0 + Display Completely Mask: 0xc0 Standard Features and Masks: - Feature 005: 128 - Feature 060: 96 - Feature 043: 64 - Standard Features: - Feature 005: Unrestricted JPEG 2000 Part 1 codestream, ITU-T Rec. T.800 | ISO/IEC 15444-1 - Feature 060: e-sRGB enumerated colorspace - Feature 043: Deprecated - compositing layer uses restricted ICC profile + Feature 005: 0x80 Unrestricted JPEG 2000 Part 1 codestream, ITU-T Rec. T.800 | ISO/IEC 15444-1 + Feature 060: 0x60 e-sRGB enumerated colorspace + Feature 043: 0x40 Deprecated - compositing layer uses restricted ICC profile Vendor Features:""" diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 26156dc..5fe3322 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -53,7 +53,7 @@ class TestPrinting(unittest.TestCase): with self.assertRaises(TypeError): glymur.set_printoptions(hi='low') - def test_printopt_no_codestr_then_no_xml(self): + def test_propts_no_codestream_then_no_xml(self): """Verify printed output when codestream=False and xml=False, #162""" # The print options should be persistent across invocations. glymur.set_printoptions(codestream=False) @@ -629,7 +629,7 @@ class TestPrinting(unittest.TestCase): def test_xml_latin1(self): """Should be able to print an XMLBox with utf-8 encoding (latin1).""" # Seems to be inconsistencies between different versions of python2.x - # as to what gets printed. + # as to what gets printed. # # 2.7.5 (fedora 19) prints xml entities. # 2.7.3 seems to want to print hex escapes. @@ -658,7 +658,7 @@ class TestPrinting(unittest.TestCase): def test_xml_cyrrilic(self): """Should be able to print an XMLBox with utf-8 encoding (cyrrillic).""" # Seems to be inconsistencies between different versions of python2.x - # as to what gets printed. + # as to what gets printed. # # 2.7.5 (fedora 19) prints xml entities. # 2.7.3 seems to want to print hex escapes. From 1906bb60041dfcfe7a2adadf5bc48c3be34194e2 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 12 Feb 2014 10:00:35 -0500 Subject: [PATCH 083/326] Added 'jpxb' to accepted items in the ftyp compatibility list. #172 --- glymur/jp2k.py | 7 ++++--- glymur/test/test_jp2box_jpx.py | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index fbbf999..d159fcc 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -1293,11 +1293,12 @@ def _validate_jpx_compatibility(boxes, compatibility_list): """ If there is a JPX box then the compatibility list must also contain 'jpx '. """ + jpx_cl = set(compatibility_list) for box in boxes: if box.box_id in JPX_IDS: - if 'jpx ' not in compatibility_list: - msg = "A JPX box requires that 'jpx ' be present in the " - msg += "ftype compatibility list." + if len(set(['jpx ', 'jpxb']).intersection(jpx_cl)) == 0: + msg = "A JPX box requires that either 'jpx ' or 'jpxb' be " + msg += "present in the ftype compatibility list." raise RuntimeError(msg) if hasattr(box, 'box') != 0: # Same set of checks on any child boxes. diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 93b821f..2ebeb1f 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -40,6 +40,33 @@ class TestJPXWrap(unittest.TestCase): def tearDown(self): os.unlink(self.xmlfile) + def test_jpxb_compatibility(self): + """Wrap JP2 to JPX, state jpxb compatibility""" + jp2 = Jp2k(self.jp2file) + boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] + + # The ftyp box must be modified to jpx with jp2 compatibility. + boxes[1].brand = 'jpx ' + boxes[1].compatibility_list = ['jp2 ', 'jpxb'] + + numbers = (0, 1) + nlst = glymur.jp2box.NumberListBox(numbers) + the_xml = ET.fromstring('0') + xmlb = glymur.jp2box.XMLBox(xml=the_xml) + asoc = glymur.jp2box.AssociationBox([nlst, xmlb]) + boxes.append(asoc) + + with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: + jpx = jp2.wrap(tfile.name, boxes=boxes) + + self.assertEqual(jpx.box[1].compatibility_list, ['jp2 ', 'jpxb']) + self.assertEqual(jpx.box[-1].box_id, 'asoc') + self.assertEqual(jpx.box[-1].box[0].box_id, 'nlst') + self.assertEqual(jpx.box[-1].box[1].box_id, 'xml ') + self.assertEqual(jpx.box[-1].box[0].associations, numbers) + self.assertEqual(ET.tostring(jpx.box[-1].box[1].xml.getroot()), + b'0') + def test_association_box(self): """Wrap JP2 to JPX with asoc(nlst, xml)""" jp2 = Jp2k(self.jp2file) From d016fc504abd4f160124737dde68a0b1ee6fbc4f Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 12 Feb 2014 19:35:45 -0500 Subject: [PATCH 084/326] Added another XMP example. #169 --- docs/source/how_do_i.rst | 73 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index dde5b60..a1fe089 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -5,8 +5,9 @@ How do I...? ... read the lowest resolution thumbnail? ========================================= -Printing the Jp2k object should reveal the number of resolutions (look in the -COD segment section), but you can take a shortcut by supplying -1 as the +Printing the Jp2k object should reveal the number of resolutions +(look in the COD segment section of the codestream), but you can +take a shortcut by supplying -1 as the resolution level. :: >>> import glymur @@ -381,6 +382,8 @@ Here's how the Preview application on the mac shows the RGBA image. ... work with XMP UUIDs? ======================== +XMP is metadata on steroids. + The example JP2 file shipped with glymur has an XMP UUID. :: >>> import glymur @@ -518,3 +521,69 @@ though, you have to know the UUID that signifies XMP data.:: + +You can also build up XMP metadata from scratch. For instance, if we try to +wrap `goodstuff.j2k` again:: + + >>> import glymur + >>> jfile = glymur.data.goodstuff() + >>> j2k = glymur.Jp2k(jfile) + >>> jp2 = j2k.wrap("goodstuff.jp2") + +Now build up the metadata piece-by-piece. It would help to have the XMP +standard close at hand:: + + >>> from libxmp import XMPMeta + >>> from libxmp.consts import XMP_NS_TIFF as NS_TIFF + >>> from libxmp.consts import XMP_NS_DC as NS_DC + >>> xmp = XMPMeta() + >>> ihdr = jp2.box[2].box[0] + >>> xmp.set_property(NS_TIFF, "ImageWidth", str(ihdr.width)) + >>> xmp.set_property(NS_TIFF, "ImageHeight", str(ihdr.height)) + >>> xmp.set_property(NS_TIFF, "BitsPerSample", '3') + >>> xmp.set_property(NS_DC, "Title", u'Stürm und Drang') + >>> xmp.set_property(NS_DC, "Creator", 'Glymur') + +We can then append the XMP in a UUID box just as before:: + + >>> import uuid + >>> xmp_uuid = uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac') + >>> box = glymur.jp2box.UUIDBox(xmp_uuid, str(xmp).encode()) + >>> jp2.append(box) + >>> glymur.set_printoptions(codestream=False) + >>> print(jp2) + File: goodstuff.jp2 + JPEG 2000 Signature Box (jP ) @ (0, 12) + Signature: 0d0a870a + File Type Box (ftyp) @ (12, 20) + Brand: jp2 + Compatibility: ['jp2 '] + JP2 Header Box (jp2h) @ (32, 45) + Image Header Box (ihdr) @ (40, 22) + Size: [800 480 3] + Bitdepth: 8 + Signed: False + Compression: wavelet + Colorspace Unknown: False + Colour Specification Box (colr) @ (62, 15) + Method: enumerated colorspace + Precedence: 0 + Colorspace: sRGB + Contiguous Codestream Box (jp2c) @ (77, 115228) + UUID Box (uuid) @ (115305, 671) + UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) + UUID Data: + + + + 480 + 800 + 3 + + + Stürm und Drang + Glymur + + + + From f264bb00f3cbfe157a8b77ea756275027bc8ddd2 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 13 Feb 2014 11:30:04 -0500 Subject: [PATCH 085/326] Default value for codestream argument should be [0], not 0. --- bin/jp2dump | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/jp2dump b/bin/jp2dump index 08907e5..7632aff 100755 --- a/bin/jp2dump +++ b/bin/jp2dump @@ -15,7 +15,7 @@ parser.add_argument('-c', '--codestream', help=chelp, nargs=1, type=int, - default=0) + default=[0]) parser.add_argument('filename') args = parser.parse_args() if args.noxml: From 5ba6fd5d536909d4f338c328d860fcb6a2b683bb Mon Sep 17 00:00:00 2001 From: jevans Date: Fri, 14 Feb 2014 21:45:40 -0500 Subject: [PATCH 086/326] Added write support for Label box. #168 --- glymur/jp2box.py | 8 ++++++++ glymur/jp2k.py | 16 ++++++++++++++++ glymur/test/test_jp2box_jpx.py | 29 +++++++++++++++++++++++++---- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index d5e79e1..a599f84 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2286,6 +2286,14 @@ class LabelBox(Jp2kBox): msg = 'glymur.jp2box.LabelBox("{0}")'.format(self.label) return msg + def write(self, fptr): + """Write a Label box to file. + """ + length = 8 + len(self.label.encode()) + fptr.write(struct.pack('>I', length)) + fptr.write(self.box_id.encode()) + fptr.write(self.label.encode()) + @staticmethod def parse(fptr, offset, length): """Parse Label box. diff --git a/glymur/jp2k.py b/glymur/jp2k.py index d159fcc..96b6e68 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -1119,6 +1119,7 @@ def _validate_jp2_box_sequence(boxes): _validate_jp2h(boxes) _validate_jp2c(boxes) _validate_association(boxes) + _validate_label(boxes) _validate_jpx_brand(boxes, boxes[1].brand) _validate_jpx_compatibility(boxes, boxes[1].compatibility_list) _validate_singletons(boxes) @@ -1304,6 +1305,21 @@ def _validate_jpx_compatibility(boxes, compatibility_list): # Same set of checks on any child boxes. _validate_jpx_compatibility(box.box, compatibility_list) +def _validate_label(boxes): + """ + Label boxes can only be inside association, codestream headers, or + compositing layer header boxes. + """ + for box in boxes: + if box.box_id != 'asoc': + if hasattr(box, 'box'): + for boxi in box.box: + if boxi.box_id == 'lbl ': + msg = "A label box cannot be nested inside a {0} box." + msg = msg.format(box.box_id) + raise IOError(msg) + # Same set of checks on any child boxes. + _validate_label(box.box) def _validate_association(boxes): """ diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 2ebeb1f..8b5e075 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -67,8 +67,8 @@ class TestJPXWrap(unittest.TestCase): self.assertEqual(ET.tostring(jpx.box[-1].box[1].xml.getroot()), b'0') - def test_association_box(self): - """Wrap JP2 to JPX with asoc(nlst, xml)""" + def test_association_label_box(self): + """Wrap JP2 to JPX with asoc, label, and nlst boxes""" jp2 = Jp2k(self.jp2file) boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] @@ -76,11 +76,13 @@ class TestJPXWrap(unittest.TestCase): boxes[1].brand = 'jpx ' boxes[1].compatibility_list = ['jp2 ', 'jpx '] + label = 'this is a test' + lblb = glymur.jp2box.LabelBox(label) numbers = (0, 1) nlst = glymur.jp2box.NumberListBox(numbers) the_xml = ET.fromstring('0') xmlb = glymur.jp2box.XMLBox(xml=the_xml) - asoc = glymur.jp2box.AssociationBox([nlst, xmlb]) + asoc = glymur.jp2box.AssociationBox([nlst, xmlb, lblb]) boxes.append(asoc) with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: @@ -89,10 +91,12 @@ class TestJPXWrap(unittest.TestCase): self.assertEqual(jpx.box[1].compatibility_list, ['jp2 ', 'jpx ']) self.assertEqual(jpx.box[-1].box_id, 'asoc') self.assertEqual(jpx.box[-1].box[0].box_id, 'nlst') - self.assertEqual(jpx.box[-1].box[1].box_id, 'xml ') self.assertEqual(jpx.box[-1].box[0].associations, numbers) + self.assertEqual(jpx.box[-1].box[1].box_id, 'xml ') self.assertEqual(ET.tostring(jpx.box[-1].box[1].xml.getroot()), b'0') + self.assertEqual(jpx.box[-1].box[2].box_id, 'lbl ') + self.assertEqual(jpx.box[-1].box[2].label, label) def test_only_one_data_reference(self): """Data reference boxes cannot be inside a superbox .""" @@ -111,6 +115,23 @@ class TestJPXWrap(unittest.TestCase): with self.assertRaises(IOError): jpx = jp2.wrap(tfile.name, boxes=boxes) + def test_lbl_at_top_level(self): + """Label boxes can only be inside a asoc box .""" + jp2 = Jp2k(self.jp2file) + boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] + + flag = 0 + version = (0, 0, 0) + url = 'file:////usr/local/bin' + lblb = glymur.jp2box.LabelBox('hi there') + + # Put it inside the jp2 header box. + boxes[2].box.append(lblb) + + with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: + with self.assertRaises(IOError): + jpx = jp2.wrap(tfile.name, boxes=boxes) + def test_data_reference_not_at_top_level(self): """Data reference boxes cannot be inside a superbox .""" jp2 = Jp2k(self.jp2file) From 75785e4ab067288e58ba0c0335272ac2de563521 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 15 Feb 2014 16:37:08 -0500 Subject: [PATCH 087/326] Refactored __str__ for superboxes. #175 --- glymur/jp2box.py | 83 ++++++++++++------------------------------------ 1 file changed, 20 insertions(+), 63 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index a599f84..e358eab 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -88,6 +88,18 @@ class Jp2kBox(object): msg = "Not supported for {0} box.".format(self.longname) raise NotImplementedError(msg) + def _str_superbox(self): + """__str__ method for all superboxes.""" + msg = Jp2kBox.__str__(self) + for box in self.box: + boxstr = str(box) + + # Add indentation. + strs = [('\n ' + x) for x in boxstr.split('\n')] + msg += ''.join(strs) + return msg + + def _write_superbox(self, fptr): """Write a superbox. @@ -592,16 +604,7 @@ class CodestreamHeaderBox(Jp2kBox): return msg def __str__(self): - msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: - return msg - - for box in self.box: - boxstr = str(box) - - # Add indentation. - strs = [('\n ' + x) for x in boxstr.split('\n')] - msg += ''.join(strs) + msg = self._str_superbox() return msg @staticmethod @@ -659,16 +662,7 @@ class CompositingLayerHeaderBox(Jp2kBox): return msg def __str__(self): - msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: - return msg - - for box in self.box: - boxstr = str(box) - - # Add indentation. - strs = [('\n ' + x) for x in boxstr.split('\n')] - msg += ''.join(strs) + msg = self._str_superbox() return msg @staticmethod @@ -831,10 +825,10 @@ class ContiguousCodestreamBox(Jp2kBox): msg += '\n Main header:' for segment in self.main_header.segment: segstr = str(segment) - # Add indentation. strs = [('\n ' + x) for x in segstr.split('\n')] msg += ''.join(strs) + return msg @staticmethod @@ -1156,16 +1150,7 @@ class FragmentTableBox(Jp2kBox): return msg def __str__(self): - msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: - return msg - - for box in self.box: - boxstr = str(box) - - # Add indentation. - strs = [('\n ' + x) for x in boxstr.split('\n')] - msg += ''.join(strs) + msg = self._str_superbox() return msg @staticmethod @@ -1415,16 +1400,7 @@ class AssociationBox(Jp2kBox): return msg def __str__(self): - msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: - return msg - - for box in self.box: - boxstr = str(box) - - # Add indentation. - strs = [('\n ' + x) for x in boxstr.split('\n')] - msg += ''.join(strs) + msg = self._str_superbox() return msg @staticmethod @@ -1485,13 +1461,7 @@ class JP2HeaderBox(Jp2kBox): return msg def __str__(self): - msg = Jp2kBox.__str__(self) - for box in self.box: - boxstr = str(box) - - # Add indentation. - strs = [('\n ' + x) for x in boxstr.split('\n')] - msg += ''.join(strs) + msg = self._str_superbox() return msg def write(self, fptr): @@ -2086,13 +2056,7 @@ class ResolutionBox(Jp2kBox): return msg def __str__(self): - msg = Jp2kBox.__str__(self) - for box in self.box: - boxstr = str(box) - - # Add indentation. - strs = [('\n ' + x) for x in boxstr.split('\n')] - msg += ''.join(strs) + msg = self._str_superbox() return msg @staticmethod @@ -2612,14 +2576,7 @@ class UUIDInfoBox(Jp2kBox): return msg def __str__(self): - msg = Jp2kBox.__str__(self) - for box in self.box: - box_str = str(box) - - # Add indentation. - lst = [('\n ' + x) for x in box_str.split('\n')] - msg += ''.join(lst) - + msg = self._str_superbox() return msg @staticmethod From 3c333e999da41cb6129cd9ce346e5b72caf2c7e1 Mon Sep 17 00:00:00 2001 From: jevans Date: Sun, 16 Feb 2014 20:05:39 -0500 Subject: [PATCH 088/326] Parsing unknown superboxes with known 1st sub box. #175 --- glymur/jp2box.py | 22 +++++++++++++++++++++- glymur/test/test_jp2box_jpx.py | 20 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index e358eab..4810834 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -164,12 +164,32 @@ class Jp2kBox(object): # Call the proper parser for the given box with ID "T". try: box = _BOX_WITH_ID[box_id].parse(fptr, start, num_bytes) - except KeyError: + except KeyError as err: msg = 'Unrecognized box ({0}) encountered.'.format(box_id) warnings.warn(msg) box = Jp2kBox(box_id, offset=start, length=num_bytes, longname='Unknown box') + if fptr.tell() != start + 8: + # If the file pointer has advanced, then the KeyError + # ocurred during the parsing of the box. + pass + else: + # Could it be a superbox with recognizable child boxes? + # Peek ahead to see. + pos = fptr.tell() + read_buffer = fptr.read(8) + sub_length, sub_id = struct.unpack('>I4s', read_buffer) + sub_id = sub_id.decode('utf-8') + + # Regardless of whether or not we recognize the box, rewind back + # to properly advance to the next box. + fptr.seek(pos) + + # Now process any child boxes if we actually did recognize it. + if sub_id in _BOX_WITH_ID.keys(): + box.box = box.parse_superbox(fptr) + superbox.append(box) # Position to the start of the next box. diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 8b5e075..6624291 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -4,6 +4,7 @@ Test suite specifically targeting JPX box layout. """ import os +import shutil import struct import sys import tempfile @@ -202,6 +203,25 @@ class TestJPX(unittest.TestCase): self.assertEqual(jpx.box[2].standard_flag, (5, 42, 45, 2, 18, 19, 1, 8, 12, 31, 20)) + @unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.") + def test_unknown_superbox(self): + """Verify that we can handle an unknown superbox.""" + with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile: + with open(self.jpxfile, 'rb') as ifile: + tfile.write(ifile.read()) + + # Add the header for an unknwon superbox. + write_buffer = struct.pack('>I4s', 20, 'grp '.encode()) + tfile.write(write_buffer) + write_buffer = struct.pack('>I4sI', 12, 'free'.encode(), 0) + tfile.write(write_buffer) + tfile.flush() + + with self.assertWarns(UserWarning): + jpx = Jp2k(tfile.name) + self.assertEqual(jpx.box[-1].box_id, 'grp ') + self.assertEqual(jpx.box[-1].box[0].box_id, 'free') + def test_free_box(self): """Verify that we can handle a free box.""" j = Jp2k(self.jpxfile) From 9999a60656592d0b6b613f5010a0f2863d30591a Mon Sep 17 00:00:00 2001 From: jevans Date: Sun, 16 Feb 2014 21:43:05 -0500 Subject: [PATCH 089/326] Improved the warning when an exif tag is bad. #175 --- glymur/_uuid_io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glymur/_uuid_io.py b/glymur/_uuid_io.py index d4971a1..ed610e5 100644 --- a/glymur/_uuid_io.py +++ b/glymur/_uuid_io.py @@ -146,7 +146,7 @@ class _Ifd(object): tag_name = tagnum2name[tag] except KeyError: # Ok, we don't recognize this tag. Just use the numeric id. - msg = 'Unrecognized Exif tag "{0}".'.format(tag) + msg = 'Unrecognized Exif tag: {0}'.format(tag) warnings.warn(msg, UserWarning) tag_name = tag self.processed_ifd[tag_name] = value From 331f901dae997b6babf9440d48fda26d33daba43 Mon Sep 17 00:00:00 2001 From: jevans Date: Sun, 16 Feb 2014 22:03:33 -0500 Subject: [PATCH 090/326] Refactored superbox parsing. #175 --- glymur/jp2box.py | 77 ++++++++++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 28 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 4810834..d2e374b 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -120,6 +120,53 @@ class Jp2kBox(object): fptr.write(struct.pack('>I', end_pos - orig_pos)) fptr.seek(end_pos) + def parse_this_box(self, fptr, box_id, start, num_bytes): + """Parse the current box. + + Parameters + ---------- + fptr : file + Open file object. + box_id : str + 4-letter identifier for the current box. + start, num_bytes: int + Byte offset and length of the current box. + + Returns + ------- + box : Jp2kBox + object corresponding to the current box + """ + try: + box = _BOX_WITH_ID[box_id].parse(fptr, start, num_bytes) + except KeyError as err: + msg = 'Unrecognized box ({0}) encountered.'.format(box_id) + warnings.warn(msg) + box = Jp2kBox(box_id, offset=start, length=num_bytes, + longname='Unknown box') + + if fptr.tell() != start + 8: + # If the file pointer has advanced, then the KeyError + # ocurred during the parsing of the box. + pass + else: + # Could it be a superbox with recognizable child boxes? + # Peek ahead to see. + pos = fptr.tell() + read_buffer = fptr.read(8) + sub_length, sub_id = struct.unpack('>I4s', read_buffer) + sub_id = sub_id.decode('utf-8') + + # Regardless of whether or not we recognize the box, rewind back + # to properly advance to the next box. + fptr.seek(pos) + + # Now process any child boxes if we actually did recognize it. + if sub_id in _BOX_WITH_ID.keys(): + box.box = box.parse_superbox(fptr) + + return box + def parse_superbox(self, fptr): """Parse a superbox (box consisting of nothing but other boxes. @@ -159,36 +206,10 @@ class Jp2kBox(object): num_bytes, = struct.unpack('>Q', read_buffer) else: + # The box_length value really is the length of the box! num_bytes = box_length - # Call the proper parser for the given box with ID "T". - try: - box = _BOX_WITH_ID[box_id].parse(fptr, start, num_bytes) - except KeyError as err: - msg = 'Unrecognized box ({0}) encountered.'.format(box_id) - warnings.warn(msg) - box = Jp2kBox(box_id, offset=start, length=num_bytes, - longname='Unknown box') - - if fptr.tell() != start + 8: - # If the file pointer has advanced, then the KeyError - # ocurred during the parsing of the box. - pass - else: - # Could it be a superbox with recognizable child boxes? - # Peek ahead to see. - pos = fptr.tell() - read_buffer = fptr.read(8) - sub_length, sub_id = struct.unpack('>I4s', read_buffer) - sub_id = sub_id.decode('utf-8') - - # Regardless of whether or not we recognize the box, rewind back - # to properly advance to the next box. - fptr.seek(pos) - - # Now process any child boxes if we actually did recognize it. - if sub_id in _BOX_WITH_ID.keys(): - box.box = box.parse_superbox(fptr) + box = self.parse_this_box(fptr, box_id, start, num_bytes) superbox.append(box) From df89b5523aafd43cb0aead80c66ddb719c97da01 Mon Sep 17 00:00:00 2001 From: jevans Date: Sun, 16 Feb 2014 22:16:46 -0500 Subject: [PATCH 091/326] Some pylint work. --- glymur/jp2box.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index d2e374b..ab8a6e5 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -63,6 +63,8 @@ class Jp2kBox(object): offset of the box from the start of the file. longname : str more verbose description of the box. + box : list + List of JPEG 2000 boxes. """ def __init__(self, box_id='', offset=0, length=0, longname=''): @@ -70,6 +72,7 @@ class Jp2kBox(object): self.length = length self.offset = offset self.longname = longname + self.box = [] def __repr__(self): msg = "glymur.jp2box.Jp2kBox(box_id='{0}', offset={1}, length={2}, " @@ -130,7 +133,7 @@ class Jp2kBox(object): box_id : str 4-letter identifier for the current box. start, num_bytes: int - Byte offset and length of the current box. + Byte offset and length of the current box. Returns ------- @@ -139,7 +142,7 @@ class Jp2kBox(object): """ try: box = _BOX_WITH_ID[box_id].parse(fptr, start, num_bytes) - except KeyError as err: + except KeyError: msg = 'Unrecognized box ({0}) encountered.'.format(box_id) warnings.warn(msg) box = Jp2kBox(box_id, offset=start, length=num_bytes, @@ -154,7 +157,7 @@ class Jp2kBox(object): # Peek ahead to see. pos = fptr.tell() read_buffer = fptr.read(8) - sub_length, sub_id = struct.unpack('>I4s', read_buffer) + _, sub_id = struct.unpack('>I4s', read_buffer) sub_id = sub_id.decode('utf-8') # Regardless of whether or not we recognize the box, rewind back @@ -869,7 +872,7 @@ class ContiguousCodestreamBox(Jp2kBox): # Add indentation. strs = [('\n ' + x) for x in segstr.split('\n')] msg += ''.join(strs) - + return msg @staticmethod @@ -1725,8 +1728,7 @@ class PaletteBox(Jp2kBox): palette[j] = struct.unpack_from(fmt, read_buffer, offset=j * row_nbytes) - box = PaletteBox(palette, bps, signed, length=length, offset=offset) - return box + return PaletteBox(palette, bps, signed, length=length, offset=offset) # Map rreq codes to display text. From 5e76d1cf3927941b7563c3a52a006761447d6947 Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 17 Feb 2014 17:38:26 -0500 Subject: [PATCH 092/326] Added print support for unknown boxes. #175 --- glymur/jp2box.py | 35 ++++++++++++++++++++++++-- glymur/test/fixtures.py | 8 ++++++ glymur/test/test_printing.py | 48 ++++++++++++++++++++---------------- 3 files changed, 68 insertions(+), 23 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index ab8a6e5..043a1be 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -145,8 +145,8 @@ class Jp2kBox(object): except KeyError: msg = 'Unrecognized box ({0}) encountered.'.format(box_id) warnings.warn(msg) - box = Jp2kBox(box_id, offset=start, length=num_bytes, - longname='Unknown box') + box = UnknownBox(box_id, offset=start, length=num_bytes, + longname='Unknown') if fptr.tell() != start + 8: # If the file pointer has advanced, then the KeyError @@ -2739,6 +2739,37 @@ class DataEntryURLBox(Jp2kBox): return box +class UnknownBox(Jp2kBox): + """Container for unrecognized boxes. + + Attributes + ---------- + box_id : str + 4-character identifier for the box. + length : int + length of the box in bytes. + offset : int + offset of the box from the start of the file. + longname : str + more verbose description of the box. + """ + def __init__(self, box_id, length=0, offset=-1, longname=''): + Jp2kBox.__init__(self, box_id=box_id, longname=longname) + self.length = length + self.offset = offset + + def __repr__(self): + msg = "glymur.jp2box.UnknownBox({0})".format(self.box_id) + return msg + + def __str__(self): + if len(self.box) > 0: + msg = self._str_superbox() + else: + msg = Jp2kBox.__str__(self) + return msg + + class UUIDBox(Jp2kBox): """Container for UUID box information. diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 6e0a76c..a7906c5 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -690,3 +690,11 @@ file7_rreq = r"""Reader Requirements Box (rreq) @ (44, 24) Feature 060: 0x60 e-sRGB enumerated colorspace Feature 043: 0x40 Deprecated - compositing layer uses restricted ICC profile Vendor Features:""" + +file1_xml = r"""XML Box (xml ) @ (36, 439) + + + 2001-11-01T13:45:00.000-06:00 + Professional 120 Image + + """ diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 5fe3322..132d1ec 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -39,6 +39,7 @@ from .fixtures import text_gbr_27, text_gbr_33, text_gbr_34 class TestPrinting(unittest.TestCase): """Tests for verifying how printing works.""" def setUp(self): + self.jpxfile = glymur.data.jpxfile() self.jp2file = glymur.data.nemo() self.j2kfile = glymur.data.goodstuff() @@ -48,6 +49,31 @@ class TestPrinting(unittest.TestCase): def tearDown(self): pass + @unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.") + def test_unknown_superbox(self): + """Verify that we can handle an unknown superbox.""" + with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile: + with open(self.jpxfile, 'rb') as ifile: + tfile.write(ifile.read()) + + # Add the header for an unknwon superbox. + write_buffer = struct.pack('>I4s', 20, 'grp '.encode()) + tfile.write(write_buffer) + write_buffer = struct.pack('>I4sI', 12, 'free'.encode(), 0) + tfile.write(write_buffer) + tfile.flush() + + with self.assertWarns(UserWarning): + jpx = Jp2k(tfile.name) + glymur.set_printoptions(short=True) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(jpx.box[-1]) + actual = fake_out.getvalue().strip() + lines = ['Unknown Box (grp ) @ (695609, 20)', + ' Free Box (free) @ (695617, 12)'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + def test_printoptions_bad_argument(self): """Verify error when bad parameter to set_printoptions""" with self.assertRaises(TypeError): @@ -602,27 +628,7 @@ class TestPrinting(unittest.TestCase): with patch('sys.stdout', new=StringIO()) as fake_out: print(j.box[2]) actual = fake_out.getvalue().strip() - - lines = ['XML Box (xml ) @ (36, 439)', - ' ', - - ' ', - ' ' - + '2001-11-01T13:45:00.000-06:00' - + '', - - ' ' - + 'Professional 120 Image' - + '', - - ' ', - ' '] - expected = '\n'.join(lines) - self.assertEqual(actual, expected) + self.assertEqual(actual, fixtures.file1_xml) @unittest.skipIf(sys.hexversion < 0x03000000, "Only trusting python3 for printing non-ascii chars") From cd606e1f9df52dfe8207dac6fb15b7a3f5c7ae80 Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 18 Feb 2014 20:01:28 -0500 Subject: [PATCH 093/326] Starting write support for ftbl and flst boxes. #175 --- glymur/jp2box.py | 50 +++++++++++++++++++++-- glymur/test/test_jp2box_jpx.py | 73 ++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 3 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 043a1be..4ce47ff 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -148,7 +148,8 @@ class Jp2kBox(object): box = UnknownBox(box_id, offset=start, length=num_bytes, longname='Unknown') - if fptr.tell() != start + 8: + cpos = fptr.tell() + if not ((cpos == start + 8) or (cpos == start + 16)): # If the file pointer has advanced, then the KeyError # ocurred during the parsing of the box. pass @@ -1122,6 +1123,18 @@ class FragmentListBox(Jp2kBox): self.length = length self.offset = offset + def _validate(self): + """Validate internal correctness.""" + if (((len(self.fragment_offset) != len(self.fragment_length)) or + (len(self.fragment_length) != len(self.data_reference)))): + msg = "The lengths of the fragment offsets, fragment lengths, and " + msg += "data reference items must be the same." + raise IOError(msg) + if any([x <= 0 for x in self.fragment_offset]): + raise IOError("Fragment offsets must all be positive.") + if any([x <= 0 for x in self.fragment_length]): + raise IOError("Fragment lengths must all be positive.") + def __repr__(self): msg = "glymur.jp2box.FragmentListBox()" return msg @@ -1141,6 +1154,22 @@ class FragmentListBox(Jp2kBox): return msg + def write(self, fptr): + """Write a fragment list box to file. + """ + self._validate() + num_items = len(self.fragment_offset) + length = 8 + 2 + num_items * 14 + fptr.write(struct.pack('>I', length)) + fptr.write(self.box_id.encode()) + fptr.write(struct.pack('>H', num_items)) + for j in range(num_items): + write_buffer = struct.pack('>QIH', + self.fragment_offset[j], + self.fragment_length[j], + self.data_reference[j]) + fptr.write(write_buffer) + @staticmethod def parse(fptr, offset, length): """Parse JPX free box. @@ -1184,10 +1213,11 @@ class FragmentTableBox(Jp2kBox): longname : str more verbose description of the box. """ - def __init__(self, length=0, offset=-1): + def __init__(self, box=None, length=0, offset=-1): Jp2kBox.__init__(self, box_id='ftbl', longname='Fragment Table') self.length = length self.offset = offset + self.box = box if box is not None else [] def __repr__(self): msg = "glymur.jp2box.FragmentTableBox()" @@ -1212,7 +1242,7 @@ class FragmentTableBox(Jp2kBox): Returns ------- - FreeBox instance + FragmentTableBox instance """ box = FragmentTableBox(length=length, offset=offset) @@ -1222,6 +1252,20 @@ class FragmentTableBox(Jp2kBox): return box + def _validate(self): + """Self-validate the box before writing.""" + box_ids = [box.box_id for box in self.box] + if len(box_ids) != 1 or box_ids[0] != 'flst': + msg = "Fragment table boxes must have a single fragment list " + msg += "box as a child box." + raise IOError(msg) + + def write(self, fptr): + """Write a fragment table box to file. + """ + self._validate() + self._write_superbox(fptr) + class FreeBox(Jp2kBox): diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 6624291..99250c5 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -41,6 +41,34 @@ class TestJPXWrap(unittest.TestCase): def tearDown(self): os.unlink(self.xmlfile) + def test_ftbl(self): + """Write a fragment table box.""" + # Add a negative test where offset < 0 + # Add a negative test where length < 0 + # Add a negative test where ref > 0 but no data reference box. + # Add a negative test where more than one flst + # Add negative test where ftbl contained in a superbox. + jp2 = Jp2k(self.jp2file) + boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] + + # The ftyp box must be modified to jpx. + boxes[1].brand = 'jpx ' + boxes[1].compatibility_list = ['jp2 ', 'jpxb'] + + offset = [89] + length = [1132288] + reference = [0] + flst = glymur.jp2box.FragmentListBox(offset, length, reference) + ftbl = glymur.jp2box.FragmentTableBox(box=[flst]) + boxes.append(ftbl) + + with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: + jpx = jp2.wrap(tfile.name, boxes=boxes) + + self.assertEqual(jpx.box[1].compatibility_list, ['jp2 ', 'jpxb']) + self.assertEqual(jpx.box[-1].box_id, 'ftbl') + self.assertEqual(jpx.box[-1].box[0].box_id, 'flst') + def test_jpxb_compatibility(self): """Wrap JP2 to JPX, state jpxb compatibility""" jp2 = Jp2k(self.jp2file) @@ -194,6 +222,51 @@ class TestJPX(unittest.TestCase): def tearDown(self): pass + def test_flst_lens_not_the_same(self): + """A fragment list box items must be the same length.""" + offset = [89] + length = [1132288] + reference = [0, 0] + flst = glymur.jp2box.FragmentListBox(offset, length, reference) + with self.assertRaises(IOError): + with tempfile.TemporaryFile() as tfile: + flst.write(tfile) + + def test_flst_offsets_not_positive(self): + """A fragment list box offsets must be positive.""" + offset = [0] + length = [1132288] + reference = [0] + flst = glymur.jp2box.FragmentListBox(offset, length, reference) + with self.assertRaises(IOError): + with tempfile.TemporaryFile() as tfile: + flst.write(tfile) + + def test_flst_lengths_not_positive(self): + """A fragment list box lengths must be positive.""" + offset = [89] + length = [0] + reference = [0] + flst = glymur.jp2box.FragmentListBox(offset, length, reference) + with self.assertRaises(IOError): + with tempfile.TemporaryFile() as tfile: + flst.write(tfile) + + def test_ftbl_boxes_empty(self): + """A fragment table box must have at least one child box.""" + ftbl = glymur.jp2box.FragmentTableBox() + with self.assertRaises(IOError): + with tempfile.TemporaryFile() as tfile: + ftbl.write(tfile) + + def test_ftbl_child_not_flst(self): + """A fragment table box can only contain a fragment list.""" + free = glymur.jp2box.FreeBox() + ftbl = glymur.jp2box.FragmentTableBox(box=[free]) + with self.assertRaises(IOError): + with tempfile.TemporaryFile() as tfile: + ftbl.write(tfile) + def test_jpx_rreq_mask_length_3(self): """There are some JPX files with rreq mask length of 3.""" jpx = Jp2k(self.jpxfile) From c82d525dd2286d95811a078b3237cf58641563ab Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 19 Feb 2014 20:25:08 -0500 Subject: [PATCH 094/326] FileType box sanity checks. #175 --- glymur/jp2box.py | 13 +++++++++++++ glymur/test/test_jp2box.py | 23 +++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 4ce47ff..0a370f9 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1046,9 +1046,22 @@ class FileTypeBox(Jp2kBox): return msg + def _validate(self): + """Validate the box before writing to file.""" + if self.brand not in ['jp2 ', 'jpx ']: + msg = "The file type brand must be either 'jp2 ' or 'jpx '." + raise IOError(msg) + valid_cls = ['jp2 ', 'jpx ', 'jpxb'] + for item in self.compatibility_list: + if item not in valid_cls: + msg = "The file type compatibility list item '{0}' is not " + msg += "valid: valid entries are {1}" + raise IOError(msg.format(item, valid_cls)) + def write(self, fptr): """Write a File Type box to file. """ + self._validate() length = 16 + 4*len(self.compatibility_list) fptr.write(struct.pack('>I', length)) fptr.write('ftyp'.encode()) diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index feffec9..426bf66 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -340,6 +340,29 @@ class TestChannelDefinition(unittest.TestCase): association=association) +class TestFileTypeBox(unittest.TestCase): + """Test suite for ftyp box issues.""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_brand_unknown(self): + """A ftyp box brand must be 'jp2 ' or 'jpx '.""" + ftyp = glymur.jp2box.FileTypeBox(brand='jp3') + with self.assertRaises(IOError): + with tempfile.TemporaryFile() as tfile: + ftyp.write(tfile) + + def test_cl_entry_unknown(self): + """A ftyp box cl list can only contain 'jp2 ', 'jpx ', or 'jpxb'.""" + ftyp = glymur.jp2box.FileTypeBox(compatibility_list=['jp3']) + with self.assertRaises(IOError): + with tempfile.TemporaryFile() as tfile: + ftyp.write(tfile) + class TestColourSpecificationBox(unittest.TestCase): """Test suite for colr box instantiation.""" From 4276a978eab3e12516d9f077159b24b89048c002 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 20 Feb 2014 09:19:41 -0500 Subject: [PATCH 095/326] Allowing DataReferenceBoxes to be empty. #175 --- glymur/jp2box.py | 7 +++++-- glymur/test/test_jp2box_jpx.py | 13 +++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 0a370f9..49cde5d 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -915,9 +915,12 @@ class DataReferenceBox(Jp2kBox): DR : list Data Entry URL boxes. """ - def __init__(self, data_entry_url_boxes, length=0, offset=-1): + def __init__(self, data_entry_url_boxes=None, length=0, offset=-1): Jp2kBox.__init__(self, box_id='dtbl', longname='Data Reference') - self.DR = data_entry_url_boxes + if data_entry_url_boxes is None: + self.DR = [] + else: + self.DR = data_entry_url_boxes self.length = length self.offset = offset diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 99250c5..5d6cca2 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -127,6 +127,19 @@ class TestJPXWrap(unittest.TestCase): self.assertEqual(jpx.box[-1].box[2].box_id, 'lbl ') self.assertEqual(jpx.box[-1].box[2].label, label) + def test_empty_data_reference(self): + """Data reference boxes can be empty.""" + jp2 = Jp2k(self.jp2file) + boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] + + dref = glymur.jp2box.DataReferenceBox() + boxes.append(dref) + + with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: + jpx = jp2.wrap(tfile.name, boxes=boxes) + self.assertEqual(jpx.box[-1].box_id, 'dtbl') + self.assertEqual(len(jpx.box[-1].box), 0) + def test_only_one_data_reference(self): """Data reference boxes cannot be inside a superbox .""" jp2 = Jp2k(self.jp2file) From 4caa13a9559f4020f52fec56148803497acb9798 Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 20 Feb 2014 22:34:52 -0500 Subject: [PATCH 096/326] More negative tests. #175 --- glymur/jp2box.py | 50 ++++++++++++++++++++++++++-------- glymur/test/test_jp2box.py | 12 +++++++- glymur/test/test_jp2box_jpx.py | 19 +++++++++++++ 3 files changed, 69 insertions(+), 12 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 49cde5d..f3fce20 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -29,6 +29,7 @@ import numpy as np from .codestream import Codestream from .core import _COLORSPACE_MAP_DISPLAY from .core import _COLOR_TYPE_MAP_DISPLAY +from .core import SRGB, GREYSCALE, YCC from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE from .core import ANY_ICC_PROFILE, VENDOR_COLOR_METHOD from .core import _pretty_print_xml @@ -270,13 +271,6 @@ class ColourSpecificationBox(Jp2kBox): approximation=0, colorspace=None, icc_profile=None, length=0, offset=-1): Jp2kBox.__init__(self, box_id='colr', longname='Colour Specification') - - if colorspace is not None and icc_profile is not None: - raise IOError("colorspace and icc_profile cannot both be set.") - if method not in (1, 2, 3, 4): - raise IOError("Invalid method.") - if approximation not in (0, 1, 2, 3, 4): - raise IOError("Invalid approximation.") self.method = method self.precedence = precedence self.approximation = approximation @@ -284,6 +278,33 @@ class ColourSpecificationBox(Jp2kBox): self.icc_profile = icc_profile self.length = length self.offset = offset + self._validate() + + def _validate(self): + """Verify that the box obeys the specifications.""" + if self.colorspace is not None and self.icc_profile is not None: + raise IOError("colorspace and icc_profile cannot both be set.") + if self.method not in (1, 2, 3, 4): + raise IOError("Invalid method.") + if self.approximation not in (0, 1, 2, 3, 4): + raise IOError("Invalid approximation.") + + def _write_validate(self): + """In addition to constructor validation steps, run validation steps + for writing.""" + if self.colorspace is None: + msg = "Writing Colour Specification boxes without enumerated " + msg += "colorspaces is not supported at this time." + raise IOError(msg) + + if self.icc_profile is None: + if self.colorspace not in [SRGB, GREYSCALE, YCC]: + msg = "Colorspace should correspond to one of SRGB, GREYSCALE, " + msg += "or YCC." + raise IOError(msg) + + self._validate() + def __repr__(self): msg = "glymur.jp2box.ColourSpecificationBox(" @@ -327,10 +348,7 @@ class ColourSpecificationBox(Jp2kBox): def write(self, fptr): """Write an Colour Specification box to file. """ - if self.colorspace is None: - msg = "Writing Colour Specification boxes without enumerated " - msg += "colorspaces is not supported at this time." - raise NotImplementedError(msg) + self._write_validate() length = 15 if self.icc_profile is None else 11 + len(self.icc_profile) fptr.write(struct.pack('>I', length)) fptr.write('colr'.encode()) @@ -923,10 +941,20 @@ class DataReferenceBox(Jp2kBox): self.DR = data_entry_url_boxes self.length = length self.offset = offset + self._validate() + + def _validate(self): + """Verify that the box obeys the specifications.""" + for box in self.DR: + if box.box_id != 'url ': + msg = 'All child boxes of a data reference box must be data ' + msg += 'entry URL boxes.' + raise IOError(msg) def write(self, fptr): """Write a Data Reference box to file. """ + self._validate() # Very similar to the say a superbox is written. orig_pos = fptr.tell() diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 426bf66..b6f0524 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -394,7 +394,7 @@ class TestColourSpecificationBox(unittest.TestCase): 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): + with self.assertRaises(IOError): j2k.wrap(tfile.name, boxes=boxes) @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @@ -440,6 +440,16 @@ class TestColourSpecificationBox(unittest.TestCase): glymur.jp2box.ColourSpecificationBox(colorspace=colorspace, approximation=approx) + def test_colr_with_bad_color(self): + """colr must have a valid color, strange as though that may sound.""" + colorspace = -1 + approx = 0 + colr = glymur.jp2box.ColourSpecificationBox(colorspace=colorspace, + approximation=approx) + with tempfile.TemporaryFile() as tfile: + with self.assertRaises(IOError): + colr.write(tfile) + class TestAppend(unittest.TestCase): """Tests for append method.""" diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 5d6cca2..b2916c5 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -140,6 +140,25 @@ class TestJPXWrap(unittest.TestCase): self.assertEqual(jpx.box[-1].box_id, 'dtbl') self.assertEqual(len(jpx.box[-1].box), 0) + def test_deurl_child_of_dtbl(self): + """Data reference boxes can only contain data entry url boxes.""" + jp2 = Jp2k(self.jp2file) + boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] + + ftyp = glymur.jp2box.FileTypeBox() + with self.assertRaises(IOError): + dref = glymur.jp2box.DataReferenceBox([ftyp]) + + # Try to get around it by appending the ftyp box after creation. + dref = glymur.jp2box.DataReferenceBox() + dref.DR.append(ftyp) + + boxes.append(dref) + + with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: + with self.assertRaises(IOError): + jpx = jp2.wrap(tfile.name, boxes=boxes) + def test_only_one_data_reference(self): """Data reference boxes cannot be inside a superbox .""" jp2 = Jp2k(self.jp2file) From f8ced317dbf3da38861e69ede3e6fe86f5d35604 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 22 Feb 2014 21:30:52 -0500 Subject: [PATCH 097/326] More negative tests. Incompatible change to ChannelDefinitionBox. #175 --- CHANGES.txt | 21 +++++++++-------- docs/source/changelog.rst | 1 + docs/source/how_do_i.rst | 2 +- glymur/jp2box.py | 47 +++++++++++++++++++++++--------------- glymur/test/test_jp2box.py | 32 +++++++++++++++++++++++++- 5 files changed, 73 insertions(+), 30 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 1febdfb..24d9616 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,13 +1,14 @@ -Feb 09, 2014 - Removed support for Python 2.6. Added write support for JP2 - UUID, DataEntryURL, Palette and Component Mapping boxes, JPX - Association, NumberList and DataReference boxes. Added read - support for JPX free, number list, data reference, fragment - table, and fragment list boxes. Improved JPX Reader Requirements box - support. Added get_printoptions, set_printoptions functions. - Palette box now a 2D numpy array instead of a list of 1D arrays. - JP2 super box constructors now take optional box list argument. - Fixed bug where JPX files with more than one codestream but - advertising jp2 compatibility were not being read. +Feb 09, 2014 - Changed constructor for ChannelDefinition box. Removed support + for Python 2.6. Added write support for JP2 UUID, DataEntryURL, + Palette and Component Mapping boxes, JPX Association, NumberList + and DataReference boxes. Added read support for JPX free, + number list, data reference, fragment table, and fragment list + boxes. Improved JPX Reader Requirements box support. Added + get_printoptions, set_printoptions functions. Palette box now + a 2D numpy array instead of a list of 1D arrays. JP2 super box + constructors now take optional box list argument. Fixed bug + where JPX files with more than one codestream but advertising + jp2 compatibility were not being read. Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask length is unsupported. diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index f7e5921..ad4e40b 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -11,6 +11,7 @@ ChangeLog * added write support for JP2 UUID, dataEntryURL, palette, and component mapping boxes * added read/write support for JPX free, number list, and data reference boxes * Added read support for JPX fragment list and fragment table boxes + * incompatible change to channel definition box constructor, channel_type and association are no longer keyword arguments * incompatible change to palette box constructor, it now takes a 2D numpy array instead of a list of 1D arrays 0.5.0 (September 16, 2013) diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index a1fe089..fa8f098 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -359,7 +359,7 @@ channel, but we aren't doing that). :: >>> from glymur.core import RED, GREEN, BLUE, WHOLE_IMAGE >>> asoc = [RED, GREEN, BLUE, WHOLE_IMAGE] - >>> cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=ctype, association=asoc) + >>> cdef = glymur.jp2box.ChannelDefinitionBox(ctype, asoc) >>> print(cdef) Channel Definition Box (cdef) @ (0, 0) Channel 0 (color) ==> (1) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index f3fce20..f82151a 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -544,23 +544,28 @@ class ChannelDefinitionBox(Jp2kBox): association : list index of the associated color """ - def __init__(self, index=None, channel_type=None, association=None, - **kwargs): + def __init__(self, channel_type, association, index=None, **kwargs): Jp2kBox.__init__(self, box_id='cdef', longname='Channel Definition') - # channel type and association must be specified. - if channel_type is None or association is None: - raise IOError("channel_type and association must be specified.") - if index is None: - index = list(range(len(channel_type))) + self.index = tuple(range(len(channel_type))) + else: + self.index = tuple(index) - if len(index) != len(channel_type) or len(index) != len(association): + self.channel_type = tuple(channel_type) + self.association = tuple(association) + self.__dict__.update(**kwargs) + self._validate() + + def _validate(self): + """Verify that the box obeys the specifications.""" + # channel type and association must be specified. + if not (len(self.index) == len(self.channel_type) == len(self.association)): msg = "Length of channel definition box inputs must be the same." raise IOError(msg) # channel types must be one of 0, 1, 2, 65535 - if any(x not in [0, 1, 2, 65535] for x in channel_type): + if any(x not in [0, 1, 2, 65535] for x in self.channel_type): msg = "Channel types must be in the set of\n\n" msg += " 0 - colour image data for associated color\n" msg += " 1 - opacity\n" @@ -568,10 +573,6 @@ class ChannelDefinitionBox(Jp2kBox): msg += " 65535 - unspecified" raise IOError(msg) - self.index = tuple(index) - self.channel_type = tuple(channel_type) - self.association = tuple(association) - self.__dict__.update(**kwargs) def __str__(self): msg = Jp2kBox.__str__(self) @@ -597,6 +598,7 @@ class ChannelDefinitionBox(Jp2kBox): def write(self, fptr): """Write a channel definition box to file. """ + self._validate() num_components = len(self.association) fptr.write(struct.pack('>I', 8 + 2 + num_components * 6)) fptr.write('cdef'.encode('utf-8')) @@ -634,9 +636,10 @@ class ChannelDefinitionBox(Jp2kBox): channel_type = data[1:num_components * 6:3] association = data[2:num_components * 6:3] - box = ChannelDefinitionBox(index=index, channel_type=channel_type, - association=association, length=length, - offset=offset) + box = ChannelDefinitionBox(index=tuple(index), + channel_type=tuple(channel_type), + association=tuple(association), + length=length, offset=offset) return box @@ -795,7 +798,6 @@ class ComponentMappingBox(Jp2kBox): if _printoptions['short'] == True: return msg - for k in range(len(self.component_index)): if self.mapping_type[k] == 1: msg += '\n Component {0} ==> palette column {1}' @@ -1050,7 +1052,6 @@ class FileTypeBox(Jp2kBox): self.brand = brand self.minor_version = minor_version if compatibility_list is None: - # see W0102, pylint self.compatibility_list = ['jp2 '] else: self.compatibility_list = compatibility_list @@ -1718,6 +1719,15 @@ class PaletteBox(Jp2kBox): self.signed = signed self.length = length self.offset = offset + self._validate() + + def _validate(self): + """Verify that the box obeys the specifications.""" + if ((len(self.bits_per_component) != len(self.signed)) or + (len(self.signed) != self.palette.shape[1])): + msg = "The length of the 'bits_per_component' and the 'signed' " + msg += "members must equal the number of columns of the palette." + raise IOError(msg) def __repr__(self): msg = "glymur.jp2box.PaletteBox({0}, bits_per_component={1}, " @@ -1737,6 +1747,7 @@ class PaletteBox(Jp2kBox): def write(self, fptr): """Write a Palette box to file. """ + self._validate() bytes_per_row = sum(self.bits_per_component) / 8 bytes_per_palette = bytes_per_row * self.palette.shape[0] box_length = 8 + 3 + self.palette.shape[1] + bytes_per_palette diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index b6f0524..fb15e26 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -139,7 +139,7 @@ class TestChannelDefinition(unittest.TestCase): def test_cdef_no_inputs(self): """channel_type and association are required inputs.""" - with self.assertRaises(IOError): + with self.assertRaises(TypeError): glymur.jp2box.ChannelDefinitionBox() def test_rgb_with_index(self): @@ -451,6 +451,36 @@ class TestColourSpecificationBox(unittest.TestCase): colr.write(tfile) +@unittest.skipIf(os.name == "nt", + "Problems using NamedTemporaryFile on windows.") +class TestPaletteBox(unittest.TestCase): + """Test suite for pclr box instantiation.""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_mismatched_bitdepth_signed(self): + """bitdepth and signed arguments must have equal length""" + palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint8) + bps = (8, 8, 8) + signed = (False, False) + with self.assertRaises(IOError): + pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps, + signed=signed) + + def test_mismatched_signed_palette(self): + """bitdepth and signed arguments must have equal length""" + palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint8) + bps = (8, 8, 8, 8) + signed = (False, False, False, False) + with self.assertRaises(IOError): + pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps, + signed=signed) + + class TestAppend(unittest.TestCase): """Tests for append method.""" From 16ce2ef88380dcad85e2edfda957b46ec9a29948 Mon Sep 17 00:00:00 2001 From: jevans Date: Sun, 23 Feb 2014 12:37:47 -0500 Subject: [PATCH 098/326] More negative tests, pylint work. #175 --- glymur/jp2box.py | 17 ++++++--- glymur/jp2k.py | 17 ++++++++- glymur/test/test_jp2box_jpx.py | 65 ++++++++++++++++++++++------------ 3 files changed, 72 insertions(+), 27 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index f82151a..18f994d 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -124,7 +124,7 @@ class Jp2kBox(object): fptr.write(struct.pack('>I', end_pos - orig_pos)) fptr.seek(end_pos) - def parse_this_box(self, fptr, box_id, start, num_bytes): + def _parse_this_box(self, fptr, box_id, start, num_bytes): """Parse the current box. Parameters @@ -214,7 +214,7 @@ class Jp2kBox(object): # The box_length value really is the length of the box! num_bytes = box_length - box = self.parse_this_box(fptr, box_id, start, num_bytes) + box = self._parse_this_box(fptr, box_id, start, num_bytes) superbox.append(box) @@ -560,7 +560,8 @@ class ChannelDefinitionBox(Jp2kBox): def _validate(self): """Verify that the box obeys the specifications.""" # channel type and association must be specified. - if not (len(self.index) == len(self.channel_type) == len(self.association)): + if not ((len(self.index) == len(self.channel_type)) and + (len(self.channel_type) == len(self.association))): msg = "Length of channel definition box inputs must be the same." raise IOError(msg) @@ -953,10 +954,18 @@ class DataReferenceBox(Jp2kBox): msg += 'entry URL boxes.' raise IOError(msg) + def _write_validate(self): + """Verify that the box obeys the specifications for writing. + """ + if len(self.DR) == 0: + msg = "A data reference box cannot be empty when written to a file." + raise IOError(msg) + self._validate() + def write(self, fptr): """Write a Data Reference box to file. """ - self._validate() + self._write_validate() # Very similar to the say a superbox is written. orig_pos = fptr.tell() diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 96b6e68..988e814 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -1110,6 +1110,10 @@ def _validate_nonzero_image_size(nrows, ncols, component_index): raise IOError(msg) +JP2_IDS = ['colr', 'cdef', 'cmap', 'jp2c', 'ftyp', 'ihdr', 'jp2h', 'jP ', + 'pclr', 'res ', 'resc', 'resd', 'xml ', 'ulst', 'uinf', 'url ', + 'uuid'] + def _validate_jp2_box_sequence(boxes): """Run through series of tests for JP2 box legality. @@ -1118,6 +1122,18 @@ def _validate_jp2_box_sequence(boxes): _validate_signature_compatibility(boxes) _validate_jp2h(boxes) _validate_jp2c(boxes) + if boxes[1].brand == 'jpx ': + _validate_jpx_box_sequence(boxes) + else: + count = _collect_box_count(boxes) + for id in count.keys(): + if id not in JP2_IDS: + msg = "The presence of a '{0}' box requires that the file type " + msg += "brand be set to 'jpx '." + raise IOError(msg.format(id)) + +def _validate_jpx_box_sequence(boxes): + """Run through series of tests for JPX box legality.""" _validate_association(boxes) _validate_label(boxes) _validate_jpx_brand(boxes, boxes[1].brand) @@ -1125,7 +1141,6 @@ def _validate_jp2_box_sequence(boxes): _validate_singletons(boxes) _validate_top_level(boxes) - def _validate_signature_compatibility(boxes): """Validate the file signature and compatibility status.""" # Check for a bad sequence of boxes. diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index b2916c5..e50d4f9 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -4,12 +4,10 @@ Test suite specifically targeting JPX box layout. """ import os -import shutil import struct import sys import tempfile import unittest -import warnings import xml.etree.cElementTree as ET import glymur @@ -41,6 +39,18 @@ class TestJPXWrap(unittest.TestCase): def tearDown(self): os.unlink(self.xmlfile) + def test_jp2_with_jpx_box(self): + """If the brand is jp2, then no jpx boxes are allowed.""" + jp2 = Jp2k(self.jp2file) + boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] + boxes = jp2.box + + boxes.append(glymur.jp2box.AssociationBox()) + + with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: + with self.assertRaises(IOError): + jp2.wrap(tfile.name, boxes=boxes) + def test_ftbl(self): """Write a fragment table box.""" # Add a negative test where offset < 0 @@ -128,17 +138,18 @@ class TestJPXWrap(unittest.TestCase): self.assertEqual(jpx.box[-1].box[2].label, label) def test_empty_data_reference(self): - """Data reference boxes can be empty.""" + """Empty data reference boxes can be created, but not written.""" jp2 = Jp2k(self.jp2file) boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] + boxes[1].brand = 'jpx ' + dref = glymur.jp2box.DataReferenceBox() boxes.append(dref) with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: - jpx = jp2.wrap(tfile.name, boxes=boxes) - self.assertEqual(jpx.box[-1].box_id, 'dtbl') - self.assertEqual(len(jpx.box[-1].box), 0) + with self.assertRaises(IOError): + jp2.wrap(tfile.name, boxes=boxes) def test_deurl_child_of_dtbl(self): """Data reference boxes can only contain data entry url boxes.""" @@ -157,13 +168,16 @@ class TestJPXWrap(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: with self.assertRaises(IOError): - jpx = jp2.wrap(tfile.name, boxes=boxes) + jp2.wrap(tfile.name, boxes=boxes) def test_only_one_data_reference(self): """Data reference boxes cannot be inside a superbox .""" jp2 = Jp2k(self.jp2file) boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] + # Have to make the ftyp brand jpx. + boxes[1].brand = 'jpx ' + flag = 0 version = (0, 0, 0) url = 'file:////usr/local/bin' @@ -174,16 +188,16 @@ class TestJPXWrap(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: with self.assertRaises(IOError): - jpx = jp2.wrap(tfile.name, boxes=boxes) + jp2.wrap(tfile.name, boxes=boxes) def test_lbl_at_top_level(self): """Label boxes can only be inside a asoc box .""" jp2 = Jp2k(self.jp2file) boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] - flag = 0 - version = (0, 0, 0) - url = 'file:////usr/local/bin' + # Have to make the ftyp brand jpx. + boxes[1].brand = 'jpx ' + lblb = glymur.jp2box.LabelBox('hi there') # Put it inside the jp2 header box. @@ -191,13 +205,16 @@ class TestJPXWrap(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: with self.assertRaises(IOError): - jpx = jp2.wrap(tfile.name, boxes=boxes) + jp2.wrap(tfile.name, boxes=boxes) - def test_data_reference_not_at_top_level(self): + def test_data_reference_in_subbox(self): """Data reference boxes cannot be inside a superbox .""" jp2 = Jp2k(self.jp2file) boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] + # Have to make the ftyp brand jpx. + boxes[1].brand = 'jpx ' + flag = 0 version = (0, 0, 0) url = 'file:////usr/local/bin' @@ -209,13 +226,17 @@ class TestJPXWrap(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: with self.assertRaises(IOError): - jpx = jp2.wrap(tfile.name, boxes=boxes) + jp2.wrap(tfile.name, boxes=boxes) def test_jp2_to_jpx_sans_jp2_compatibility(self): """jp2 wrapped to jpx not including jp2 compatibility is wrong.""" jp2 = Jp2k(self.jp2file) boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] + + # Have to make the ftyp brand jpx. + boxes[1].brand = 'jpx ' boxes[1].compatibility_list.append('jp2 ') + numbers = [0, 1] nlst = glymur.jp2box.NumberListBox(numbers) the_xml = ET.fromstring('0') @@ -225,7 +246,7 @@ class TestJPXWrap(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: with self.assertRaises(RuntimeError): - jpx = jp2.wrap(tfile.name, boxes=boxes) + jp2.wrap(tfile.name, boxes=boxes) def test_jp2_to_jpx_sans_jpx_brand(self): """Verify error when jp2 wrapped to jpx does not include jpx brand.""" @@ -241,7 +262,7 @@ class TestJPXWrap(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: with self.assertRaises(RuntimeError): - jpx = jp2.wrap(tfile.name, boxes=boxes) + jp2.wrap(tfile.name, boxes=boxes) @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @@ -262,7 +283,7 @@ class TestJPX(unittest.TestCase): flst = glymur.jp2box.FragmentListBox(offset, length, reference) with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: - flst.write(tfile) + flst.write(tfile) def test_flst_offsets_not_positive(self): """A fragment list box offsets must be positive.""" @@ -272,7 +293,7 @@ class TestJPX(unittest.TestCase): flst = glymur.jp2box.FragmentListBox(offset, length, reference) with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: - flst.write(tfile) + flst.write(tfile) def test_flst_lengths_not_positive(self): """A fragment list box lengths must be positive.""" @@ -282,14 +303,14 @@ class TestJPX(unittest.TestCase): flst = glymur.jp2box.FragmentListBox(offset, length, reference) with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: - flst.write(tfile) + flst.write(tfile) def test_ftbl_boxes_empty(self): """A fragment table box must have at least one child box.""" ftbl = glymur.jp2box.FragmentTableBox() with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: - ftbl.write(tfile) + ftbl.write(tfile) def test_ftbl_child_not_flst(self): """A fragment table box can only contain a fragment list.""" @@ -297,7 +318,7 @@ class TestJPX(unittest.TestCase): ftbl = glymur.jp2box.FragmentTableBox(box=[free]) with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: - ftbl.write(tfile) + ftbl.write(tfile) def test_jpx_rreq_mask_length_3(self): """There are some JPX files with rreq mask length of 3.""" @@ -314,7 +335,7 @@ class TestJPX(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile: with open(self.jpxfile, 'rb') as ifile: tfile.write(ifile.read()) - + # Add the header for an unknwon superbox. write_buffer = struct.pack('>I4s', 20, 'grp '.encode()) tfile.write(write_buffer) From 992c036da701ab297bca4bada4d7e3507c9ed5a1 Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 24 Feb 2014 21:12:24 -0500 Subject: [PATCH 099/326] Added ftbl/data reference test. Removed bad asoc check. #175 --- glymur/jp2k.py | 24 ++++++------------------ glymur/test/test_jp2box_jpx.py | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 988e814..6db0198 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -1134,7 +1134,6 @@ def _validate_jp2_box_sequence(boxes): def _validate_jpx_box_sequence(boxes): """Run through series of tests for JPX box legality.""" - _validate_association(boxes) _validate_label(boxes) _validate_jpx_brand(boxes, boxes[1].brand) _validate_jpx_compatibility(boxes, boxes[1].compatibility_list) @@ -1283,6 +1282,12 @@ def _validate_top_level(boxes): if 'dtbl' in multiples: raise IOError('There can only be one dtbl box in a file.') + # If there is one data reference box, then there must also be one ftbl. + if 'dtbl' in count and 'ftbl' not in count: + msg = 'The presence of a data reference box requires the presence of ' + msg += 'a fragment table box as well.' + raise IOError(msg) + def _validate_singletons(boxes): """Several boxes can only occur once.""" count = _collect_box_count(boxes) @@ -1336,23 +1341,6 @@ def _validate_label(boxes): # Same set of checks on any child boxes. _validate_label(box.box) -def _validate_association(boxes): - """ - Association boxes can only contain number list boxes and xml boxes, as far - as we know. - """ - for box in boxes: - if box.box_id == 'asoc': - if box.box[0].box_id != 'nlst' or box.box[1].box_id != 'xml ': - msg = "An Association box can only contain a NumberList box " - msg += "followed by an XML box." - raise RuntimeError(msg) - if hasattr(box, 'box') != 0: - # Same set of checks on any child boxes. - _validate_association(box.box) - - - def extract_image_cube(image): """Extract 3D image from openjpeg data structure. """ diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index e50d4f9..1eb5198 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -270,6 +270,7 @@ class TestJPX(unittest.TestCase): """Test suite for other JPX boxes.""" def setUp(self): + self.jp2file = glymur.data.nemo() self.jpxfile = glymur.data.jpxfile() def tearDown(self): @@ -354,6 +355,25 @@ class TestJPX(unittest.TestCase): self.assertEqual(j.box[16].box[0].box_id, 'free') self.assertEqual(type(j.box[16].box[0]), glymur.jp2box.FreeBox) + def test_data_reference_requires_dtbl(self): + """The existance of a data reference box requires a ftbl box as well.""" + flag = 0 + version = (0, 0, 0) + url1 = 'file:////usr/local/bin' + url2 = 'http://glymur.readthedocs.org' + jpx1 = glymur.Jp2k(self.jp2file) + boxes = jpx1.box + boxes[1].brand = 'jpx ' + + deurl1 = glymur.jp2box.DataEntryURLBox(flag, version, url1) + deurl2 = glymur.jp2box.DataEntryURLBox(flag, version, url2) + dref = glymur.jp2box.DataReferenceBox([deurl1, deurl2]) + boxes.append(dref) + + with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile: + with self.assertRaises(IOError): + jpx2 = jpx1.wrap(tfile.name, boxes=boxes) + def test_dtbl(self): """Verify that we can interpret Data Reference boxes.""" # Copy the existing JPX file, add a data reference box onto the end. From 99d1a584d8619f75c7218ba920f935886c44eb8a Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 25 Feb 2014 07:15:12 -0500 Subject: [PATCH 100/326] Verifying that data entry url box URLs are null terminated. #175 --- glymur/jp2box.py | 9 +++++++-- glymur/test/test_jp2box.py | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 18f994d..3cf637d 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2788,13 +2788,18 @@ class DataEntryURLBox(Jp2kBox): def write(self, fptr): """Write a data entry url box to file. """ - length = 8 + 1 + 3 + len(self.url.encode()) + # Make sure it is written out as null-terminated. + url = self.url + if self.url[-1] != chr(0): + url = url + chr(0) + + length = 8 + 1 + 3 + len(url.encode()) write_buffer = struct.pack('>I4sBBBB', length, self.box_id.encode(), self.version, self.flag[0], self.flag[1], self.flag[2]) fptr.write(write_buffer) - fptr.write(self.url.encode()) + fptr.write(url.encode()) def __repr__(self): diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index fb15e26..5861971 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -77,6 +77,28 @@ class TestDataEntryURL(unittest.TestCase): self.assertEqual(jp22.box[4].flag, (0, 0, 0)) self.assertEqual(jp22.box[4].url, url) + def test_null_termination(self): + """I.9.3.2 specifies that the location field must be null terminated.""" + jp2 = Jp2k(self.jp2file) + + url = 'http://glymur.readthedocs.org' + deurl = glymur.jp2box.DataEntryURLBox(0, (0, 0, 0), url) + boxes = [box for box in jp2.box if box.box_id != 'uuid'] + boxes.append(deurl) + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + jp22 = jp2.wrap(tfile.name, boxes=boxes) + + self.assertEqual(jp22.box[-1].length, 42) + + # Go to the last box. Seek past the L, T, version, and flag fields. + with open(tfile.name, 'rb') as fptr: + fptr.seek(jp22.box[-1].offset + 4 + 4 + 1 + 3) + + nbytes = jp22.box[-1].offset + jp22.box[-1].length - fptr.tell() + read_buffer = fptr.read(nbytes) + read_url = read_buffer.decode('utf-8') + self.assertEqual(url + chr(0), read_url) + @unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2 or OPENJP2_IS_V2_OFFICIAL, From 04b143e1ff59cae24b9ad390d321d3e253535e4a Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 25 Feb 2014 21:41:02 -0500 Subject: [PATCH 101/326] Added proof of concept test for linking exterior codestreams. Closes #175 --- glymur/test/test_jp2box_jpx.py | 60 ++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 1eb5198..7493dea 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -12,7 +12,8 @@ import xml.etree.cElementTree as ET import glymur from glymur import Jp2k - +from glymur.jp2box import DataEntryURLBox, FileTypeBox, JPEG2000SignatureBox +from glymur.jp2box import DataReferenceBox, FragmentListBox, FragmentTableBox @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") class TestJPXWrap(unittest.TestCase): @@ -20,6 +21,7 @@ class TestJPXWrap(unittest.TestCase): def setUp(self): self.jp2file = glymur.data.nemo() + self.j2kfile = glymur.data.goodstuff() raw_xml = b""" @@ -39,6 +41,60 @@ class TestJPXWrap(unittest.TestCase): def tearDown(self): os.unlink(self.xmlfile) + def test_jpx_ftbl_no_codestream(self): + """Can have a jpx with no codestream.""" + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1: + with open(self.jp2file, 'rb') as fptr: + tfile1.write(fptr.read()) + tfile1.flush() + jp2_1 = Jp2k(tfile1.name) + + jp2c = [box for box in jp2_1.box if box.box_id == 'jp2c'][0] + + # coff and clen will be the offset and length input arguments + # to the fragment list box. dr_idx is the data reference index. + coff = [] + clen = [] + dr_idx = [] + + coff.append(jp2c.offset + 8) + clen.append(jp2c.length - (coff[0] - jp2c.offset)) + dr_idx.append(1) + + # Make the url box for this codestream. + url1 = DataEntryURLBox(0, [0, 0, 0], 'file://' + tfile1.name) + + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2: + + j2k = Jp2k(self.j2kfile) + jp2_2 = j2k.wrap(tfile2.name) + + jp2c = [box for box in jp2_2.box if box.box_id == 'jp2c'][0] + coff.append(jp2c.offset + 8) + clen.append(jp2c.length - (coff[0] - jp2c.offset)) + dr_idx.append(2) + + # Make the url box for this codestream. + url2 = DataEntryURLBox(0, [0, 0, 0], 'file://' + tfile2.name) + + boxes = [JPEG2000SignatureBox(), + FileTypeBox(brand='jpx ', + compatibility_list=['jpx ', + 'jp2 ', 'jpxb']), + jp2_1.box[2]] + with tempfile.NamedTemporaryFile(suffix='.jpx') as tjpx: + for box in boxes: + box.write(tjpx) + + flst = FragmentListBox(coff, clen, dr_idx) + ftbl = FragmentTableBox([flst]) + ftbl.write(tjpx) + + boxes = [url1, url2] + dtbl = DataReferenceBox(data_entry_url_boxes=boxes) + dtbl.write(tjpx) + tjpx.flush() + def test_jp2_with_jpx_box(self): """If the brand is jp2, then no jpx boxes are allowed.""" jp2 = Jp2k(self.jp2file) @@ -372,7 +428,7 @@ class TestJPX(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile: with self.assertRaises(IOError): - jpx2 = jpx1.wrap(tfile.name, boxes=boxes) + jpx1.wrap(tfile.name, boxes=boxes) def test_dtbl(self): """Verify that we can interpret Data Reference boxes.""" From 63fdde230db21774adfeebfe35d3390ef160b81b Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 26 Feb 2014 20:56:25 -0500 Subject: [PATCH 102/326] Migrated from ElementTree to lxml. #176 --- glymur/_uuid_io.py | 3 +- glymur/core.py | 8 +- glymur/jp2box.py | 13 +- glymur/test/fixtures.py | 524 ++++++++++++++++---------------- glymur/test/test_jp2box.py | 4 +- glymur/test/test_jp2box_jpx.py | 2 +- glymur/test/test_jp2box_uuid.py | 5 +- glymur/test/test_jp2box_xml.py | 3 +- glymur/test/test_printing.py | 15 +- 9 files changed, 296 insertions(+), 281 deletions(-) diff --git a/glymur/_uuid_io.py b/glymur/_uuid_io.py index ed610e5..399fa99 100644 --- a/glymur/_uuid_io.py +++ b/glymur/_uuid_io.py @@ -7,7 +7,8 @@ import re import struct import sys import warnings -import xml.etree.cElementTree as ET + +import lxml.etree as ET if sys.hexversion < 0x02070000: # pylint: disable=F0401,E0611 diff --git a/glymur/core.py b/glymur/core.py index 620d0e3..30282f2 100644 --- a/glymur/core.py +++ b/glymur/core.py @@ -1,7 +1,7 @@ """Core definitions to be shared amongst the modules. """ import copy -import xml.etree.cElementTree as ET +import lxml.etree as ET # Progression order LRCP = 0 @@ -81,9 +81,9 @@ _CAPABILITIES_DISPLAY = { def _pretty_print_xml(xml, level=0): """Pretty print XML data. """ - xml = copy.deepcopy(xml) - _indent(xml.getroot(), level=level) - xmltext = ET.tostring(xml.getroot(), encoding='utf-8').decode('utf-8') + #xml = copy.deepcopy(xml) + #_indent(xml.getroot(), level=level) + xmltext = ET.tostring(xml, encoding='utf-8').decode('utf-8') # Indent it a bit. lst = [(' ' + x) for x in xmltext.split('\n')] diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 3cf637d..192763f 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -22,8 +22,8 @@ import struct import sys import uuid import warnings -import xml.etree.cElementTree as ET +import lxml.etree as ET import numpy as np from .codestream import Codestream @@ -2565,7 +2565,10 @@ class XMLBox(Jp2kBox): xml = self.xml if self.xml is not None: - msg += _pretty_print_xml(self.xml) + bstr = ET.tostring(self.xml, + encoding='utf-8', + pretty_print=True).decode('utf-8') + msg += '\n {0}'.format(bstr) else: msg += '\n {0}'.format(xml) return msg @@ -2971,8 +2974,10 @@ class UUIDBox(Jp2kBox): return msg if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): - line = '\n UUID Data: {0}' - xmlstring = _pretty_print_xml(self.data) + line = '\n UUID Data:\n{0}' + xmlstring = ET.tostring(self.data, + encoding='utf-8', + pretty_print=True).decode('utf-8') xmlstring = xmlstring.rstrip() msg += line.format(xmlstring) elif self.uuid.bytes == b'JpgTiffExif->JP2': diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index a7906c5..e7c81a3 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -183,90 +183,92 @@ def read_pgx_header(pgx_file): nemo_xmp_box = """UUID Box (uuid) @ (77, 3146) UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) - UUID Data: - - - - Google - 2013-02-09T14:47:53 - - - 1 - 72/1 - 72/1 - 2 - HTC - HTC Glacier - 2592 - 1456 - - - 8 - 8 - 8 - - - 2 - 3 - - - 1343036288/4294967295 - 1413044224/4294967295 - - - - - 2748779008/4294967295 - 1417339264/4294967295 - 1288490240/4294967295 - 2576980480/4294967295 - 644245120/4294967295 - 257698032/4294967295 - - - - - 1 - 2528 - 1424 - 353/100 - 0 - 0/1 - WGS-84 - 2013-02-09T14:47:53 - - - 76 - - - 0220 - 0100 - - - 1 - 2 - 3 - 0 - - - 42,20.56N - 71,5.29W - 2013-02-09T19:47:53Z - NETWORK - - - 2013-02-09T14:47:53 - - - - - Glymur - Python XMP Toolkit - - - - - """ + UUID Data: + + + + + Google + 2013-02-09T14:47:53 + + + 1 + 72/1 + 72/1 + 2 + HTC + HTC Glacier + 2592 + 1456 + + + 8 + 8 + 8 + + + 2 + 3 + + + 1343036288/4294967295 + 1413044224/4294967295 + + + + + 2748779008/4294967295 + 1417339264/4294967295 + 1288490240/4294967295 + 2576980480/4294967295 + 644245120/4294967295 + 257698032/4294967295 + + + + + 1 + 2528 + 1424 + 353/100 + 0 + 0/1 + WGS-84 + 2013-02-09T14:47:53 + + + 76 + + + 0220 + 0100 + + + 1 + 2 + 3 + 0 + + + 42,20.56N + 71,5.29W + 2013-02-09T19:47:53Z + NETWORK + + + 2013-02-09T14:47:53 + + + + + Glymur + Python XMP Toolkit + + + + + +""" SimpleRDF = """ - - - Google - 2013-02-09T14:47:53 - - - 1 - 72/1 - 72/1 - 2 - HTC - HTC Glacier - 2592 - 1456 - - - 8 - 8 - 8 - - - 2 - 3 - - - 1343036288/4294967295 - 1413044224/4294967295 - - - - - 2748779008/4294967295 - 1417339264/4294967295 - 1288490240/4294967295 - 2576980480/4294967295 - 644245120/4294967295 - 257698032/4294967295 - - - - - 1 - 2528 - 1424 - 353/100 - 0 - 0/1 - WGS-84 - 2013-02-09T14:47:53 - - - 76 - - - 0220 - 0100 - - - 1 - 2 - 3 - 0 - - - 42,20.56N - 71,5.29W - 2013-02-09T19:47:53Z - NETWORK - - - 2013-02-09T14:47:53 - - - - - Glymur - Python XMP Toolkit - - - - - + UUID Data: + + + + + Google + 2013-02-09T14:47:53 + + + 1 + 72/1 + 72/1 + 2 + HTC + HTC Glacier + 2592 + 1456 + + + 8 + 8 + 8 + + + 2 + 3 + + + 1343036288/4294967295 + 1413044224/4294967295 + + + + + 2748779008/4294967295 + 1417339264/4294967295 + 1288490240/4294967295 + 2576980480/4294967295 + 644245120/4294967295 + 257698032/4294967295 + + + + + 1 + 2528 + 1424 + 353/100 + 0 + 0/1 + WGS-84 + 2013-02-09T14:47:53 + + + 76 + + + 0220 + 0100 + + + 1 + 2 + 3 + 0 + + + 42,20.56N + 71,5.29W + 2013-02-09T19:47:53Z + NETWORK + + + 2013-02-09T14:47:53 + + + + + Glymur + Python XMP Toolkit + + + + + + Contiguous Codestream Box (jp2c) @ (3223, 1132296) Main header: SOC marker segment @ (3231, 0) @@ -575,90 +579,92 @@ JP2 Header Box (jp2h) @ (32, 45) Colorspace: sRGB UUID Box (uuid) @ (77, 3146) UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) - UUID Data: - - - - Google - 2013-02-09T14:47:53 - - - 1 - 72/1 - 72/1 - 2 - HTC - HTC Glacier - 2592 - 1456 - - - 8 - 8 - 8 - - - 2 - 3 - - - 1343036288/4294967295 - 1413044224/4294967295 - - - - - 2748779008/4294967295 - 1417339264/4294967295 - 1288490240/4294967295 - 2576980480/4294967295 - 644245120/4294967295 - 257698032/4294967295 - - - - - 1 - 2528 - 1424 - 353/100 - 0 - 0/1 - WGS-84 - 2013-02-09T14:47:53 - - - 76 - - - 0220 - 0100 - - - 1 - 2 - 3 - 0 - - - 42,20.56N - 71,5.29W - 2013-02-09T19:47:53Z - NETWORK - - - 2013-02-09T14:47:53 - - - - - Glymur - Python XMP Toolkit - - - - - + UUID Data: + + + + + Google + 2013-02-09T14:47:53 + + + 1 + 72/1 + 72/1 + 2 + HTC + HTC Glacier + 2592 + 1456 + + + 8 + 8 + 8 + + + 2 + 3 + + + 1343036288/4294967295 + 1413044224/4294967295 + + + + + 2748779008/4294967295 + 1417339264/4294967295 + 1288490240/4294967295 + 2576980480/4294967295 + 644245120/4294967295 + 257698032/4294967295 + + + + + 1 + 2528 + 1424 + 353/100 + 0 + 0/1 + WGS-84 + 2013-02-09T14:47:53 + + + 76 + + + 0220 + 0100 + + + 1 + 2 + 3 + 0 + + + 42,20.56N + 71,5.29W + 2013-02-09T19:47:53Z + NETWORK + + + 2013-02-09T14:47:53 + + + + + Glymur + Python XMP Toolkit + + + + + + Contiguous Codestream Box (jp2c) @ (3223, 1132296)""" nemo_dump_no_codestream_no_xml = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) @@ -691,10 +697,10 @@ file7_rreq = r"""Reader Requirements Box (rreq) @ (44, 24) Feature 043: 0x40 Deprecated - compositing layer uses restricted ICC profile Vendor Features:""" -file1_xml = r"""XML Box (xml ) @ (36, 439) - - - 2001-11-01T13:45:00.000-06:00 - Professional 120 Image - - """ +file1_xml = """XML Box (xml ) @ (36, 439) + +\t +\t\t2001-11-01T13:45:00.000-06:00 +\t\tProfessional 120 Image +\t +""" diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 5861971..5dedbcc 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -22,10 +22,10 @@ import sys import tempfile import uuid from uuid import UUID -import xml.etree.cElementTree as ET import unittest import warnings +import lxml.etree as ET import numpy as np import glymur @@ -1056,7 +1056,7 @@ class TestRepr(unittest.TestCase): box = glymur.jp2box.XMLBox(xml=tree) regexp = r"""glymur.jp2box.XMLBox""" - regexp += r"""\(xml=<(xml.etree.ElementTree.){0,1}ElementTree object """ + regexp += r"""\(xml=\)""" if sys.hexversion < 0x03000000: diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 7493dea..f8e9db8 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -8,7 +8,7 @@ import struct import sys import tempfile import unittest -import xml.etree.cElementTree as ET +import lxml.etree as ET import glymur from glymur import Jp2k diff --git a/glymur/test/test_jp2box_uuid.py b/glymur/test/test_jp2box_uuid.py index 82dc253..a904174 100644 --- a/glymur/test/test_jp2box_uuid.py +++ b/glymur/test/test_jp2box_uuid.py @@ -18,7 +18,6 @@ import sys import tempfile import uuid import warnings -import xml.etree if sys.hexversion < 0x02070000: import unittest2 as unittest @@ -35,6 +34,8 @@ if sys.hexversion <= 0x03030000: else: from unittest.mock import patch +import lxml.etree + from .fixtures import HAS_PYTHON_XMP_TOOLKIT, OPJ_DATA_ROOT if HAS_PYTHON_XMP_TOOLKIT: from libxmp import XMPMeta @@ -71,7 +72,7 @@ class TestUUIDXMP(unittest.TestCase): # The data should be an XMP packet, which gets interpreted as # an ElementTree. self.assertTrue(isinstance(jp2.box[-1].data, - xml.etree.ElementTree.ElementTree)) + lxml.etree._ElementTree)) class TestUUIDExif(unittest.TestCase): """Tests for UUIDs of Exif type.""" diff --git a/glymur/test/test_jp2box_xml.py b/glymur/test/test_jp2box_xml.py index 4950da4..facc4df 100644 --- a/glymur/test/test_jp2box_xml.py +++ b/glymur/test/test_jp2box_xml.py @@ -20,7 +20,6 @@ import sys import tempfile import unittest import warnings -import xml.etree.cElementTree as ET if sys.hexversion < 0x03000000: from StringIO import StringIO @@ -32,6 +31,8 @@ if sys.hexversion <= 0x03030000: else: from unittest.mock import patch +import lxml.etree as ET + import glymur from glymur import Jp2k from glymur.jp2box import ColourSpecificationBox, ContiguousCodestreamBox diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 132d1ec..45b21df 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -15,7 +15,6 @@ import struct import sys import tempfile import warnings -from xml.etree import cElementTree as ET import unittest if sys.hexversion < 0x03000000: @@ -28,10 +27,12 @@ if sys.hexversion <= 0x03030000: else: from unittest.mock import patch +import lxml.etree as ET + import glymur from glymur import Jp2k from . import fixtures -from .fixtures import OPJ_DATA_ROOT, opj_data_file, nemo_xmp_box +from .fixtures import OPJ_DATA_ROOT, opj_data_file from .fixtures import text_gbr_27, text_gbr_33, text_gbr_34 @@ -118,6 +119,7 @@ class TestPrinting(unittest.TestCase): lst = actual.split('\n') lst = lst[1:] actual = '\n'.join(lst) + self.maxDiff = None self.assertEqual(actual, fixtures.nemo_dump_no_codestream) def test_printoptions_no_xml(self): @@ -209,6 +211,7 @@ class TestPrinting(unittest.TestCase): lst = actual.split('\n') lst = lst[1:] actual = '\n'.join(lst) + self.maxDiff = None self.assertEqual(actual, fixtures.nemo_dump_full) def test_entire_file(self): @@ -567,7 +570,7 @@ class TestPrinting(unittest.TestCase): print(j.box[3]) actual = fake_out.getvalue().strip() - expected = nemo_xmp_box + expected = fixtures.nemo_xmp_box self.assertEqual(actual, expected) def test_codestream(self): @@ -639,8 +642,7 @@ class TestPrinting(unittest.TestCase): # # 2.7.5 (fedora 19) prints xml entities. # 2.7.3 seems to want to print hex escapes. - text = u""" - Strömung""" + text = u"""Strömung""" if sys.hexversion < 0x03000000: xml = ET.parse(StringIO(text.encode('utf-8'))) else: @@ -668,8 +670,7 @@ class TestPrinting(unittest.TestCase): # # 2.7.5 (fedora 19) prints xml entities. # 2.7.3 seems to want to print hex escapes. - text = u""" - Россия""" + text = u"""Россия""" if sys.hexversion < 0x03000000: xml = ET.parse(StringIO(text.encode('utf-8'))) else: From f176204b2cd914496133720ea7fb6cd2eeb5371b Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 27 Feb 2014 06:55:30 -0500 Subject: [PATCH 103/326] Updated requirements for lxml. #176 --- .travis.yml | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2f45864..c055dce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,8 @@ before_install: # command to install dependencies install: - - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors contextlib2 mock; fi - - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install --use-mirrors numpy; fi + - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors lxml contextlib2 mock; fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install --use-mirrors lxml numpy; fi # command to run tests script: diff --git a/setup.py b/setup.py index b1332d8..41deb1a 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ kwargs = {'name': 'Glymur', 'license': 'MIT', 'test_suite': 'glymur.test'} -instllrqrs = ['numpy>=1.4.1'] +instllrqrs = ['lxml>=3.3', 'numpy>=1.4.1'] if sys.hexversion < 0x03030000: instllrqrs.append('contextlib2>=0.4') instllrqrs.append('mock>=1.0.1') From e8a0681a99ac9e12e19049c7cf307dad55eb9d09 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 27 Feb 2014 11:07:02 -0500 Subject: [PATCH 104/326] Confirmed to work with lxml 3.2.3, down from 3.3. #176 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 41deb1a..7769392 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ kwargs = {'name': 'Glymur', 'license': 'MIT', 'test_suite': 'glymur.test'} -instllrqrs = ['lxml>=3.3', 'numpy>=1.4.1'] +instllrqrs = ['lxml>=3.2.3', 'numpy>=1.4.1'] if sys.hexversion < 0x03030000: instllrqrs.append('contextlib2>=0.4') instllrqrs.append('mock>=1.0.1') From 5943aa6c7a1ac63b366f965a541cac4ea5d391c5 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 27 Feb 2014 18:48:48 -0500 Subject: [PATCH 105/326] Removed orphaned pretty print code for old element tree interface. #176 --- glymur/core.py | 42 ------------------------------------------ glymur/jp2box.py | 1 - 2 files changed, 43 deletions(-) diff --git a/glymur/core.py b/glymur/core.py index 30282f2..4e8f950 100644 --- a/glymur/core.py +++ b/glymur/core.py @@ -76,45 +76,3 @@ _CAPABILITIES_DISPLAY = { 1: '0', 2: '1', 3: '3'} - - -def _pretty_print_xml(xml, level=0): - """Pretty print XML data. - """ - #xml = copy.deepcopy(xml) - #_indent(xml.getroot(), level=level) - xmltext = ET.tostring(xml, encoding='utf-8').decode('utf-8') - - # Indent it a bit. - lst = [(' ' + x) for x in xmltext.split('\n')] - try: - xml = '\n'.join(lst) - return '\n{0}'.format(xml) - except UnicodeEncodeError: - # This can happen on python 2.x if the character set contains certain - # non-ascii characters. Just print out the corresponding xml char - # entities instead. - xml = u'\n'.join(lst) - text = u'\n{0}'.format(xml) - text = text.encode('ascii', 'xmlcharrefreplace') - return text - - -def _indent(elem, level=0): - """Recipe for pretty printing XML. Please see - - http://effbot.org/zone/element-lib.htm#prettyprint - """ - i = "\n" + level * " " - if len(elem): - if not elem.text or not elem.text.strip(): - elem.text = i + " " - if not elem.tail or not elem.tail.strip(): - elem.tail = i - for elem in elem: - _indent(elem, level + 1) - if not elem.tail or not elem.tail.strip(): - elem.tail = i - else: - if level and (not elem.tail or not elem.tail.strip()): - elem.tail = i diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 192763f..b4cc803 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -32,7 +32,6 @@ from .core import _COLOR_TYPE_MAP_DISPLAY from .core import SRGB, GREYSCALE, YCC from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE from .core import ANY_ICC_PROFILE, VENDOR_COLOR_METHOD -from .core import _pretty_print_xml from . import _uuid_io From 7828dc75e6683353cc6363d97c77bc4c61648887 Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 27 Feb 2014 19:44:05 -0500 Subject: [PATCH 106/326] Updated lxml requirements, checked on Raspbian. #176 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b1332d8..5b91590 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ kwargs = {'name': 'Glymur', 'license': 'MIT', 'test_suite': 'glymur.test'} -instllrqrs = ['numpy>=1.4.1'] +instllrqrs = ['numpy>=1.4.1', 'lxml>=2.3.2'] if sys.hexversion < 0x03030000: instllrqrs.append('contextlib2>=0.4') instllrqrs.append('mock>=1.0.1') From 892715e21d6dad688992de2e204b26ff784802f0 Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 27 Feb 2014 20:19:18 -0500 Subject: [PATCH 107/326] Updated docs for lxml requirement. #176 --- docs/source/changelog.rst | 1 + docs/source/detailed_installation.rst | 3 ++- docs/source/how_do_i.rst | 10 ++++------ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index ad4e40b..bbba98e 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -5,6 +5,7 @@ ChangeLog 0.6.0 (pending) =============== + * Added lxml requirement. * added set_printoptions, get_printoptions function * dropped support for Python 2.6, added support for Python 3.4 * dropped windows support diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index d1b3084..cfbb168 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -68,8 +68,8 @@ For python 3.3, you should install the following set of ports: * python33 * py33-numpy + * py33-lxml * py33-distribute - * py33-matplotlib (optional, for running certain tests) * py33-Pillow (optional, for running certain tests) MacPorts supplies both OpenJPEG 1.5.0 and OpenJPEG 2.0.0. @@ -85,6 +85,7 @@ packages may also need to be installed. Consult your package manager documentation or use pip. * setuptools + * python-lxml * matplotlib * pillow * contextlib2 (2.7 only) diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index fa8f098..4bde854 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -131,7 +131,6 @@ From within Python, it is as simple as printing the Jp2k object, i.e. :: - Contiguous Codestream Box (jp2c) @ (3223, 1132296) Main header: SOC marker segment @ (3231, 0) @@ -228,7 +227,6 @@ The :py:meth:`append` method can add an XML box as shown below:: >>> import shutil >>> import glymur >>> shutil.copyfile(glymur.data.nemo(), 'myfile.jp2') - >>> from xml.etree import cElementTree as ET >>> jp2 = glymur.Jp2k('myfile.jp2') >>> xmlbox = glymur.jp2box.XMLBox(filename='data.xml') >>> jp2.append(xmlbox) @@ -408,12 +406,12 @@ The example JP2 file shipped with glymur has an XMP UUID. :: . -Since the UUID data in this case is returned as an ElementTree instance, -one can use ElementTree from the standard library to access the data. -For example, to extract the **CreatorTool** attribute value, one could do the +Since the UUID data in this case is returned as an lxml ElementTree +instance, one can use lxml to access the data. For example, to +extract the **CreatorTool** attribute value, one could do the following - >>> xmp = j.box[3].data.packet + >>> xmp = j.box[3].data >>> rdf = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}' >>> ns2 = '{http://ns.adobe.com/xap/1.0/}' >>> name = '{0}RDF/{0}Description/{1}CreatorTool'.format(rdf, ns2) From 7be9e726388bb236e58b639aec82113384748698 Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 27 Feb 2014 20:43:57 -0500 Subject: [PATCH 108/326] refactored nemo xml #176 --- glymur/test/fixtures.py | 186 +++------------------------------------- 1 file changed, 10 insertions(+), 176 deletions(-) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index e7c81a3..3914173 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -181,10 +181,7 @@ def read_pgx_header(pgx_file): header = header.rstrip() return header, pos -nemo_xmp_box = """UUID Box (uuid) @ (77, 3146) - UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) - UUID Data: - +nemo_xml = """ @@ -270,6 +267,11 @@ nemo_xmp_box = """UUID Box (uuid) @ (77, 3146) """ +nemo_xmp_box = """UUID Box (uuid) @ (77, 3146) + UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) + UUID Data: +{0}""".format(nemo_xml) + SimpleRDF = """ @@ -374,91 +376,7 @@ JP2 Header Box (jp2h) @ (32, 45) UUID Box (uuid) @ (77, 3146) UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) UUID Data: - - - - - Google - 2013-02-09T14:47:53 - - - 1 - 72/1 - 72/1 - 2 - HTC - HTC Glacier - 2592 - 1456 - - - 8 - 8 - 8 - - - 2 - 3 - - - 1343036288/4294967295 - 1413044224/4294967295 - - - - - 2748779008/4294967295 - 1417339264/4294967295 - 1288490240/4294967295 - 2576980480/4294967295 - 644245120/4294967295 - 257698032/4294967295 - - - - - 1 - 2528 - 1424 - 353/100 - 0 - 0/1 - WGS-84 - 2013-02-09T14:47:53 - - - 76 - - - 0220 - 0100 - - - 1 - 2 - 3 - 0 - - - 42,20.56N - 71,5.29W - 2013-02-09T19:47:53Z - NETWORK - - - 2013-02-09T14:47:53 - - - - - Glymur - Python XMP Toolkit - - - - - - +{0} Contiguous Codestream Box (jp2c) @ (3223, 1132296) Main header: SOC marker segment @ (3231, 0) @@ -495,7 +413,7 @@ Contiguous Codestream Box (jp2c) @ (3223, 1132296) Quantization style: no quantization, 2 guard bits Step size: [(0, 8), (0, 9), (0, 9), (0, 10)] CME marker segment @ (3305, 37) - "Created by OpenJPEG version 2.0.0"''' + "Created by OpenJPEG version 2.0.0"'''.format(nemo_xml) nemo_dump_short = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) File Type Box (ftyp) @ (12, 20) @@ -580,92 +498,8 @@ JP2 Header Box (jp2h) @ (32, 45) UUID Box (uuid) @ (77, 3146) UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) UUID Data: - - - - - Google - 2013-02-09T14:47:53 - - - 1 - 72/1 - 72/1 - 2 - HTC - HTC Glacier - 2592 - 1456 - - - 8 - 8 - 8 - - - 2 - 3 - - - 1343036288/4294967295 - 1413044224/4294967295 - - - - - 2748779008/4294967295 - 1417339264/4294967295 - 1288490240/4294967295 - 2576980480/4294967295 - 644245120/4294967295 - 257698032/4294967295 - - - - - 1 - 2528 - 1424 - 353/100 - 0 - 0/1 - WGS-84 - 2013-02-09T14:47:53 - - - 76 - - - 0220 - 0100 - - - 1 - 2 - 3 - 0 - - - 42,20.56N - 71,5.29W - 2013-02-09T19:47:53Z - NETWORK - - - 2013-02-09T14:47:53 - - - - - Glymur - Python XMP Toolkit - - - - - - -Contiguous Codestream Box (jp2c) @ (3223, 1132296)""" +{0} +Contiguous Codestream Box (jp2c) @ (3223, 1132296)""".format(nemo_xml) nemo_dump_no_codestream_no_xml = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) Signature: 0d0a870a From 3f3749480310e776057e2918820ff82d4e9a27f1 Mon Sep 17 00:00:00 2001 From: John Evans Date: Sat, 1 Mar 2014 12:16:42 -0500 Subject: [PATCH 109/326] Fixing indentation issues with textwrap. #176 XML had been flush left since introducing lxml, but the textwrap module fixes that easily. --- glymur/jp2box.py | 27 +++-- glymur/test/fixtures.py | 198 +++-------------------------------- glymur/test/test_printing.py | 1 + 3 files changed, 30 insertions(+), 196 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index b4cc803..5a0febf 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -20,6 +20,7 @@ import os import pprint import struct import sys +import textwrap import uuid import warnings @@ -96,10 +97,8 @@ class Jp2kBox(object): msg = Jp2kBox.__str__(self) for box in self.box: boxstr = str(box) - - # Add indentation. - strs = [('\n ' + x) for x in boxstr.split('\n')] - msg += ''.join(strs) + # Indent the child boxes to make the association clear. + msg += '\n' + textwrap.indent(boxstr, ' ') return msg @@ -889,10 +888,7 @@ class ContiguousCodestreamBox(Jp2kBox): msg += '\n Main header:' for segment in self.main_header.segment: - segstr = str(segment) - # Add indentation. - strs = [('\n ' + x) for x in segstr.split('\n')] - msg += ''.join(strs) + msg += '\n' + textwrap.indent(str(segment), ' ') return msg @@ -2562,14 +2558,14 @@ class XMLBox(Jp2kBox): if _printoptions['xml'] == False: return msg - xml = self.xml + msg += '\n' if self.xml is not None: - bstr = ET.tostring(self.xml, - encoding='utf-8', - pretty_print=True).decode('utf-8') - msg += '\n {0}'.format(bstr) + xmlstring = ET.tostring(self.xml, + encoding='utf-8', + pretty_print=True).decode('utf-8') else: - msg += '\n {0}'.format(xml) + xmlstring = 'None' + msg += textwrap.indent(xmlstring, ' ') return msg def write(self, fptr): @@ -2977,7 +2973,8 @@ class UUIDBox(Jp2kBox): xmlstring = ET.tostring(self.data, encoding='utf-8', pretty_print=True).decode('utf-8') - xmlstring = xmlstring.rstrip() + # indent it a bit + xmlstring = textwrap.indent(xmlstring.rstrip(), ' ') msg += line.format(xmlstring) elif self.uuid.bytes == b'JpgTiffExif->JP2': msg += '\n UUID Data: {0}'.format(str(self.data)) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index e7c81a3..b20331c 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -4,6 +4,7 @@ Test fixtures common to more than one test point. import os import re import sys +import textwrap import warnings import numpy as np @@ -181,10 +182,7 @@ def read_pgx_header(pgx_file): header = header.rstrip() return header, pos -nemo_xmp_box = """UUID Box (uuid) @ (77, 3146) - UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) - UUID Data: - +nemo_xmp = """ @@ -269,6 +267,10 @@ nemo_xmp_box = """UUID Box (uuid) @ (77, 3146) """ +nemo_xmp_box = """UUID Box (uuid) @ (77, 3146) + UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) + UUID Data: +{0}""".format(textwrap.indent(nemo_xmp, ' ')) SimpleRDF = """ - - - - Google - 2013-02-09T14:47:53 - - - 1 - 72/1 - 72/1 - 2 - HTC - HTC Glacier - 2592 - 1456 - - - 8 - 8 - 8 - - - 2 - 3 - - - 1343036288/4294967295 - 1413044224/4294967295 - - - - - 2748779008/4294967295 - 1417339264/4294967295 - 1288490240/4294967295 - 2576980480/4294967295 - 644245120/4294967295 - 257698032/4294967295 - - - - - 1 - 2528 - 1424 - 353/100 - 0 - 0/1 - WGS-84 - 2013-02-09T14:47:53 - - - 76 - - - 0220 - 0100 - - - 1 - 2 - 3 - 0 - - - 42,20.56N - 71,5.29W - 2013-02-09T19:47:53Z - NETWORK - - - 2013-02-09T14:47:53 - - - - - Glymur - Python XMP Toolkit - - - - - - +{0} Contiguous Codestream Box (jp2c) @ (3223, 1132296) Main header: SOC marker segment @ (3231, 0) @@ -496,6 +414,7 @@ Contiguous Codestream Box (jp2c) @ (3223, 1132296) Step size: [(0, 8), (0, 9), (0, 9), (0, 10)] CME marker segment @ (3305, 37) "Created by OpenJPEG version 2.0.0"''' +nemo_dump_full = dump.format(textwrap.indent(nemo_xmp, ' ')) nemo_dump_short = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) File Type Box (ftyp) @ (12, 20) @@ -561,7 +480,7 @@ Contiguous Codestream Box (jp2c) @ (3223, 1132296) CME marker segment @ (3305, 37) "Created by OpenJPEG version 2.0.0"''' -nemo_dump_no_codestream = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) +dump = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) Signature: 0d0a870a File Type Box (ftyp) @ (12, 20) Brand: jp2 @@ -580,92 +499,9 @@ JP2 Header Box (jp2h) @ (32, 45) UUID Box (uuid) @ (77, 3146) UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) UUID Data: - - - - - Google - 2013-02-09T14:47:53 - - - 1 - 72/1 - 72/1 - 2 - HTC - HTC Glacier - 2592 - 1456 - - - 8 - 8 - 8 - - - 2 - 3 - - - 1343036288/4294967295 - 1413044224/4294967295 - - - - - 2748779008/4294967295 - 1417339264/4294967295 - 1288490240/4294967295 - 2576980480/4294967295 - 644245120/4294967295 - 257698032/4294967295 - - - - - 1 - 2528 - 1424 - 353/100 - 0 - 0/1 - WGS-84 - 2013-02-09T14:47:53 - - - 76 - - - 0220 - 0100 - - - 1 - 2 - 3 - 0 - - - 42,20.56N - 71,5.29W - 2013-02-09T19:47:53Z - NETWORK - - - 2013-02-09T14:47:53 - - - - - Glymur - Python XMP Toolkit - - - - - - +{0} Contiguous Codestream Box (jp2c) @ (3223, 1132296)""" +nemo_dump_no_codestream = dump.format(textwrap.indent(nemo_xmp, ' ')) nemo_dump_no_codestream_no_xml = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) Signature: 0d0a870a @@ -699,8 +535,8 @@ file7_rreq = r"""Reader Requirements Box (rreq) @ (44, 24) file1_xml = """XML Box (xml ) @ (36, 439) -\t -\t\t2001-11-01T13:45:00.000-06:00 -\t\tProfessional 120 Image -\t -""" + \t + \t\t2001-11-01T13:45:00.000-06:00 + \t\tProfessional 120 Image + \t + """ diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 45b21df..923420b 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -631,6 +631,7 @@ class TestPrinting(unittest.TestCase): with patch('sys.stdout', new=StringIO()) as fake_out: print(j.box[2]) actual = fake_out.getvalue().strip() + self.maxDiff = None self.assertEqual(actual, fixtures.file1_xml) @unittest.skipIf(sys.hexversion < 0x03000000, From c01079d677aaca06ef9fb5f6cf46143aa4d5ccac Mon Sep 17 00:00:00 2001 From: John Evans Date: Sun, 2 Mar 2014 07:58:48 -0500 Subject: [PATCH 110/326] Refactored filetype box to use struct.unpack_from. #130 --- glymur/jp2box.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 5a0febf..0f710c9 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -994,7 +994,7 @@ class DataReferenceBox(Jp2kBox): @staticmethod def parse(fptr, offset, length): - """Parse Label box. + """Parse data reference box. Parameters ---------- @@ -1124,19 +1124,17 @@ class FileTypeBox(Jp2kBox): ------- FileTypeBox instance """ - # Read the brand, minor version. - read_buffer = fptr.read(8) - (brand, minor_version) = struct.unpack('>4sI', read_buffer) + read_buffer = fptr.read(length - 8) + # Extract the brand, minor version. + (brand, minor_version) = struct.unpack_from('>4sI', read_buffer, 0) if sys.hexversion >= 0x030000: brand = brand.decode('utf-8') - # Read the compatibility list. Each entry has 4 bytes. - current_pos = fptr.tell() - num_bytes = (offset + length - current_pos) / 4 - read_buffer = fptr.read(int(num_bytes) * 4) + # Extract the compatibility list. Each entry has 4 bytes. + num_entries = int((length - 16)/ 4) compatibility_list = [] - for j in range(int(num_bytes)): - entry, = struct.unpack('>4s', read_buffer[4*j:4*(j+1)]) + for j in range(int(num_entries)): + entry, = struct.unpack_from('>4s', read_buffer, 8 + j * 4) if sys.hexversion >= 0x03000000: entry = entry.decode('utf-8') compatibility_list.append(entry) From 53f46fa0d930b87e400b9906be4254d23792fc65 Mon Sep 17 00:00:00 2001 From: John Evans Date: Sun, 2 Mar 2014 11:17:44 -0500 Subject: [PATCH 111/326] Wrapping indentation for sake of 2.7. #177 Textwrap's indent method is not available on 2.7, so we have to fake it ourselves. --- glymur/jp2box.py | 34 ++++++++++++++++++++++++++++++---- glymur/test/fixtures.py | 34 ++++++++++++++++++++++++++++++---- glymur/test/test_printing.py | 4 +++- 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 5a0febf..6db6c87 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -98,10 +98,36 @@ class Jp2kBox(object): for box in self.box: boxstr = str(box) # Indent the child boxes to make the association clear. - msg += '\n' + textwrap.indent(boxstr, ' ') + msg += '\n' + self._indent(boxstr) return msg + def _indent(self, textstr, indent_level=4): + """ + Indent a string. + + Textwrap's indent method only exists for 3.3 or above. In 2.7 we have + to fake it. + + Parameters + ---------- + textstring : str + String to be indented. + indent_level : str + Number of spaces of indentation to add. + + Returns + ------- + indented_string : str + Possibly multi-line string indented a certain bit. + """ + if sys.hexversion >= 0x03030000: + return textwrap.indent(textstr, ' ' * indent_level) + else: + lst = [(' ' * indent_level + x) for x in textstr.split('\n')] + return '\n'.join(lst) + + def _write_superbox(self, fptr): """Write a superbox. @@ -888,7 +914,7 @@ class ContiguousCodestreamBox(Jp2kBox): msg += '\n Main header:' for segment in self.main_header.segment: - msg += '\n' + textwrap.indent(str(segment), ' ') + msg += '\n' + self._indent(str(segment), indent_level=8) return msg @@ -2565,7 +2591,7 @@ class XMLBox(Jp2kBox): pretty_print=True).decode('utf-8') else: xmlstring = 'None' - msg += textwrap.indent(xmlstring, ' ') + msg += self._indent(xmlstring) return msg def write(self, fptr): @@ -2974,7 +3000,7 @@ class UUIDBox(Jp2kBox): encoding='utf-8', pretty_print=True).decode('utf-8') # indent it a bit - xmlstring = textwrap.indent(xmlstring.rstrip(), ' ') + xmlstring = self._indent(xmlstring.rstrip()) msg += line.format(xmlstring) elif self.uuid.bytes == b'JpgTiffExif->JP2': msg += '\n UUID Data: {0}'.format(str(self.data)) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index fe0777e..666fb6b 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -45,6 +45,32 @@ except: raise +def _indent(textstr): + """ + Indent a string. + + Textwrap's indent method only exists for 3.3 or above. In 2.7 we have + to fake it. + + Parameters + ---------- + textstring : str + String to be indented. + indent_level : str + Number of spaces of indentation to add. + + Returns + ------- + indented_string : str + Possibly multi-line string indented a certain bit. + """ + if sys.hexversion >= 0x03030000: + return textwrap.indent(textstr, ' ') + else: + lst = [(' ' + x) for x in textstr.split('\n')] + return '\n'.join(lst) + + def opj_data_file(relative_file_name): """Compact way of forming a full filename from OpenJPEG's test suite.""" jfile = os.path.join(OPJ_DATA_ROOT, relative_file_name) @@ -270,12 +296,12 @@ nemo_xmp = """ nemo_xmp_box = """UUID Box (uuid) @ (77, 3146) UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) UUID Data: -{0}""".format(textwrap.indent(nemo_xmp, ' ')) +{0}""".format(_indent(nemo_xmp)) nemo_xmp_box = """UUID Box (uuid) @ (77, 3146) UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) UUID Data: -{0}""".format(textwrap.indent(nemo_xmp, ' ')) +{0}""".format(_indent(nemo_xmp)) SimpleRDF = """ Date: Mon, 3 Mar 2014 20:49:00 -0500 Subject: [PATCH 112/326] colr, flst, and pclr box. #130 The pclr box is the problematic one. If the box length is wrong, then it's hard to reliably parse when reading just one raw buffer. --- glymur/jp2box.py | 48 +++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index b440cf6..a06d87d 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -401,28 +401,30 @@ class ColourSpecificationBox(Jp2kBox): ------- ColourSpecificationBox instance """ + num_bytes = offset + length - fptr.tell() + read_buffer = fptr.read(num_bytes) # Read the brand, minor version. - read_buffer = fptr.read(3) - (method, precedence, approximation) = struct.unpack('>BBB', - read_buffer) + (method, precedence, approximation) = struct.unpack_from('>BBB', + read_buffer, + 0) if method == 1: # enumerated colour space - read_buffer = fptr.read(4) - colorspace, = struct.unpack('>I', read_buffer) + colorspace, = struct.unpack_from('>I', read_buffer, 3) icc_profile = None else: # ICC profile colorspace = None - numbytes = offset + length - fptr.tell() - if numbytes < 128: + if num_bytes < 131: + # If the number of bytes is less than 128 + 3, then there + # cannot possibly be enough for an ICC profile. msg = "ICC profile header is corrupt, length is " msg += "only {0} instead of 128." - warnings.warn(msg.format(numbytes), UserWarning) + warnings.warn(msg.format(num_bytes - 3), UserWarning) icc_profile = None else: - profile = _ICCProfile(fptr.read(numbytes)) + profile = _ICCProfile(read_buffer[3:]) icc_profile = profile.header box = ColourSpecificationBox(method=method, @@ -1245,7 +1247,7 @@ class FragmentListBox(Jp2kBox): @staticmethod def parse(fptr, offset, length): - """Parse JPX free box. + """Parse JPX fragment list box. Parameters ---------- @@ -1260,11 +1262,13 @@ class FragmentListBox(Jp2kBox): ------- FragmentListBox instance """ - read_buffer = fptr.read(2) - num_fragments, = struct.unpack('>H', read_buffer) + num_bytes = offset + length - fptr.tell() + read_buffer = fptr.read(num_bytes) + num_fragments, = struct.unpack_from('>H', read_buffer, offset=0) - read_buffer = fptr.read(num_fragments * 14) - lst = struct.unpack('>' + 'QIH' * num_fragments, read_buffer) + lst = struct.unpack_from('>' + 'QIH' * num_fragments, + read_buffer, + offset=2) frag_offset = lst[0::3] frag_len = lst[1::3] data_reference = lst[2::3] @@ -1826,13 +1830,12 @@ class PaletteBox(Jp2kBox): ------- PaletteBox instance """ - # Get the size of the palette. - read_buffer = fptr.read(3) - (num_entries, num_columns) = struct.unpack('>HB', read_buffer) + num_bytes = offset + length - fptr.tell() + read_buffer = fptr.read(num_bytes) + (nrows, ncols) = struct.unpack_from('>HB', read_buffer, offset=0) # Need to determine bps and signed or not - read_buffer = fptr.read(num_columns) - data = struct.unpack('>' + 'B' * num_columns, read_buffer) + data = struct.unpack_from('>' + 'B' * ncols, read_buffer, offset=3) bps = [((x & 0x7f) + 1) for x in data] signed = [((x & 0x80) > 1) for x in data] @@ -1849,11 +1852,10 @@ class PaletteBox(Jp2kBox): # That means a list comprehension does this in one shot. row_nbytes = sum([int(math.ceil(x/8.0)) for x in bps]) - read_buffer = fptr.read(num_entries * row_nbytes) - palette = np.zeros((num_entries, num_columns), dtype=np.int32) - for j in range(num_entries): + palette = np.zeros((nrows, ncols), dtype=np.int32) + for j in range(nrows): palette[j] = struct.unpack_from(fmt, read_buffer, - offset=j * row_nbytes) + offset=j * row_nbytes + 3 + ncols) return PaletteBox(palette, bps, signed, length=length, offset=offset) From 2fbdf146b42fd4b4fdaf0e43f73e7f67a10167ef Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 5 Mar 2014 07:04:10 -0500 Subject: [PATCH 113/326] Verified through r2366. #139 423 of 457 glymur tests passing, 34 skips 21 of 214 openjpeg tests failed --- docs/source/detailed_installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index cfbb168..ae0ce08 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -13,7 +13,7 @@ both read and write JPEG 2000 files, but you may wish to install version 2.0 or the 2.0+ version from OpenJPEG's development trunk for better performance. If you do that, you should compile it as a shared library (named *openjp2* instead of *openjpeg*) from the developmental source that you can retrieve -via subversion. As of this time of writing, svn revision r2354 works. +via subversion. As of this time of writing, svn revision r2366 works. You should also download the test data for the purpose of configuring and running OpenJPEG's test suite, check their instructions for all this. You should set the **OPJ_DATA_ROOT** environment variable for the purpose From b6a17d60f1a4e5bbf388c30d0c290fb749381c4d Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 5 Mar 2014 21:17:35 -0500 Subject: [PATCH 114/326] Starting to implement cinema2k support. #139 Need to add scikit-image with freeimage backend in order to read 16-bit TIFFs. --- glymur/jp2k.py | 61 ++++++++++++++++++++++++++++- glymur/test/test_conformance.py | 54 ++++++++++++++++--------- glymur/test/test_opj_suite_write.py | 10 +++++ 3 files changed, 106 insertions(+), 19 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 6db0198..04fa23e 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -219,6 +219,63 @@ class Jp2k(Jp2kBox): cparams.tcp_numlayers = 1 cparams.cp_disto_alloc = 1 + if 'cinema2K' in kwargs: + cparams.cp_rsiz = kwargs['cinema2K'] + # No tiling + cparams.tile_size_on = opj2.FALSE + cparams.cp_tdx = 1 + cparams.cp_tdy = 1 + + # One tile part for each component. + cparams.tp_cflag = ord('C') + cparams.tp_on = 1 + + # tile and image shall be as (0,0) + cparams.cp_tx0 = 0 + cparams.cp_ty0 = 0 + cparams.image_offset_x0 = 0 + cparams.image_offset_y0 = 0 + + # Codeblock size = 32 * 32 + cparams.cblockw_init = 32 + cparams.cblockh_init = 32 + + # code block style, no mode switch enabled. + cparams.mode = 0 + + # no ROI + cparams.roi_compno = -1 + + # no subsampling + cparams.subsampling_dx = 1 + cparams.subsampling_dy = 1 + + # 9-7 transform + cparams.irreversible = 1 + + # number of layers + if cparams.tcp_layers > 1: + # TODO: warning or error + cparams.tcp_numlayers = 1 + + if cparams.numresolution > 6: + # TODO: warning or error + cparams.numresolution = 6 + + # precincts + cparams.csty |= 0x01 + cparams.res_spec = cparams.numresolution - 1 + for j in range(cparams.res_spec): + cparams.prcw_init[i] = 256 + cparams.prch_init[i] = 256 + + # Progression order shall be CPRL + cparams.prog_order = PROGRESSION_ORDER['CPRL'] + + # progression order changes not allowed for 2K + cparams.numpocs = 0 + return cparams + if 'cbsize' in kwargs: cparams.cblockw_init = kwargs['cbsize'][1] cparams.cblockh_init = kwargs['cbsize'][0] @@ -340,6 +397,8 @@ class Jp2k(Jp2kBox): Image data to be written to file. cbsize : tuple, optional Code block size (DY, DX). + cinema2K : int, optional + either 24 or 48 colorspace : str, optional Either 'rgb' or 'gray'. cratios : iterable @@ -473,7 +532,7 @@ class Jp2k(Jp2kBox): def _write_openjp2(self, img_array, verbose=False, **kwargs): """ - Write JPEG 2000 file using OpenJPEG 1.5 interface. + Write JPEG 2000 file using OpenJPEG 2.0 interface. """ cparams, colorspace = self._process_write_inputs(img_array, **kwargs) diff --git a/glymur/test/test_conformance.py b/glymur/test/test_conformance.py index 8f4496d..765f2f6 100644 --- a/glymur/test/test_conformance.py +++ b/glymur/test/test_conformance.py @@ -11,6 +11,7 @@ import os from os.path import join import re import sys +import tempfile import unittest import glymur @@ -27,6 +28,41 @@ except KeyError: OPJ_DATA_ROOT = None +@unittest.skipIf(sys.hexversion < 0x03020000, + "Requires features introduced in 3.2 (assertWarns)") +class TestSuiteConformance(unittest.TestCase): + """Test suite for conformance.""" + + def setUp(self): + self.j2kfile = glymur.data.goodstuff() + + def tearDown(self): + pass + + @unittest.skipIf(re.match(r"""1\.[0123]""", + glymur.version.openjpeg_version) is not None, + "Needs 1.3+ to catch this.") + def test_truncated_eoc(self): + """Has one byte shaved off of EOC marker.""" + with open(self.j2kfile, 'rb') as ifile: + data = ifile.read() + with tempfile.NamedTemporaryFile(suffix='.j2k') as ofile: + ofile.write(data[:-1]) + ofile.flush() + + j2k = Jp2k(ofile.name) + with self.assertWarns(UserWarning): + codestream = j2k.get_codestream(header_only=False) + + # The last segment is truncated, so there should not be an EOC + # marker. + self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC') + + # The codestream is not as long as claimed. + with self.assertRaises(OSError): + j2k.read(rlevel=-1) + + @unittest.skipIf(FORMAT_CORPUS_DATA_ROOT is None, "FORMAT_CORPUS_DATA_ROOT environment variable not set") @unittest.skipIf(sys.hexversion < 0x03020000, @@ -34,24 +70,6 @@ except KeyError: class TestSuiteFormatCorpus(unittest.TestCase): """Test suite for files in format corpus repository.""" - @unittest.skipIf(re.match(r"""1\.[0123]""", - glymur.version.openjpeg_version) is not None, - "Needs 1.3+ to catch this.") - def test_balloon_trunc1(self): - """Has one byte shaved off of EOC marker.""" - jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT, - 'jp2k-test/byteCorruption/balloon_trunc1.jp2') - j2k = Jp2k(jfile) - with self.assertWarns(UserWarning): - codestream = j2k.get_codestream(header_only=False) - - # The last segment is truncated, so there should not be an EOC marker. - self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC') - - # The codestream is not as long as claimed. - with self.assertRaises(OSError): - j2k.read(rlevel=-1) - @unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version) is not None, "Needs 1.4+ to catch this.") diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 22a99e3..07d623f 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -38,6 +38,16 @@ class TestSuiteWrite(unittest.TestCase): def tearDown(self): pass + @unittest.skip("Cannot read input image using PILLOW???") + def test_NR_ENC_X_4_2K_24_185_CBR_WB_000_tif_15_encode(self): + relfile = 'input/nonregression/X_4_2K_24_185_CBR_WB_000.tif' + import pdb; pdb.set_trace() + infile = opj_data_file(relfile) + data = read_image(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data, 'cinema2K', 24) + def test_NR_ENC_Bretagne1_ppm_1_encode(self): """NR-ENC-Bretagne1.ppm-1-encode""" infile = opj_data_file('input/nonregression/Bretagne1.ppm') From afa611ae543fb166d5f1f2958579d5ae4448185f Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 6 Mar 2014 07:10:29 -0500 Subject: [PATCH 115/326] Can write and parse Cinema2K image. #139 Using scikit-image to read the test data. Testing still not complete. --- glymur/core.py | 20 ++++++++ glymur/jp2k.py | 43 ++++++++++++++--- glymur/test/test_opj_suite_write.py | 74 +++++++++++++++++++++++++++-- 3 files changed, 127 insertions(+), 10 deletions(-) diff --git a/glymur/core.py b/glymur/core.py index 4e8f950..95e1cbf 100644 --- a/glymur/core.py +++ b/glymur/core.py @@ -10,6 +10,26 @@ RPCL = 2 PCRL = 3 CPRL = 4 +STD = 0 +CINEMA2K = 3 +CINEMA4K = 4 + +RSIZ = { + 'STD': STD, + 'CINEMA2K': CINEMA2K, + 'CINEMA4K': CINEMA4K} + +OFF = 0 +CINEMA2K_24 = 1 +CINEMA2K_48 = 2 +CINEMA4K_24 = 3 + +CINEMA_MODE = { + 'off': OFF, + 'cinema2k_24': CINEMA2K_24, + 'cinema2k_48': CINEMA2K_48, + 'cinema4k_24': CINEMA4K_24, } + PROGRESSION_ORDER = { 'LRCP': LRCP, 'RLCP': RLCP, diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 04fa23e..890c2fd 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -28,7 +28,7 @@ import numpy as np from .codestream import Codestream from .core import SRGB, GREYSCALE -from .core import PROGRESSION_ORDER +from .core import PROGRESSION_ORDER, RSIZ, CINEMA_MODE from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE from .jp2box import Jp2kBox from .jp2box import JPEG2000SignatureBox, FileTypeBox, JP2HeaderBox @@ -39,6 +39,8 @@ from .lib import openjp2 as opj2 from . import version from .lib import c as libc +CINEMA_24_CS = 1302083 + JP2_IDS = ['colr', 'cdef', 'cmap', 'jp2c', 'ftyp', 'ihdr', 'jp2h', 'jP ', 'pclr', 'res ', 'resc', 'resd', 'xml ', 'ulst', 'uinf', 'url ', 'uuid'] @@ -220,14 +222,17 @@ class Jp2k(Jp2kBox): cparams.cp_disto_alloc = 1 if 'cinema2K' in kwargs: - cparams.cp_rsiz = kwargs['cinema2K'] + # TODO: error if either 24 or 48 + cparams.cp_cinema = kwargs['cinema2K'] + + cparams.cp_rsiz = RSIZ['CINEMA2K'] # No tiling cparams.tile_size_on = opj2.FALSE cparams.cp_tdx = 1 cparams.cp_tdy = 1 # One tile part for each component. - cparams.tp_cflag = ord('C') + cparams.tp_flag = ord('C') cparams.tp_on = 1 # tile and image shall be as (0,0) @@ -254,7 +259,7 @@ class Jp2k(Jp2kBox): cparams.irreversible = 1 # number of layers - if cparams.tcp_layers > 1: + if cparams.tcp_numlayers > 1: # TODO: warning or error cparams.tcp_numlayers = 1 @@ -266,8 +271,8 @@ class Jp2k(Jp2kBox): cparams.csty |= 0x01 cparams.res_spec = cparams.numresolution - 1 for j in range(cparams.res_spec): - cparams.prcw_init[i] = 256 - cparams.prch_init[i] = 256 + cparams.prcw_init[j] = 256 + cparams.prch_init[j] = 256 # Progression order shall be CPRL cparams.prog_order = PROGRESSION_ORDER['CPRL'] @@ -530,6 +535,29 @@ class Jp2k(Jp2kBox): self.parse() + def _set_cinema_rate(self, cparams, image): + max_rate = 0 + temp_rate = 0 + cparams.cp_disto_alloc = 1 + + if cparams.cp_cinema in [CINEMA_MODE['cinema2k_24'], + CINEMA_MODE['cinema2k_48']]: + num_pixels = image.contents.comps[0].w * image.contents.comps[0].h + rate_numerator = num_pixels * image.contents.comps[0].prec + max_rate = rate_numerator / (CINEMA_24_CS * 8 * num_pixels) + if cparams.tcp_rates[0] == 0: + cparams.tcp_rates[0] = max_rate + else: + temp_rate = rate_numerator / (cparams.tcp_rates[0] * 8 * num_pixels) + if temp_rate > CINEMA_24_CS: + # TODO warning, reset + cparams.tcp_rates[0] = max_rate + else: + # TODO warning + pass + + cparams.max_comp_size = COMP_24_CS + def _write_openjp2(self, img_array, verbose=False, **kwargs): """ Write JPEG 2000 file using OpenJPEG 2.0 interface. @@ -549,6 +577,9 @@ class Jp2k(Jp2kBox): _populate_image_struct(cparams, image, img_array) + if 'cinema2K' in kwargs: + self._set_cinema_rate(cparams, image) + codec = opj2.create_compress(cparams.codec_fmt) stack.callback(opj2.destroy_codec, codec) diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 07d623f..335202d 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -12,6 +12,13 @@ import sys import tempfile import unittest +try: + import skimage.io + skimage.io.use_plugin('freeimage', 'imread') + _HAS_SKIMAGE_FREEIMAGE_SUPPORT = True +except ImportError: + _HAS_SKIMAGE_FREEIMAGE_SUPPORT = False + from .fixtures import read_image, NO_READ_BACKEND, NO_READ_BACKEND_MSG from .fixtures import OPJ_DATA_ROOT, opj_data_file @@ -38,15 +45,74 @@ class TestSuiteWrite(unittest.TestCase): def tearDown(self): pass - @unittest.skip("Cannot read input image using PILLOW???") + @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, + "Cannot read input image without scikit-image/freeimage") def test_NR_ENC_X_4_2K_24_185_CBR_WB_000_tif_15_encode(self): relfile = 'input/nonregression/X_4_2K_24_185_CBR_WB_000.tif' - import pdb; pdb.set_trace() infile = opj_data_file(relfile) - data = read_image(infile) + data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, 'wb') - j.write(data, 'cinema2K', 24) + j.write(data, cinema2K=24) + + codestream = j.get_codestream() + + # SIZ: Image and tile size + # Profile: "3" means cinema2K + self.assertEqual(codestream.segment[1].rsiz, 3) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + (1998, 1080)) + # Reference grid offset + self.assertEqual((codestream.segment[1].xosiz, + codestream.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((codestream.segment[1].xtsiz, + codestream.segment[1].ytsiz), + (1998, 1080)) + # Tile offset + self.assertEqual((codestream.segment[1].xtosiz, + codestream.segment[1].ytosiz), + (0, 0)) + # bitdepth + self.assertEqual(codestream.segment[1].bitdepth, (12, 12, 12)) + # signed + self.assertEqual(codestream.segment[1].signed, + (False, False, False)) + # subsampling + self.assertEqual(list(zip(codestream.segment[1].xrsiz, + codestream.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(codestream.segment[2].scod & 2) # no sop + self.assertFalse(codestream.segment[2].scod & 4) # no eph + self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CRLP) + self.assertEqual(codestream.segment[2].layers, 3) # layers = 3 + self.assertEqual(codestream.segment[2].spcod[3], 1) # mct + self.assertEqual(codestream.segment[2].spcod[4], 5) # levels + self.assertEqual(tuple(codestream.segment[2].code_block_size), + (64, 64)) # cblksz + # Selective arithmetic coding bypass + self.assertFalse(codestream.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(codestream.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(codestream.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(codestream.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) + self.assertEqual(codestream.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(codestream.segment[2].spcod), 9) + + + + def test_NR_ENC_Bretagne1_ppm_1_encode(self): """NR-ENC-Bretagne1.ppm-1-encode""" From 3bad8a4b2e5444484e959d74b966845d7221a39c Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 6 Mar 2014 09:20:21 -0500 Subject: [PATCH 116/326] Added negative test for cinema2k frame rate not 24 or 48. #139 Some refactoring of cinema2k code. --- glymur/jp2k.py | 133 +++++++++++++++------------- glymur/test/fixtures.py | 2 +- glymur/test/test_opj_suite_write.py | 16 +++- glymur/test/test_printing.py | 5 +- 4 files changed, 88 insertions(+), 68 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 890c2fd..255e2be 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -155,6 +155,73 @@ class Jp2k(Jp2kBox): msg += "profile if the file type box brand is 'jp2 '." warnings.warn(msg) + def _set_cinema_params(self, cparams, fps): + """Populate compression parameters structure for cinema2K. + + Parameters + ---------- + fps : int + Frames per second, should be either 24 or 48. + """ + if fps not in [24, 48]: + raise IOError('Cinema2K frame rate must be either 24 or 48.') + cparams.cp_cinema = fps + + cparams.cp_rsiz = RSIZ['CINEMA2K'] + # No tiling + cparams.tile_size_on = opj2.FALSE + cparams.cp_tdx = 1 + cparams.cp_tdy = 1 + + # One tile part for each component. + cparams.tp_flag = ord('C') + cparams.tp_on = 1 + + # tile and image shall be as (0,0) + cparams.cp_tx0 = 0 + cparams.cp_ty0 = 0 + cparams.image_offset_x0 = 0 + cparams.image_offset_y0 = 0 + + # Codeblock size = 32 * 32 + cparams.cblockw_init = 32 + cparams.cblockh_init = 32 + + # code block style, no mode switch enabled. + cparams.mode = 0 + + # no ROI + cparams.roi_compno = -1 + + # no subsampling + cparams.subsampling_dx = 1 + cparams.subsampling_dy = 1 + + # 9-7 transform + cparams.irreversible = 1 + + # number of layers + if cparams.tcp_numlayers > 1: + # TODO: warning or error + cparams.tcp_numlayers = 1 + + if cparams.numresolution > 6: + # TODO: warning or error + cparams.numresolution = 6 + + # precincts + cparams.csty |= 0x01 + cparams.res_spec = cparams.numresolution - 1 + for j in range(cparams.res_spec): + cparams.prcw_init[j] = 256 + cparams.prch_init[j] = 256 + + # Progression order shall be CPRL + cparams.prog_order = PROGRESSION_ORDER['CPRL'] + + # progression order changes not allowed for 2K + cparams.numpocs = 0 + def _populate_cparams(self, **kwargs): """Populate compression parameters structure from input arguments. @@ -221,64 +288,8 @@ class Jp2k(Jp2kBox): cparams.tcp_numlayers = 1 cparams.cp_disto_alloc = 1 - if 'cinema2K' in kwargs: - # TODO: error if either 24 or 48 - cparams.cp_cinema = kwargs['cinema2K'] - - cparams.cp_rsiz = RSIZ['CINEMA2K'] - # No tiling - cparams.tile_size_on = opj2.FALSE - cparams.cp_tdx = 1 - cparams.cp_tdy = 1 - - # One tile part for each component. - cparams.tp_flag = ord('C') - cparams.tp_on = 1 - - # tile and image shall be as (0,0) - cparams.cp_tx0 = 0 - cparams.cp_ty0 = 0 - cparams.image_offset_x0 = 0 - cparams.image_offset_y0 = 0 - - # Codeblock size = 32 * 32 - cparams.cblockw_init = 32 - cparams.cblockh_init = 32 - - # code block style, no mode switch enabled. - cparams.mode = 0 - - # no ROI - cparams.roi_compno = -1 - - # no subsampling - cparams.subsampling_dx = 1 - cparams.subsampling_dy = 1 - - # 9-7 transform - cparams.irreversible = 1 - - # number of layers - if cparams.tcp_numlayers > 1: - # TODO: warning or error - cparams.tcp_numlayers = 1 - - if cparams.numresolution > 6: - # TODO: warning or error - cparams.numresolution = 6 - - # precincts - cparams.csty |= 0x01 - cparams.res_spec = cparams.numresolution - 1 - for j in range(cparams.res_spec): - cparams.prcw_init[j] = 256 - cparams.prch_init[j] = 256 - - # Progression order shall be CPRL - cparams.prog_order = PROGRESSION_ORDER['CPRL'] - - # progression order changes not allowed for 2K - cparams.numpocs = 0 + if 'cinema2k' in kwargs: + self._set_cinema_params(cparams, kwargs['cinema2k']) return cparams if 'cbsize' in kwargs: @@ -402,8 +413,8 @@ class Jp2k(Jp2kBox): Image data to be written to file. cbsize : tuple, optional Code block size (DY, DX). - cinema2K : int, optional - either 24 or 48 + cinema2k : int, optional + frames per second, either 24 or 48 colorspace : str, optional Either 'rgb' or 'gray'. cratios : iterable @@ -577,7 +588,7 @@ class Jp2k(Jp2kBox): _populate_image_struct(cparams, image, img_array) - if 'cinema2K' in kwargs: + if 'cinema2k' in kwargs: self._set_cinema_rate(cparams, image) codec = opj2.create_compress(cparams.codec_fmt) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 666fb6b..7e4d9e8 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -22,7 +22,7 @@ try: HAS_PYTHON_XMP_TOOLKIT = True else: HAS_PYTHON_XMP_TOOLKIT = False -except ImportError: +except: HAS_PYTHON_XMP_TOOLKIT = False # Need to know of the libopenjp2 version is the official 2.0.0 release and NOT diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 335202d..21c2578 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -16,7 +16,7 @@ try: import skimage.io skimage.io.use_plugin('freeimage', 'imread') _HAS_SKIMAGE_FREEIMAGE_SUPPORT = True -except ImportError: +except ((ImportError, RuntimeError)): _HAS_SKIMAGE_FREEIMAGE_SUPPORT = False from .fixtures import read_image, NO_READ_BACKEND, NO_READ_BACKEND_MSG @@ -25,7 +25,6 @@ from .fixtures import OPJ_DATA_ROOT, opj_data_file from glymur import Jp2k import glymur - @unittest.skipIf(os.name == "nt", "no write support on windows, period") @unittest.skipIf(re.match(r"""1\.[01234]\.\d""", glymur.version.openjpeg_version) is not None, @@ -53,7 +52,7 @@ class TestSuiteWrite(unittest.TestCase): data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, 'wb') - j.write(data, cinema2K=24) + j.write(data, cinema2k=24) codestream = j.get_codestream() @@ -111,7 +110,16 @@ class TestSuiteWrite(unittest.TestCase): self.assertEqual(len(codestream.segment[2].spcod), 9) - + @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, + "Cannot read input image without scikit-image/freeimage") + def test_cinema2k_bad_frame_rate(self): + relfile = 'input/nonregression/X_4_2K_24_185_CBR_WB_000.tif' + infile = opj_data_file(relfile) + data = skimage.io.imread(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError): + j.write(data, cinema2k=36) def test_NR_ENC_Bretagne1_ppm_1_encode(self): diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index f90ade3..c59d73f 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -133,7 +133,6 @@ class TestPrinting(unittest.TestCase): lst = lst[1:] actual = '\n'.join(lst) expected = fixtures.nemo_dump_no_xml - self.maxDiff = None self.assertEqual(actual, expected) def test_printoptions_short(self): @@ -745,7 +744,9 @@ class TestPrinting(unittest.TestCase): with patch('sys.stdout', new=StringIO()) as fake_out: print(j.box[2]) actual = fake_out.getvalue().strip() - self.assertEqual(actual, fixtures.file7_rreq) + self.maxDiff = None + expected = fixtures.file7_rreq + self.assertEqual(actual, expected) @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") From 80326718a595fbfad9f93b6d8975801cd98908a2 Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 6 Mar 2014 21:13:40 -0500 Subject: [PATCH 117/326] Finished Cinema2K upstream tests. #139 --- CHANGES.txt | 3 +- docs/source/changelog.rst | 3 +- glymur/jp2k.py | 25 ++++-- glymur/test/test_opj_suite_write.py | 123 ++++++++++++++++++++++++---- 4 files changed, 127 insertions(+), 27 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 24d9616..c906d29 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ -Feb 09, 2014 - Changed constructor for ChannelDefinition box. Removed support +Mar 06, 2014 - Added Cinema2K write support. + Changed constructor for ChannelDefinition box. Removed support for Python 2.6. Added write support for JP2 UUID, DataEntryURL, Palette and Component Mapping boxes, JPX Association, NumberList and DataReference boxes. Added read support for JPX free, diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index bbba98e..320ff0f 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -5,10 +5,11 @@ ChangeLog 0.6.0 (pending) =============== + * Added Cinema2K write support. * Added lxml requirement. * added set_printoptions, get_printoptions function * dropped support for Python 2.6, added support for Python 3.4 - * dropped windows support + * dropped windows support (it might work, it might not, I don't much care) * added write support for JP2 UUID, dataEntryURL, palette, and component mapping boxes * added read/write support for JPX free, number list, and data reference boxes * Added read support for JPX fragment list and fragment table boxes diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 255e2be..627082b 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -163,9 +163,12 @@ class Jp2k(Jp2kBox): fps : int Frames per second, should be either 24 or 48. """ - if fps not in [24, 48]: + if fps == 24: + cparams.cp_cinema = CINEMA_MODE['cinema2k_24'] + elif fps == 48: + cparams.cp_cinema = CINEMA_MODE['cinema2k_48'] + else: raise IOError('Cinema2K frame rate must be either 24 or 48.') - cparams.cp_cinema = fps cparams.cp_rsiz = RSIZ['CINEMA2K'] # No tiling @@ -205,7 +208,7 @@ class Jp2k(Jp2kBox): # TODO: warning or error cparams.tcp_numlayers = 1 - if cparams.numresolution > 6: + if cparams.numresolution > 6: #TODO only for cinema2k # TODO: warning or error cparams.numresolution = 6 @@ -219,7 +222,7 @@ class Jp2k(Jp2kBox): # Progression order shall be CPRL cparams.prog_order = PROGRESSION_ORDER['CPRL'] - # progression order changes not allowed for 2K + # progression order changes not allowed for 2K # TODO not for 4K: cparams.numpocs = 0 def _populate_cparams(self, **kwargs): @@ -554,8 +557,12 @@ class Jp2k(Jp2kBox): if cparams.cp_cinema in [CINEMA_MODE['cinema2k_24'], CINEMA_MODE['cinema2k_48']]: num_pixels = image.contents.comps[0].w * image.contents.comps[0].h - rate_numerator = num_pixels * image.contents.comps[0].prec - max_rate = rate_numerator / (CINEMA_24_CS * 8 * num_pixels) + num_samples = num_pixels * image.contents.numcomps + rate_numerator = num_samples * image.contents.comps[0].prec + rate_denominator = CINEMA_24_CS * 8 + rate_denominator *= image.contents.comps[0].dx + rate_denominator *= image.contents.comps[0].dy + max_rate = rate_numerator / rate_denominator if cparams.tcp_rates[0] == 0: cparams.tcp_rates[0] = max_rate else: @@ -567,7 +574,7 @@ class Jp2k(Jp2kBox): # TODO warning pass - cparams.max_comp_size = COMP_24_CS + cparams.max_comp_size = CINEMA_24_CS def _write_openjp2(self, img_array, verbose=False, **kwargs): """ @@ -1608,6 +1615,10 @@ def _populate_image_struct(cparams, image, imgdata): # Stage the image data to the openjpeg data structure. for k in range(0, num_comps): + if cparams.cp_cinema: + image.contents.comps[k].prec = 12 + image.contents.comps[k].bpp = 12 + layer = np.ascontiguousarray(imgdata[:, :, k], dtype=np.int32) dest = image.contents.comps[k].data src = layer.ctypes.data diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 21c2578..ca3bc83 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -44,6 +44,108 @@ class TestSuiteWrite(unittest.TestCase): def tearDown(self): pass + @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, + "Cannot read input image without scikit-image/freeimage") + def test_NR_ENC_X_6_2K_24_FULL_CBR_CIRCLE_000_tif_17_encode(self): + relfile = 'input/nonregression/X_6_2K_24_FULL_CBR_CIRCLE_000.tif' + infile = opj_data_file(relfile) + data = skimage.io.imread(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data, cinema2k=24) + + codestream = j.get_codestream() + + # SIZ: Image and tile size + # Profile: "3" means cinema2K + self.assertEqual(codestream.segment[1].rsiz, 3) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + (2048, 1080)) + # Reference grid offset + self.assertEqual((codestream.segment[1].xosiz, + codestream.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((codestream.segment[1].xtsiz, + codestream.segment[1].ytsiz), + (2048, 1080)) + # Tile offset + self.assertEqual((codestream.segment[1].xtosiz, + codestream.segment[1].ytosiz), + (0, 0)) + # bitdepth + self.assertEqual(codestream.segment[1].bitdepth, (12, 12, 12)) + # signed + self.assertEqual(codestream.segment[1].signed, + (False, False, False)) + # subsampling + self.assertEqual(list(zip(codestream.segment[1].xrsiz, + codestream.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(codestream.segment[2].scod & 2) # no sop + self.assertFalse(codestream.segment[2].scod & 4) # no eph + self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL) + self.assertEqual(codestream.segment[2].layers, 1) + self.assertEqual(codestream.segment[2].spcod[3], 1) # mct + self.assertEqual(codestream.segment[2].spcod[4], 5) # levels + self.assertEqual(tuple(codestream.segment[2].code_block_size), + (32, 32)) # cblksz + + + @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, + "Cannot read input image without scikit-image/freeimage") + def test_NR_ENC_X_5_2K_24_235_CBR_STEM24_000_tif_16_encode(self): + relfile = 'input/nonregression/X_5_2K_24_235_CBR_STEM24_000.tif' + infile = opj_data_file(relfile) + data = skimage.io.imread(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data, cinema2k=24) + + codestream = j.get_codestream() + + # SIZ: Image and tile size + # Profile: "3" means cinema2K + self.assertEqual(codestream.segment[1].rsiz, 3) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + (2048, 857)) + # Reference grid offset + self.assertEqual((codestream.segment[1].xosiz, + codestream.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((codestream.segment[1].xtsiz, + codestream.segment[1].ytsiz), + (2048, 857)) + # Tile offset + self.assertEqual((codestream.segment[1].xtosiz, + codestream.segment[1].ytosiz), + (0, 0)) + # bitdepth + self.assertEqual(codestream.segment[1].bitdepth, (12, 12, 12)) + # signed + self.assertEqual(codestream.segment[1].signed, + (False, False, False)) + # subsampling + self.assertEqual(list(zip(codestream.segment[1].xrsiz, + codestream.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(codestream.segment[2].scod & 2) # no sop + self.assertFalse(codestream.segment[2].scod & 4) # no eph + self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL) + self.assertEqual(codestream.segment[2].layers, 1) + self.assertEqual(codestream.segment[2].spcod[3], 1) # mct + self.assertEqual(codestream.segment[2].spcod[4], 5) # levels + self.assertEqual(tuple(codestream.segment[2].code_block_size), + (32, 32)) # cblksz + + @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, "Cannot read input image without scikit-image/freeimage") def test_NR_ENC_X_4_2K_24_185_CBR_WB_000_tif_15_encode(self): @@ -87,27 +189,12 @@ class TestSuiteWrite(unittest.TestCase): # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop self.assertFalse(codestream.segment[2].scod & 4) # no eph - self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CRLP) - self.assertEqual(codestream.segment[2].layers, 3) # layers = 3 + self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL) + self.assertEqual(codestream.segment[2].layers, 1) self.assertEqual(codestream.segment[2].spcod[3], 1) # mct self.assertEqual(codestream.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), - (64, 64)) # cblksz - # Selective arithmetic coding bypass - self.assertFalse(codestream.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(codestream.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(codestream.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(codestream.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) - self.assertEqual(codestream.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(codestream.segment[2].spcod), 9) + (32, 32)) # cblksz @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, From 9a74109501fa89cc2daec8ceba8a928c6b30677e Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 7 Mar 2014 07:00:02 -0500 Subject: [PATCH 118/326] Added cinema2k/48 support. #139 --- glymur/jp2k.py | 39 ++++++- glymur/test/test_opj_suite_write.py | 153 ++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+), 2 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 627082b..1d9e1b2 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -39,7 +39,15 @@ from .lib import openjp2 as opj2 from . import version from .lib import c as libc +# Codestream lengths for 24fps, 48fps CINEMA_24_CS = 1302083 +CINEMA_48_CS = 651041 + +# Maximum size per color components for 2K and 4K at 24 fps +COMP_24_CS = 1041666 + +# Maximum size per color components for 2K at 48 fps +COMP_48_CS = 520833 JP2_IDS = ['colr', 'cdef', 'cmap', 'jp2c', 'ftyp', 'ihdr', 'jp2h', 'jP ', 'pclr', 'res ', 'resc', 'resd', 'xml ', 'ulst', 'uinf', 'url ', @@ -295,6 +303,10 @@ class Jp2k(Jp2kBox): self._set_cinema_params(cparams, kwargs['cinema2k']) return cparams + if 'cinema4k' in kwargs: + self._set_cinema_params(cparams, kwargs['cinema2k']) + return cparams + if 'cbsize' in kwargs: cparams.cblockw_init = kwargs['cbsize'][1] cparams.cblockh_init = kwargs['cbsize'][0] @@ -555,7 +567,7 @@ class Jp2k(Jp2kBox): cparams.cp_disto_alloc = 1 if cparams.cp_cinema in [CINEMA_MODE['cinema2k_24'], - CINEMA_MODE['cinema2k_48']]: + CINEMA_MODE['cinema4k_24']]: num_pixels = image.contents.comps[0].w * image.contents.comps[0].h num_samples = num_pixels * image.contents.numcomps rate_numerator = num_samples * image.contents.comps[0].prec @@ -574,7 +586,28 @@ class Jp2k(Jp2kBox): # TODO warning pass - cparams.max_comp_size = CINEMA_24_CS + cparams.max_comp_size = COMP_24_CS + + else: + num_pixels = image.contents.comps[0].w * image.contents.comps[0].h + num_samples = num_pixels * image.contents.numcomps + rate_numerator = num_samples * image.contents.comps[0].prec + rate_denominator = CINEMA_48_CS * 8 + rate_denominator *= image.contents.comps[0].dx + rate_denominator *= image.contents.comps[0].dy + max_rate = rate_numerator / rate_denominator + if cparams.tcp_rates[0] == 0: + cparams.tcp_rates[0] = max_rate + else: + temp_rate = rate_numerator / (cparams.tcp_rates[0] * 8 * num_pixels) + if temp_rate > CINEMA_48_CS: + # TODO warning, reset + cparams.tcp_rates[0] = max_rate + else: + # TODO warning + pass + + cparams.max_comp_size = COMP_48_CS def _write_openjp2(self, img_array, verbose=False, **kwargs): """ @@ -597,6 +630,8 @@ class Jp2k(Jp2kBox): if 'cinema2k' in kwargs: self._set_cinema_rate(cparams, image) + if 'cinema4k' in kwargs: + self._set_cinema_rate(cparams, image) codec = opj2.create_compress(cparams.codec_fmt) stack.callback(opj2.destroy_codec, codec) diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index ca3bc83..d0e449e 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -44,6 +44,108 @@ class TestSuiteWrite(unittest.TestCase): def tearDown(self): pass + @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, + "Cannot read input image without scikit-image/freeimage") + def test_NR_ENC_X_5_2K_24_235_CBR_STEM24_000_tif_19_encode(self): + relfile = 'input/nonregression/X_5_2K_24_235_CBR_STEM24_000.tif' + infile = opj_data_file(relfile) + data = skimage.io.imread(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data, cinema2k=48) + + codestream = j.get_codestream() + + # SIZ: Image and tile size + # Profile: "3" means cinema2K + self.assertEqual(codestream.segment[1].rsiz, 3) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + (2048, 857)) + # Reference grid offset + self.assertEqual((codestream.segment[1].xosiz, + codestream.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((codestream.segment[1].xtsiz, + codestream.segment[1].ytsiz), + (2048, 857)) + # Tile offset + self.assertEqual((codestream.segment[1].xtosiz, + codestream.segment[1].ytosiz), + (0, 0)) + # bitdepth + self.assertEqual(codestream.segment[1].bitdepth, (12, 12, 12)) + # signed + self.assertEqual(codestream.segment[1].signed, + (False, False, False)) + # subsampling + self.assertEqual(list(zip(codestream.segment[1].xrsiz, + codestream.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(codestream.segment[2].scod & 2) # no sop + self.assertFalse(codestream.segment[2].scod & 4) # no eph + self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL) + self.assertEqual(codestream.segment[2].layers, 1) + self.assertEqual(codestream.segment[2].spcod[3], 1) # mct + self.assertEqual(codestream.segment[2].spcod[4], 5) # levels + self.assertEqual(tuple(codestream.segment[2].code_block_size), + (32, 32)) # cblksz + + + @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, + "Cannot read input image without scikit-image/freeimage") + def test_NR_ENC_X_6_2K_24_FULL_CBR_CIRCLE_000_tif_20_encode(self): + relfile = 'input/nonregression/X_6_2K_24_FULL_CBR_CIRCLE_000.tif' + infile = opj_data_file(relfile) + data = skimage.io.imread(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data, cinema2k=48) + + codestream = j.get_codestream() + + # SIZ: Image and tile size + # Profile: "3" means cinema2K + self.assertEqual(codestream.segment[1].rsiz, 3) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + (2048, 1080)) + # Reference grid offset + self.assertEqual((codestream.segment[1].xosiz, + codestream.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((codestream.segment[1].xtsiz, + codestream.segment[1].ytsiz), + (2048, 1080)) + # Tile offset + self.assertEqual((codestream.segment[1].xtosiz, + codestream.segment[1].ytosiz), + (0, 0)) + # bitdepth + self.assertEqual(codestream.segment[1].bitdepth, (12, 12, 12)) + # signed + self.assertEqual(codestream.segment[1].signed, + (False, False, False)) + # subsampling + self.assertEqual(list(zip(codestream.segment[1].xrsiz, + codestream.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(codestream.segment[2].scod & 2) # no sop + self.assertFalse(codestream.segment[2].scod & 4) # no eph + self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL) + self.assertEqual(codestream.segment[2].layers, 1) + self.assertEqual(codestream.segment[2].spcod[3], 1) # mct + self.assertEqual(codestream.segment[2].spcod[4], 5) # levels + self.assertEqual(tuple(codestream.segment[2].code_block_size), + (32, 32)) # cblksz + + @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, "Cannot read input image without scikit-image/freeimage") def test_NR_ENC_X_6_2K_24_FULL_CBR_CIRCLE_000_tif_17_encode(self): @@ -146,6 +248,57 @@ class TestSuiteWrite(unittest.TestCase): (32, 32)) # cblksz + @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, + "Cannot read input image without scikit-image/freeimage") + def test_NR_ENC_X_4_2K_24_185_CBR_WB_000_tif_18_encode(self): + relfile = 'input/nonregression/X_4_2K_24_185_CBR_WB_000.tif' + infile = opj_data_file(relfile) + data = skimage.io.imread(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data, cinema2k=48) + + codestream = j.get_codestream() + + # SIZ: Image and tile size + # Profile: "3" means cinema2K + self.assertEqual(codestream.segment[1].rsiz, 3) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + (1998, 1080)) + # Reference grid offset + self.assertEqual((codestream.segment[1].xosiz, + codestream.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((codestream.segment[1].xtsiz, + codestream.segment[1].ytsiz), + (1998, 1080)) + # Tile offset + self.assertEqual((codestream.segment[1].xtosiz, + codestream.segment[1].ytosiz), + (0, 0)) + # bitdepth + self.assertEqual(codestream.segment[1].bitdepth, (12, 12, 12)) + # signed + self.assertEqual(codestream.segment[1].signed, + (False, False, False)) + # subsampling + self.assertEqual(list(zip(codestream.segment[1].xrsiz, + codestream.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(codestream.segment[2].scod & 2) # no sop + self.assertFalse(codestream.segment[2].scod & 4) # no eph + self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL) + self.assertEqual(codestream.segment[2].layers, 1) + self.assertEqual(codestream.segment[2].spcod[3], 1) # mct + self.assertEqual(codestream.segment[2].spcod[4], 5) # levels + self.assertEqual(tuple(codestream.segment[2].code_block_size), + (32, 32)) # cblksz + + @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, "Cannot read input image without scikit-image/freeimage") def test_NR_ENC_X_4_2K_24_185_CBR_WB_000_tif_15_encode(self): From af4b6e64bfc70e6c97c7cceda509dd2685c25b68 Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 7 Mar 2014 08:40:14 -0500 Subject: [PATCH 119/326] Refactoring. #139 --- glymur/jp2k.py | 22 +-- glymur/test/test_opj_suite_write.py | 276 +++++----------------------- 2 files changed, 56 insertions(+), 242 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 1d9e1b2..00d3d24 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -566,15 +566,15 @@ class Jp2k(Jp2kBox): temp_rate = 0 cparams.cp_disto_alloc = 1 + num_pixels = image.contents.comps[0].w * image.contents.comps[0].h + num_samples = num_pixels * image.contents.numcomps + rate_numerator = num_samples * image.contents.comps[0].prec + rate_denominator = 8 * image.contents.comps[0].dx + rate_denominator *= image.contents.comps[0].dy + if cparams.cp_cinema in [CINEMA_MODE['cinema2k_24'], CINEMA_MODE['cinema4k_24']]: - num_pixels = image.contents.comps[0].w * image.contents.comps[0].h - num_samples = num_pixels * image.contents.numcomps - rate_numerator = num_samples * image.contents.comps[0].prec - rate_denominator = CINEMA_24_CS * 8 - rate_denominator *= image.contents.comps[0].dx - rate_denominator *= image.contents.comps[0].dy - max_rate = rate_numerator / rate_denominator + max_rate = rate_numerator / (rate_denominator * CINEMA_24_CS) if cparams.tcp_rates[0] == 0: cparams.tcp_rates[0] = max_rate else: @@ -589,13 +589,7 @@ class Jp2k(Jp2kBox): cparams.max_comp_size = COMP_24_CS else: - num_pixels = image.contents.comps[0].w * image.contents.comps[0].h - num_samples = num_pixels * image.contents.numcomps - rate_numerator = num_samples * image.contents.comps[0].prec - rate_denominator = CINEMA_48_CS * 8 - rate_denominator *= image.contents.comps[0].dx - rate_denominator *= image.contents.comps[0].dy - max_rate = rate_numerator / rate_denominator + max_rate = rate_numerator / (rate_denominator * CINEMA_48_CS) if cparams.tcp_rates[0] == 0: cparams.tcp_rates[0] = max_rate else: diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index d0e449e..87a6fc4 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -44,6 +44,48 @@ class TestSuiteWrite(unittest.TestCase): def tearDown(self): pass + def check_cinema2k_codestream(self, codestream, image_size): + """Common out for cinema2k tests.""" + # SIZ: Image and tile size + # Profile: "3" means cinema2K + self.assertEqual(codestream.segment[1].rsiz, 3) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + image_size) + # Reference grid offset + self.assertEqual((codestream.segment[1].xosiz, + codestream.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((codestream.segment[1].xtsiz, + codestream.segment[1].ytsiz), + image_size) + # Tile offset + self.assertEqual((codestream.segment[1].xtosiz, + codestream.segment[1].ytosiz), + (0, 0)) + # bitdepth + self.assertEqual(codestream.segment[1].bitdepth, (12, 12, 12)) + # signed + self.assertEqual(codestream.segment[1].signed, + (False, False, False)) + # subsampling + self.assertEqual(list(zip(codestream.segment[1].xrsiz, + codestream.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(codestream.segment[2].scod & 2) # no sop + self.assertFalse(codestream.segment[2].scod & 4) # no eph + self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL) + self.assertEqual(codestream.segment[2].layers, 1) + self.assertEqual(codestream.segment[2].spcod[3], 1) # mct + self.assertEqual(codestream.segment[2].spcod[4], 5) # levels + self.assertEqual(tuple(codestream.segment[2].code_block_size), + (32, 32)) # cblksz + + + @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, "Cannot read input image without scikit-image/freeimage") def test_NR_ENC_X_5_2K_24_235_CBR_STEM24_000_tif_19_encode(self): @@ -55,44 +97,7 @@ class TestSuiteWrite(unittest.TestCase): j.write(data, cinema2k=48) codestream = j.get_codestream() - - # SIZ: Image and tile size - # Profile: "3" means cinema2K - self.assertEqual(codestream.segment[1].rsiz, 3) - # Reference grid size - self.assertEqual((codestream.segment[1].xsiz, - codestream.segment[1].ysiz), - (2048, 857)) - # Reference grid offset - self.assertEqual((codestream.segment[1].xosiz, - codestream.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((codestream.segment[1].xtsiz, - codestream.segment[1].ytsiz), - (2048, 857)) - # Tile offset - self.assertEqual((codestream.segment[1].xtosiz, - codestream.segment[1].ytosiz), - (0, 0)) - # bitdepth - self.assertEqual(codestream.segment[1].bitdepth, (12, 12, 12)) - # signed - self.assertEqual(codestream.segment[1].signed, - (False, False, False)) - # subsampling - self.assertEqual(list(zip(codestream.segment[1].xrsiz, - codestream.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(codestream.segment[2].scod & 2) # no sop - self.assertFalse(codestream.segment[2].scod & 4) # no eph - self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL) - self.assertEqual(codestream.segment[2].layers, 1) - self.assertEqual(codestream.segment[2].spcod[3], 1) # mct - self.assertEqual(codestream.segment[2].spcod[4], 5) # levels - self.assertEqual(tuple(codestream.segment[2].code_block_size), - (32, 32)) # cblksz + self.check_cinema2k_codestream(codestream, (2048, 857)) @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, @@ -106,44 +111,7 @@ class TestSuiteWrite(unittest.TestCase): j.write(data, cinema2k=48) codestream = j.get_codestream() - - # SIZ: Image and tile size - # Profile: "3" means cinema2K - self.assertEqual(codestream.segment[1].rsiz, 3) - # Reference grid size - self.assertEqual((codestream.segment[1].xsiz, - codestream.segment[1].ysiz), - (2048, 1080)) - # Reference grid offset - self.assertEqual((codestream.segment[1].xosiz, - codestream.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((codestream.segment[1].xtsiz, - codestream.segment[1].ytsiz), - (2048, 1080)) - # Tile offset - self.assertEqual((codestream.segment[1].xtosiz, - codestream.segment[1].ytosiz), - (0, 0)) - # bitdepth - self.assertEqual(codestream.segment[1].bitdepth, (12, 12, 12)) - # signed - self.assertEqual(codestream.segment[1].signed, - (False, False, False)) - # subsampling - self.assertEqual(list(zip(codestream.segment[1].xrsiz, - codestream.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(codestream.segment[2].scod & 2) # no sop - self.assertFalse(codestream.segment[2].scod & 4) # no eph - self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL) - self.assertEqual(codestream.segment[2].layers, 1) - self.assertEqual(codestream.segment[2].spcod[3], 1) # mct - self.assertEqual(codestream.segment[2].spcod[4], 5) # levels - self.assertEqual(tuple(codestream.segment[2].code_block_size), - (32, 32)) # cblksz + self.check_cinema2k_codestream(codestream, (2048, 1080)) @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, @@ -157,44 +125,7 @@ class TestSuiteWrite(unittest.TestCase): j.write(data, cinema2k=24) codestream = j.get_codestream() - - # SIZ: Image and tile size - # Profile: "3" means cinema2K - self.assertEqual(codestream.segment[1].rsiz, 3) - # Reference grid size - self.assertEqual((codestream.segment[1].xsiz, - codestream.segment[1].ysiz), - (2048, 1080)) - # Reference grid offset - self.assertEqual((codestream.segment[1].xosiz, - codestream.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((codestream.segment[1].xtsiz, - codestream.segment[1].ytsiz), - (2048, 1080)) - # Tile offset - self.assertEqual((codestream.segment[1].xtosiz, - codestream.segment[1].ytosiz), - (0, 0)) - # bitdepth - self.assertEqual(codestream.segment[1].bitdepth, (12, 12, 12)) - # signed - self.assertEqual(codestream.segment[1].signed, - (False, False, False)) - # subsampling - self.assertEqual(list(zip(codestream.segment[1].xrsiz, - codestream.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(codestream.segment[2].scod & 2) # no sop - self.assertFalse(codestream.segment[2].scod & 4) # no eph - self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL) - self.assertEqual(codestream.segment[2].layers, 1) - self.assertEqual(codestream.segment[2].spcod[3], 1) # mct - self.assertEqual(codestream.segment[2].spcod[4], 5) # levels - self.assertEqual(tuple(codestream.segment[2].code_block_size), - (32, 32)) # cblksz + self.check_cinema2k_codestream(codestream, (2048, 1080)) @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, @@ -208,44 +139,7 @@ class TestSuiteWrite(unittest.TestCase): j.write(data, cinema2k=24) codestream = j.get_codestream() - - # SIZ: Image and tile size - # Profile: "3" means cinema2K - self.assertEqual(codestream.segment[1].rsiz, 3) - # Reference grid size - self.assertEqual((codestream.segment[1].xsiz, - codestream.segment[1].ysiz), - (2048, 857)) - # Reference grid offset - self.assertEqual((codestream.segment[1].xosiz, - codestream.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((codestream.segment[1].xtsiz, - codestream.segment[1].ytsiz), - (2048, 857)) - # Tile offset - self.assertEqual((codestream.segment[1].xtosiz, - codestream.segment[1].ytosiz), - (0, 0)) - # bitdepth - self.assertEqual(codestream.segment[1].bitdepth, (12, 12, 12)) - # signed - self.assertEqual(codestream.segment[1].signed, - (False, False, False)) - # subsampling - self.assertEqual(list(zip(codestream.segment[1].xrsiz, - codestream.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(codestream.segment[2].scod & 2) # no sop - self.assertFalse(codestream.segment[2].scod & 4) # no eph - self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL) - self.assertEqual(codestream.segment[2].layers, 1) - self.assertEqual(codestream.segment[2].spcod[3], 1) # mct - self.assertEqual(codestream.segment[2].spcod[4], 5) # levels - self.assertEqual(tuple(codestream.segment[2].code_block_size), - (32, 32)) # cblksz + self.check_cinema2k_codestream(codestream, (2048, 857)) @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, @@ -259,44 +153,7 @@ class TestSuiteWrite(unittest.TestCase): j.write(data, cinema2k=48) codestream = j.get_codestream() - - # SIZ: Image and tile size - # Profile: "3" means cinema2K - self.assertEqual(codestream.segment[1].rsiz, 3) - # Reference grid size - self.assertEqual((codestream.segment[1].xsiz, - codestream.segment[1].ysiz), - (1998, 1080)) - # Reference grid offset - self.assertEqual((codestream.segment[1].xosiz, - codestream.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((codestream.segment[1].xtsiz, - codestream.segment[1].ytsiz), - (1998, 1080)) - # Tile offset - self.assertEqual((codestream.segment[1].xtosiz, - codestream.segment[1].ytosiz), - (0, 0)) - # bitdepth - self.assertEqual(codestream.segment[1].bitdepth, (12, 12, 12)) - # signed - self.assertEqual(codestream.segment[1].signed, - (False, False, False)) - # subsampling - self.assertEqual(list(zip(codestream.segment[1].xrsiz, - codestream.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(codestream.segment[2].scod & 2) # no sop - self.assertFalse(codestream.segment[2].scod & 4) # no eph - self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL) - self.assertEqual(codestream.segment[2].layers, 1) - self.assertEqual(codestream.segment[2].spcod[3], 1) # mct - self.assertEqual(codestream.segment[2].spcod[4], 5) # levels - self.assertEqual(tuple(codestream.segment[2].code_block_size), - (32, 32)) # cblksz + self.check_cinema2k_codestream(codestream, (1998, 1080)) @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, @@ -310,44 +167,7 @@ class TestSuiteWrite(unittest.TestCase): j.write(data, cinema2k=24) codestream = j.get_codestream() - - # SIZ: Image and tile size - # Profile: "3" means cinema2K - self.assertEqual(codestream.segment[1].rsiz, 3) - # Reference grid size - self.assertEqual((codestream.segment[1].xsiz, - codestream.segment[1].ysiz), - (1998, 1080)) - # Reference grid offset - self.assertEqual((codestream.segment[1].xosiz, - codestream.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((codestream.segment[1].xtsiz, - codestream.segment[1].ytsiz), - (1998, 1080)) - # Tile offset - self.assertEqual((codestream.segment[1].xtosiz, - codestream.segment[1].ytosiz), - (0, 0)) - # bitdepth - self.assertEqual(codestream.segment[1].bitdepth, (12, 12, 12)) - # signed - self.assertEqual(codestream.segment[1].signed, - (False, False, False)) - # subsampling - self.assertEqual(list(zip(codestream.segment[1].xrsiz, - codestream.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(codestream.segment[2].scod & 2) # no sop - self.assertFalse(codestream.segment[2].scod & 4) # no eph - self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL) - self.assertEqual(codestream.segment[2].layers, 1) - self.assertEqual(codestream.segment[2].spcod[3], 1) # mct - self.assertEqual(codestream.segment[2].spcod[4], 5) # levels - self.assertEqual(tuple(codestream.segment[2].code_block_size), - (32, 32)) # cblksz + self.check_cinema2k_codestream(codestream, (1998, 1080)) @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, From d68b1aacb1f888adfef13cd2054421989607a21c Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 7 Mar 2014 11:00:20 -0500 Subject: [PATCH 120/326] Cinema4K seems to be working. #139 --- CHANGES.txt | 2 +- docs/source/changelog.rst | 2 +- docs/source/detailed_installation.rst | 2 +- glymur/jp2k.py | 63 +++++++++++++++++++++------ glymur/test/test_opj_suite_write.py | 56 ++++++++++++++++++++++++ 5 files changed, 108 insertions(+), 17 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index c906d29..44d6832 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,4 @@ -Mar 06, 2014 - Added Cinema2K write support. +Mar 06, 2014 - Added Cinema2K, Cinema4K write support. Changed constructor for ChannelDefinition box. Removed support for Python 2.6. Added write support for JP2 UUID, DataEntryURL, Palette and Component Mapping boxes, JPX Association, NumberList diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 320ff0f..7a96571 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -5,7 +5,7 @@ ChangeLog 0.6.0 (pending) =============== - * Added Cinema2K write support. + * Added Cinema2K, Cinema4K write support. * Added lxml requirement. * added set_printoptions, get_printoptions function * dropped support for Python 2.6, added support for Python 3.4 diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index ae0ce08..9111dbd 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -13,7 +13,7 @@ both read and write JPEG 2000 files, but you may wish to install version 2.0 or the 2.0+ version from OpenJPEG's development trunk for better performance. If you do that, you should compile it as a shared library (named *openjp2* instead of *openjpeg*) from the developmental source that you can retrieve -via subversion. As of this time of writing, svn revision r2366 works. +via subversion. As of this time of writing, svn revision r2369 works. You should also download the test data for the purpose of configuring and running OpenJPEG's test suite, check their instructions for all this. You should set the **OPJ_DATA_ROOT** environment variable for the purpose diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 00d3d24..e861053 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -163,22 +163,32 @@ class Jp2k(Jp2kBox): msg += "profile if the file type box brand is 'jp2 '." warnings.warn(msg) - def _set_cinema_params(self, cparams, fps): + def _set_cinema_params(self, cparams, cinema_mode, fps): """Populate compression parameters structure for cinema2K. Parameters ---------- + params : ctypes struct + Corresponds to compression parameters structure used by the + library. + cinema_mode : str + Either 'cinema2k' or 'cinema4k' fps : int Frames per second, should be either 24 or 48. """ - if fps == 24: - cparams.cp_cinema = CINEMA_MODE['cinema2k_24'] - elif fps == 48: - cparams.cp_cinema = CINEMA_MODE['cinema2k_48'] + if cinema_mode == 'cinema2k': + if fps == 24: + cparams.cp_cinema = CINEMA_MODE['cinema2k_24'] + elif fps == 48: + cparams.cp_cinema = CINEMA_MODE['cinema2k_48'] + else: + raise IOError('Cinema2K frame rate must be either 24 or 48.') + cparams.cp_rsiz = RSIZ['CINEMA2K'] else: - raise IOError('Cinema2K frame rate must be either 24 or 48.') + cparams.cp_cinema = CINEMA_MODE['cinema4k_24'] + cparams.cp_rsiz = RSIZ['CINEMA4K'] + - cparams.cp_rsiz = RSIZ['CINEMA2K'] # No tiling cparams.tile_size_on = opj2.FALSE cparams.cp_tdx = 1 @@ -216,9 +226,17 @@ class Jp2k(Jp2kBox): # TODO: warning or error cparams.tcp_numlayers = 1 - if cparams.numresolution > 6: #TODO only for cinema2k - # TODO: warning or error - cparams.numresolution = 6 + if cinema_mode == 'cinema2k': + if cparams.numresolution > 6: + # TODO: warning or error + cparams.numresolution = 6 + else: + if cparams.numresolution < 2: + # TODO: warning or error + cparams.numresolution = 1 + elif cparams.numresolution > 7: + cparams.numresolution = 7 + # precincts cparams.csty |= 0x01 @@ -230,8 +248,25 @@ class Jp2k(Jp2kBox): # Progression order shall be CPRL cparams.prog_order = PROGRESSION_ORDER['CPRL'] - # progression order changes not allowed for 2K # TODO not for 4K: - cparams.numpocs = 0 + # progression order changes not allowed for 2K + if cinema_mode == 'cinema2k': + cparams.numpocs = 0 + else: + cparams.poc[0].tile = 1 + cparams.poc[0].resno0 = 0 + cparams.poc[0].compno0 = 0 + cparams.poc[0].layno1 = 1 + cparams.poc[0].resno1 = cparams.numresolution - 1 + cparams.poc[0].compno1 = 3 + cparams.poc[0].prg1 = PROGRESSION_ORDER['CPRL'] + cparams.poc[1].tile = 1 + cparams.poc[1].resno0 = 0 + cparams.poc[1].compno0 = 0 + cparams.poc[1].layno1 = 1 + cparams.poc[1].resno1 = cparams.numresolution + cparams.poc[1].compno1 = 3 + cparams.poc[1].prg1 = PROGRESSION_ORDER['CPRL'] + cparams.numpocs = 2 def _populate_cparams(self, **kwargs): """Populate compression parameters structure from input arguments. @@ -300,11 +335,11 @@ class Jp2k(Jp2kBox): cparams.cp_disto_alloc = 1 if 'cinema2k' in kwargs: - self._set_cinema_params(cparams, kwargs['cinema2k']) + self._set_cinema_params(cparams, 'cinema2k', kwargs['cinema2k']) return cparams if 'cinema4k' in kwargs: - self._set_cinema_params(cparams, kwargs['cinema2k']) + self._set_cinema_params(cparams, 'cinema4k', kwargs['cinema4k']) return cparams if 'cbsize' in kwargs: diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 87a6fc4..0e7f2d3 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -44,6 +44,48 @@ class TestSuiteWrite(unittest.TestCase): def tearDown(self): pass + def check_cinema4k_codestream(self, codestream, image_size): + """Common out for cinema2k tests.""" + # SIZ: Image and tile size + # Profile: "3" means cinema2K + self.assertEqual(codestream.segment[1].rsiz, 4) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + image_size) + # Reference grid offset + self.assertEqual((codestream.segment[1].xosiz, + codestream.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((codestream.segment[1].xtsiz, + codestream.segment[1].ytsiz), + image_size) + # Tile offset + self.assertEqual((codestream.segment[1].xtosiz, + codestream.segment[1].ytosiz), + (0, 0)) + # bitdepth + self.assertEqual(codestream.segment[1].bitdepth, (12, 12, 12)) + # signed + self.assertEqual(codestream.segment[1].signed, + (False, False, False)) + # subsampling + self.assertEqual(list(zip(codestream.segment[1].xrsiz, + codestream.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(codestream.segment[2].scod & 2) # no sop + self.assertFalse(codestream.segment[2].scod & 4) # no eph + self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL) + self.assertEqual(codestream.segment[2].layers, 1) + self.assertEqual(codestream.segment[2].spcod[3], 1) # mct + self.assertEqual(codestream.segment[2].spcod[4], 5) # levels + self.assertEqual(tuple(codestream.segment[2].code_block_size), + (32, 32)) # cblksz + + + def check_cinema2k_codestream(self, codestream, image_size): """Common out for cinema2k tests.""" # SIZ: Image and tile size @@ -86,6 +128,20 @@ class TestSuiteWrite(unittest.TestCase): + @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, + "Cannot read input image without scikit-image/freeimage") + def test_NR_ENC_ElephantDream_4K_tif_21_encode(self): + relfile = 'input/nonregression/ElephantDream_4K.tif' + infile = opj_data_file(relfile) + data = skimage.io.imread(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data, cinema4k=True) + + codestream = j.get_codestream() + self.check_cinema4k_codestream(codestream, (4096, 2160)) + + @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, "Cannot read input image without scikit-image/freeimage") def test_NR_ENC_X_5_2K_24_235_CBR_STEM24_000_tif_19_encode(self): From 4ecc23b6bf8c5de89f3df14ab73efa38980022aa Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 7 Mar 2014 12:08:46 -0500 Subject: [PATCH 121/326] Verified through r2386. #139 --- docs/source/detailed_installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index 9111dbd..5b9f1ff 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -13,7 +13,7 @@ both read and write JPEG 2000 files, but you may wish to install version 2.0 or the 2.0+ version from OpenJPEG's development trunk for better performance. If you do that, you should compile it as a shared library (named *openjp2* instead of *openjpeg*) from the developmental source that you can retrieve -via subversion. As of this time of writing, svn revision r2369 works. +via subversion. As of this time of writing, svn revision r2386 works. You should also download the test data for the purpose of configuring and running OpenJPEG's test suite, check their instructions for all this. You should set the **OPJ_DATA_ROOT** environment variable for the purpose From 47b8dad0038bb7edff8cd067110fd0c7b4d87f6c Mon Sep 17 00:00:00 2001 From: John Evans Date: Sat, 8 Mar 2014 07:46:12 -0500 Subject: [PATCH 122/326] Check for supplying cinema2k/4k with other options. #139 --- glymur/jp2k.py | 4 ++++ glymur/test/test_opj_suite_write.py | 25 +++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index e861053..d17195d 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -422,6 +422,10 @@ class Jp2k(Jp2kBox): Either CLRSPC_SRGB or CLRSPC_GRAY """ + if ('cinema2k' in kwargs or 'cinema4k' in kwargs) and len(set(kwargs)) > 1: + msg = "Cannot specify cinema2k/cinema4k along with other options." + raise IOError(msg) + if 'cratios' in kwargs and 'psnr' in kwargs: msg = "Cannot specify cratios and psnr together." raise IOError(msg) diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 0e7f2d3..6539e21 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -35,8 +35,8 @@ import glymur class TestSuiteWrite(unittest.TestCase): """Tests for writing with openjp2 backend. - These tests roughly correspond with those tests with similar names in the - OpenJPEG test suite. + These tests either roughly correspond with those tests with similar names + in the OpenJPEG test suite or are closely associated. """ def setUp(self): pass @@ -44,6 +44,26 @@ class TestSuiteWrite(unittest.TestCase): def tearDown(self): pass + def test_cinema2K_with_others(self): + """Can't specify cinema2k with any other options.""" + relfile = 'input/nonregression/X_5_2K_24_235_CBR_STEM24_000.tif' + infile = opj_data_file(relfile) + data = skimage.io.imread(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError): + j.write(data, cinema2k=48, cratios=[200, 100, 50]) + + def test_cinema4K_with_others(self): + """Can't specify cinema4k with any other options.""" + relfile = 'input/nonregression/ElephantDream_4K.tif' + infile = opj_data_file(relfile) + data = skimage.io.imread(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError): + j.write(data, cinema4k=True, cratios=[200, 100, 50]) + def check_cinema4k_codestream(self, codestream, image_size): """Common out for cinema2k tests.""" # SIZ: Image and tile size @@ -1052,5 +1072,6 @@ class TestSuiteWrite(unittest.TestCase): glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) + if __name__ == "__main__": unittest.main() From 82f60b328b4c436f96a21e37cd66033899bbf48b Mon Sep 17 00:00:00 2001 From: John Evans Date: Sat, 8 Mar 2014 13:19:17 -0500 Subject: [PATCH 123/326] Validated through r2436. #139 Most cinema code was moved into the library between 2386 and 2436, so most of glymur's was removed period. --- docs/source/detailed_installation.rst | 2 +- glymur/jp2k.py | 141 +------------------------- glymur/test/test_opj_suite.py | 1 + 3 files changed, 5 insertions(+), 139 deletions(-) diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index 5b9f1ff..5c8d7ad 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -13,7 +13,7 @@ both read and write JPEG 2000 files, but you may wish to install version 2.0 or the 2.0+ version from OpenJPEG's development trunk for better performance. If you do that, you should compile it as a shared library (named *openjp2* instead of *openjpeg*) from the developmental source that you can retrieve -via subversion. As of this time of writing, svn revision r2386 works. +via subversion. As of this time of writing, svn revision r2436 works. You should also download the test data for the purpose of configuring and running OpenJPEG's test suite, check their instructions for all this. You should set the **OPJ_DATA_ROOT** environment variable for the purpose diff --git a/glymur/jp2k.py b/glymur/jp2k.py index d17195d..905dbd8 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -39,16 +39,6 @@ from .lib import openjp2 as opj2 from . import version from .lib import c as libc -# Codestream lengths for 24fps, 48fps -CINEMA_24_CS = 1302083 -CINEMA_48_CS = 651041 - -# Maximum size per color components for 2K and 4K at 24 fps -COMP_24_CS = 1041666 - -# Maximum size per color components for 2K at 48 fps -COMP_48_CS = 520833 - JP2_IDS = ['colr', 'cdef', 'cmap', 'jp2c', 'ftyp', 'ihdr', 'jp2h', 'jP ', 'pclr', 'res ', 'resc', 'resd', 'xml ', 'ulst', 'uinf', 'url ', 'uuid'] @@ -183,90 +173,10 @@ class Jp2k(Jp2kBox): cparams.cp_cinema = CINEMA_MODE['cinema2k_48'] else: raise IOError('Cinema2K frame rate must be either 24 or 48.') - cparams.cp_rsiz = RSIZ['CINEMA2K'] else: cparams.cp_cinema = CINEMA_MODE['cinema4k_24'] - cparams.cp_rsiz = RSIZ['CINEMA4K'] - - # No tiling - cparams.tile_size_on = opj2.FALSE - cparams.cp_tdx = 1 - cparams.cp_tdy = 1 - - # One tile part for each component. - cparams.tp_flag = ord('C') - cparams.tp_on = 1 - - # tile and image shall be as (0,0) - cparams.cp_tx0 = 0 - cparams.cp_ty0 = 0 - cparams.image_offset_x0 = 0 - cparams.image_offset_y0 = 0 - - # Codeblock size = 32 * 32 - cparams.cblockw_init = 32 - cparams.cblockh_init = 32 - - # code block style, no mode switch enabled. - cparams.mode = 0 - - # no ROI - cparams.roi_compno = -1 - - # no subsampling - cparams.subsampling_dx = 1 - cparams.subsampling_dy = 1 - - # 9-7 transform - cparams.irreversible = 1 - - # number of layers - if cparams.tcp_numlayers > 1: - # TODO: warning or error - cparams.tcp_numlayers = 1 - - if cinema_mode == 'cinema2k': - if cparams.numresolution > 6: - # TODO: warning or error - cparams.numresolution = 6 - else: - if cparams.numresolution < 2: - # TODO: warning or error - cparams.numresolution = 1 - elif cparams.numresolution > 7: - cparams.numresolution = 7 - - - # precincts - cparams.csty |= 0x01 - cparams.res_spec = cparams.numresolution - 1 - for j in range(cparams.res_spec): - cparams.prcw_init[j] = 256 - cparams.prch_init[j] = 256 - - # Progression order shall be CPRL - cparams.prog_order = PROGRESSION_ORDER['CPRL'] - - # progression order changes not allowed for 2K - if cinema_mode == 'cinema2k': - cparams.numpocs = 0 - else: - cparams.poc[0].tile = 1 - cparams.poc[0].resno0 = 0 - cparams.poc[0].compno0 = 0 - cparams.poc[0].layno1 = 1 - cparams.poc[0].resno1 = cparams.numresolution - 1 - cparams.poc[0].compno1 = 3 - cparams.poc[0].prg1 = PROGRESSION_ORDER['CPRL'] - cparams.poc[1].tile = 1 - cparams.poc[1].resno0 = 0 - cparams.poc[1].compno0 = 0 - cparams.poc[1].layno1 = 1 - cparams.poc[1].resno1 = cparams.numresolution - cparams.poc[1].compno1 = 3 - cparams.poc[1].prg1 = PROGRESSION_ORDER['CPRL'] - cparams.numpocs = 2 + return def _populate_cparams(self, **kwargs): """Populate compression parameters structure from input arguments. @@ -469,6 +379,8 @@ class Jp2k(Jp2kBox): Code block size (DY, DX). cinema2k : int, optional frames per second, either 24 or 48 + cinema4k : bool, optional + Set to True to specify Cinema4K mode, defaults to false. colorspace : str, optional Either 'rgb' or 'gray'. cratios : iterable @@ -600,48 +512,6 @@ class Jp2k(Jp2kBox): self.parse() - def _set_cinema_rate(self, cparams, image): - max_rate = 0 - temp_rate = 0 - cparams.cp_disto_alloc = 1 - - num_pixels = image.contents.comps[0].w * image.contents.comps[0].h - num_samples = num_pixels * image.contents.numcomps - rate_numerator = num_samples * image.contents.comps[0].prec - rate_denominator = 8 * image.contents.comps[0].dx - rate_denominator *= image.contents.comps[0].dy - - if cparams.cp_cinema in [CINEMA_MODE['cinema2k_24'], - CINEMA_MODE['cinema4k_24']]: - max_rate = rate_numerator / (rate_denominator * CINEMA_24_CS) - if cparams.tcp_rates[0] == 0: - cparams.tcp_rates[0] = max_rate - else: - temp_rate = rate_numerator / (cparams.tcp_rates[0] * 8 * num_pixels) - if temp_rate > CINEMA_24_CS: - # TODO warning, reset - cparams.tcp_rates[0] = max_rate - else: - # TODO warning - pass - - cparams.max_comp_size = COMP_24_CS - - else: - max_rate = rate_numerator / (rate_denominator * CINEMA_48_CS) - if cparams.tcp_rates[0] == 0: - cparams.tcp_rates[0] = max_rate - else: - temp_rate = rate_numerator / (cparams.tcp_rates[0] * 8 * num_pixels) - if temp_rate > CINEMA_48_CS: - # TODO warning, reset - cparams.tcp_rates[0] = max_rate - else: - # TODO warning - pass - - cparams.max_comp_size = COMP_48_CS - def _write_openjp2(self, img_array, verbose=False, **kwargs): """ Write JPEG 2000 file using OpenJPEG 2.0 interface. @@ -661,11 +531,6 @@ class Jp2k(Jp2kBox): _populate_image_struct(cparams, image, img_array) - if 'cinema2k' in kwargs: - self._set_cinema_rate(cparams, image) - if 'cinema4k' in kwargs: - self._set_cinema_rate(cparams, image) - codec = opj2.create_compress(cparams.codec_fmt) stack.callback(opj2.destroy_codec, codec) diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index abe271a..4cff15b 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -6647,6 +6647,7 @@ class TestSuite2point1(unittest.TestCase): Jp2k(jfile).read() self.assertTrue(True) + @unittest.skip("Failing as of r2436") def test_NR_DEC_mem_b2ace68c_1381_jp2_34_decode(self): jfile = opj_data_file('input/nonregression/mem-b2ace68c-1381.jp2') with warnings.catch_warnings(): From ed5c6a37b405e5692273225b7b65108934e36c6a Mon Sep 17 00:00:00 2001 From: John Evans Date: Sat, 8 Mar 2014 13:57:33 -0500 Subject: [PATCH 124/326] Added .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc From 554f066d0b048d6c86b9cbeec9ebe27ff552a709 Mon Sep 17 00:00:00 2001 From: John Evans Date: Sat, 8 Mar 2014 14:13:36 -0500 Subject: [PATCH 125/326] Added alpha member. Updated through r2451. #139 --- docs/source/detailed_installation.rst | 2 +- glymur/lib/openjp2.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index 5c8d7ad..f18d77e 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -13,7 +13,7 @@ both read and write JPEG 2000 files, but you may wish to install version 2.0 or the 2.0+ version from OpenJPEG's development trunk for better performance. If you do that, you should compile it as a shared library (named *openjp2* instead of *openjpeg*) from the developmental source that you can retrieve -via subversion. As of this time of writing, svn revision r2436 works. +via subversion. As of this time of writing, svn revision r2451 works. You should also download the test data for the purpose of configuring and running OpenJPEG's test suite, check their instructions for all this. You should set the **OPJ_DATA_ROOT** environment variable for the purpose diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py index f056db0..5430110 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -35,6 +35,7 @@ CLRSPC_UNSPECIFIED = 0 CLRSPC_SRGB = 1 CLRSPC_GRAY = 2 CLRSPC_YCC = 3 +CLRSPC_EYCC = 4 COLOR_SPACE_TYPE = ctypes.c_int # supported codec @@ -390,7 +391,11 @@ class ImageCompType(ctypes.Structure): ("factor", ctypes.c_uint32), # image component data - ("data", ctypes.POINTER(ctypes.c_int32))] + ("data", ctypes.POINTER(ctypes.c_int32)), + + # alpha channel + # TODO: exclude for 2.0, 1.5 + ("alpha", ctypes.POINTER(ctypes.c_uint16))] class ImageType(ctypes.Structure): From 8827908b284b8292aee439a1c538534cb9ad187c Mon Sep 17 00:00:00 2001 From: John Evans Date: Sat, 8 Mar 2014 15:23:35 -0500 Subject: [PATCH 126/326] Updated through r2651. #139 --- docs/source/detailed_installation.rst | 2 +- glymur/test/test_icc.py | 2 +- glymur/test/test_opj_suite.py | 92 ++++++++++----------------- glymur/test/test_printing.py | 1 + 4 files changed, 38 insertions(+), 59 deletions(-) diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index f18d77e..6bbc2f2 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -13,7 +13,7 @@ both read and write JPEG 2000 files, but you may wish to install version 2.0 or the 2.0+ version from OpenJPEG's development trunk for better performance. If you do that, you should compile it as a shared library (named *openjp2* instead of *openjpeg*) from the developmental source that you can retrieve -via subversion. As of this time of writing, svn revision r2451 works. +via subversion. As of this time of writing, svn revision r2651 works. You should also download the test data for the purpose of configuring and running OpenJPEG's test suite, check their instructions for all this. You should set the **OPJ_DATA_ROOT** environment variable for the purpose diff --git a/glymur/test/test_icc.py b/glymur/test/test_icc.py index 46d4345..0ef166b 100644 --- a/glymur/test/test_icc.py +++ b/glymur/test/test_icc.py @@ -31,7 +31,7 @@ class TestICC(unittest.TestCase): """basic ICC profile""" filename = opj_data_file('input/conformance/file5.jp2') j = Jp2k(filename) - profile = j.box[3].box[1].icc_profile + profile = j.box[2].box[1].icc_profile self.assertEqual(profile['Size'], 546) self.assertEqual(profile['Preferred CMM Type'], 0) self.assertEqual(profile['Version'], '2.2.0') diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 4cff15b..426ae02 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -3297,7 +3297,7 @@ class TestSuiteDump(unittest.TestCase): def test_NR_file5_dump(self): # Three 8-bit components in the ROMM-RGB colourspace, encapsulated in a - # JP2 compatible JPX file. The components have been transformed using + # JPX file. The components have been transformed using # the RCT. The colourspace is specified using both a Restricted ICC # profile and using the JPX-defined enumerated code for the ROMM-RGB # colourspace. @@ -3305,49 +3305,38 @@ class TestSuiteDump(unittest.TestCase): jp2 = Jp2k(jfile) ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'colr']) + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) # Signature box. Check for corruption. self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) # File type box. - self.assertEqual(jp2.box[1].brand, 'jpx ') + self.assertEqual(jp2.box[1].brand, 'jp2 ') self.assertEqual(jp2.box[1].minor_version, 0) self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') - self.assertEqual(jp2.box[1].compatibility_list[3], 'jpxb') # Jp2 Header # Image header - self.assertEqual(jp2.box[3].box[0].height, 512) - self.assertEqual(jp2.box[3].box[0].width, 768) - self.assertEqual(jp2.box[3].box[0].num_components, 3) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) + self.assertEqual(jp2.box[2].box[0].height, 512) + self.assertEqual(jp2.box[2].box[0].width, 768) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) # Jp2 Header # Colour specification - self.assertEqual(jp2.box[3].box[1].method, + self.assertEqual(jp2.box[2].box[1].method, glymur.core.RESTRICTED_ICC_PROFILE) # enumerated - self.assertEqual(jp2.box[3].box[1].precedence, 0) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 546) - self.assertIsNone(jp2.box[3].box[1].colorspace) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact + self.assertEqual(jp2.box[2].box[1].icc_profile['Size'], 546) + self.assertIsNone(jp2.box[2].box[1].colorspace) - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[2].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[2].precedence, 1) - self.assertEqual(jp2.box[3].box[2].approximation, 1) # JPX exact - self.assertIsNone(jp2.box[3].box[2].icc_profile) - self.assertEqual(jp2.box[3].box[2].colorspace, - glymur.core.ROMM_RGB) def test_NR_file6_dump(self): jfile = opj_data_file('input/conformance/file6.jp2') @@ -3398,54 +3387,43 @@ class TestSuiteDump(unittest.TestCase): jp2 = Jp2k(jfile) ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'colr']) + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) # Signature box. Check for corruption. self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) # File type box. - self.assertEqual(jp2.box[1].brand, 'jpx ') + self.assertEqual(jp2.box[1].brand, 'jp2 ') self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') - self.assertEqual(jp2.box[1].compatibility_list[3], 'jpxb') self.assertEqual(jp2.box[1].minor_version, 0) # Reader requirements talk. # e-SRGB enumerated colourspace - self.assertTrue(60 in jp2.box[2].standard_flag) + #self.assertTrue(60 in jp2.box[2].standard_flag) # Jp2 Header # Image header - self.assertEqual(jp2.box[3].box[0].height, 640) - self.assertEqual(jp2.box[3].box[0].width, 480) - self.assertEqual(jp2.box[3].box[0].num_components, 3) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 16) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) + self.assertEqual(jp2.box[2].box[0].height, 640) + self.assertEqual(jp2.box[2].box[0].width, 480) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 16) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) # Jp2 Header # Colour specification - self.assertEqual(jp2.box[3].box[1].method, + self.assertEqual(jp2.box[2].box[1].method, glymur.core.RESTRICTED_ICC_PROFILE) - self.assertEqual(jp2.box[3].box[1].precedence, 0) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 13332) - self.assertIsNone(jp2.box[3].box[1].colorspace) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact + self.assertEqual(jp2.box[2].box[1].icc_profile['Size'], 13332) + self.assertIsNone(jp2.box[2].box[1].colorspace) - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[2].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[2].precedence, 1) - self.assertEqual(jp2.box[3].box[2].approximation, 1) # JPX exact - self.assertIsNone(jp2.box[3].box[2].icc_profile) - self.assertEqual(jp2.box[3].box[2].colorspace, - glymur.core.E_SRGB) def test_NR_file8_dump(self): # One 8-bit component in a gamma 1.8 space. The colourspace is diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index c59d73f..bc12216 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -735,6 +735,7 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) + @unittest.skip("file7 no longer has a rreq") @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") def test_rreq(self): From 4a3c768a4ba135e3cf2fcfdbdf217c349a25581d Mon Sep 17 00:00:00 2001 From: John Evans Date: Sat, 8 Mar 2014 20:36:05 -0500 Subject: [PATCH 127/326] Updated tests for r2651 in opj_data_root. --- glymur/test/fixtures.py | 22 +++++--- glymur/test/test_icc.py | 2 +- glymur/test/test_jp2k.py | 1 + glymur/test/test_opj_suite.py | 95 ++++++++++++----------------------- glymur/test/test_printing.py | 4 +- 5 files changed, 51 insertions(+), 73 deletions(-) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 666fb6b..cf37aa5 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -554,15 +554,21 @@ UUID Box (uuid) @ (77, 3146) UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) Contiguous Codestream Box (jp2c) @ (3223, 1132296)""" -# Output of reader requirement printing for file7.jp2 -file7_rreq = r"""Reader Requirements Box (rreq) @ (44, 24) - Fully Understands Aspect Mask: 0xa0 - Display Completely Mask: 0xc0 +# Output of reader requirements printing for text_GBR.jp2 +text_GBR_rreq = r"""Reader Requirements Box (rreq) @ (40, 109) + Fully Understands Aspect Mask: 0xffff + Display Completely Mask: 0xf8f0 Standard Features and Masks: - Feature 005: 0x80 Unrestricted JPEG 2000 Part 1 codestream, ITU-T Rec. T.800 | ISO/IEC 15444-1 - Feature 060: 0x60 e-sRGB enumerated colorspace - Feature 043: 0x40 Deprecated - compositing layer uses restricted ICC profile - Vendor Features:""" + Feature 001: 0x8000 Deprecated - contains no extensions + Feature 005: 0x4080 Unrestricted JPEG 2000 Part 1 codestream, ITU-T Rec. T.800 | ISO/IEC 15444-1 + Feature 012: 0x2040 Deprecated - codestream is contiguous + Feature 018: 0x1020 Deprecated - support for compositing is not required + Feature 044: 0x810 Compositing layer uses Any ICC profile + Vendor Features: + UUID 3a0d0218-0ae9-4115-b376-4bca41ce0e71 + UUID 47c92ccc-d1a1-4581-b904-38bb5467713b + UUID bc45a774-dd50-4ec6-a9f6-f3a137f47e90 + UUID d7c8c5ef-951f-43b2-8757-042500f538e8""" file1_xml = """XML Box (xml ) @ (36, 439) diff --git a/glymur/test/test_icc.py b/glymur/test/test_icc.py index 46d4345..0ef166b 100644 --- a/glymur/test/test_icc.py +++ b/glymur/test/test_icc.py @@ -31,7 +31,7 @@ class TestICC(unittest.TestCase): """basic ICC profile""" filename = opj_data_file('input/conformance/file5.jp2') j = Jp2k(filename) - profile = j.box[3].box[1].icc_profile + profile = j.box[2].box[1].icc_profile self.assertEqual(profile['Size'], 546) self.assertEqual(profile['Preferred CMM Type'], 0) self.assertEqual(profile['Version'], '2.2.0') diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 386f077..05e8141 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -376,6 +376,7 @@ class TestJp2k(unittest.TestCase): creator_tool = xmp.get_property(libxmp.consts.XMP_NS_XMP, 'CreatorTool') self.assertEqual(creator_tool, 'Google') + @unittest.skip("Failing as of r2651.") def test_jpx_mult_codestreams_jp2_brand(self): """Read JPX codestream when jp2-compatible.""" # The file in question has multiple codestreams. diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index abe271a..a84fead 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -3305,49 +3305,37 @@ class TestSuiteDump(unittest.TestCase): jp2 = Jp2k(jfile) ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'colr']) + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) # Signature box. Check for corruption. self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) # File type box. - self.assertEqual(jp2.box[1].brand, 'jpx ') + self.assertEqual(jp2.box[1].brand, 'jp2 ') self.assertEqual(jp2.box[1].minor_version, 0) self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') - self.assertEqual(jp2.box[1].compatibility_list[3], 'jpxb') # Jp2 Header # Image header - self.assertEqual(jp2.box[3].box[0].height, 512) - self.assertEqual(jp2.box[3].box[0].width, 768) - self.assertEqual(jp2.box[3].box[0].num_components, 3) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) + self.assertEqual(jp2.box[2].box[0].height, 512) + self.assertEqual(jp2.box[2].box[0].width, 768) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) # Jp2 Header # Colour specification - self.assertEqual(jp2.box[3].box[1].method, + self.assertEqual(jp2.box[2].box[1].method, glymur.core.RESTRICTED_ICC_PROFILE) # enumerated - self.assertEqual(jp2.box[3].box[1].precedence, 0) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 546) - self.assertIsNone(jp2.box[3].box[1].colorspace) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[2].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[2].precedence, 1) - self.assertEqual(jp2.box[3].box[2].approximation, 1) # JPX exact - self.assertIsNone(jp2.box[3].box[2].icc_profile) - self.assertEqual(jp2.box[3].box[2].colorspace, - glymur.core.ROMM_RGB) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact + self.assertEqual(jp2.box[2].box[1].icc_profile['Size'], 546) + self.assertIsNone(jp2.box[2].box[1].colorspace) def test_NR_file6_dump(self): jfile = opj_data_file('input/conformance/file6.jp2') @@ -3398,54 +3386,37 @@ class TestSuiteDump(unittest.TestCase): jp2 = Jp2k(jfile) ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'colr']) + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) # Signature box. Check for corruption. self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) # File type box. - self.assertEqual(jp2.box[1].brand, 'jpx ') + self.assertEqual(jp2.box[1].brand, 'jp2 ') self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') - self.assertEqual(jp2.box[1].compatibility_list[3], 'jpxb') - self.assertEqual(jp2.box[1].minor_version, 0) - - # Reader requirements talk. - # e-SRGB enumerated colourspace - self.assertTrue(60 in jp2.box[2].standard_flag) # Jp2 Header # Image header - self.assertEqual(jp2.box[3].box[0].height, 640) - self.assertEqual(jp2.box[3].box[0].width, 480) - self.assertEqual(jp2.box[3].box[0].num_components, 3) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 16) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) + self.assertEqual(jp2.box[2].box[0].height, 640) + self.assertEqual(jp2.box[2].box[0].width, 480) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 16) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) # Jp2 Header # Colour specification - self.assertEqual(jp2.box[3].box[1].method, + self.assertEqual(jp2.box[2].box[1].method, glymur.core.RESTRICTED_ICC_PROFILE) - self.assertEqual(jp2.box[3].box[1].precedence, 0) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 13332) - self.assertIsNone(jp2.box[3].box[1].colorspace) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[2].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[2].precedence, 1) - self.assertEqual(jp2.box[3].box[2].approximation, 1) # JPX exact - self.assertIsNone(jp2.box[3].box[2].icc_profile) - self.assertEqual(jp2.box[3].box[2].colorspace, - glymur.core.E_SRGB) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 1) + self.assertEqual(jp2.box[2].box[1].icc_profile['Size'], 13332) + self.assertIsNone(jp2.box[2].box[1].colorspace) def test_NR_file8_dump(self): # One 8-bit component in a gamma 1.8 space. The colourspace is diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index f90ade3..95b7e63 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -740,12 +740,12 @@ class TestPrinting(unittest.TestCase): "OPJ_DATA_ROOT environment variable not set") def test_rreq(self): """verify printing of reader requirements box""" - filename = opj_data_file('input/conformance/file7.jp2') + filename = opj_data_file('input/nonregression/text_GBR.jp2') j = glymur.Jp2k(filename) with patch('sys.stdout', new=StringIO()) as fake_out: print(j.box[2]) actual = fake_out.getvalue().strip() - self.assertEqual(actual, fixtures.file7_rreq) + self.assertEqual(actual, fixtures.text_GBR_rreq) @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") From 6f1d15eda9af60750fe4648dcc1375472de1bfda Mon Sep 17 00:00:00 2001 From: John Evans Date: Sun, 9 Mar 2014 11:56:37 -0400 Subject: [PATCH 128/326] Adding no_cxform parameter to read. Added pclr no_cxform test. #179 --- glymur/jp2k.py | 36 ++++++++++++++++++++++++++---------- glymur/test/test_jp2k.py | 17 +++++++++++++++++ 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 6db0198..e01d3d1 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -703,7 +703,7 @@ class Jp2k(Jp2kBox): msg += "the read_bands method instead." raise RuntimeError(msg) - def _read_openjpeg(self, rlevel=0, verbose=False): + def _read_openjpeg(self, rlevel=0, no_cxform=False, verbose=False): """Read a JPEG 2000 image using libopenjpeg. Parameters @@ -711,6 +711,8 @@ class Jp2k(Jp2kBox): rlevel : int, optional Factor by which to rlevel output resolution. Use -1 to get the lowest resolution thumbnail. + no_cxform : bool + Whether or not to apply intended color transforms. verbose : bool, optional Print informational messages produced by the OpenJPEG library. @@ -742,8 +744,14 @@ class Jp2k(Jp2kBox): with ExitStack() as stack: try: # Set decoding parameters. + # TODO: look to refactor, use _populate_dparam dparameters = opj.DecompressionParametersType() opj.set_default_decoder_parameters(ctypes.byref(dparameters)) + + if no_cxform is True: + # Return raw codestream components. + dparameters.flags |= 1 + dparameters.cp_reduce = rlevel dparameters.decod_format = self._codec_format @@ -788,7 +796,7 @@ class Jp2k(Jp2kBox): return data def _read_openjp2(self, rlevel=0, layer=0, area=None, tile=None, - verbose=False): + verbose=False, no_cxform=False): """Read a JPEG 2000 image using libopenjp2. Parameters @@ -818,7 +826,7 @@ class Jp2k(Jp2kBox): """ self._subsampling_sanity_check() - dparam = self._populate_dparam(layer, rlevel, area, tile) + dparam = self._populate_dparam(layer, rlevel, area, tile, no_cxform) with ExitStack() as stack: if hasattr(opj2.OPENJP2, @@ -862,20 +870,22 @@ class Jp2k(Jp2kBox): return img_array - def _populate_dparam(self, layer, rlevel, area, tile): + def _populate_dparam(self, layer, rlevel, area, tile, no_cxform): """Populate decompression structure with appropriate input parameters. Parameters ---------- - layer : int, optional + layer : int Number of quality layer to decode. - rlevel : int, optional + rlevel : int Factor by which to rlevel output resolution. - area : tuple, optional + area : tuple Specifies decoding image area, (first_row, first_col, last_row, last_col) - tile : int, optional + tile : int Number of tile to decode. + no_cxform : bool + Whether or not to apply intended color transforms. Returns ------- @@ -913,10 +923,14 @@ class Jp2k(Jp2kBox): dparam.tile_index = tile dparam.nb_tile_to_decode = 1 + if no_cxform is True: + # Return raw codestream components. + dparam.flags |= 1 + return dparam def read_bands(self, rlevel=0, layer=0, area=None, tile=None, - verbose=False): + verbose=False, no_cxform=False): """Read a JPEG 2000 image. The only time you should use this method is when the image has @@ -934,6 +948,8 @@ class Jp2k(Jp2kBox): (first_row, first_col, last_row, last_col) tile : int, optional Number of tile to decode. + no_cxform : bool + Whether or not to apply intended color transforms. verbose : bool, optional Print informational messages produced by the OpenJPEG library. @@ -963,7 +979,7 @@ class Jp2k(Jp2kBox): "of OpenJP2 installed before using " "this functionality.") - dparam = self._populate_dparam(layer, rlevel, area, tile) + dparam = self._populate_dparam(layer, rlevel, area, tile, no_cxform) with ExitStack() as stack: if hasattr(opj2.OPENJP2, diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 05e8141..aa6a0f3 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -94,6 +94,23 @@ class TestJp2k(unittest.TestCase): with self.assertRaises(IOError): Jp2k(filename) + def test_no_cxform(self): + """Indices for jpxfile if no color transform""" + j = Jp2k(self.jpxfile) + rgb = j.read() + idx = j.read(no_cxform=True) + self.assertEqual(rgb.shape, (1024, 1024, 3)) + self.assertEqual(idx.shape, (1024, 1024)) + + # Should be able to manually reconstruct the RGB image from the palette + # and indices. + palette = j.box[3].box[2].palette + rgb_from_idx = np.zeros(rgb.shape, dtype=np.uint8) + for r in np.arange(1024): + for c in np.arange(1024): + rgb_from_idx[r, c] = palette[idx[r, c]] + np.testing.assert_array_equal(rgb, rgb_from_idx) + def test_file_not_present(self): """Should error out if reading from a file that does not exist""" # Verify that we error out appropriately if not given an existing file From 95cae598419ea0d298c460629d4dad5e721f4266 Mon Sep 17 00:00:00 2001 From: John Evans Date: Sun, 9 Mar 2014 11:57:42 -0400 Subject: [PATCH 129/326] Removing --use-mirros flag. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c055dce..6a57264 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,8 @@ before_install: # command to install dependencies install: - - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors lxml contextlib2 mock; fi - - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install --use-mirrors lxml numpy; fi + - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install lxml contextlib2 mock; fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install lxml numpy; fi # command to run tests script: From de6984e204c87f18aff56a2030913c5c0ed020fe Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 10 Mar 2014 19:54:01 -0400 Subject: [PATCH 130/326] Validated through 2686. #139 --- glymur/jp2k.py | 9 +++- glymur/test/test_jp2k.py | 5 ++ glymur/test/test_opj_suite_neg.py | 25 ++++++++++ glymur/test/test_opj_suite_write.py | 73 ++++++++++++++++------------- 4 files changed, 78 insertions(+), 34 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 905dbd8..7522d35 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -166,6 +166,11 @@ class Jp2k(Jp2kBox): fps : int Frames per second, should be either 24 or 48. """ + if version.openjpeg_version_tuple[0] == 1: + msg = "Writing Cinema2K or Cinema4K files is not supported with " + msg += 'openjpeg library versions less than 2.0.1.' + raise IOError(msg) + if cinema_mode == 'cinema2k': if fps == 24: cparams.cp_cinema = CINEMA_MODE['cinema2k_24'] @@ -331,8 +336,8 @@ class Jp2k(Jp2kBox): colorspace : int Either CLRSPC_SRGB or CLRSPC_GRAY """ - - if ('cinema2k' in kwargs or 'cinema4k' in kwargs) and len(set(kwargs)) > 1: + if (('cinema2k' in kwargs or 'cinema4k' in kwargs) and + (len(set(kwargs)) > 1)): msg = "Cannot specify cinema2k/cinema4k along with other options." raise IOError(msg) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 386f077..dd55299 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -35,6 +35,7 @@ if HAS_PYTHON_XMP_TOOLKIT: from libxmp import XMPMeta from .fixtures import OPJ_DATA_ROOT, opj_data_file +from . import fixtures # Doc tests should be run as well. @@ -376,6 +377,10 @@ class TestJp2k(unittest.TestCase): creator_tool = xmp.get_property(libxmp.consts.XMP_NS_XMP, 'CreatorTool') self.assertEqual(creator_tool, 'Google') + @unittest.skipIf(fixtures.OPENJP2_IS_V2_OFFICIAL, + "Feature not supported in 2.0.0 official") + @unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1, + "Feature not supported in 1.5") def test_jpx_mult_codestreams_jp2_brand(self): """Read JPX codestream when jp2-compatible.""" # The file in question has multiple codestreams. diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index 4b14f4d..d31ea48 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -23,6 +23,31 @@ from glymur import Jp2k import glymur +@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version), + "Functionality not implemented for 1.3, 1.4") +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_OPJ_DATA_ROOT environment variable not set") +class TestSuiteNegative2pointzero(unittest.TestCase): + """Feature set not supported for versions less than 2.0""" + + def setUp(self): + self.jp2file = glymur.data.nemo() + self.j2kfile = glymur.data.goodstuff() + + def tearDown(self): + pass + + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") + def test_cinema_mode(self): + """Cinema mode not supported for less than 2.0.1.""" + infile = opj_data_file('input/nonregression/Bretagne1.ppm') + data = read_image(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError): + j.write(data, psnr=[30, 35, 40], cratios=[2, 3, 4]) + + @unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version), "Functionality not implemented for 1.3, 1.4") @unittest.skipIf(OPJ_DATA_ROOT is None, diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 6539e21..220115c 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -21,18 +21,21 @@ except ((ImportError, RuntimeError)): from .fixtures import read_image, NO_READ_BACKEND, NO_READ_BACKEND_MSG from .fixtures import OPJ_DATA_ROOT, opj_data_file +from . import fixtures from glymur import Jp2k import glymur +@unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, + "Cannot read input image without scikit-image/freeimage") @unittest.skipIf(os.name == "nt", "no write support on windows, period") -@unittest.skipIf(re.match(r"""1\.[01234]\.\d""", - glymur.version.openjpeg_version) is not None, - "Writing only supported with openjpeg version 1.5+.") -@unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG) +@unittest.skipIf(fixtures.OPENJP2_IS_V2_OFFICIAL, + "Feature not supported in 2.0.0 official") +@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1, + "Feature not supported in 1.5") @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") -class TestSuiteWrite(unittest.TestCase): +class TestSuiteWriteCinema(unittest.TestCase): """Tests for writing with openjp2 backend. These tests either roughly correspond with those tests with similar names @@ -148,8 +151,6 @@ class TestSuiteWrite(unittest.TestCase): - @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, - "Cannot read input image without scikit-image/freeimage") def test_NR_ENC_ElephantDream_4K_tif_21_encode(self): relfile = 'input/nonregression/ElephantDream_4K.tif' infile = opj_data_file(relfile) @@ -162,8 +163,6 @@ class TestSuiteWrite(unittest.TestCase): self.check_cinema4k_codestream(codestream, (4096, 2160)) - @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, - "Cannot read input image without scikit-image/freeimage") def test_NR_ENC_X_5_2K_24_235_CBR_STEM24_000_tif_19_encode(self): relfile = 'input/nonregression/X_5_2K_24_235_CBR_STEM24_000.tif' infile = opj_data_file(relfile) @@ -176,8 +175,6 @@ class TestSuiteWrite(unittest.TestCase): self.check_cinema2k_codestream(codestream, (2048, 857)) - @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, - "Cannot read input image without scikit-image/freeimage") def test_NR_ENC_X_6_2K_24_FULL_CBR_CIRCLE_000_tif_20_encode(self): relfile = 'input/nonregression/X_6_2K_24_FULL_CBR_CIRCLE_000.tif' infile = opj_data_file(relfile) @@ -190,8 +187,6 @@ class TestSuiteWrite(unittest.TestCase): self.check_cinema2k_codestream(codestream, (2048, 1080)) - @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, - "Cannot read input image without scikit-image/freeimage") def test_NR_ENC_X_6_2K_24_FULL_CBR_CIRCLE_000_tif_17_encode(self): relfile = 'input/nonregression/X_6_2K_24_FULL_CBR_CIRCLE_000.tif' infile = opj_data_file(relfile) @@ -204,8 +199,6 @@ class TestSuiteWrite(unittest.TestCase): self.check_cinema2k_codestream(codestream, (2048, 1080)) - @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, - "Cannot read input image without scikit-image/freeimage") def test_NR_ENC_X_5_2K_24_235_CBR_STEM24_000_tif_16_encode(self): relfile = 'input/nonregression/X_5_2K_24_235_CBR_STEM24_000.tif' infile = opj_data_file(relfile) @@ -218,8 +211,6 @@ class TestSuiteWrite(unittest.TestCase): self.check_cinema2k_codestream(codestream, (2048, 857)) - @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, - "Cannot read input image without scikit-image/freeimage") def test_NR_ENC_X_4_2K_24_185_CBR_WB_000_tif_18_encode(self): relfile = 'input/nonregression/X_4_2K_24_185_CBR_WB_000.tif' infile = opj_data_file(relfile) @@ -232,32 +223,50 @@ class TestSuiteWrite(unittest.TestCase): self.check_cinema2k_codestream(codestream, (1998, 1080)) - @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, - "Cannot read input image without scikit-image/freeimage") - def test_NR_ENC_X_4_2K_24_185_CBR_WB_000_tif_15_encode(self): - relfile = 'input/nonregression/X_4_2K_24_185_CBR_WB_000.tif' - infile = opj_data_file(relfile) - data = skimage.io.imread(infile) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - j.write(data, cinema2k=24) +@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") +@unittest.skipIf(re.match(r"""2\.0""", glymur.version.openjpeg_version), + "Functionality implemented for 2.1") +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_OPJ_DATA_ROOT environment variable not set") +class TestSuiteNegative2pointzero(unittest.TestCase): + """Feature set not supported for versions less than 2.0""" - codestream = j.get_codestream() - self.check_cinema2k_codestream(codestream, (1998, 1080)) + def setUp(self): + self.jp2file = glymur.data.nemo() + self.j2kfile = glymur.data.goodstuff() + def tearDown(self): + pass - @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, - "Cannot read input image without scikit-image/freeimage") - def test_cinema2k_bad_frame_rate(self): + def test_cinema_mode(self): relfile = 'input/nonregression/X_4_2K_24_185_CBR_WB_000.tif' infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, 'wb') with self.assertRaises(IOError): - j.write(data, cinema2k=36) + j.write(data, cinema2k=48) +@unittest.skipIf(os.name == "nt", "no write support on windows, period") +@unittest.skipIf(re.match(r"""1\.[01234]\.\d""", + glymur.version.openjpeg_version) is not None, + "Writing only supported with openjpeg version 1.5+.") +@unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG) +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class TestSuiteWrite(unittest.TestCase): + """Tests for writing with openjp2 backend. + + These tests either roughly correspond with those tests with similar names + in the OpenJPEG test suite or are closely associated. + """ + def setUp(self): + pass + + def tearDown(self): + pass + def test_NR_ENC_Bretagne1_ppm_1_encode(self): """NR-ENC-Bretagne1.ppm-1-encode""" infile = opj_data_file('input/nonregression/Bretagne1.ppm') From 3e490d0c28c962358f4b62a1ec43bec58ee9f9f6 Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 10 Mar 2014 20:44:24 -0400 Subject: [PATCH 131/326] Rewrote the test for a file truncated by 5000 bytes. #180 --- glymur/test/test_conformance.py | 38 +++++++++++++++++---------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/glymur/test/test_conformance.py b/glymur/test/test_conformance.py index 765f2f6..48db78a 100644 --- a/glymur/test/test_conformance.py +++ b/glymur/test/test_conformance.py @@ -62,6 +62,26 @@ class TestSuiteConformance(unittest.TestCase): with self.assertRaises(OSError): j2k.read(rlevel=-1) + def test_truncated_5000(self): + """File is missing last 5000 bytes.""" + with open(self.j2kfile, 'rb') as ifile: + data = ifile.read() + with tempfile.NamedTemporaryFile(suffix='.j2k') as ofile: + ofile.write(data[:-5000]) + ofile.flush() + + j2k = Jp2k(ofile.name) + with self.assertWarns(UserWarning): + codestream = j2k.get_codestream(header_only=False) + + # The last segment is truncated, so there should not be an EOC + # marker. + self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC') + + # The codestream is not as long as claimed. + with self.assertRaises(OSError): + j2k.read(rlevel=-1) + @unittest.skipIf(FORMAT_CORPUS_DATA_ROOT is None, "FORMAT_CORPUS_DATA_ROOT environment variable not set") @@ -70,24 +90,6 @@ class TestSuiteConformance(unittest.TestCase): class TestSuiteFormatCorpus(unittest.TestCase): """Test suite for files in format corpus repository.""" - @unittest.skipIf(re.match(r"""1\.[01234]""", - glymur.version.openjpeg_version) is not None, - "Needs 1.4+ to catch this.") - def test_balloon_trunc2(self): - """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): - codestream = j2k.get_codestream(header_only=False) - - # The last segment is truncated, so there should not be an EOC marker. - self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC') - - # The codestream is not as long as claimed. - with self.assertRaises(OSError): - j2k.read(rlevel=-1) - def test_balloon_trunc3(self): """Most of last tile is missing.""" jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT, From 417503764e350ae3fe738a211653550c0215ad4f Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 11 Mar 2014 15:46:17 -0400 Subject: [PATCH 132/326] Updated through r2691. #139 --- docs/source/detailed_installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index 6bbc2f2..67c230a 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -13,7 +13,7 @@ both read and write JPEG 2000 files, but you may wish to install version 2.0 or the 2.0+ version from OpenJPEG's development trunk for better performance. If you do that, you should compile it as a shared library (named *openjp2* instead of *openjpeg*) from the developmental source that you can retrieve -via subversion. As of this time of writing, svn revision r2651 works. +via subversion. As of this time of writing, svn revision r2691 works. You should also download the test data for the purpose of configuring and running OpenJPEG's test suite, check their instructions for all this. You should set the **OPJ_DATA_ROOT** environment variable for the purpose From 1cefd80a76b3e47e0ac688814eff3dce38161bda Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 11 Mar 2014 21:02:15 -0400 Subject: [PATCH 133/326] Just remove the conformance tests, they aren't really needed. #180 --- glymur/test/test_conformance.py | 150 -------------------------------- glymur/test/test_opj_suite.py | 13 ++- 2 files changed, 9 insertions(+), 154 deletions(-) delete mode 100644 glymur/test/test_conformance.py diff --git a/glymur/test/test_conformance.py b/glymur/test/test_conformance.py deleted file mode 100644 index 48db78a..0000000 --- a/glymur/test/test_conformance.py +++ /dev/null @@ -1,150 +0,0 @@ -""" -These tests deal with JPX/JP2/J2K images in the format-corpus repository. -""" -# R0904: Not too many methods in unittest. -# pylint: disable=R0904 - -# E1101: assertWarns introduced in python 3.2 -# pylint: disable=E1101 - -import os -from os.path import join -import re -import sys -import tempfile -import unittest - -import glymur -from glymur import Jp2k - -try: - FORMAT_CORPUS_DATA_ROOT = os.environ['FORMAT_CORPUS_DATA_ROOT'] -except KeyError: - FORMAT_CORPUS_DATA_ROOT = None - -try: - OPJ_DATA_ROOT = os.environ['OPJ_DATA_ROOT'] -except KeyError: - OPJ_DATA_ROOT = None - - -@unittest.skipIf(sys.hexversion < 0x03020000, - "Requires features introduced in 3.2 (assertWarns)") -class TestSuiteConformance(unittest.TestCase): - """Test suite for conformance.""" - - def setUp(self): - self.j2kfile = glymur.data.goodstuff() - - def tearDown(self): - pass - - @unittest.skipIf(re.match(r"""1\.[0123]""", - glymur.version.openjpeg_version) is not None, - "Needs 1.3+ to catch this.") - def test_truncated_eoc(self): - """Has one byte shaved off of EOC marker.""" - with open(self.j2kfile, 'rb') as ifile: - data = ifile.read() - with tempfile.NamedTemporaryFile(suffix='.j2k') as ofile: - ofile.write(data[:-1]) - ofile.flush() - - j2k = Jp2k(ofile.name) - with self.assertWarns(UserWarning): - codestream = j2k.get_codestream(header_only=False) - - # The last segment is truncated, so there should not be an EOC - # marker. - self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC') - - # The codestream is not as long as claimed. - with self.assertRaises(OSError): - j2k.read(rlevel=-1) - - def test_truncated_5000(self): - """File is missing last 5000 bytes.""" - with open(self.j2kfile, 'rb') as ifile: - data = ifile.read() - with tempfile.NamedTemporaryFile(suffix='.j2k') as ofile: - ofile.write(data[:-5000]) - ofile.flush() - - j2k = Jp2k(ofile.name) - with self.assertWarns(UserWarning): - codestream = j2k.get_codestream(header_only=False) - - # The last segment is truncated, so there should not be an EOC - # marker. - self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC') - - # The codestream is not as long as claimed. - with self.assertRaises(OSError): - j2k.read(rlevel=-1) - - -@unittest.skipIf(FORMAT_CORPUS_DATA_ROOT is None, - "FORMAT_CORPUS_DATA_ROOT environment variable not set") -@unittest.skipIf(sys.hexversion < 0x03020000, - "Requires features introduced in 3.2 (assertWarns)") -class TestSuiteFormatCorpus(unittest.TestCase): - """Test suite for files in format corpus repository.""" - - def test_balloon_trunc3(self): - """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): - codestream = j2k.get_codestream(header_only=False) - - # The last segment is truncated, so there should not be an EOC marker. - self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC') - - # Should error out, it does not. - #with self.assertRaises(OSError): - # j2k.read(rlevel=-1) - - 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): - Jp2k(jfile) - - 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. - jfile = join(FORMAT_CORPUS_DATA_ROOT, 'jp2k-test', 'icc', - 'balloon_eciRGBv2_ps_adobeplugin_jp2compatible.jpf') - with self.assertWarns(UserWarning): - Jp2k(jfile) - - -@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 - - def tearDown(self): - pass - - 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): - Jp2k(filename) - -if __name__ == "__main__": - unittest.main() diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index be85de9..df4cde2 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -6307,11 +6307,16 @@ class TestSuiteDump(unittest.TestCase): [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) def test_NR_text_GBR_dump(self): + # brand is 'jp2 ', but has any icc profile. + # Verify the warning on python3, but ignore it otherwise. jfile = opj_data_file('input/nonregression/text_GBR.jp2') - with warnings.catch_warnings(): - # brand is 'jp2 ', but has any icc profile. - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) + if sys.hexversion > 0x03030000: + with self.assertWarns(UserWarning): + jp2 = Jp2k(jfile) + else: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) ids = [box.box_id for box in jp2.box] lst = ['jP ', 'ftyp', 'rreq', 'jp2h', From d098dd5d5ed863e755fe9c9abc678eebb7150233 Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 11 Mar 2014 21:40:19 -0400 Subject: [PATCH 134/326] Removed consideration of openjpeg versions 1.3 and 1.4. #159 --- docs/source/changelog.rst | 1 + docs/source/introduction.rst | 12 +----------- glymur/jp2k.py | 13 ------------- glymur/test/test_jp2k.py | 8 +------- glymur/test/test_opj_suite.py | 6 +----- glymur/test/test_opj_suite_neg.py | 4 ---- glymur/test/test_opj_suite_write.py | 3 --- 7 files changed, 4 insertions(+), 43 deletions(-) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 7a96571..c3eae6c 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -9,6 +9,7 @@ ChangeLog * Added lxml requirement. * added set_printoptions, get_printoptions function * dropped support for Python 2.6, added support for Python 3.4 + * dropped support for OpenJPEG versions 1.3 and 1.4 * dropped windows support (it might work, it might not, I don't much care) * added write support for JP2 UUID, dataEntryURL, palette, and component mapping boxes * added read/write support for JPX free, number list, and data reference boxes diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index 38193eb..10df117 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -14,17 +14,7 @@ XMP UUIDs. There is some very limited support for reading JPX metadata. Glymur 0.6 works on Python versions 2.7, 3.3 and 3.4. If you have Python 2.6, you should use the 0.5 series of Glymur. -OpenJPEG Installation -===================== -Glymur will read JPEG 2000 images with versions 1.3, 1.4, 1.5, 2.0, -and the trunk/development version of OpenJPEG. Writing images is -only supported with the 1.5 or better, however, and the trunk/development -version of OpenJPEG is strongly recommended. For more information about -OpenJPEG, please consult http://www.openjpeg.org. - -If you use MacPorts or if you have a sufficiently recent version of -Linux, your package manager should already provide you with a version of -OpenJPEG 1.X which glymur can already use. +For more information about OpenJPEG, please consult http://www.openjpeg.org. Glymur Installation =================== diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 7522d35..3467426 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -772,19 +772,6 @@ class Jp2k(Jp2kBox): """ self._subsampling_sanity_check() - if rlevel != 0: - # Must check the specified rlevel against the maximum. - # OpenJPEG 1.3 will segfault if rlevel is too high. - codestream = self.get_codestream() - max_rlevel = codestream.segment[2].spcod[4] - if rlevel == -1: - # -1 is shorthand for the largest rlevel - rlevel = max_rlevel - if rlevel < -1 or rlevel > max_rlevel: - msg = "rlevel must be in the range [-1, {0}] for this image." - msg = msg.format(max_rlevel) - raise IOError(msg) - with ExitStack() as stack: try: # Set decoding parameters. diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index dd55299..433531f 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -386,15 +386,9 @@ class TestJp2k(unittest.TestCase): # The file in question has multiple codestreams. jpx = Jp2k(self.jpxfile) data = jpx.read() - if re.match(r"""1\.[0123]""", glymur.version.openjpeg_version): - # openjpeg 1.3 doesn't apply the palette, so it's a 2D image here - self.assertEqual(data.shape, (1024, 1024)) - else: - self.assertEqual(data.shape, (1024, 1024, 3)) + self.assertEqual(data.shape, (1024, 1024, 3)) -@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version), - "Requires at least version 1.5") class TestJp2k_write(unittest.TestCase): """Write tests, can be run by versions 1.5+""" diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index df4cde2..5cc9250 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -381,11 +381,7 @@ class TestSuite(unittest.TestCase): jfile = opj_data_file('input/conformance/file9.jp2') jp2k = Jp2k(jfile) jpdata = jp2k.read() - if re.match(r"""1\.3""", glymur.version.openjpeg_version): - # Version 1.3 reads the indexed image as indices, not as RGB. - self.assertEqual(jpdata.shape, (512, 768)) - else: - self.assertEqual(jpdata.shape, (512, 768, 3)) + self.assertEqual(jpdata.shape, (512, 768, 3)) def test_NR_DEC_Bretagne2_j2k_1_decode(self): jfile = opj_data_file('input/nonregression/Bretagne2.j2k') diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index d31ea48..c93d624 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -23,8 +23,6 @@ from glymur import Jp2k import glymur -@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version), - "Functionality not implemented for 1.3, 1.4") @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_OPJ_DATA_ROOT environment variable not set") class TestSuiteNegative2pointzero(unittest.TestCase): @@ -48,8 +46,6 @@ class TestSuiteNegative2pointzero(unittest.TestCase): j.write(data, psnr=[30, 35, 40], cratios=[2, 3, 4]) -@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version), - "Functionality not implemented for 1.3, 1.4") @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_OPJ_DATA_ROOT environment variable not set") class TestSuiteNegative(unittest.TestCase): diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 220115c..1db11c4 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -249,9 +249,6 @@ class TestSuiteNegative2pointzero(unittest.TestCase): @unittest.skipIf(os.name == "nt", "no write support on windows, period") -@unittest.skipIf(re.match(r"""1\.[01234]\.\d""", - glymur.version.openjpeg_version) is not None, - "Writing only supported with openjpeg version 1.5+.") @unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG) @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") From ac3aa4d6648c1955b220f158a9b81f94c9ca7b47 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 12 Mar 2014 08:57:11 -0400 Subject: [PATCH 135/326] alpha is a uint16, not a uint16 pointer. #139 --- glymur/lib/openjp2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py index 5430110..39dd5a4 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -395,7 +395,7 @@ class ImageCompType(ctypes.Structure): # alpha channel # TODO: exclude for 2.0, 1.5 - ("alpha", ctypes.POINTER(ctypes.c_uint16))] + ("alpha", ctypes.c_uint16)] class ImageType(ctypes.Structure): From 539ee438f65637c5cacab5b8f6d551b313b582ca Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 12 Mar 2014 09:16:15 -0400 Subject: [PATCH 136/326] Removed repeated/mangled testpoint. --- glymur/test/test_opj_suite_neg.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index c93d624..c9a7e8c 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -23,29 +23,6 @@ from glymur import Jp2k import glymur -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_OPJ_DATA_ROOT environment variable not set") -class TestSuiteNegative2pointzero(unittest.TestCase): - """Feature set not supported for versions less than 2.0""" - - def setUp(self): - self.jp2file = glymur.data.nemo() - self.j2kfile = glymur.data.goodstuff() - - def tearDown(self): - pass - - @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") - def test_cinema_mode(self): - """Cinema mode not supported for less than 2.0.1.""" - infile = opj_data_file('input/nonregression/Bretagne1.ppm') - data = read_image(infile) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - with self.assertRaises(IOError): - j.write(data, psnr=[30, 35, 40], cratios=[2, 3, 4]) - - @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_OPJ_DATA_ROOT environment variable not set") class TestSuiteNegative(unittest.TestCase): From b479ece7b488e236418014f9dd1ff5db1877b522 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 12 Mar 2014 10:10:38 -0400 Subject: [PATCH 137/326] Bring back check for bad rlevel. #159 OpenJPEG 1.5 needs the protection, it would seem. --- glymur/jp2k.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 3467426..7a513fe 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -772,6 +772,19 @@ class Jp2k(Jp2kBox): """ self._subsampling_sanity_check() + # Must check the specified rlevel against the maximum. + if rlevel != 0: + # Must check the specified rlevel against the maximum. + codestream = self.get_codestream() + max_rlevel = codestream.segment[2].spcod[4] + if rlevel == -1: + # -1 is shorthand for the largest rlevel + rlevel = max_rlevel + elif rlevel < -1 or rlevel > max_rlevel: + msg = "rlevel must be in the range [-1, {0}] for this image." + msg = msg.format(max_rlevel) + raise IOError(msg) + with ExitStack() as stack: try: # Set decoding parameters. From c56b919c36b0a7312dc9ea2c10617984549c0457 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 12 Mar 2014 12:27:52 -0400 Subject: [PATCH 138/326] Fixed printing of cmap boxes when not pclr. #182 --- glymur/jp2box.py | 2 +- glymur/test/fixtures.py | 5 +++++ glymur/test/test_printing.py | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 6db6c87..a106ee4 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -829,7 +829,7 @@ class ComponentMappingBox(Jp2kBox): msg = msg.format(self.component_index[k], self.palette_index[k]) else: - msg += '\n Component %d ==> %d' + msg += '\n Component {0} ==> {1}' msg = msg.format(self.component_index[k], k) return msg diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 2004d2b..63f5837 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -577,3 +577,8 @@ file1_xml = """XML Box (xml ) @ (36, 439) \t\tProfessional 120 Image \t """ + +issue_182_cmap = """Component Mapping Box (cmap) @ (130, 24) + Component 0 ==> palette column 0 + Component 1 ==> palette column 0 + Component 2 ==> 2""" diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 0deaebd..3dd58f5 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -905,6 +905,22 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) + def test_issue182(self): + """Should not show the format string in output.""" + # The cmap box is wildly broken, but printing was still wrong. + # Format strings like %d were showing up in the output. + filename = opj_data_file('input/nonregression/mem-b2ace68c-1381.jp2') + + with warnings.catch_warnings(): + # Ignore warning about bad pclr box. + warnings.simplefilter("ignore") + jp2 = Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(jp2.box[3].box[3]) + actual = fake_out.getvalue().strip() + self.assertEqual(actual, fixtures.issue_182_cmap) + + @unittest.skipIf(sys.hexversion < 0x03000000, "Ordered dicts not printing well in 2.7") def test_exif_uuid(self): From 77d2ab194a6f1d1b7d486c11b55bbff85a190f70 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 12 Mar 2014 19:21:35 -0400 Subject: [PATCH 139/326] Checking for ICC profile that is None. #183 --- glymur/jp2box.py | 15 +++++++++------ glymur/test/fixtures.py | 5 +++++ glymur/test/test_printing.py | 11 +++++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index a106ee4..c309a26 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -359,13 +359,16 @@ class ColourSpecificationBox(Jp2kBox): else: # 2.7 has trouble pretty-printing ordered dicts so we just have # to print as a regular dict in this case. - if sys.hexversion < 0x03000000: - icc_profile = dict(self.icc_profile) + if self.icc_profile is None: + msg += '\n ICC Profile: None' else: - icc_profile = self.icc_profile - dispvalue = pprint.pformat(icc_profile) - lines = [' ' * 8 + y for y in dispvalue.split('\n')] - msg += '\n ICC Profile:\n{0}'.format('\n'.join(lines)) + if sys.hexversion < 0x03000000: + icc_profile = dict(self.icc_profile) + else: + icc_profile = self.icc_profile + dispvalue = pprint.pformat(icc_profile) + lines = [' ' * 8 + y for y in dispvalue.split('\n')] + msg += '\n ICC Profile:\n{0}'.format('\n'.join(lines)) return msg diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 63f5837..b47f211 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -582,3 +582,8 @@ issue_182_cmap = """Component Mapping Box (cmap) @ (130, 24) Component 0 ==> palette column 0 Component 1 ==> palette column 0 Component 2 ==> 2""" + +issue_183_colr = """Colour Specification Box (colr) @ (62, 12) + Method: restricted ICC profile + Precedence: 0 + ICC Profile: None""" diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 3dd58f5..6a819ac 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -920,6 +920,17 @@ class TestPrinting(unittest.TestCase): actual = fake_out.getvalue().strip() self.assertEqual(actual, fixtures.issue_182_cmap) + def test_issue183(self): + filename = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2') + + with warnings.catch_warnings(): + # Ignore warning about bad pclr box. + warnings.simplefilter("ignore") + jp2 = Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(jp2.box[2].box[1]) + actual = fake_out.getvalue().strip() + self.assertEqual(actual, fixtures.issue_183_colr) @unittest.skipIf(sys.hexversion < 0x03000000, "Ordered dicts not printing well in 2.7") From 2ae848b6f0cf3d025c1877851e249cea03118738 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 12 Mar 2014 20:03:04 -0400 Subject: [PATCH 140/326] Checking for division by zero with bad tile dimensions. #181 --- glymur/codestream.py | 20 ++++++++++------- glymur/test/test_codestream.py | 39 +++++++++++++++++++++------------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/glymur/codestream.py b/glymur/codestream.py index 4259788..cf21ec5 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -673,6 +673,18 @@ class Codestream(object): msg = msg.format(j, subsampling[0], subsampling[1]) warnings.warn(msg) + try: + num_tiles_x = (xysiz[0] - xyosiz[0]) / (xytsiz[0] - xytosiz[0]) + num_tiles_y = (xysiz[1] - xyosiz[1]) / (xytsiz[1] - xytosiz[1]) + except ZeroDivisionError as err: + warnings.warn("Invalid tile dimensions.") + else: + numtiles = math.ceil(num_tiles_x) * math.ceil(num_tiles_y) + if numtiles > 65535: + msg = "Invalid number of tiles ({0}).".format(numtiles) + warnings.warn(msg) + + kwargs = {'rsiz': rsiz, 'xysiz': xysiz, 'xyosiz': xyosiz, @@ -1514,14 +1526,6 @@ class SIZsegment(Segment): lst.append(bitdepth - 1) self.ssiz = tuple(lst) - num_tiles_x = (self.xsiz - self.xosiz) / (self.xtsiz - self.xtosiz) - num_tiles_y = (self.ysiz - self.yosiz) / (self.ytsiz - self.ytosiz) - numtiles = math.ceil(num_tiles_x) * math.ceil(num_tiles_y) - if numtiles > 65535: - msg = "Invalid number of tiles ({0}).".format(numtiles) - warnings.warn(msg) - - def __repr__(self): msg = "glymur.codestream.SIZsegment(rsiz={rsiz}, xysiz={xysiz}, " msg += "xyosiz={xyosiz}, xytsiz={xytsiz}, xytosiz={xytosiz}, " diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py index 5edd144..6e5cc43 100644 --- a/glymur/test/test_codestream.py +++ b/glymur/test/test_codestream.py @@ -13,17 +13,12 @@ import struct import sys import tempfile import unittest +import warnings from glymur import Jp2k import glymur -try: - DATA_ROOT = os.environ['OPJ_DATA_ROOT'] -except KeyError: - DATA_ROOT = None -except: - raise - +from .fixtures import opj_data_file, OPJ_DATA_ROOT class TestCodestream(unittest.TestCase): """Test suite for unusual codestream cases.""" @@ -34,7 +29,21 @@ class TestCodestream(unittest.TestCase): def tearDown(self): pass - @unittest.skipIf(DATA_ROOT is None, + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_tile_height_is_zero(self): + """Zero tile height should not cause an exception.""" + filename = opj_data_file('input/nonregression/2539.pdf.SIGFPE.706.1712.jp2') + if sys.hexversion < 0x03000000: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + Jp2k(filename) + else: + with self.assertWarns(UserWarning): + Jp2k(jfile) + + + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_reserved_marker_segment(self): @@ -45,7 +54,7 @@ class TestCodestream(unittest.TestCase): # # 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(OPJ_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. @@ -67,7 +76,7 @@ class TestCodestream(unittest.TestCase): self.assertEqual(codestream.segment[2].length, 3) self.assertEqual(codestream.segment[2].data, b'\x00') - @unittest.skipIf(DATA_ROOT is None, + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(sys.hexversion < 0x03020000, "Uses features introduced in 3.2.") @@ -77,7 +86,7 @@ class TestCodestream(unittest.TestCase): # 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(OPJ_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. @@ -100,11 +109,11 @@ class TestCodestream(unittest.TestCase): self.assertEqual(codestream.segment[2].length, 3) self.assertEqual(codestream.segment[2].data, b'\x00') - @unittest.skipIf(DATA_ROOT is None, + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") def test_psot_is_zero(self): """Psot=0 in SOT is perfectly legal. Issue #78.""" - filename = os.path.join(DATA_ROOT, + filename = os.path.join(OPJ_DATA_ROOT, 'input/nonregression/123.j2c') j = Jp2k(filename) codestream = j.get_codestream(header_only=False) @@ -125,11 +134,11 @@ class TestCodestream(unittest.TestCase): self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7)) - @unittest.skipIf(DATA_ROOT is None, + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") def test_siz_segment_ssiz_signed(self): """ssiz attribute to be removed in future release""" - filename = os.path.join(DATA_ROOT, 'input/conformance/p0_03.j2k') + filename = os.path.join(OPJ_DATA_ROOT, 'input/conformance/p0_03.j2k') j = Jp2k(filename) codestream = j.get_codestream() From 42e1b213561b3952e42321218ae87bc1108cdf4a Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 12 Mar 2014 20:50:42 -0400 Subject: [PATCH 141/326] Should not raise an exception during parsing of XML box. #184 --- glymur/jp2box.py | 15 +++++++++------ glymur/test/test_codestream.py | 2 +- glymur/test/test_jp2box_xml.py | 17 +++++++++++++++++ 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index c309a26..5dba568 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2631,19 +2631,22 @@ class XMLBox(Jp2kBox): read_buffer = fptr.read(num_bytes) try: text = read_buffer.decode('utf-8') - except UnicodeDecodeError as ude: + except UnicodeDecodeError as err: # Possibly bad string of bytes to begin with. # Try to search for -1: - text = read_buffer[decl_start:].decode('utf-8') - else: - raise + if decl_start <= -1: + msg = 'A problem was encountered while parsing an XML box:' + msg += '\n\n\t"{0}"\n\nNo XML was retrieved.' + warnings.warn(msg.format(str(err))) + return XMLBox(xml=None, length=length, offset=offset) + + text = read_buffer[decl_start:].decode('utf-8') # Let the user know that the XML box was problematic. msg = 'A UnicodeDecodeError was encountered parsing an XML box at ' msg += 'byte position {0} ({1}), but the XML was still recovered.' - msg = msg.format(offset, ude.reason) + msg = msg.format(offset, err.reason) warnings.warn(msg, UserWarning) # Strip out any trailing nulls, as they can foul up XML parsing. diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py index 6e5cc43..ce03ef7 100644 --- a/glymur/test/test_codestream.py +++ b/glymur/test/test_codestream.py @@ -40,7 +40,7 @@ class TestCodestream(unittest.TestCase): Jp2k(filename) else: with self.assertWarns(UserWarning): - Jp2k(jfile) + Jp2k(filename) @unittest.skipIf(OPJ_DATA_ROOT is None, diff --git a/glymur/test/test_jp2box_xml.py b/glymur/test/test_jp2box_xml.py index facc4df..9194a49 100644 --- a/glymur/test/test_jp2box_xml.py +++ b/glymur/test/test_jp2box_xml.py @@ -39,6 +39,7 @@ from glymur.jp2box import ColourSpecificationBox, ContiguousCodestreamBox from glymur.jp2box import FileTypeBox, ImageHeaderBox, JP2HeaderBox from glymur.jp2box import JPEG2000SignatureBox +from .fixtures import OPJ_DATA_ROOT, opj_data_file @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") class TestXML(unittest.TestCase): @@ -93,6 +94,22 @@ class TestXML(unittest.TestCase): def tearDown(self): os.unlink(self.xmlfile) + def test_invalid_utf8(self): + """Bad byte sequence that cannot be parsed.""" + filename = opj_data_file(os.path.join('input', + 'nonregression', + '26ccf3651020967f7778238ef5af08af.SIGFPE.d25.527.jp2')) + if sys.hexversion < 0x03000000: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + jp2 = Jp2k(filename) + else: + with self.assertWarns(UserWarning): + jp2 = Jp2k(filename) + + self.assertIsNone(jp2.box[3].box[1].box[1].xml) + + def test_negative_file_and_xml(self): """The XML should come from only one source.""" xml_object = ET.parse(self.xmlfile) From 78351219e965deb8461ee147f080e83b2e346354 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 13 Mar 2014 07:15:07 -0400 Subject: [PATCH 142/326] Added test for reversed components and cdef box. #179 --- glymur/jp2k.py | 2 ++ glymur/test/test_jp2k.py | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 6a10616..1239821 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -695,6 +695,8 @@ class Jp2k(Jp2kBox): (first_row, first_col, last_row, last_col) tile : int, optional Number of tile to decode. + no_cxform : bool + Whether or not to apply intended color transforms. verbose : bool, optional Print informational messages produced by the OpenJPEG library. diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 330f2bf..98173a0 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -95,8 +95,8 @@ class TestJp2k(unittest.TestCase): with self.assertRaises(IOError): Jp2k(filename) - def test_no_cxform(self): - """Indices for jpxfile if no color transform""" + def test_no_cxform_pclr(self): + """Indices for pclr jpxfile if no color transform""" j = Jp2k(self.jpxfile) rgb = j.read() idx = j.read(no_cxform=True) @@ -112,6 +112,21 @@ class TestJp2k(unittest.TestCase): rgb_from_idx[r, c] = palette[idx[r, c]] np.testing.assert_array_equal(rgb, rgb_from_idx) + def test_no_cxform_cmap(self): + """Bands as physically ordered, not as physically intended""" + # This file has the components physically reversed. The cmap box + # tells the decoder how to order them, but this flag prevents that. + filename = opj_data_file('input/conformance/file2.jp2') + j = Jp2k(filename) + ycbcr = j.read() + crcby = j.read(no_cxform=True) + + expected = np.zeros(ycbcr.shape, ycbcr.dtype) + for k in range(crcby.shape[2]): + expected[:,:,crcby.shape[2] - k - 1] = crcby[:,:,k] + + np.testing.assert_array_equal(ycbcr, expected) + 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 From 8f95a67013153c558b1a35825487d2d50b237998 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 13 Mar 2014 12:24:31 -0400 Subject: [PATCH 143/326] Updated test to skip since it needs OPJ_DATA_ROOT. #182 Updated tests for #183 and #184 as well. Refactored tests in test_printing, split into those needing OPJ_DATA_ROOT and those who don't. --- glymur/test/test_jp2box_xml.py | 2 + glymur/test/test_printing.py | 615 ++++++++++++++++----------------- 2 files changed, 300 insertions(+), 317 deletions(-) diff --git a/glymur/test/test_jp2box_xml.py b/glymur/test/test_jp2box_xml.py index 9194a49..8650895 100644 --- a/glymur/test/test_jp2box_xml.py +++ b/glymur/test/test_jp2box_xml.py @@ -94,6 +94,8 @@ class TestXML(unittest.TestCase): def tearDown(self): os.unlink(self.xmlfile) + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") def test_invalid_utf8(self): """Bad byte sequence that cannot be parsed.""" filename = opj_data_file(os.path.join('input', diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 6a819ac..2b8b26c 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -289,69 +289,6 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") - def test_crg(self): - """verify printing of CRG segment""" - filename = opj_data_file('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 @ (87, 6)', - ' Vertical, Horizontal offset: (0.50, 1.00)'] - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") - def test_rgn(self): - """verify printing of RGN segment""" - filename = opj_data_file('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: - print(codestream.segment[12]) - actual = fake_out.getvalue().strip() - lines = ['RGN marker segment @ (310, 5)', - ' Associated component: 0', - ' ROI style: 0', - ' Parameter: 7'] - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") - def test_sop(self): - """verify printing of SOP segment""" - filename = opj_data_file('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: - print(codestream.segment[-2]) - actual = fake_out.getvalue().strip() - lines = ['SOP marker segment @ (12836, 4)', - ' Nsop: 15'] - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") - def test_cme(self): - """Test printing a CME or comment marker segment.""" - filename = opj_data_file('input/conformance/p0_02.j2k') - j = glymur.Jp2k(filename) - codestream = j.get_codestream() - # 2nd to last segment in the main header - with patch('sys.stdout', new=StringIO()) as fake_out: - print(codestream.segment[-2]) - actual = fake_out.getvalue().strip() - lines = ['CME marker segment @ (85, 45)', - ' "Creator: AV-J2K (c) 2000,2001 Algo Vision"'] - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - def test_eoc_segment(self): """verify printing of eoc segment""" j = glymur.Jp2k(self.jp2file) @@ -364,91 +301,6 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") - def test_plt_segment(self): - """verify printing of PLT segment""" - filename = opj_data_file('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: - print(codestream.segment[49935]) - actual = fake_out.getvalue().strip() - - lines = ['PLT marker segment @ (7871146, 38)', - ' Index: 0', - ' Iplt: [9, 122, 19, 30, 27, 9, 41, 62, 18, 29, 261,' - + ' 55, 82, 299, 93, 941, 951, 687, 1729, 1443, 1008, 2168,' - + ' 2188, 2223]'] - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") - def test_pod_segment(self): - """verify printing of POD segment""" - filename = opj_data_file('input/conformance/p0_13.j2k') - j = glymur.Jp2k(filename) - codestream = j.get_codestream() - with patch('sys.stdout', new=StringIO()) as fake_out: - print(codestream.segment[8]) - actual = fake_out.getvalue().strip() - - lines = ['POD marker segment @ (878, 20)', - ' Progression change 0:', - ' Resolution index start: 0', - ' Component index start: 0', - ' Layer index end: 1', - ' Resolution index end: 33', - ' Component index end: 128', - ' Progression order: RLCP', - ' Progression change 1:', - ' Resolution index start: 0', - ' Component index start: 128', - ' Layer index end: 1', - ' Resolution index end: 33', - ' Component index end: 257', - ' Progression order: CPRL'] - - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") - def test_ppm_segment(self): - """verify printing of PPM segment""" - filename = opj_data_file('input/conformance/p1_03.j2k') - j = glymur.Jp2k(filename) - codestream = j.get_codestream() - with patch('sys.stdout', new=StringIO()) as fake_out: - print(codestream.segment[9]) - actual = fake_out.getvalue().strip() - - lines = ['PPM marker segment @ (213, 43712)', - ' Index: 0', - ' Data: 43709 uninterpreted bytes'] - - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") - def test_ppt_segment(self): - """verify printing of ppt segment""" - filename = opj_data_file('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: - print(codestream.segment[6]) - actual = fake_out.getvalue().strip() - - lines = ['PPT marker segment @ (155, 109)', - ' Index: 0', - ' Packet headers: 106 uninterpreted bytes'] - - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - def test_qcc_segment(self): """verify printing of qcc segment""" j = glymur.Jp2k(self.jp2file) @@ -543,25 +395,6 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") - def test_tlm_segment(self): - """verify printing of TLM segment""" - filename = opj_data_file('input/conformance/p0_15.j2k') - j = glymur.Jp2k(filename) - codestream = j.get_codestream() - with patch('sys.stdout', new=StringIO()) as fake_out: - print(codestream.segment[10]) - actual = fake_out.getvalue().strip() - - lines = ['TLM marker segment @ (268, 28)', - ' Index: 0', - ' Tile number: (0, 1, 2, 3)', - ' Length: (4267, 2117, 4080, 2081)'] - - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - def test_xmp(self): """Verify the printing of a UUID/XMP box.""" j = glymur.Jp2k(self.jp2file) @@ -621,17 +454,6 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lst) self.assertEqual(actual, expected) - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") - def test_xml(self): - """verify printing of XML box""" - filename = opj_data_file('input/conformance/file1.jp2') - j = glymur.Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(j.box[2]) - actual = fake_out.getvalue().strip() - self.assertEqual(actual, fixtures.file1_xml) - @unittest.skipIf(sys.hexversion < 0x03000000, "Only trusting python3 for printing non-ascii chars") def test_xml_latin1(self): @@ -689,101 +511,6 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") - def test_channel_definition(self): - """verify printing of cdef box""" - filename = opj_data_file('input/conformance/file2.jp2') - j = glymur.Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(j.box[2].box[2]) - actual = fake_out.getvalue().strip() - lines = ['Channel Definition Box (cdef) @ (81, 28)', - ' Channel 0 (color) ==> (3)', - ' Channel 1 (color) ==> (2)', - ' Channel 2 (color) ==> (1)'] - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") - def test_component_mapping(self): - """verify printing of cmap box""" - filename = opj_data_file('input/conformance/file9.jp2') - j = glymur.Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(j.box[2].box[2]) - actual = fake_out.getvalue().strip() - lines = ['Component Mapping Box (cmap) @ (848, 20)', - ' Component 0 ==> palette column 0', - ' Component 0 ==> palette column 1', - ' Component 0 ==> palette column 2'] - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") - def test_palette7(self): - """verify printing of pclr box""" - filename = opj_data_file('input/conformance/file9.jp2') - j = glymur.Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(j.box[2].box[1]) - actual = fake_out.getvalue().strip() - lines = ['Palette Box (pclr) @ (66, 782)', - ' Size: (256 x 3)'] - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - - @unittest.skip("file7 no longer has a rreq") - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") - def test_rreq(self): - """verify printing of reader requirements box""" - filename = opj_data_file('input/nonregression/text_GBR.jp2') - j = glymur.Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(j.box[2]) - actual = fake_out.getvalue().strip() - self.assertEqual(actual, fixtures.text_GBR_rreq) - - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") - def test_differing_subsamples(self): - """verify printing of SIZ with different subsampling... Issue 86.""" - filename = opj_data_file('input/conformance/p0_05.j2k') - j = glymur.Jp2k(filename) - codestream = j.get_codestream() - with patch('sys.stdout', new=StringIO()) as fake_out: - print(codestream.segment[1]) - actual = fake_out.getvalue().strip() - lines = ['SIZ marker segment @ (2, 50)', - ' Profile: 0', - ' Reference Grid Height, Width: (1024 x 1024)', - ' Vertical, Horizontal Reference Grid Offset: (0 x 0)', - ' Reference Tile Height, Width: (1024 x 1024)', - ' Vertical, Horizontal Reference Tile Offset: (0 x 0)', - ' Bitdepth: (8, 8, 8, 8)', - ' Signed: (False, False, False, False)', - ' Vertical, Horizontal Subsampling: ' - + '((1, 1), (1, 1), (2, 2), (2, 2))'] - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - - @unittest.skipIf(OPJ_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 = opj_data_file('input/conformance/file9.jp2') - j = glymur.Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(j.box[2].box[1]) - actual = fake_out.getvalue().strip() - lines = ['Palette Box (pclr) @ (66, 782)', - ' Size: (256 x 3)'] - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - @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""" @@ -861,8 +588,304 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") + @unittest.skipIf(sys.hexversion < 0x03000000, + "Ordered dicts not printing well in 2.7") + def test_exif_uuid(self): + """Verify printing of exif information""" + with tempfile.NamedTemporaryFile(suffix='.jp2', mode='wb') as tfile: + + with open(self.jp2file, 'rb') as ifptr: + tfile.write(ifptr.read()) + + # Write L, T, UUID identifier. + tfile.write(struct.pack('>I4s', 76, b'uuid')) + tfile.write(b'JpgTiffExif->JP2') + + tfile.write(b'Exif\x00\x00') + xbuffer = struct.pack(' (3)', + ' Channel 1 (color) ==> (2)', + ' Channel 2 (color) ==> (1)'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + def test_component_mapping(self): + """verify printing of cmap box""" + filename = opj_data_file('input/conformance/file9.jp2') + j = glymur.Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(j.box[2].box[2]) + actual = fake_out.getvalue().strip() + lines = ['Component Mapping Box (cmap) @ (848, 20)', + ' Component 0 ==> palette column 0', + ' Component 0 ==> palette column 1', + ' Component 0 ==> palette column 2'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + def test_palette7(self): + """verify printing of pclr box""" + filename = opj_data_file('input/conformance/file9.jp2') + j = glymur.Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(j.box[2].box[1]) + actual = fake_out.getvalue().strip() + lines = ['Palette Box (pclr) @ (66, 782)', + ' Size: (256 x 3)'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skip("file7 no longer has a rreq") + def test_rreq(self): + """verify printing of reader requirements box""" + filename = opj_data_file('input/nonregression/text_GBR.jp2') + j = glymur.Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(j.box[2]) + actual = fake_out.getvalue().strip() + self.assertEqual(actual, fixtures.text_GBR_rreq) + + def test_differing_subsamples(self): + """verify printing of SIZ with different subsampling... Issue 86.""" + filename = opj_data_file('input/conformance/p0_05.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream() + with patch('sys.stdout', new=StringIO()) as fake_out: + print(codestream.segment[1]) + actual = fake_out.getvalue().strip() + lines = ['SIZ marker segment @ (2, 50)', + ' Profile: 0', + ' Reference Grid Height, Width: (1024 x 1024)', + ' Vertical, Horizontal Reference Grid Offset: (0 x 0)', + ' Reference Tile Height, Width: (1024 x 1024)', + ' Vertical, Horizontal Reference Tile Offset: (0 x 0)', + ' Bitdepth: (8, 8, 8, 8)', + ' Signed: (False, False, False, False)', + ' Vertical, Horizontal Subsampling: ' + + '((1, 1), (1, 1), (2, 2), (2, 2))'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + def test_palette_box(self): + """Verify that palette (pclr) boxes are printed without error.""" + filename = opj_data_file('input/conformance/file9.jp2') + j = glymur.Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(j.box[2].box[1]) + actual = fake_out.getvalue().strip() + lines = ['Palette Box (pclr) @ (66, 782)', + ' Size: (256 x 3)'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + def test_icc_profile(self): """verify icc profile printing with a jpx""" # ICC profiles may be used in JP2, but the approximation field should @@ -885,8 +908,6 @@ class TestPrinting(unittest.TestCase): self.assertEqual(actual, expected) - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") def test_uuid(self): """verify printing of UUID box""" filename = opj_data_file('input/nonregression/text_GBR.jp2') @@ -932,46 +953,6 @@ class TestPrinting(unittest.TestCase): actual = fake_out.getvalue().strip() self.assertEqual(actual, fixtures.issue_183_colr) - @unittest.skipIf(sys.hexversion < 0x03000000, - "Ordered dicts not printing well in 2.7") - def test_exif_uuid(self): - """Verify printing of exif information""" - with tempfile.NamedTemporaryFile(suffix='.jp2', mode='wb') as tfile: - - with open(self.jp2file, 'rb') as ifptr: - tfile.write(ifptr.read()) - - # Write L, T, UUID identifier. - tfile.write(struct.pack('>I4s', 76, b'uuid')) - tfile.write(b'JpgTiffExif->JP2') - - tfile.write(b'Exif\x00\x00') - xbuffer = struct.pack(' Date: Thu, 13 Mar 2014 14:34:16 -0400 Subject: [PATCH 144/326] Don't error out when progression order is invalid. #186 Instead of a regular dictionary for the display of progression order, use a subclass of defaultdict instead. The defaultdict's __missing__ method is overridden to supply a custom error message that used the offending key. Everybody wins!!! --- glymur/codestream.py | 30 +++++++++++++++---- glymur/test/fixtures.py | 23 +++++++++++++++ glymur/test/test_codestream.py | 54 +++++++++++++++++++++------------- glymur/test/test_printing.py | 12 ++++++++ 4 files changed, 92 insertions(+), 27 deletions(-) diff --git a/glymur/codestream.py b/glymur/codestream.py index cf21ec5..35e3fee 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -17,6 +17,7 @@ codestreams. # the base Segment class. # pylint: disable=R0903 +import collections import math import struct import sys @@ -30,12 +31,26 @@ from .core import WAVELET_XFORM_5X3_REVERSIBLE from .core import _CAPABILITIES_DISPLAY from .lib import openjp2 as opj2 -_PROGRESSION_ORDER_DISPLAY = { - LRCP: 'LRCP', - RLCP: 'RLCP', - RPCL: 'RPCL', - PCRL: 'PCRL', - CPRL: 'CPRL'} +class _keydefaultdict(collections.defaultdict): + """Unlisted keys help form their own error message. + + Normally defaultdict uses a factory function with no input arguments, but + that's not quite the behavior we want. + """ + def __missing__(self, key): + if self.default_factory is None: + raise KeyError(key) + else: + ret = self[key] = self.default_factory(key) + return ret + +_factory = lambda x: '{0} (invalid)'.format(x) +_PROGRESSION_ORDER_DISPLAY = _keydefaultdict(_factory, + { LRCP: 'LRCP', + RLCP: 'RLCP', + RPCL: 'RPCL', + PCRL: 'PCRL', + CPRL: 'CPRL'}) _WAVELET_TRANSFORM_DISPLAY = { WAVELET_XFORM_9X7_IRREVERSIBLE: '9-7 irreversible', @@ -371,6 +386,9 @@ class Codestream(object): numbytes = offset + 2 + length - fptr.tell() spcod = fptr.read(numbytes) spcod = np.frombuffer(spcod, dtype=np.uint8) + if spcod[0] not in [LRCP, RLCP, RPCL, PCRL, CPRL]: + msg = "Invalid progression order in COD segment: {0}." + warnings.warn(msg.format(spcod[0])) sop = (scod & 2) > 0 eph = (scod & 4) > 0 diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index b47f211..8e8deb8 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -587,3 +587,26 @@ issue_183_colr = """Colour Specification Box (colr) @ (62, 12) Method: restricted ICC profile Precedence: 0 ICC Profile: None""" + + +# Progression order is invalid. +issue_186_progression_order = """COD marker segment @ (174, 12) + Coding style: + Entropy coder, without partitions + SOP marker segments: False + EPH marker segments: False + Coding style parameters: + Progression order: 33 (invalid) + Number of layers: 1 + Multiple component transformation usage: reversible + Number of resolutions: 6 + Code block height, width: (32 x 32) + Wavelet transform: 9-7 irreversible + Precinct size: default, 2^15 x 2^15 + Code block context: + Selective arithmetic coding bypass: False + Reset context probabilities on coding pass boundaries: False + Termination on each coding pass: False + Vertically stripe causal context: False + Predictable termination: False + Segmentation symbols: False""" diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py index ce03ef7..28ea0f8 100644 --- a/glymur/test/test_codestream.py +++ b/glymur/test/test_codestream.py @@ -29,8 +29,39 @@ class TestCodestream(unittest.TestCase): def tearDown(self): pass - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") + def test_siz_segment_ssiz_unsigned(self): + """ssiz attribute to be removed in future release""" + j = Jp2k(self.jp2file) + codestream = j.get_codestream() + + # The ssiz attribute was simply a tuple of raw bytes. + # The first 7 bits are interpreted as the bitdepth, the MSB determines + # whether or not it is signed. + self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7)) + + +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class TestCodestreamOpjData(unittest.TestCase): + """Test suite for unusual codestream cases. Uses OPJ_DATA_ROOT""" + + def setUp(self): + self.jp2file = glymur.data.nemo() + + def tearDown(self): + pass + + def test_invalid_progression_order(self): + """Should still be able to parse even if prog order is invalid.""" + jfile = opj_data_file('input/nonregression/2977.pdf.asan.67.2198.jp2') + if sys.hexversion < 0x03000000: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + Jp2k(jfile) + else: + with self.assertWarns(UserWarning): + Jp2k(jfile) + def test_tile_height_is_zero(self): """Zero tile height should not cause an exception.""" filename = opj_data_file('input/nonregression/2539.pdf.SIGFPE.706.1712.jp2') @@ -43,8 +74,6 @@ class TestCodestream(unittest.TestCase): Jp2k(filename) - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_reserved_marker_segment(self): """Reserved marker segments are ok.""" @@ -76,8 +105,6 @@ class TestCodestream(unittest.TestCase): self.assertEqual(codestream.segment[2].length, 3) self.assertEqual(codestream.segment[2].data, b'\x00') - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(sys.hexversion < 0x03020000, "Uses features introduced in 3.2.") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @@ -109,8 +136,6 @@ class TestCodestream(unittest.TestCase): self.assertEqual(codestream.segment[2].length, 3) self.assertEqual(codestream.segment[2].data, b'\x00') - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") def test_psot_is_zero(self): """Psot=0 in SOT is perfectly legal. Issue #78.""" filename = os.path.join(OPJ_DATA_ROOT, @@ -123,19 +148,6 @@ class TestCodestream(unittest.TestCase): self.assertEqual(codestream.segment[-1].marker_id, 'EOC') - def test_siz_segment_ssiz_unsigned(self): - """ssiz attribute to be removed in future release""" - j = Jp2k(self.jp2file) - codestream = j.get_codestream() - - # The ssiz attribute was simply a tuple of raw bytes. - # The first 7 bits are interpreted as the bitdepth, the MSB determines - # whether or not it is signed. - self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7)) - - - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") def test_siz_segment_ssiz_signed(self): """ssiz attribute to be removed in future release""" filename = os.path.join(OPJ_DATA_ROOT, 'input/conformance/p0_03.j2k') diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 2b8b26c..554e6de 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -645,6 +645,18 @@ class TestPrintingOpjDataRoot(unittest.TestCase): def tearDown(self): pass + def test_invalid_progression_order(self): + """Should still be able to print even if prog order is invalid.""" + jfile = opj_data_file('input/nonregression/2977.pdf.asan.67.2198.jp2') + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) + codestream = jp2.get_codestream() + with patch('sys.stdout', new=StringIO()) as fake_out: + print(codestream.segment[2]) + actual = fake_out.getvalue().strip() + self.assertEqual(actual, fixtures.issue_186_progression_order) + def test_crg(self): """verify printing of CRG segment""" filename = opj_data_file('input/conformance/p0_03.j2k') From d76305683fba105533f6a4b86e558b042ef220d1 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 13 Mar 2014 18:46:29 -0400 Subject: [PATCH 145/326] Added jp2 and jpx cxform tests. jpx fails on i386 linux. --- glymur/test/test_jp2k.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 98173a0..3046422 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -95,7 +95,27 @@ class TestJp2k(unittest.TestCase): with self.assertRaises(IOError): Jp2k(filename) - def test_no_cxform_pclr(self): + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_no_cxform_pclr_jp2(self): + """Indices for pclr jpxfile if no color transform""" + filename = opj_data_file('input/conformance/file9.jp2') + j = Jp2k(filename) + rgb = j.read() + idx = j.read(no_cxform=True) + self.assertEqual(rgb.shape, (512, 768, 3)) + self.assertEqual(idx.shape, (512, 768)) + + # Should be able to manually reconstruct the RGB image from the palette + # and indices. + palette = j.box[2].box[1].palette + rgb_from_idx = np.zeros(rgb.shape, dtype=np.uint8) + for r in np.arange(rgb.shape[0]): + for c in np.arange(rgb.shape[1]): + rgb_from_idx[r, c] = palette[idx[r, c]] + np.testing.assert_array_equal(rgb, rgb_from_idx) + + def test_no_cxform_pclr_jpx(self): """Indices for pclr jpxfile if no color transform""" j = Jp2k(self.jpxfile) rgb = j.read() From d82c9b465e8d2027f1531e111c332390705c7237 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 13 Mar 2014 19:41:05 -0400 Subject: [PATCH 146/326] Less than 8 bytes at end of file indicates corruption. #187 --- glymur/jp2box.py | 8 +++- glymur/test/test_jp2k.py | 84 ++++++++++++++++++++++------------------ 2 files changed, 54 insertions(+), 38 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 5dba568..971818f 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -220,7 +220,13 @@ class Jp2kBox(object): break read_buffer = fptr.read(8) - (box_length, box_id) = struct.unpack('>I4s', read_buffer) + try: + (box_length, box_id) = struct.unpack('>I4s', read_buffer) + except Exception as err: + msg = "Extra bytes at end of file ignored." + warnings.warn(msg) + return superbox + if sys.hexversion >= 0x03000000: box_id = box_id.decode('utf-8') diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 3046422..16a1b80 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -37,7 +37,6 @@ if HAS_PYTHON_XMP_TOOLKIT: from .fixtures import OPJ_DATA_ROOT, opj_data_file from . import fixtures - # Doc tests should be run as well. def load_tests(loader, tests, ignore): # W0613: "loader" and "ignore" are necessary for the protocol @@ -54,9 +53,7 @@ def load_tests(loader, tests, ignore): class TestJp2k(unittest.TestCase): - """Test suite for openjpeg software starting at 1.3""" - - # These tests should be run by just about all configuration. + """These tests should be run by just about all configuration.""" def setUp(self): self.jp2file = glymur.data.nemo() @@ -95,26 +92,6 @@ class TestJp2k(unittest.TestCase): with self.assertRaises(IOError): Jp2k(filename) - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") - def test_no_cxform_pclr_jp2(self): - """Indices for pclr jpxfile if no color transform""" - filename = opj_data_file('input/conformance/file9.jp2') - j = Jp2k(filename) - rgb = j.read() - idx = j.read(no_cxform=True) - self.assertEqual(rgb.shape, (512, 768, 3)) - self.assertEqual(idx.shape, (512, 768)) - - # Should be able to manually reconstruct the RGB image from the palette - # and indices. - palette = j.box[2].box[1].palette - rgb_from_idx = np.zeros(rgb.shape, dtype=np.uint8) - for r in np.arange(rgb.shape[0]): - for c in np.arange(rgb.shape[1]): - rgb_from_idx[r, c] = palette[idx[r, c]] - np.testing.assert_array_equal(rgb, rgb_from_idx) - def test_no_cxform_pclr_jpx(self): """Indices for pclr jpxfile if no color transform""" j = Jp2k(self.jpxfile) @@ -300,19 +277,6 @@ class TestJp2k(unittest.TestCase): j2k = Jp2k(self.j2kfile) j2k.read() - @unittest.skipIf(OPJ_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 = opj_data_file('input/conformance/p0_05.j2k') - j = Jp2k(filename) - with self.assertRaises(RuntimeError): - j.read() - def test_empty_box_with_j2k(self): """Verify that the list of boxes in a J2C/J2K file is present, but empty. @@ -804,6 +768,52 @@ class TestJp2k_2_1(unittest.TestCase): with self.assertRaisesRegex((IOError, OSError), regexp): j.read(rlevel=1) +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class TestJp2kOpjDataRoot(unittest.TestCase): + """These tests should be run by just about all configuration.""" + + def test_no_cxform_pclr_jp2(self): + """Indices for pclr jpxfile if no color transform""" + filename = opj_data_file('input/conformance/file9.jp2') + j = Jp2k(filename) + rgb = j.read() + idx = j.read(no_cxform=True) + self.assertEqual(rgb.shape, (512, 768, 3)) + self.assertEqual(idx.shape, (512, 768)) + + # Should be able to manually reconstruct the RGB image from the palette + # and indices. + palette = j.box[2].box[1].palette + rgb_from_idx = np.zeros(rgb.shape, dtype=np.uint8) + for r in np.arange(rgb.shape[0]): + for c in np.arange(rgb.shape[1]): + rgb_from_idx[r, c] = palette[idx[r, c]] + np.testing.assert_array_equal(rgb, rgb_from_idx) + + def test_stupid_windows_eol_at_end(self): + """Garbage characters at the end of the file.""" + filename = opj_data_file('input/nonregression/issue211.jp2') + if sys.hexversion < 0x03000000: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + jp2 = Jp2k(filename) + else: + with self.assertWarns(UserWarning): + jp2 = Jp2k(filename) + + 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 = opj_data_file('input/conformance/p0_05.j2k') + j = Jp2k(filename) + with self.assertRaises(RuntimeError): + j.read() + + if __name__ == "__main__": unittest.main() From 58d3d9dc047d645258dc8f6d31dc7458001a324d Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 13 Mar 2014 21:17:53 -0400 Subject: [PATCH 147/326] Remove any BOM and encoding declaration from xml text. #185 --- glymur/jp2box.py | 12 +++++++- glymur/test/test_jp2box_xml.py | 53 ++++++++++++++++++++++------------ 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 971818f..578ef36 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2656,10 +2656,20 @@ class XMLBox(Jp2kBox): warnings.warn(msg, UserWarning) # Strip out any trailing nulls, as they can foul up XML parsing. + # Remove any byte order markers. text = text.rstrip(chr(0)) + if u'\ufeff' in text: + msg = 'An illegal BOM (byte order marker) was detected and ' + msg += 'removed from the XML contents in the box starting at byte ' + msg += 'offset {0}'.format(offset) + warnings.warn(msg) + text = text.replace(u'\ufeff', '') + # Remove any encoding declaration. + if text.startswith(''): + text = text[38:] try: - elt = ET.fromstring(text.encode('utf-8')) + elt = ET.fromstring(text) xml = ET.ElementTree(elt) except ET.ParseError as err: msg = 'A problem was encountered while parsing an XML box:' diff --git a/glymur/test/test_jp2box_xml.py b/glymur/test/test_jp2box_xml.py index 8650895..ff589f9 100644 --- a/glymur/test/test_jp2box_xml.py +++ b/glymur/test/test_jp2box_xml.py @@ -94,24 +94,6 @@ class TestXML(unittest.TestCase): def tearDown(self): os.unlink(self.xmlfile) - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") - def test_invalid_utf8(self): - """Bad byte sequence that cannot be parsed.""" - filename = opj_data_file(os.path.join('input', - 'nonregression', - '26ccf3651020967f7778238ef5af08af.SIGFPE.d25.527.jp2')) - if sys.hexversion < 0x03000000: - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - jp2 = Jp2k(filename) - else: - with self.assertWarns(UserWarning): - jp2 = Jp2k(filename) - - self.assertIsNone(jp2.box[3].box[1].box[1].xml) - - def test_negative_file_and_xml(self): """The XML should come from only one source.""" xml_object = ET.parse(self.xmlfile) @@ -305,3 +287,38 @@ class TestBadButRecoverableXmlFile(unittest.TestCase): b'this is a test') +class TestXML_OpjDataRoot(unittest.TestCase): + """Test suite for XML boxes, requires OPJ_DATA_ROOT.""" + + def test_bom(self): + """Byte order markers are illegal in UTF-8. Issue 185""" + filename = opj_data_file(os.path.join('input', + 'nonregression', + 'issue171.jp2')) + if sys.hexversion < 0x03000000: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + jp2 = Jp2k(filename) + else: + with self.assertWarns(UserWarning): + jp2 = Jp2k(filename) + self.assertIsNotNone(jp2.box[3].xml) + + + def test_invalid_utf8(self): + """Bad byte sequence that cannot be parsed.""" + filename = opj_data_file(os.path.join('input', + 'nonregression', + '26ccf3651020967f7778238ef5af08af.SIGFPE.d25.527.jp2')) + if sys.hexversion < 0x03000000: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + jp2 = Jp2k(filename) + else: + with self.assertWarns(UserWarning): + jp2 = Jp2k(filename) + + self.assertIsNone(jp2.box[3].box[1].box[1].xml) + + + From 86ac1c4ae5cc48ae6cb4142dfa2e804e686b89da Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 13 Mar 2014 21:39:24 -0400 Subject: [PATCH 148/326] Fixed test skipping when no OPJ_DATA_ROOT present. #185 --- glymur/test/test_jp2box_xml.py | 2 ++ glymur/test/test_jp2k.py | 30 +++++++++++++++--------------- glymur/test/test_printing.py | 13 +++++++++++++ 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/glymur/test/test_jp2box_xml.py b/glymur/test/test_jp2box_xml.py index ff589f9..2531f0d 100644 --- a/glymur/test/test_jp2box_xml.py +++ b/glymur/test/test_jp2box_xml.py @@ -287,6 +287,8 @@ class TestBadButRecoverableXmlFile(unittest.TestCase): b'this is a test') +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") class TestXML_OpjDataRoot(unittest.TestCase): """Test suite for XML boxes, requires OPJ_DATA_ROOT.""" diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 16a1b80..f6eb691 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -109,21 +109,6 @@ class TestJp2k(unittest.TestCase): rgb_from_idx[r, c] = palette[idx[r, c]] np.testing.assert_array_equal(rgb, rgb_from_idx) - def test_no_cxform_cmap(self): - """Bands as physically ordered, not as physically intended""" - # This file has the components physically reversed. The cmap box - # tells the decoder how to order them, but this flag prevents that. - filename = opj_data_file('input/conformance/file2.jp2') - j = Jp2k(filename) - ycbcr = j.read() - crcby = j.read(no_cxform=True) - - expected = np.zeros(ycbcr.shape, ycbcr.dtype) - for k in range(crcby.shape[2]): - expected[:,:,crcby.shape[2] - k - 1] = crcby[:,:,k] - - np.testing.assert_array_equal(ycbcr, expected) - 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 @@ -813,6 +798,21 @@ class TestJp2kOpjDataRoot(unittest.TestCase): with self.assertRaises(RuntimeError): j.read() + def test_no_cxform_cmap(self): + """Bands as physically ordered, not as physically intended""" + # This file has the components physically reversed. The cmap box + # tells the decoder how to order them, but this flag prevents that. + filename = opj_data_file('input/conformance/file2.jp2') + j = Jp2k(filename) + ycbcr = j.read() + crcby = j.read(no_cxform=True) + + expected = np.zeros(ycbcr.shape, ycbcr.dtype) + for k in range(crcby.shape[2]): + expected[:,:,crcby.shape[2] - k - 1] = crcby[:,:,k] + + np.testing.assert_array_equal(ycbcr, expected) + if __name__ == "__main__": diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 554e6de..fe09583 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -965,6 +965,19 @@ class TestPrintingOpjDataRoot(unittest.TestCase): actual = fake_out.getvalue().strip() self.assertEqual(actual, fixtures.issue_183_colr) + def test_bom(self): + """Byte order markers are illegal in UTF-8. Issue 185""" + filename = opj_data_file(os.path.join('input', + 'nonregression', + 'issue171.jp2')) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + jp2 = Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + # No need to verify, it's enough that we don't error out. + print(jp2) + + self.assertTrue(True) if __name__ == "__main__": unittest.main() From b614f4818f6c0c4a100ce2b0032ae67e9583e77d Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 14 Mar 2014 10:39:51 -0400 Subject: [PATCH 149/326] Added proper unittest.skipIf when skimage.io plugin freeimage not found. #190 --- glymur/test/test_opj_suite_write.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 1db11c4..8a757aa 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -223,6 +223,8 @@ class TestSuiteWriteCinema(unittest.TestCase): self.check_cinema2k_codestream(codestream, (1998, 1080)) +@unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, + "Cannot read input image without scikit-image/freeimage") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @unittest.skipIf(re.match(r"""2\.0""", glymur.version.openjpeg_version), "Functionality implemented for 2.1") From 993e669cc4f53ca6eead09d7e8967358b2d2c721 Mon Sep 17 00:00:00 2001 From: John Evans Date: Sat, 15 Mar 2014 11:24:15 -0400 Subject: [PATCH 150/326] Fixed test now that openjpeg version has rolled to 2.1.0 --- glymur/test/test_opj_suite_write.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 8a757aa..91afd8d 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -226,7 +226,7 @@ class TestSuiteWriteCinema(unittest.TestCase): @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, "Cannot read input image without scikit-image/freeimage") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") -@unittest.skipIf(re.match(r"""2\.0""", glymur.version.openjpeg_version), +@unittest.skipIf(not re.match("(1.5|2.0)", glymur.version.openjpeg_version), "Functionality implemented for 2.1") @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_OPJ_DATA_ROOT environment variable not set") From 093f9c49fb46dd76e81e68683311576f23c6c376 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 15 Mar 2014 22:09:49 -0400 Subject: [PATCH 151/326] Got 2.0.0 working again. #139 --- glymur/jp2k.py | 3 ++- glymur/lib/openjp2.py | 22 +++++++++++++++++----- glymur/test/test_opj_suite.py | 4 +++- glymur/test/test_opj_suite_write.py | 1 - 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 1239821..5d39ed5 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -20,6 +20,7 @@ from collections import Counter import ctypes import math import os +import re import struct from uuid import UUID import warnings @@ -166,7 +167,7 @@ class Jp2k(Jp2kBox): fps : int Frames per second, should be either 24 or 48. """ - if version.openjpeg_version_tuple[0] == 1: + if re.match("(1.5|2.0)", version.openjpeg_version) is not None: msg = "Writing Cinema2K or Cinema4K files is not supported with " msg += 'openjpeg library versions less than 2.0.1.' raise IOError(msg) diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py index 39dd5a4..2da8d75 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -10,6 +10,20 @@ import sys from .config import glymur_config OPENJP2, OPENJPEG = glymur_config() +def version(): + """Wrapper for opj_version library routine.""" + OPENJP2.opj_version.restype = ctypes.c_char_p + library_version = OPENJP2.opj_version() + if sys.hexversion >= 0x03000000: + return library_version.decode('utf-8') + else: + return library_version + +if OPENJP2 is not None: + _MAJOR, _MINOR, _PATCH = version().split('.') +else: + _MINOR = 0 + ERROR_MSG_LST = [] # Map certain atomic OpenJPEG datatypes to the ctypes equivalents. @@ -391,12 +405,10 @@ class ImageCompType(ctypes.Structure): ("factor", ctypes.c_uint32), # image component data - ("data", ctypes.POINTER(ctypes.c_int32)), - - # alpha channel - # TODO: exclude for 2.0, 1.5 - ("alpha", ctypes.c_uint16)] + ("data", ctypes.POINTER(ctypes.c_int32))] + if _MINOR == '1': + _fields_.append(("alpha", ctypes.c_uint16)) class ImageType(ctypes.Structure): """Defines image data and characteristics. diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 5cc9250..28e9a0f 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -461,7 +461,9 @@ class TestSuite(unittest.TestCase): def test_NR_DEC_illegalcolortransform_j2k_14_decode(self): # Stream too short, expected SOT. jfile = opj_data_file('input/nonregression/illegalcolortransform.j2k') - Jp2k(jfile).read() + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + Jp2k(jfile).read() self.assertTrue(True) def test_NR_DEC_j2k32_j2k_15_decode(self): diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 91afd8d..c92c500 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -222,7 +222,6 @@ class TestSuiteWriteCinema(unittest.TestCase): codestream = j.get_codestream() self.check_cinema2k_codestream(codestream, (1998, 1080)) - @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, "Cannot read input image without scikit-image/freeimage") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") From dec37eec4900a6ee232ea97e3ef92ee333b52a38 Mon Sep 17 00:00:00 2001 From: bogdanni Date: Mon, 17 Mar 2014 14:42:33 +0100 Subject: [PATCH 152/326] 'nlst' - compositing layer association display fix --- glymur/jp2box.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 578ef36..6c7526f 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2502,7 +2502,7 @@ class NumberListBox(Jp2kBox): elif (association >> 24) == 2: idx = association & 0x00FFFFFF msg += 'Compositing Layer {0}' - msg = msg.format(j, idx) + msg = msg.format(idx) else: msg += 'unrecognized' return msg From 74a960ed84dec7474b744d2787ea1c546a90a2fe Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 17 Mar 2014 19:57:15 -0400 Subject: [PATCH 153/326] Added printing of lxml version. #192 --- glymur/test/test_printing.py | 8 ++++++++ glymur/version.py | 6 +++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index fe09583..1e38c55 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -50,6 +50,14 @@ class TestPrinting(unittest.TestCase): def tearDown(self): pass + def test_version_info(self): + """Should be able to print(glymur.version.info)""" + with patch('sys.stdout', new=StringIO()) as fake_out: + print(glymur.version.info) + actual = fake_out.getvalue().strip() + + self.assertTrue(True) + @unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.") def test_unknown_superbox(self): """Verify that we can handle an unknown superbox.""" diff --git a/glymur/version.py b/glymur/version.py index 4bce9dc..409db9a 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -9,9 +9,11 @@ License: MIT """ import sys -import numpy as np from distutils.version import LooseVersion +import lxml.etree +import numpy as np + from .lib import openjpeg as opj from .lib import openjp2 as opj2 @@ -48,10 +50,12 @@ OPENJPEG {openjpeg} Python {python} sys.platform {platform} sys.maxsize {maxsize} +lxml {elxml} numpy {numpy} """.format(glymur=version, openjpeg=openjpeg_version, python=sys.version, platform=sys.platform, maxsize=sys.maxsize, + elxml=lxml.etree.__version__, numpy=np.__version__) From 844540df92d5430d3d2151d27471cad6cc767006 Mon Sep 17 00:00:00 2001 From: bogdanni Date: Tue, 18 Mar 2014 15:22:40 +0100 Subject: [PATCH 154/326] 'cmap' - correct stride interpreting CMP,MTYP,PCOL Fix for number of components different from 3 --- glymur/jp2box.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 6c7526f..9796f58 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -879,9 +879,9 @@ class ComponentMappingBox(Jp2kBox): read_buffer = fptr.read(num_bytes) data = struct.unpack('>' + 'HBB' * num_components, read_buffer) - component_index = data[0:num_bytes:num_components] - mapping_type = data[1:num_bytes:num_components] - palette_index = data[2:num_bytes:num_components] + component_index = data[0:num_bytes:3] + mapping_type = data[1:num_bytes:3] + palette_index = data[2:num_bytes:3] box = ComponentMappingBox(component_index, mapping_type, palette_index, length=length, offset=offset) From 1be7c4ec50eba5caf32ec52df3198ce9e7298914 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 18 Mar 2014 11:00:35 -0400 Subject: [PATCH 155/326] Changing name to "ignore_pclr_cmap_cdef". #179 --- glymur/jp2k.py | 37 ++++++++++++++++------------- glymur/test/test_jp2k.py | 4 ++-- glymur/test/test_opj_suite_write.py | 2 +- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 1239821..f76163f 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -695,8 +695,9 @@ class Jp2k(Jp2kBox): (first_row, first_col, last_row, last_col) tile : int, optional Number of tile to decode. - no_cxform : bool - Whether or not to apply intended color transforms. + ignore_pclr_cmap_cdef : bool + Whether or not to ignore the pclr, cmap, or cdef boxes during any + color transformation. Defaults to False. verbose : bool, optional Print informational messages produced by the OpenJPEG library. @@ -751,7 +752,7 @@ class Jp2k(Jp2kBox): msg += "the read_bands method instead." raise RuntimeError(msg) - def _read_openjpeg(self, rlevel=0, no_cxform=False, verbose=False): + def _read_openjpeg(self, rlevel=0, ignore_pclr_cmap_cdef=False, verbose=False): """Read a JPEG 2000 image using libopenjpeg. Parameters @@ -759,8 +760,9 @@ class Jp2k(Jp2kBox): rlevel : int, optional Factor by which to rlevel output resolution. Use -1 to get the lowest resolution thumbnail. - no_cxform : bool - Whether or not to apply intended color transforms. + ignore_pclr_cmap_cdef : bool + Whether or not to ignore the pclr, cmap, or cdef boxes during any + color transformation. Defaults to False. verbose : bool, optional Print informational messages produced by the OpenJPEG library. @@ -796,7 +798,7 @@ class Jp2k(Jp2kBox): dparameters = opj.DecompressionParametersType() opj.set_default_decoder_parameters(ctypes.byref(dparameters)) - if no_cxform is True: + if ignore_pclr_cmap_cdef is True: # Return raw codestream components. dparameters.flags |= 1 @@ -844,7 +846,7 @@ class Jp2k(Jp2kBox): return data def _read_openjp2(self, rlevel=0, layer=0, area=None, tile=None, - verbose=False, no_cxform=False): + verbose=False, ignore_pclr_cmap_cdef=False): """Read a JPEG 2000 image using libopenjp2. Parameters @@ -874,7 +876,8 @@ class Jp2k(Jp2kBox): """ self._subsampling_sanity_check() - dparam = self._populate_dparam(layer, rlevel, area, tile, no_cxform) + dparam = self._populate_dparam(layer, rlevel, area, tile, + ignore_pclr_cmap_cdef) with ExitStack() as stack: if hasattr(opj2.OPENJP2, @@ -918,7 +921,7 @@ class Jp2k(Jp2kBox): return img_array - def _populate_dparam(self, layer, rlevel, area, tile, no_cxform): + def _populate_dparam(self, layer, rlevel, area, tile, ignore_pclr_cmap_cdef): """Populate decompression structure with appropriate input parameters. Parameters @@ -932,8 +935,9 @@ class Jp2k(Jp2kBox): (first_row, first_col, last_row, last_col) tile : int Number of tile to decode. - no_cxform : bool - Whether or not to apply intended color transforms. + ignore_pclr_cmap_cdef : bool + Whether or not to ignore the pclr, cmap, or cdef boxes during any + color transformation. Defaults to False. Returns ------- @@ -971,14 +975,14 @@ class Jp2k(Jp2kBox): dparam.tile_index = tile dparam.nb_tile_to_decode = 1 - if no_cxform is True: + if ignore_pclr_cmap_cdef is True: # Return raw codestream components. dparam.flags |= 1 return dparam def read_bands(self, rlevel=0, layer=0, area=None, tile=None, - verbose=False, no_cxform=False): + verbose=False, ignore_pclr_cmap_cdef=False): """Read a JPEG 2000 image. The only time you should use this method is when the image has @@ -996,8 +1000,9 @@ class Jp2k(Jp2kBox): (first_row, first_col, last_row, last_col) tile : int, optional Number of tile to decode. - no_cxform : bool - Whether or not to apply intended color transforms. + ignore_pclr_cmap_cdef : bool + Whether or not to ignore the pclr, cmap, or cdef boxes during any + color transformation. Defaults to False. verbose : bool, optional Print informational messages produced by the OpenJPEG library. @@ -1027,7 +1032,7 @@ class Jp2k(Jp2kBox): "of OpenJP2 installed before using " "this functionality.") - dparam = self._populate_dparam(layer, rlevel, area, tile, no_cxform) + dparam = self._populate_dparam(layer, rlevel, area, tile, ignore_pclr_cmap_cdef) with ExitStack() as stack: if hasattr(opj2.OPENJP2, diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 98173a0..6a456f8 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -99,7 +99,7 @@ class TestJp2k(unittest.TestCase): """Indices for pclr jpxfile if no color transform""" j = Jp2k(self.jpxfile) rgb = j.read() - idx = j.read(no_cxform=True) + idx = j.read(ignore_pclr_cmap_cdef=True) self.assertEqual(rgb.shape, (1024, 1024, 3)) self.assertEqual(idx.shape, (1024, 1024)) @@ -119,7 +119,7 @@ class TestJp2k(unittest.TestCase): filename = opj_data_file('input/conformance/file2.jp2') j = Jp2k(filename) ycbcr = j.read() - crcby = j.read(no_cxform=True) + crcby = j.read(ignore_pclr_cmap_cdef=True) expected = np.zeros(ycbcr.shape, ycbcr.dtype) for k in range(crcby.shape[2]): diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 1db11c4..3475906 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -224,7 +224,7 @@ class TestSuiteWriteCinema(unittest.TestCase): @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") -@unittest.skipIf(re.match(r"""2\.0""", glymur.version.openjpeg_version), +@unittest.skipIf(not re.match(r"""(1.5|2.0)""", glymur.version.openjpeg_version), "Functionality implemented for 2.1") @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_OPJ_DATA_ROOT environment variable not set") From d6cd57896f7549762bc0686d2724cbf91c3f5de4 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 18 Mar 2014 11:33:49 -0400 Subject: [PATCH 156/326] One doubled up test, failing one in wrong section. #179 --- glymur/test/test_jp2k.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 3afe0a4..e4ce6a1 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -109,21 +109,6 @@ class TestJp2k(unittest.TestCase): rgb_from_idx[r, c] = palette[idx[r, c]] np.testing.assert_array_equal(rgb, rgb_from_idx) - def test_no_cxform_cmap(self): - """Bands as physically ordered, not as physically intended""" - # This file has the components physically reversed. The cmap box - # tells the decoder how to order them, but this flag prevents that. - filename = opj_data_file('input/conformance/file2.jp2') - j = Jp2k(filename) - ycbcr = j.read() - crcby = j.read(ignore_pclr_cmap_cdef=True) - - expected = np.zeros(ycbcr.shape, ycbcr.dtype) - for k in range(crcby.shape[2]): - expected[:,:,crcby.shape[2] - k - 1] = crcby[:,:,k] - - np.testing.assert_array_equal(ycbcr, expected) - 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 From 161833ee5c062dab7c21f5260cea04be1459c5a6 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 18 Mar 2014 12:08:16 -0400 Subject: [PATCH 157/326] Checking for bad wavelet transform while parsing. #195 Being more intelligent about printing in such cases. --- glymur/codestream.py | 11 ++++++++--- glymur/test/test_codestream.py | 11 +++++++++++ glymur/test/test_printing.py | 9 +++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/glymur/codestream.py b/glymur/codestream.py index 35e3fee..756277d 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -52,9 +52,9 @@ _PROGRESSION_ORDER_DISPLAY = _keydefaultdict(_factory, PCRL: 'PCRL', CPRL: 'CPRL'}) -_WAVELET_TRANSFORM_DISPLAY = { - WAVELET_XFORM_9X7_IRREVERSIBLE: '9-7 irreversible', - WAVELET_XFORM_5X3_REVERSIBLE: '5-3 reversible'} +_WAVELET_TRANSFORM_DISPLAY = _keydefaultdict(_factory, + { WAVELET_XFORM_9X7_IRREVERSIBLE: '9-7 irreversible', + WAVELET_XFORM_5X3_REVERSIBLE: '5-3 reversible'}) # Need a catch-all list of valid markers. # See table A-1 in ISO/IEC FCD15444-1. @@ -390,6 +390,11 @@ class Codestream(object): msg = "Invalid progression order in COD segment: {0}." warnings.warn(msg.format(spcod[0])) + if spcod[8] not in [WAVELET_XFORM_9X7_IRREVERSIBLE, + WAVELET_XFORM_5X3_REVERSIBLE]: + msg = "Invalid wavelet transform in COD segment: {0}." + warnings.warn(msg.format(spcod[8])) + sop = (scod & 2) > 0 eph = (scod & 4) > 0 diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py index 28ea0f8..9aba31c 100644 --- a/glymur/test/test_codestream.py +++ b/glymur/test/test_codestream.py @@ -51,6 +51,17 @@ class TestCodestreamOpjData(unittest.TestCase): def tearDown(self): pass + def test_bad_wavelet_transform(self): + """Should warn if wavelet transform is bad. Issue129""" + filename = opj_data_file('input/nonregression/edf_c2_10025.jp2') + if sys.hexversion < 0x03000000: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + j = Jp2k(filename) + else: + with self.assertWarns(UserWarning): + j = Jp2k(filename) + def test_invalid_progression_order(self): """Should still be able to parse even if prog order is invalid.""" jfile = opj_data_file('input/nonregression/2977.pdf.asan.67.2198.jp2') diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 1e38c55..df73f3f 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -653,6 +653,15 @@ class TestPrintingOpjDataRoot(unittest.TestCase): def tearDown(self): pass + def test_bad_wavelet_transform(self): + """Should still be able to print if wavelet xform is bad, issue195""" + filename = opj_data_file('input/nonregression/edf_c2_10025.jp2') + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + j = Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(j) + def test_invalid_progression_order(self): """Should still be able to print even if prog order is invalid.""" jfile = opj_data_file('input/nonregression/2977.pdf.asan.67.2198.jp2') From 181dab165012898cba5555dc0366b8e3652b2f12 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 18 Mar 2014 13:04:22 -0400 Subject: [PATCH 158/326] Fixed incorrect tests on account of #193 Probably should not have used this file as it is highly corrupt. --- glymur/test/fixtures.py | 5 +++-- glymur/test/test_opj_suite.py | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 8e8deb8..a1fa2ec 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -580,8 +580,9 @@ file1_xml = """XML Box (xml ) @ (36, 439) issue_182_cmap = """Component Mapping Box (cmap) @ (130, 24) Component 0 ==> palette column 0 - Component 1 ==> palette column 0 - Component 2 ==> 2""" + Component 0 ==> palette column 1 + Component 0 ==> palette column 2 + Component 0 ==> palette column 3""" issue_183_colr = """Colour Specification Box (colr) @ (62, 12) Method: restricted ICC profile diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 28e9a0f..069884a 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -5830,9 +5830,9 @@ class TestSuiteDump(unittest.TestCase): # Jp2 Header # Component mapping box - self.assertEqual(jp2.box[3].box[3].component_index, (0, 1, 2)) - self.assertEqual(jp2.box[3].box[3].mapping_type, (1, 1, 0)) - self.assertEqual(jp2.box[3].box[3].palette_index, (0, 0, 1)) + self.assertEqual(jp2.box[3].box[3].component_index, (0, 0, 0, 0)) + self.assertEqual(jp2.box[3].box[3].mapping_type, (1, 1, 1, 1)) + self.assertEqual(jp2.box[3].box[3].palette_index, (0, 1, 2, 3)) c = jp2.box[4].main_header From 01524dcb9930b7f42d8a73a48d5a4123387476ca Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 18 Mar 2014 15:02:41 -0400 Subject: [PATCH 159/326] Hardened the handling of RSIZ and profiles. #196 Did not have an entry for RSIZ=4 ==> Profile 4 for cinema 4K. --- glymur/codestream.py | 20 +++++++++++++++++++- glymur/core.py | 6 ------ glymur/jp2k.py | 2 +- glymur/test/fixtures.py | 4 ++-- glymur/test/test_codestream.py | 13 ++++++++++++- glymur/test/test_printing.py | 13 +++++++++++-- 6 files changed, 45 insertions(+), 13 deletions(-) diff --git a/glymur/codestream.py b/glymur/codestream.py index 756277d..9751e14 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -28,7 +28,6 @@ import numpy as np from .core import LRCP, RLCP, RPCL, PCRL, CPRL from .core import WAVELET_XFORM_9X7_IRREVERSIBLE from .core import WAVELET_XFORM_5X3_REVERSIBLE -from .core import _CAPABILITIES_DISPLAY from .lib import openjp2 as opj2 class _keydefaultdict(collections.defaultdict): @@ -56,6 +55,22 @@ _WAVELET_TRANSFORM_DISPLAY = _keydefaultdict(_factory, { WAVELET_XFORM_9X7_IRREVERSIBLE: '9-7 irreversible', WAVELET_XFORM_5X3_REVERSIBLE: '5-3 reversible'}) +_NO_PROFILE = 0 +_PROFILE_0 = 1 +_PROFILE_1 = 2 +_PROFILE_3 = 3 +_PROFILE_4 = 4 + +_KNOWN_PROFILES = [_NO_PROFILE, _PROFILE_0, _PROFILE_1, _PROFILE_3, _PROFILE_4] + +# How to display the codestream profile. +_CAPABILITIES_DISPLAY = _keydefaultdict(_factory, + { _NO_PROFILE: 'no profile', + _PROFILE_0: '0', + _PROFILE_1: '1', + _PROFILE_3: '3', + _PROFILE_4: '4'} ) + # Need a catch-all list of valid markers. # See table A-1 in ISO/IEC FCD15444-1. _VALID_MARKERS = [0xff00, 0xff01, 0xfffe] @@ -672,6 +687,9 @@ class Codestream(object): data = struct.unpack('>HIIIIIIIIH', xy_buffer) rsiz = data[0] + if rsiz not in _KNOWN_PROFILES: + warnings.warn("Invalid profile: (Rsiz={0}).".format(rsiz)) + xysiz = (data[1], data[2]) xyosiz = (data[3], data[4]) xytsiz = (data[5], data[6]) diff --git a/glymur/core.py b/glymur/core.py index 95e1cbf..e593bd7 100644 --- a/glymur/core.py +++ b/glymur/core.py @@ -90,9 +90,3 @@ _COLORSPACE = {SRGB: {"R": 1, "G": 2, "B": 3}, E_SRGB: {"R": 1, "G": 2, "B": 3}, ROMM_RGB: {"R": 1, "G": 2, "B": 3}} -# How to display the codestream profile. -_CAPABILITIES_DISPLAY = { - 0: '2', - 1: '0', - 2: '1', - 3: '3'} diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 8f97e10..1ae7839 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -1095,7 +1095,7 @@ class Jp2k(Jp2kBox): >>> codestream = jp2.get_codestream() >>> print(codestream.segment[1]) SIZ marker segment @ (3233, 47) - Profile: 2 + Profile: no profile Reference Grid Height, Width: (1456 x 2592) Vertical, Horizontal Reference Grid Offset: (0 x 0) Reference Tile Height, Width: (1456 x 2592) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index a1fa2ec..1ac2af9 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -412,7 +412,7 @@ Contiguous Codestream Box (jp2c) @ (3223, 1132296) Main header: SOC marker segment @ (3231, 0) SIZ marker segment @ (3233, 47) - Profile: 2 + Profile: no profile Reference Grid Height, Width: (1456 x 2592) Vertical, Horizontal Reference Grid Offset: (0 x 0) Reference Tile Height, Width: (1456 x 2592) @@ -477,7 +477,7 @@ Contiguous Codestream Box (jp2c) @ (3223, 1132296) Main header: SOC marker segment @ (3231, 0) SIZ marker segment @ (3233, 47) - Profile: 2 + Profile: no profile Reference Grid Height, Width: (1456 x 2592) Vertical, Horizontal Reference Grid Offset: (0 x 0) Reference Tile Height, Width: (1456 x 2592) diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py index 9aba31c..8bac19c 100644 --- a/glymur/test/test_codestream.py +++ b/glymur/test/test_codestream.py @@ -51,8 +51,19 @@ class TestCodestreamOpjData(unittest.TestCase): def tearDown(self): pass + def test_bad_rsiz(self): + """Should warn if RSIZ is bad. Issue196""" + filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') + if sys.hexversion < 0x03000000: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + j = Jp2k(filename) + else: + with self.assertWarns(UserWarning): + j = Jp2k(filename) + def test_bad_wavelet_transform(self): - """Should warn if wavelet transform is bad. Issue129""" + """Should warn if wavelet transform is bad. Issue195""" filename = opj_data_file('input/nonregression/edf_c2_10025.jp2') if sys.hexversion < 0x03000000: with warnings.catch_warnings(): diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index df73f3f..51af6e0 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -349,7 +349,7 @@ class TestPrinting(unittest.TestCase): actual = fake_out.getvalue().strip() lines = ['SIZ marker segment @ (3233, 47)', - ' Profile: 2', + ' Profile: no profile', ' Reference Grid Height, Width: (1456 x 2592)', ' Vertical, Horizontal Reference Grid Offset: (0 x 0)', ' Reference Tile Height, Width: (1456 x 2592)', @@ -422,7 +422,7 @@ class TestPrinting(unittest.TestCase): lst = ['Codestream:', ' SOC marker segment @ (3231, 0)', ' SIZ marker segment @ (3233, 47)', - ' Profile: 2', + ' Profile: no profile', ' Reference Grid Height, Width: (1456 x 2592)', ' Vertical, Horizontal Reference Grid Offset: (0 x 0)', ' Reference Tile Height, Width: (1456 x 2592)', @@ -653,6 +653,15 @@ class TestPrintingOpjDataRoot(unittest.TestCase): def tearDown(self): pass + def test_bad_rsiz(self): + """Should still be able to print if rsiz is bad, issue196""" + filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + j = Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(j) + def test_bad_wavelet_transform(self): """Should still be able to print if wavelet xform is bad, issue195""" filename = opj_data_file('input/nonregression/edf_c2_10025.jp2') From f42c8922879242ac84bb0d6115b7d1a2371e428e Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 18 Mar 2014 18:48:09 -0400 Subject: [PATCH 160/326] Another case for a modified defaultdict. #199 --- glymur/codestream.py | 21 ++++--------------- glymur/core.py | 40 +++++++++++++++++++++++++----------- glymur/jp2box.py | 3 +++ glymur/test/test_jp2k.py | 7 +++++++ glymur/test/test_printing.py | 10 +++++++++ 5 files changed, 52 insertions(+), 29 deletions(-) diff --git a/glymur/codestream.py b/glymur/codestream.py index 9751e14..2e9184a 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -17,7 +17,6 @@ codestreams. # the base Segment class. # pylint: disable=R0903 -import collections import math import struct import sys @@ -28,30 +27,18 @@ import numpy as np from .core import LRCP, RLCP, RPCL, PCRL, CPRL from .core import WAVELET_XFORM_9X7_IRREVERSIBLE from .core import WAVELET_XFORM_5X3_REVERSIBLE +from .core import _Keydefaultdict from .lib import openjp2 as opj2 -class _keydefaultdict(collections.defaultdict): - """Unlisted keys help form their own error message. - - Normally defaultdict uses a factory function with no input arguments, but - that's not quite the behavior we want. - """ - def __missing__(self, key): - if self.default_factory is None: - raise KeyError(key) - else: - ret = self[key] = self.default_factory(key) - return ret - _factory = lambda x: '{0} (invalid)'.format(x) -_PROGRESSION_ORDER_DISPLAY = _keydefaultdict(_factory, +_PROGRESSION_ORDER_DISPLAY = _Keydefaultdict(_factory, { LRCP: 'LRCP', RLCP: 'RLCP', RPCL: 'RPCL', PCRL: 'PCRL', CPRL: 'CPRL'}) -_WAVELET_TRANSFORM_DISPLAY = _keydefaultdict(_factory, +_WAVELET_TRANSFORM_DISPLAY = _Keydefaultdict(_factory, { WAVELET_XFORM_9X7_IRREVERSIBLE: '9-7 irreversible', WAVELET_XFORM_5X3_REVERSIBLE: '5-3 reversible'}) @@ -64,7 +51,7 @@ _PROFILE_4 = 4 _KNOWN_PROFILES = [_NO_PROFILE, _PROFILE_0, _PROFILE_1, _PROFILE_3, _PROFILE_4] # How to display the codestream profile. -_CAPABILITIES_DISPLAY = _keydefaultdict(_factory, +_CAPABILITIES_DISPLAY = _Keydefaultdict(_factory, { _NO_PROFILE: 'no profile', _PROFILE_0: '0', _PROFILE_1: '1', diff --git a/glymur/core.py b/glymur/core.py index e593bd7..07949f9 100644 --- a/glymur/core.py +++ b/glymur/core.py @@ -1,8 +1,22 @@ """Core definitions to be shared amongst the modules. """ +import collections import copy import lxml.etree as ET +class _Keydefaultdict(collections.defaultdict): + """Unlisted keys help form their own error message. + + Normally defaultdict uses a factory function with no input arguments, but + that's not quite the behavior we want. + """ + def __missing__(self, key): + if self.default_factory is None: + raise KeyError(key) + else: + ret = self[key] = self.default_factory(key) + return ret + # Progression order LRCP = 0 RLCP = 1 @@ -57,24 +71,26 @@ YCC = 18 E_SRGB = 20 ROMM_RGB = 21 -_COLORSPACE_MAP_DISPLAY = { - CMYK: 'CMYK', - SRGB: 'sRGB', - GREYSCALE: 'greyscale', - YCC: 'YCC', - E_SRGB: 'e-sRGB', - ROMM_RGB: 'ROMM-RGB'} +_factory = lambda x: '{0} (unrecognized)'.format(x) +_COLORSPACE_MAP_DISPLAY = _Keydefaultdict(_factory, + { CMYK: 'CMYK', + SRGB: 'sRGB', + GREYSCALE: 'greyscale', + YCC: 'YCC', + E_SRGB: 'e-sRGB', + ROMM_RGB: 'ROMM-RGB'} ) # enumerated color channel types COLOR = 0 OPACITY = 1 PRE_MULTIPLIED_OPACITY = 2 _UNSPECIFIED = 65535 -_COLOR_TYPE_MAP_DISPLAY = { - COLOR: 'color', - OPACITY: 'opacity', - PRE_MULTIPLIED_OPACITY: 'pre-multiplied opacity', - _UNSPECIFIED: 'unspecified'} +_factory = lambda x: '{0} (invalid)'.format(x) +_COLOR_TYPE_MAP_DISPLAY = _Keydefaultdict(_factory, + { COLOR: 'color', + OPACITY: 'opacity', + PRE_MULTIPLIED_OPACITY: 'pre-multiplied opacity', + _UNSPECIFIED: 'unspecified'}) # color channel definitions. RED = 1 diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 9796f58..fa84b83 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -419,6 +419,9 @@ class ColourSpecificationBox(Jp2kBox): # enumerated colour space read_buffer = fptr.read(4) colorspace, = struct.unpack('>I', read_buffer) + if colorspace not in _COLORSPACE_MAP_DISPLAY.keys(): + msg = "Unrecognized colorspace: {0}".format(colorspace) + warnings.warn(msg) icc_profile = None else: diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index e4ce6a1..6dd9ba4 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -758,6 +758,13 @@ class TestJp2k_2_1(unittest.TestCase): class TestJp2kOpjDataRoot(unittest.TestCase): """These tests should be run by just about all configuration.""" + @unittest.skipIf(sys.hexversion < 0x03000000, "Test requires Python 3.3+") + def test_invalid_colorspace(self): + """Should warn in case of invalid colorspace.""" + filename = opj_data_file('input/nonregression/edf_c2_1103421.jp2') + with self.assertWarns(UserWarning): + jp2 = Jp2k(filename) + def test_no_cxform_pclr_jp2(self): """Indices for pclr jpxfile if no color transform""" filename = opj_data_file('input/conformance/file9.jp2') diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 51af6e0..65b181e 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -653,6 +653,16 @@ class TestPrintingOpjDataRoot(unittest.TestCase): def tearDown(self): pass + def test_invalid_colorspace(self): + """An invalid colorspace shouldn't cause an error.""" + filename = opj_data_file('input/nonregression/edf_c2_1103421.jp2') + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + jp2 = Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(jp2) + + def test_bad_rsiz(self): """Should still be able to print if rsiz is bad, issue196""" filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') From 661f5bfadb1b671c40396a65b2e86e74922f7a48 Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 18 Mar 2014 20:04:50 -0400 Subject: [PATCH 161/326] Cinema 2K and Cinema 4K profiles are printed as such. #201 --- glymur/codestream.py | 4 ++-- glymur/test/fixtures.py | 11 +++++++++++ glymur/test/test_printing.py | 11 ++++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/glymur/codestream.py b/glymur/codestream.py index 2e9184a..2f9d37a 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -55,8 +55,8 @@ _CAPABILITIES_DISPLAY = _Keydefaultdict(_factory, { _NO_PROFILE: 'no profile', _PROFILE_0: '0', _PROFILE_1: '1', - _PROFILE_3: '3', - _PROFILE_4: '4'} ) + _PROFILE_3: 'Cinema 2K', + _PROFILE_4: 'Cinema 4K'} ) # Need a catch-all list of valid markers. # See table A-1 in ISO/IEC FCD15444-1. diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 1ac2af9..1ee7aa1 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -611,3 +611,14 @@ issue_186_progression_order = """COD marker segment @ (174, 12) Vertically stripe causal context: False Predictable termination: False Segmentation symbols: False""" + +# Cinema 2K profile +cinema2k_profile = """SIZ marker segment @ (2, 47) + Profile: Cinema 2K + Reference Grid Height, Width: (1080 x 1920) + Vertical, Horizontal Reference Grid Offset: (0 x 0) + Reference Tile Height, Width: (1080 x 1920) + Vertical, Horizontal Reference Tile Offset: (0 x 0) + Bitdepth: (12, 12, 12) + Signed: (False, False, False) + Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1))""" diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 65b181e..a6f1862 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -653,6 +653,16 @@ class TestPrintingOpjDataRoot(unittest.TestCase): def tearDown(self): pass + def test_cinema_profile(self): + """Should print Cinema 2K when the profile is 3.""" + filename = opj_data_file('input/nonregression/_00042.j2k') + j2k = Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + c = j2k.get_codestream() + print(c.segment[1]) + actual = fake_out.getvalue().strip() + self.assertEqual(actual, fixtures.cinema2k_profile) + def test_invalid_colorspace(self): """An invalid colorspace shouldn't cause an error.""" filename = opj_data_file('input/nonregression/edf_c2_1103421.jp2') @@ -662,7 +672,6 @@ class TestPrintingOpjDataRoot(unittest.TestCase): with patch('sys.stdout', new=StringIO()) as fake_out: print(jp2) - def test_bad_rsiz(self): """Should still be able to print if rsiz is bad, issue196""" filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') From 1808d563b94711d29e89f1b861e29efe1129e8d8 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 19 Mar 2014 09:21:10 -0400 Subject: [PATCH 162/326] Warn instead of error out when reading invalid approximation. #198 --- glymur/jp2box.py | 23 +++++++++++++---------- glymur/test/test_jp2box.py | 4 ++-- glymur/test/test_jp2k.py | 7 +++++++ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index fa84b83..7a8eb74 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -33,6 +33,7 @@ from .core import _COLOR_TYPE_MAP_DISPLAY from .core import SRGB, GREYSCALE, YCC from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE from .core import ANY_ICC_PROFILE, VENDOR_COLOR_METHOD +from .core import _Keydefaultdict from . import _uuid_io @@ -42,14 +43,12 @@ _METHOD_DISPLAY = { ANY_ICC_PROFILE: 'any ICC profile', VENDOR_COLOR_METHOD: 'vendor color method'} -_APPROX_DISPLAY = {1: 'accurately represents correct colorspace definition', - 2: 'approximates correct colorspace definition, ' - + 'exceptional quality', - 3: 'approximates correct colorspace definition, ' - + 'reasonable quality', - 4: 'approximates correct colorspace definition, ' - + 'poor quality'} - +_factory = lambda x: '{0} (invalid)'.format(x) +_APPROX_DISPLAY = _Keydefaultdict(_factory, + {1: 'accurately represents correct colorspace definition', + 2: 'approximates correct colorspace definition, exceptional quality', + 3: 'approximates correct colorspace definition, reasonable quality', + 4: 'approximates correct colorspace definition, poor quality'}) class Jp2kBox(object): """Superclass for JPEG 2000 boxes. @@ -301,13 +300,19 @@ class ColourSpecificationBox(Jp2kBox): approximation=0, colorspace=None, icc_profile=None, length=0, offset=-1): Jp2kBox.__init__(self, box_id='colr', longname='Colour Specification') + self.method = method self.precedence = precedence + + if approximation not in (0, 1, 2, 3, 4): + warnings.warn("Invalid approximation: {0}".format(approximation)) self.approximation = approximation + self.colorspace = colorspace self.icc_profile = icc_profile self.length = length self.offset = offset + self._validate() def _validate(self): @@ -316,8 +321,6 @@ class ColourSpecificationBox(Jp2kBox): raise IOError("colorspace and icc_profile cannot both be set.") if self.method not in (1, 2, 3, 4): raise IOError("Invalid method.") - if self.approximation not in (0, 1, 2, 3, 4): - raise IOError("Invalid approximation.") def _write_validate(self): """In addition to constructor validation steps, run validation steps diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 5dedbcc..856ba7a 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -455,10 +455,10 @@ class TestColourSpecificationBox(unittest.TestCase): method=method) def test_colr_with_bad_approx(self): - """colr must have a valid approximation field""" + """colr should have a valid approximation field""" colorspace = glymur.core.SRGB approx = -1 - with self.assertRaises(IOError): + with self.assertWarns(UserWarning): glymur.jp2box.ColourSpecificationBox(colorspace=colorspace, approximation=approx) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 6dd9ba4..3eab4e9 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -758,6 +758,13 @@ class TestJp2k_2_1(unittest.TestCase): class TestJp2kOpjDataRoot(unittest.TestCase): """These tests should be run by just about all configuration.""" + @unittest.skipIf(sys.hexversion < 0x03000000, "Test requires Python 3.3+") + def test_invalid_approximation(self): + """Should warn in case of invalid approximation.""" + filename = opj_data_file('input/nonregression/edf_c2_1015644.jp2') + with self.assertWarns(UserWarning): + jp2 = Jp2k(filename) + @unittest.skipIf(sys.hexversion < 0x03000000, "Test requires Python 3.3+") def test_invalid_colorspace(self): """Should warn in case of invalid colorspace.""" From f5741697d9891d36ec7c25c4748f0387f06506eb Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 19 Mar 2014 10:01:13 -0400 Subject: [PATCH 163/326] Test shouldn't run on 2.7 #198 --- glymur/test/test_jp2box.py | 1 + 1 file changed, 1 insertion(+) diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 856ba7a..9ed6292 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -454,6 +454,7 @@ class TestColourSpecificationBox(unittest.TestCase): glymur.jp2box.ColourSpecificationBox(colorspace=colorspace, method=method) + @unittest.skipIf(sys.hexversion < 0x03030000, "Requires 3.3+") def test_colr_with_bad_approx(self): """colr should have a valid approximation field""" colorspace = glymur.core.SRGB From 2ab86918986c277f37872c1166bb479d3d05251a Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 19 Mar 2014 11:22:44 -0400 Subject: [PATCH 164/326] Added warning about incorrect ftyp brand. #194 While at it, added new infrastructure for either erroring or just warning when an conformance issue is encountered. If parsing a file, we warn. If about to write a file, we error out. --- glymur/jp2box.py | 21 +++++++++++++++++---- glymur/test/test_icc.py | 6 +++++- glymur/test/test_jp2box.py | 9 +++++++-- glymur/test/test_jp2k.py | 12 +++++++++++- glymur/test/test_opj_suite.py | 10 ++++++++-- glymur/test/test_printing.py | 5 ++++- 6 files changed, 52 insertions(+), 11 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 7a8eb74..f3f0776 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -85,6 +85,17 @@ class Jp2kBox(object): msg += " @ ({0}, {1})".format(self.offset, self.length) return msg + def _dispatch_validation_error(self, msg, writing=False): + """Issue either a warning or an error depending on circumstance. + + If writing to file, then error out, as we do not wish to create bad + JP2 files. If reading, then we should be more lenient and just warn. + """ + if writing: + raise IOError(msg) + else: + warnings.warn(msg) + def write(self, _): """Must be implemented in a subclass. """ @@ -1102,6 +1113,7 @@ class FileTypeBox(Jp2kBox): self.compatibility_list = compatibility_list self.length = length self.offset = offset + self._validate(writing=False) def __repr__(self): msg = "glymur.jp2box.FileTypeBox(brand='{0}', minor_version={1}, " @@ -1123,22 +1135,23 @@ class FileTypeBox(Jp2kBox): return msg - def _validate(self): + def _validate(self, writing=False): """Validate the box before writing to file.""" if self.brand not in ['jp2 ', 'jpx ']: msg = "The file type brand must be either 'jp2 ' or 'jpx '." - raise IOError(msg) + self._dispatch_validation_error(msg, writing=writing) valid_cls = ['jp2 ', 'jpx ', 'jpxb'] for item in self.compatibility_list: if item not in valid_cls: msg = "The file type compatibility list item '{0}' is not " msg += "valid: valid entries are {1}" - raise IOError(msg.format(item, valid_cls)) + msg = msg.format(item, valid_cls) + self._dispatch_validation_error(msg, writing=writing) def write(self, fptr): """Write a File Type box to file. """ - self._validate() + self._validate(writing=True) length = 16 + 4*len(self.compatibility_list) fptr.write(struct.pack('>I', length)) fptr.write('ftyp'.encode()) diff --git a/glymur/test/test_icc.py b/glymur/test/test_icc.py index 0ef166b..c49055e 100644 --- a/glymur/test/test_icc.py +++ b/glymur/test/test_icc.py @@ -9,6 +9,7 @@ import datetime import os import sys import unittest +import warnings import numpy as np @@ -30,7 +31,10 @@ class TestICC(unittest.TestCase): def test_file5(self): """basic ICC profile""" filename = opj_data_file('input/conformance/file5.jp2') - j = Jp2k(filename) + with warnings.catch_warnings(): + # The file has a bad compatibility list entry. Not important here. + warnings.simplefilter("ignore") + j = Jp2k(filename) profile = j.box[2].box[1].icc_profile self.assertEqual(profile['Size'], 546) self.assertEqual(profile['Preferred CMM Type'], 0) diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 9ed6292..5563085 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -373,14 +373,19 @@ class TestFileTypeBox(unittest.TestCase): def test_brand_unknown(self): """A ftyp box brand must be 'jp2 ' or 'jpx '.""" - ftyp = glymur.jp2box.FileTypeBox(brand='jp3') + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + ftyp = glymur.jp2box.FileTypeBox(brand='jp3') with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: ftyp.write(tfile) def test_cl_entry_unknown(self): """A ftyp box cl list can only contain 'jp2 ', 'jpx ', or 'jpxb'.""" - ftyp = glymur.jp2box.FileTypeBox(compatibility_list=['jp3']) + with warnings.catch_warnings(): + # Bad compatibility list item. + warnings.simplefilter("ignore") + ftyp = glymur.jp2box.FileTypeBox(compatibility_list=['jp3']) with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: ftyp.write(tfile) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 3eab4e9..10363ab 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -758,6 +758,13 @@ class TestJp2k_2_1(unittest.TestCase): class TestJp2kOpjDataRoot(unittest.TestCase): """These tests should be run by just about all configuration.""" + @unittest.skipIf(sys.hexversion < 0x03000000, "Test requires Python 3.3+") + def test_invalid_approximation(self): + """Should warn in case of bad ftyp brand.""" + filename = opj_data_file('input/nonregression/edf_c2_1000290.jp2') + with self.assertWarns(UserWarning): + jp2 = Jp2k(filename) + @unittest.skipIf(sys.hexversion < 0x03000000, "Test requires Python 3.3+") def test_invalid_approximation(self): """Should warn in case of invalid approximation.""" @@ -817,7 +824,10 @@ class TestJp2kOpjDataRoot(unittest.TestCase): # This file has the components physically reversed. The cmap box # tells the decoder how to order them, but this flag prevents that. filename = opj_data_file('input/conformance/file2.jp2') - j = Jp2k(filename) + with warnings.catch_warnings(): + # The file has a bad compatibility list entry. Not important here. + warnings.simplefilter("ignore") + j = Jp2k(filename) ycbcr = j.read() crcby = j.read(ignore_pclr_cmap_cdef=True) diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 069884a..2a49b5d 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -327,7 +327,10 @@ class TestSuite(unittest.TestCase): def test_ETS_JP2_file1(self): jfile = opj_data_file('input/conformance/file1.jp2') - jp2k = Jp2k(jfile) + with warnings.catch_warnings(): + # Bad compatibility list item. + warnings.simplefilter("ignore") + jp2k = Jp2k(jfile) jpdata = jp2k.read() self.assertEqual(jpdata.shape, (512, 768, 3)) @@ -3114,7 +3117,10 @@ class TestSuiteDump(unittest.TestCase): def test_NR_file1_dump(self): jfile = opj_data_file('input/conformance/file1.jp2') - jp2 = Jp2k(jfile) + with warnings.catch_warnings(): + # Bad compatibility list item. + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) ids = [box.box_id for box in jp2.box] self.assertEqual(ids, ['jP ', 'ftyp', 'xml ', 'jp2h', 'xml ', diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index a6f1862..7a17e4e 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -863,7 +863,10 @@ class TestPrintingOpjDataRoot(unittest.TestCase): def test_channel_definition(self): """verify printing of cdef box""" filename = opj_data_file('input/conformance/file2.jp2') - j = glymur.Jp2k(filename) + with warnings.catch_warnings(): + # Bad compatibility list item. + warnings.simplefilter("ignore") + j = glymur.Jp2k(filename) with patch('sys.stdout', new=StringIO()) as fake_out: print(j.box[2].box[2]) actual = fake_out.getvalue().strip() From 55a01cbc39186f111e919c9f1fa2ab8272eab5b0 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 19 Mar 2014 15:15:55 -0400 Subject: [PATCH 165/326] Not decoding box ID anymore. #197 We don't actually use the box ID as parsed from the file, so there was no need to decode it. This means that unrecognized box IDs are actually kept as bytes, but no big deal. The code is simplified a bit because of this. --- glymur/_uuid_io.py | 2 +- glymur/jp2box.py | 76 ++++++++++++++++++---------------- glymur/test/test_jp2box_jpx.py | 2 +- glymur/test/test_jp2k.py | 17 +++++++- glymur/test/test_opj_suite.py | 2 +- glymur/test/test_printing.py | 2 +- 6 files changed, 61 insertions(+), 40 deletions(-) diff --git a/glymur/_uuid_io.py b/glymur/_uuid_io.py index 399fa99..8cd5bcf 100644 --- a/glymur/_uuid_io.py +++ b/glymur/_uuid_io.py @@ -43,7 +43,7 @@ def tiff_header(read_buffer): endian = '>' else: msg = "Bad byte order indication: {0}".format(read_buffer[6:8]) - raise RuntimeError(msg) + raise IOError(msg) _, offset = struct.unpack(endian + 'HI', read_buffer[8:14]) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index f3f0776..723b5a3 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -177,6 +177,13 @@ class Jp2kBox(object): """ try: box = _BOX_WITH_ID[box_id].parse(fptr, start, num_bytes) + + except UnicodeDecodeError: + msg = 'Unrecognized box ({0}) encountered.'.format(box_id) + warnings.warn(msg) + box = UnknownBox(' ', offset=start, length=num_bytes, + longname='Unknown') + except KeyError: msg = 'Unrecognized box ({0}) encountered.'.format(box_id) warnings.warn(msg) @@ -194,7 +201,6 @@ class Jp2kBox(object): pos = fptr.tell() read_buffer = fptr.read(8) _, sub_id = struct.unpack('>I4s', read_buffer) - sub_id = sub_id.decode('utf-8') # Regardless of whether or not we recognize the box, rewind back # to properly advance to the next box. @@ -230,16 +236,12 @@ class Jp2kBox(object): break read_buffer = fptr.read(8) - try: - (box_length, box_id) = struct.unpack('>I4s', read_buffer) - except Exception as err: + if len(read_buffer) < 8: msg = "Extra bytes at end of file ignored." warnings.warn(msg) return superbox - if sys.hexversion >= 0x03000000: - box_id = box_id.decode('utf-8') - + (box_length, box_id) = struct.unpack('>I4s', read_buffer) if box_length == 0: # The length of the box is presumed to last until the end of # the file. Compute the effective length of the box. @@ -2998,7 +3000,11 @@ class UUIDBox(Jp2kBox): try: self._parse_raw_data() - except RuntimeError as error: + except KeyError as error: + # Such as when an Exif tag is unrecognized. + warnings.warn(str(error)) + except IOError as error: + # Such as when Exif byte order is unrecognized. warnings.warn(str(error)) def _parse_raw_data(self): @@ -3088,33 +3094,33 @@ class UUIDBox(Jp2kBox): # Map each box ID to the corresponding class. _BOX_WITH_ID = { - 'asoc': AssociationBox, - 'cdef': ChannelDefinitionBox, - 'cmap': ComponentMappingBox, - 'colr': ColourSpecificationBox, - 'dtbl': DataReferenceBox, - 'ftyp': FileTypeBox, - 'ihdr': ImageHeaderBox, - 'jP ': JPEG2000SignatureBox, - 'jpch': CodestreamHeaderBox, - 'jplh': CompositingLayerHeaderBox, - 'jp2c': ContiguousCodestreamBox, - 'free': FreeBox, - 'flst': FragmentListBox, - 'ftbl': FragmentTableBox, - 'jp2h': JP2HeaderBox, - 'lbl ': LabelBox, - 'nlst': NumberListBox, - 'pclr': PaletteBox, - 'res ': ResolutionBox, - 'resc': CaptureResolutionBox, - 'resd': DisplayResolutionBox, - 'rreq': ReaderRequirementsBox, - 'uinf': UUIDInfoBox, - 'ulst': UUIDListBox, - 'url ': DataEntryURLBox, - 'uuid': UUIDBox, - 'xml ': XMLBox} + b'asoc': AssociationBox, + b'cdef': ChannelDefinitionBox, + b'cmap': ComponentMappingBox, + b'colr': ColourSpecificationBox, + b'dtbl': DataReferenceBox, + b'ftyp': FileTypeBox, + b'ihdr': ImageHeaderBox, + b'jP ': JPEG2000SignatureBox, + b'jpch': CodestreamHeaderBox, + b'jplh': CompositingLayerHeaderBox, + b'jp2c': ContiguousCodestreamBox, + b'free': FreeBox, + b'flst': FragmentListBox, + b'ftbl': FragmentTableBox, + b'jp2h': JP2HeaderBox, + b'lbl ': LabelBox, + b'nlst': NumberListBox, + b'pclr': PaletteBox, + b'res ': ResolutionBox, + b'resc': CaptureResolutionBox, + b'resd': DisplayResolutionBox, + b'rreq': ReaderRequirementsBox, + b'uinf': UUIDInfoBox, + b'ulst': UUIDListBox, + b'url ': DataEntryURLBox, + b'uuid': UUIDBox, + b'xml ': XMLBox} _printoptions = {'short': False, 'xml': True, 'codestream': True} diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index f8e9db8..f71f409 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -402,7 +402,7 @@ class TestJPX(unittest.TestCase): with self.assertWarns(UserWarning): jpx = Jp2k(tfile.name) - self.assertEqual(jpx.box[-1].box_id, 'grp ') + self.assertEqual(jpx.box[-1].box_id, b'grp ') self.assertEqual(jpx.box[-1].box[0].box_id, 'free') def test_free_box(self): diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 10363ab..222cde8 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -758,7 +758,22 @@ class TestJp2k_2_1(unittest.TestCase): class TestJp2kOpjDataRoot(unittest.TestCase): """These tests should be run by just about all configuration.""" - @unittest.skipIf(sys.hexversion < 0x03000000, "Test requires Python 3.3+") + def test_undecodeable_box_id(self): + """Should warn in case of undecodeable box ID but not error out.""" + filename = opj_data_file('input/nonregression/edf_c2_1013627.jp2') + if sys.hexversion < 0x03000000: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + jp2 = Jp2k(filename) + else: + with self.assertWarns(UserWarning): + jp2 = Jp2k(filename) + + # Now make sure we got all of the boxes. Ignore the last, which was + # bad. + box_ids = [box.box_id for box in jp2.box[:-1]] + self.assertEqual(box_ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + def test_invalid_approximation(self): """Should warn in case of bad ftyp brand.""" filename = opj_data_file('input/nonregression/edf_c2_1000290.jp2') diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 2a49b5d..40bfa68 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -5482,7 +5482,7 @@ class TestSuiteDump(unittest.TestCase): jp2 = Jp2k(jfile) ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'XML ', 'jp2c']) + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', b'XML ', 'jp2c']) ids = [box.box_id for box in jp2.box[2].box] self.assertEqual(ids, ['ihdr', 'colr']) diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 7a17e4e..5cd8347 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -78,7 +78,7 @@ class TestPrinting(unittest.TestCase): with patch('sys.stdout', new=StringIO()) as fake_out: print(jpx.box[-1]) actual = fake_out.getvalue().strip() - lines = ['Unknown Box (grp ) @ (695609, 20)', + lines = ["Unknown Box (b'grp ') @ (695609, 20)", ' Free Box (free) @ (695617, 12)'] expected = '\n'.join(lines) self.assertEqual(actual, expected) From 0a826ad882b7016469cfceb0ac013ba93881a991 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 19 Mar 2014 16:52:12 -0400 Subject: [PATCH 166/326] Most parsing and writing warning/exceptions now regularized. #203 Only exception was ftbl and flst, because ftbl is the one box consisting of boxes that is not a superbox. --- glymur/jp2box.py | 69 ++++++++++++++++++---------------- glymur/test/test_jp2box.py | 18 ++++++--- glymur/test/test_jp2box_jpx.py | 23 +++++++++--- 3 files changed, 66 insertions(+), 44 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 723b5a3..ea8ca62 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -316,9 +316,6 @@ class ColourSpecificationBox(Jp2kBox): self.method = method self.precedence = precedence - - if approximation not in (0, 1, 2, 3, 4): - warnings.warn("Invalid approximation: {0}".format(approximation)) self.approximation = approximation self.colorspace = colorspace @@ -326,14 +323,19 @@ class ColourSpecificationBox(Jp2kBox): self.length = length self.offset = offset - self._validate() + self._validate(writing=False) - def _validate(self): + def _validate(self, writing=False): """Verify that the box obeys the specifications.""" if self.colorspace is not None and self.icc_profile is not None: - raise IOError("colorspace and icc_profile cannot both be set.") + msg = "Colorspace and icc_profile cannot both be set." + self._dispatch_validation_error(msg, writing=writing) if self.method not in (1, 2, 3, 4): - raise IOError("Invalid method.") + msg = "Invalid method.".format(self.method) + self._dispatch_validation_error(msg, writing=writing) + if self.approximation not in (0, 1, 2, 3, 4): + msg = "Invalid approximation: {0}".format(self.approximation) + self._dispatch_validation_error(msg, writing=writing) def _write_validate(self): """In addition to constructor validation steps, run validation steps @@ -341,15 +343,15 @@ class ColourSpecificationBox(Jp2kBox): if self.colorspace is None: msg = "Writing Colour Specification boxes without enumerated " msg += "colorspaces is not supported at this time." - raise IOError(msg) + self._dispatch_validation_error(msg, writing=True) if self.icc_profile is None: if self.colorspace not in [SRGB, GREYSCALE, YCC]: msg = "Colorspace should correspond to one of SRGB, GREYSCALE, " msg += "or YCC." - raise IOError(msg) + self._dispatch_validation_error(msg, writing=True) - self._validate() + self._validate(writing=True) def __repr__(self): @@ -607,15 +609,15 @@ class ChannelDefinitionBox(Jp2kBox): self.channel_type = tuple(channel_type) self.association = tuple(association) self.__dict__.update(**kwargs) - self._validate() + self._validate(writing=False) - def _validate(self): + def _validate(self, writing=False): """Verify that the box obeys the specifications.""" # channel type and association must be specified. if not ((len(self.index) == len(self.channel_type)) and (len(self.channel_type) == len(self.association))): msg = "Length of channel definition box inputs must be the same." - raise IOError(msg) + self._dispatch_validation_error(msg, writing=writing) # channel types must be one of 0, 1, 2, 65535 if any(x not in [0, 1, 2, 65535] for x in self.channel_type): @@ -624,7 +626,7 @@ class ChannelDefinitionBox(Jp2kBox): msg += " 1 - opacity\n" msg += " 2 - premultiplied opacity\n" msg += " 65535 - unspecified" - raise IOError(msg) + self._dispatch_validation_error(msg, writing=writing) def __str__(self): @@ -651,7 +653,7 @@ class ChannelDefinitionBox(Jp2kBox): def write(self, fptr): """Write a channel definition box to file. """ - self._validate() + self._validate(writing=True) num_components = len(self.association) fptr.write(struct.pack('>I', 8 + 2 + num_components * 6)) fptr.write('cdef'.encode('utf-8')) @@ -993,23 +995,23 @@ class DataReferenceBox(Jp2kBox): self.DR = data_entry_url_boxes self.length = length self.offset = offset - self._validate() + self._validate(writing=False) - def _validate(self): + def _validate(self, writing=False): """Verify that the box obeys the specifications.""" for box in self.DR: if box.box_id != 'url ': msg = 'All child boxes of a data reference box must be data ' msg += 'entry URL boxes.' - raise IOError(msg) + self._dispatch_validation_error(msg, writing=writing) def _write_validate(self): """Verify that the box obeys the specifications for writing. """ if len(self.DR) == 0: msg = "A data reference box cannot be empty when written to a file." - raise IOError(msg) - self._validate() + self._dispatch_validation_error(msg, writing=True) + self._validate(writing=True) def write(self, fptr): """Write a Data Reference box to file. @@ -1227,18 +1229,21 @@ class FragmentListBox(Jp2kBox): self.data_reference = data_reference self.length = length self.offset = offset + self._validate(writing=False) - def _validate(self): + def _validate(self, writing=False): """Validate internal correctness.""" if (((len(self.fragment_offset) != len(self.fragment_length)) or (len(self.fragment_length) != len(self.data_reference)))): msg = "The lengths of the fragment offsets, fragment lengths, and " msg += "data reference items must be the same." - raise IOError(msg) + self._dispatch_validation_error(msg, writing=writing) if any([x <= 0 for x in self.fragment_offset]): - raise IOError("Fragment offsets must all be positive.") + msg = "Fragment offsets must all be positive." + self._dispatch_validation_error(msg, writing=writing) if any([x <= 0 for x in self.fragment_length]): - raise IOError("Fragment lengths must all be positive.") + msg = "Fragment lengths must all be positive." + self._dispatch_validation_error(msg, writing=writing) def __repr__(self): msg = "glymur.jp2box.FragmentListBox()" @@ -1262,7 +1267,7 @@ class FragmentListBox(Jp2kBox): def write(self, fptr): """Write a fragment list box to file. """ - self._validate() + self._validate(writing=True) num_items = len(self.fragment_offset) length = 8 + 2 + num_items * 14 fptr.write(struct.pack('>I', length)) @@ -1357,18 +1362,18 @@ class FragmentTableBox(Jp2kBox): return box - def _validate(self): + def _validate(self, writing=False): """Self-validate the box before writing.""" box_ids = [box.box_id for box in self.box] if len(box_ids) != 1 or box_ids[0] != 'flst': msg = "Fragment table boxes must have a single fragment list " msg += "box as a child box." - raise IOError(msg) + self._dispatch_validation_error(msg, writing=writing) def write(self, fptr): """Write a fragment table box to file. """ - self._validate() + self._validate(writing=True) self._write_superbox(fptr) @@ -1779,15 +1784,15 @@ class PaletteBox(Jp2kBox): self.signed = signed self.length = length self.offset = offset - self._validate() + self._validate(writing=False) - def _validate(self): + def _validate(self, writing=False): """Verify that the box obeys the specifications.""" if ((len(self.bits_per_component) != len(self.signed)) or (len(self.signed) != self.palette.shape[1])): msg = "The length of the 'bits_per_component' and the 'signed' " msg += "members must equal the number of columns of the palette." - raise IOError(msg) + self._dispatch_validation_error(msg, writing=writing) def __repr__(self): msg = "glymur.jp2box.PaletteBox({0}, bits_per_component={1}, " @@ -1807,7 +1812,7 @@ class PaletteBox(Jp2kBox): def write(self, fptr): """Write a Palette box to file. """ - self._validate() + self._validate(writing=True) bytes_per_row = sum(self.bits_per_component) / 8 bytes_per_palette = bytes_per_row * self.palette.shape[0] box_length = 8 + 3 + self.palette.shape[1] + bytes_per_palette diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 5563085..82a9ad6 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -340,6 +340,7 @@ class TestChannelDefinition(unittest.TestCase): with self.assertRaises((IOError, OSError)): j2k.wrap(tfile.name, boxes=boxes) + @unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.") def test_bad_type(self): """Channel types are limited to 0, 1, 2, 65535 Should reject if not all of index, channel_type, association the @@ -347,17 +348,18 @@ class TestChannelDefinition(unittest.TestCase): """ channel_type = (COLOR, COLOR, 3) association = (RED, GREEN, BLUE) - with self.assertRaises(IOError): + with self.assertWarns(UserWarning): glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type, association=association) + @unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.") def test_wrong_lengths(self): """Should reject if not all of index, channel_type, association the same length. """ channel_type = (COLOR, COLOR) association = (RED, GREEN, BLUE) - with self.assertRaises(IOError): + with self.assertWarns(UserWarning): glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type, association=association) @@ -443,19 +445,21 @@ class TestColourSpecificationBox(unittest.TestCase): self.assertEqual(colr.colorspace, glymur.core.SRGB) self.assertIsNone(colr.icc_profile) + @unittest.skipIf(sys.hexversion < 0x03030000, "Requires 3.3+") def test_colr_with_cspace_and_icc(self): """Colour specification boxes can't have both.""" - with self.assertRaises((OSError, IOError)): + with self.assertWarns(UserWarning): colorspace = glymur.core.SRGB rawb = b'\x01\x02\x03\x04' glymur.jp2box.ColourSpecificationBox(colorspace=colorspace, icc_profile=rawb) + @unittest.skipIf(sys.hexversion < 0x03030000, "Requires 3.3+") def test_colr_with_bad_method(self): """colr must have a valid method field""" colorspace = glymur.core.SRGB method = -1 - with self.assertRaises(IOError): + with self.assertWarns(UserWarning): glymur.jp2box.ColourSpecificationBox(colorspace=colorspace, method=method) @@ -490,21 +494,23 @@ class TestPaletteBox(unittest.TestCase): def tearDown(self): pass + @unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.") def test_mismatched_bitdepth_signed(self): """bitdepth and signed arguments must have equal length""" palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint8) bps = (8, 8, 8) signed = (False, False) - with self.assertRaises(IOError): + with self.assertWarns(UserWarning): pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps, signed=signed) + @unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.") def test_mismatched_signed_palette(self): """bitdepth and signed arguments must have equal length""" palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint8) bps = (8, 8, 8, 8) signed = (False, False, False, False) - with self.assertRaises(IOError): + with self.assertWarns(UserWarning): pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps, signed=signed) diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index f71f409..65fb925 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -8,6 +8,8 @@ import struct import sys import tempfile import unittest +import warnings + import lxml.etree as ET import glymur @@ -207,13 +209,14 @@ class TestJPXWrap(unittest.TestCase): with self.assertRaises(IOError): jp2.wrap(tfile.name, boxes=boxes) + @unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.") def test_deurl_child_of_dtbl(self): """Data reference boxes can only contain data entry url boxes.""" jp2 = Jp2k(self.jp2file) boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] ftyp = glymur.jp2box.FileTypeBox() - with self.assertRaises(IOError): + with self.assertWarns(UserWarning): dref = glymur.jp2box.DataReferenceBox([ftyp]) # Try to get around it by appending the ftyp box after creation. @@ -337,7 +340,9 @@ class TestJPX(unittest.TestCase): offset = [89] length = [1132288] reference = [0, 0] - flst = glymur.jp2box.FragmentListBox(offset, length, reference) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + flst = glymur.jp2box.FragmentListBox(offset, length, reference) with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: flst.write(tfile) @@ -347,8 +352,10 @@ class TestJPX(unittest.TestCase): offset = [0] length = [1132288] reference = [0] - flst = glymur.jp2box.FragmentListBox(offset, length, reference) - with self.assertRaises(IOError): + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + flst = glymur.jp2box.FragmentListBox(offset, length, reference) + with self.assertRaises((IOError, OSError)): with tempfile.TemporaryFile() as tfile: flst.write(tfile) @@ -357,14 +364,18 @@ class TestJPX(unittest.TestCase): offset = [89] length = [0] reference = [0] - flst = glymur.jp2box.FragmentListBox(offset, length, reference) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + flst = glymur.jp2box.FragmentListBox(offset, length, reference) with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: flst.write(tfile) def test_ftbl_boxes_empty(self): """A fragment table box must have at least one child box.""" - ftbl = glymur.jp2box.FragmentTableBox() + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + ftbl = glymur.jp2box.FragmentTableBox() with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: ftbl.write(tfile) From cf4317df57190dd33711b0ade9929eac48bec4da Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 19 Mar 2014 20:14:46 -0400 Subject: [PATCH 167/326] Added Colour group box support. #188 --- glymur/jp2box.py | 70 ++++++++++++++++++++++++++++++++++ glymur/test/test_jp2box_jpx.py | 43 +++++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index ea8ca62..969ed2c 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -754,6 +754,75 @@ class CodestreamHeaderBox(Jp2kBox): return box +class ColourGroupBox(Jp2kBox): + """Container for colour group box information. + + Attributes + ---------- + box_id : str + 4-character identifier for the box. + length : int + length of the box in bytes. + offset : int + offset of the box from the start of the file. + longname : str + more verbose description of the box. + box : list + List of boxes contained in this superbox. + """ + def __init__(self, box=None, length=0, offset=-1): + Jp2kBox.__init__(self, box_id='cgrp', longname='Colour Group') + self.length = length + self.offset = offset + self.box = box if box is not None else [] + + def __repr__(self): + msg = "glymur.jp2box.ColourGroupBox(box={0})".format(self.box) + return msg + + def __str__(self): + msg = self._str_superbox() + return msg + + def _validate(self, writing=True): + """Verify that the box obeys the specifications.""" + if any([box.box_id != 'colr' for box in self.box]): + msg = "Colour group boxes can only contain colour specification " + msg += "boxes." + self._dispatch_validation_error(msg, writing=writing) + + def write(self, fptr): + """Write an association box to file. + """ + self._validate(writing=True) + self._write_superbox(fptr) + + @staticmethod + def parse(fptr, offset, length): + """Parse colour group box. + + Parameters + ---------- + fptr : file + Open file object. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + ColourGroupBox instance + """ + box = ColourGroupBox(length=length, offset=offset) + + # The colour group box is a superbox, so go ahead and parse its + # child boxes. + box.box = box.parse_superbox(fptr) + + return box + + class CompositingLayerHeaderBox(Jp2kBox): """Container for compositing layer header box information. @@ -3101,6 +3170,7 @@ class UUIDBox(Jp2kBox): _BOX_WITH_ID = { b'asoc': AssociationBox, b'cdef': ChannelDefinitionBox, + b'cgrp': ColourGroupBox, b'cmap': ComponentMappingBox, b'colr': ColourSpecificationBox, b'dtbl': DataReferenceBox, diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 65fb925..75b9792 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -16,6 +16,7 @@ import glymur from glymur import Jp2k from glymur.jp2box import DataEntryURLBox, FileTypeBox, JPEG2000SignatureBox from glymur.jp2box import DataReferenceBox, FragmentListBox, FragmentTableBox +from glymur.jp2box import ColourSpecificationBox @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") class TestJPXWrap(unittest.TestCase): @@ -109,6 +110,48 @@ class TestJPXWrap(unittest.TestCase): with self.assertRaises(IOError): jp2.wrap(tfile.name, boxes=boxes) + def test_cgrp(self): + """Write a color group box.""" + jp2 = Jp2k(self.jp2file) + boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] + + # The ftyp box must be modified to jpx. + boxes[1].brand = 'jpx ' + boxes[1].compatibility_list = ['jp2 ', 'jpxb'] + + colr_rgb = ColourSpecificationBox(colorspace=glymur.core.SRGB) + colr_gr = ColourSpecificationBox(colorspace=glymur.core.GREYSCALE) + box = [colr_rgb, colr_gr] + + cgrp = glymur.jp2box.ColourGroupBox(box=box) + boxes.append(cgrp) + + with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: + jpx = jp2.wrap(tfile.name, boxes=boxes) + + self.assertEqual(jpx.box[-1].box_id, 'cgrp') + self.assertEqual(jpx.box[-1].box[0].box_id, 'colr') + self.assertEqual(jpx.box[-1].box[1].box_id, 'colr') + + def test_cgrp_neg(self): + """Can't write a cgrp with anything but colr sub boxes""" + jp2 = Jp2k(self.jp2file) + boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] + + # The ftyp box must be modified to jpx. + boxes[1].brand = 'jpx ' + boxes[1].compatibility_list = ['jp2 ', 'jpxb'] + + lblb = glymur.jp2box.LabelBox("Just a test") + box = [lblb] + + cgrp = glymur.jp2box.ColourGroupBox(box=box) + boxes.append(cgrp) + + with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: + with self.assertRaises(IOError): + jpx = jp2.wrap(tfile.name, boxes=boxes) + def test_ftbl(self): """Write a fragment table box.""" # Add a negative test where offset < 0 From 6060ccd37d0c73b56daff4194641bc9e071157f4 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 20 Mar 2014 09:10:24 -0400 Subject: [PATCH 168/326] Readded jpch, jplh write support. #188 --- glymur/jp2box.py | 12 +++++++++++- glymur/test/test_jp2box_jpx.py | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 969ed2c..f1409ee 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -728,6 +728,11 @@ class CodestreamHeaderBox(Jp2kBox): msg = self._str_superbox() return msg + def write(self, fptr): + """Write a codestream header box to file. + """ + self._write_superbox(fptr) + @staticmethod def parse(fptr, offset, length): """Parse codestream header box. @@ -792,7 +797,7 @@ class ColourGroupBox(Jp2kBox): self._dispatch_validation_error(msg, writing=writing) def write(self, fptr): - """Write an association box to file. + """Write a colour group box to file. """ self._validate(writing=True) self._write_superbox(fptr) @@ -855,6 +860,11 @@ class CompositingLayerHeaderBox(Jp2kBox): msg = self._str_superbox() return msg + def write(self, fptr): + """Write a compositing layer header box to file. + """ + self._write_superbox(fptr) + @staticmethod def parse(fptr, offset, length): """Parse compositing layer header box. diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 75b9792..1f29524 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -110,6 +110,26 @@ class TestJPXWrap(unittest.TestCase): with self.assertRaises(IOError): jp2.wrap(tfile.name, boxes=boxes) + def test_jpch_jplh(self): + """Write a codestream header, compositing layer header box.""" + jp2 = Jp2k(self.jp2file) + boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] + + # The ftyp box must be modified to jpx. + boxes[1].brand = 'jpx ' + boxes[1].compatibility_list = ['jp2 ', 'jpxb'] + + jpch = glymur.jp2box.CodestreamHeaderBox() + boxes.append(jpch) + jplh = glymur.jp2box.CompositingLayerHeaderBox() + boxes.append(jplh) + + with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: + jpx = jp2.wrap(tfile.name, boxes=boxes) + + self.assertEqual(jpx.box[-2].box_id, 'jpch') + self.assertEqual(jpx.box[-1].box_id, 'jplh') + def test_cgrp(self): """Write a color group box.""" jp2 = Jp2k(self.jp2file) From 992cd5b205f4766ade1af67513ad9ff3b65da73f Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 22 Mar 2014 20:49:04 -0400 Subject: [PATCH 169/326] not explicitly encoding non-superbox IDs upon write anymore. #204 --- glymur/jp2box.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index f1409ee..dc1041b 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -402,7 +402,7 @@ class ColourSpecificationBox(Jp2kBox): self._write_validate() length = 15 if self.icc_profile is None else 11 + len(self.icc_profile) fptr.write(struct.pack('>I', length)) - fptr.write('colr'.encode()) + fptr.write(b'colr') read_buffer = struct.pack('>BBBI', self.method, @@ -656,7 +656,7 @@ class ChannelDefinitionBox(Jp2kBox): self._validate(writing=True) num_components = len(self.association) fptr.write(struct.pack('>I', 8 + 2 + num_components * 6)) - fptr.write('cdef'.encode('utf-8')) + fptr.write(b'cdef') fptr.write(struct.pack('>H', num_components)) for j in range(num_components): fptr.write(struct.pack('>' + 'H' * 3, @@ -946,7 +946,7 @@ class ComponentMappingBox(Jp2kBox): """Write a Component Mapping box to file. """ length = 8 + 4 * len(self.component_index) - write_buffer = struct.pack('>I4s', length, self.box_id.encode()) + write_buffer = struct.pack('>I4s', length, b'cmap') fptr.write(write_buffer) for j in range(len(self.component_index)): @@ -1100,7 +1100,7 @@ class DataReferenceBox(Jp2kBox): # Very similar to the say a superbox is written. orig_pos = fptr.tell() fptr.write(struct.pack('>I', 0)) - fptr.write(self.box_id.encode()) + fptr.write(b'dtbl') # Write the number of data entry url boxes. write_buffer = struct.pack('>H', len(self.DR)) @@ -1237,7 +1237,7 @@ class FileTypeBox(Jp2kBox): self._validate(writing=True) length = 16 + 4*len(self.compatibility_list) fptr.write(struct.pack('>I', length)) - fptr.write('ftyp'.encode()) + fptr.write(b'ftyp') fptr.write(self.brand.encode()) fptr.write(struct.pack('>I', self.minor_version)) @@ -1350,7 +1350,7 @@ class FragmentListBox(Jp2kBox): num_items = len(self.fragment_offset) length = 8 + 2 + num_items * 14 fptr.write(struct.pack('>I', length)) - fptr.write(self.box_id.encode()) + fptr.write(b'flst') fptr.write(struct.pack('>H', num_items)) for j in range(num_items): write_buffer = struct.pack('>QIH', @@ -1596,7 +1596,7 @@ class ImageHeaderBox(Jp2kBox): """Write an Image Header box to file. """ fptr.write(struct.pack('>I', 22)) - fptr.write('ihdr'.encode()) + fptr.write(b'ihdr') # signedness and bps are stored together in a single byte bit_depth_signedness = 0x80 if self.signed else 0x00 @@ -1811,7 +1811,7 @@ class JPEG2000SignatureBox(Jp2kBox): """Write a JPEG 2000 Signature box to file. """ fptr.write(struct.pack('>I', 12)) - fptr.write(self.box_id.encode()) + fptr.write(b'jP ') fptr.write(struct.pack('>BBBB', *self.signature)) @staticmethod @@ -1897,8 +1897,7 @@ class PaletteBox(Jp2kBox): box_length = 8 + 3 + self.palette.shape[1] + bytes_per_palette # Write the usual header. - write_buffer = struct.pack('>I4s', - int(box_length), self.box_id.encode()) + write_buffer = struct.pack('>I4s', int(box_length), b'pclr') fptr.write(write_buffer) write_buffer = struct.pack('>HB', self.palette.shape[0], @@ -2541,7 +2540,7 @@ class LabelBox(Jp2kBox): """ length = 8 + len(self.label.encode()) fptr.write(struct.pack('>I', length)) - fptr.write(self.box_id.encode()) + fptr.write(b'lbl ') fptr.write(self.label.encode()) @staticmethod @@ -2618,7 +2617,7 @@ class NumberListBox(Jp2kBox): @staticmethod def parse(fptr, offset, length): - """Parse Label box. + """Parse number list box. Parameters ---------- @@ -2644,7 +2643,7 @@ class NumberListBox(Jp2kBox): """Write a NumberList box to file. """ fptr.write(struct.pack('>I', len(self.associations) * 4 + 8)) - fptr.write(self.box_id.encode()) + fptr.write(b'nlst') fmt = '>' + 'I' * len(self.associations) write_buffer = struct.pack(fmt, *self.associations) @@ -2718,7 +2717,7 @@ class XMLBox(Jp2kBox): read_buffer = ET.tostring(self.xml.getroot(), encoding='utf-8') fptr.write(struct.pack('>I', len(read_buffer) + 8)) - fptr.write(self.box_id.encode()) + fptr.write(b'xml ') fptr.write(read_buffer) @staticmethod @@ -2946,7 +2945,7 @@ class DataEntryURLBox(Jp2kBox): length = 8 + 1 + 3 + len(url.encode()) write_buffer = struct.pack('>I4sBBBB', - length, self.box_id.encode(), + length, b'url ', self.version, self.flag[0], self.flag[1], self.flag[2]) fptr.write(write_buffer) From 9399ef8496b78c714dddf3207e06f07d2c3135d8 Mon Sep 17 00:00:00 2001 From: John Evans Date: Sun, 23 Mar 2014 11:33:03 -0400 Subject: [PATCH 170/326] Adjusted tests for newer JPX file. #205 --- glymur/data/12-v6.4.jpx | Bin 695609 -> 0 bytes glymur/data/__init__.py | 4 ++-- glymur/data/heliov.jpx | Bin 0 -> 1399071 bytes glymur/test/test_jp2box.py | 5 +++-- glymur/test/test_jp2box_jpx.py | 21 ++++++++------------ glymur/test/test_jp2k.py | 35 +++++++++++++++++---------------- glymur/test/test_printing.py | 4 ++-- 7 files changed, 33 insertions(+), 36 deletions(-) delete mode 100644 glymur/data/12-v6.4.jpx create mode 100644 glymur/data/heliov.jpx diff --git a/glymur/data/12-v6.4.jpx b/glymur/data/12-v6.4.jpx deleted file mode 100644 index a3e0c60e09d66f88581daabd4ae4c9f077b16455..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 695609 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 z zt@y}xK*S|qDL39^4iRlOv!izrhBNS&&J6X7&`*h#a4#B-PqJoR1iB;Q#Fh)uBz@Wd zWV3Rr{<#ejVZwwX>P=~AB{&;D&VG9)6Hy}FK^;EEl#!_d;+TU_6^CMf^?lR=$1CXC zbKSBi*0Lf!t_bpW&pM@p8Ckqg;o(FSX3WSz4)9H4faF|cc?}?juhG9;3|MxECXPPQu4KvI8FtCQFOg~X?bqMJ71tO2E}+ZTTdOEqK9Mt z^B&EauXU;RzCWgRsRnoiXkHN-+BeE$L6b6N4k)cfR|J!q-F6IT(H|LRE5Xy~@p&bo zC&&n}bE5>)g*X8Uj%uVb#ZsRif;f!&Uh_T-o7=^sYNw+k#rZ?$?DkRJj!2j-E;#ZS zKh*;1Ps|u9xFlsU*WdVITBb5bOoTx$3H!rqHUD&N=-IVqIH&#*tti7at@ej7!C3_<&YbiL-^{cu$GP{7FcdkIoES7kVkFQYyruS)* zJ?cGZQb!N-jyWia&l+)2Qul5|l4bv7#Se#AO>p^enJy?TQG*&BUG8ehMFjS>6i@>H zaz^bo+C9+`0j3_bz*oKMThGW?{Eaf*B}Kz(owooOqDSYgC2^^xy?uiQ=mYVWQJXkE z?-IY_cRCzU#MlIkk}47xu^J5awFBm4YFbOFE^!y!HNOLu-xo~9jTiagf5n)<hHkY5#qQ?voOl5!1z6dKwX&i^0gg9Pq9L|>7M%}R3ZtnoD=x|odGcgVn@m_ zbx}HI4cHV_L$X(KPp)o<5hsorwK#Di4kuvbO(o&@h64{{E0_BYJyx*PvyP6Z!Z~PA zEl0))J?&4JX_-eQrFXxEo58w+|%>!(TE5?RtW^P3ehl|t)MmQx!Ac#R7yM{RE#{@*sa`# zJ|;-^<0}4Cy!g78i!cCH8cfjzgz;Cx#5-vsTY@^n%BpDSaP-PR-Qnglz&5HF8jD}o&mXd z7)5Ha%q})fb|{*oLjPBtTsgk)t$-441Aal0m1nj|3-P9Jv$L%3YDTnV*&=u7#M)~X zp}g^kkz>qw+nAeyY!pAs$g62eI2N19p=(`%eg8=J-~qo?czAy?hBp=UU3+fVesItC zmyXks;=30|TY_}Fv~U~)w8rD&-0C4C(VH>j($_WFMcic!YqS;dTgtw}MRZQ>m3Mx8 z(lA5CBQ_4bg*7O1IS8W_EIlbBZ)Xl$lF}5~#kl|=Q+LdL8}9&zJ3ZqT0Gx+OAG(z8w3ic_PS3pH2Bi(rLEq&C+yz&GVgM2#X9 z20npK9=?K1Wbjr~!;f>g=PrjH;_lHEuCk^PjgSO(qk1~avlbRZ;g7}CcmGJ*GYqqb z4DDCUk9_iuvZWZ=E-lYhJXGsh_RZ%2+V|nRNx*Tdl=Pe0@XcbO+sY3v~b-{5g zZslPYz|R1vP~Vq$4H13mDtb05E`$;7K18bL3xz-Nb6^YdXv5fVj${HAl3`F;d^7>q zWS!E=Yzs6S{e)*IHuY-y{|tv=L}+rUV%)$Lj`M}nXK&_@om&=?-WT=1ZvO%?EPK4ai@H6 zbK0H~FzZv7WyV2OSov!M}D@Ei$6H(`)Bd?K*QS#gmtdyS&($jMJjuu%aSqA!_A(l#i^t5u&*T1_#Z`6UtIGtCLd%)xqo;l94G88j>#__*cenyg&sD>a#*jwsO#XuO2KJ zl>XTVGc;xj7O(1W@o84Ytu&Z~=5&u)$xL@`{@z@ELkEFpUGz6p-UB6&acO3=EhY7K zF#vcu$EGl2R={sTcY%8fq$ih$_inK$=DpI#7fv>HVEDf$(854CEz9@)bY;kgmxR3d zU91E%hTYH8+~mRW-p?UpqQ6<^#^x5X6r##H9jRR>H6iH`f78m0O~J;*z%38e_pABf zy-bo-Ch~B|xsmF4?Tw>6m(ty06<-TlwXfi01kk?}FX_%bYu1_?YWcyq<>H{UR!X(4 zumUAqqjPy%G&sHFdh|GVdYIP!f&2sp7Zp6I7j?X?Oc?Njslbz7i?>iDAuVmSWua0! zIp~Ai!@D&e;fk!IBTEuejXdD8oig&fC<+h9%5s?+!2-ywuZ2DGR zm%=(v;$W|>-z=u^s|A~26n7>xqB_AW<94#W;W2Mz)h>FGDOxs81-7#RtcKMluNXm! zmN_X>_|8V<8bNTIc(a=))Z?#}>NNNF;Kkikjco{S1qsJ7O@aCE-e}OLPNs0S zHsM`vqrq?HB;5S*3$OxEHO$YYs2t8Eje@>&y?N_%YF`HM&@AL`?_Ii&*^SLYe)KFaw2Zfvog{;Ga8Ra3m_Cam7X%&tzu zj)lXqY6iAGHZ)h;YH!O~zUbQ_hs>Q~?A%C1#}zYP(!u-KswAYyuE9;1D-L6fUy5AF)Pd2%B4BZ%9;%#b zHRId6*T%`mxcLEB#V#>C9?dy>LNFrzTU(PgYy(4Z4;wK7mI>9or#9AIe#9 z<%yC`0OEcC_}t*wvS}*M<{?pU`lbNN4-Wc!_6Mff9!&(RnYf!2oIp9)kJEHZMn=uQ zvyX|iH%htDuL%|&w>RmkZb0v;UmUFPnrIxf=X_VOL--#XH9mH$B`pe5l7c|u(e+8< ze+qjQzQBQRQM~lQ)_Ull360?lCx7k3Gxzp|lJdr8$&E^mGh~B*gGcF8%0dC@Y{$;v zCG0c=z=Iiy1+Fhn7I2h8l1$xzHXH>K$6=bVxuTFp;!`uF@JLG8FJb$ja95-GE-LLg zo4)HN?&_SkBZZb^ixWnECajuRKvOngQwcO50}Eo{PU&y7b9DRhd6k}0-yA|w08$}4MgN{?jLGgI(o|yBrv=WkU$@-Mj(K|;^23$KHN$ zcSib+Vo?UmzyT@H(lY|*)SQzW-$t%tlV%s2NWjz8Z(8$2ve8I^!`~HGpv@9~ELbKf(OOcQ$)%@qNKhm`e47 zr5rw}Q*(iWln&ox*AHfjH}pg0uCeitolC2KzG`|z!!Nvj!`pFBKkWudp5|wx^JEsTxQR^H zQwNf1d1t9Cir-lIhyr-Up2!bw6XB4B?Vkr)A3Kf}+}$H(IjeAIEwsVNGF;#;fi(P! z7$o$<(91)DBYIKH@RSVmzo{^^95gNeD&vcdS)+1vk1*y#LDz_^`R7+%q$cCqJ~rLU zax}cBM`*aT-80%Pq#t()En_;roWJo-(o$G%_a$_91qmPb_m3{qI&(?&;%dz1h=BK! z$S-$-g%D*{Fz@t%zFg6ORpE4sS=&oz!z4_v9@oBdPDBq*#q~hB$1QZ$?@t2_QS0=n zLGLyz)<;mU^?8m~bx=NxeYBPVFm}tBRMHL=X(7?oKg~!R%;xxOGDS`JP`|urKCae? zaS?KW%rB34NT1IjX1n*(XRxUIT4Szut~c{j>tX+H%94;_Gt=f2!IZ1iZ0w{?z{Q&+ z#9YFSc#8fS(zb(D=t12)|)9C zyOiar)2UQLb3%(q7on$`9-qJ2+g}M%c*ttFwXaR?9F~%houC5}_!%iiAN;;>MBhHl zzq9WNWbw}vJy##UD=Z3b#SXyU)BfSw`U2#zUdRsHV2=Lrt}6@r2A0#qu>7`U;qd>{ZHWPCTZMq+vfa9 z0M&7iM`)cw<7dw7d4$Al?H_IY+GA^p_Q+zxIS@NkK$|P;UxP_%;ZBtkzqKD70&Tvl zJtNiV?p6**sB-a|U7s-IQWeopiLSV-NTl!Fa5^9CEhZv~=LIEMb7NQ%$aOh$tv4 zu3-Gs-ZA#k7Pg<$#2=8aYO&AiiI#((gS;cyz@vG>B zuXH^LlW3VD@zB7kQtPIy0TpbSBOM7u&e|biC=CLqvJKP06*Uyv2csl3#*M2Kx6h=Y zp5@qh8G+crYA_^R2AWaTp5;m7&%$F0pqQL8B(ioCrxm$US|w&lj-F_E@OWMw@8q zdPM}RcMZpP9tlMz#`Tcas8W04fZ@s$No}l_S~OjfX)9qLqjLL)L{Lqi9pblm(MQS5HiTgK_GZwJwvrR5aDTUPU zfE#}#tL}|aBKIRH@q8$e3f+-ka)hv*s$svGRx=;p&uO~ngU8)wdRst=8vjpiEH^%IrmGHRXUpU%R<&4-4|efmJ^W9 zGyw@I**o$h9z5=}w?!H%U59oxcPN`dDlB`HkZrUJk3x5PMU$<`w;Zxz9J?pvzcZAMwf3*qg@V(mxdD|V&NZQmY?smEB$kJoW1Zj&8){nm zdjNTKan_DKXy=7#OJJWgQY+q}P-|sbh^IoE2&eGh^-fdB?2{T_>|Su%uSDex7y>&} zu4=6(d})D?fR0k?>h{v`10)~VFCkL8@kk%XF^{~L-_z>-K0VMs56QHkOZq+@=_?8U zDDsibL<}Bb1yGSK%*43K(tJDQ>oKem=z175mLV(1ZT+FRac*_3u+ZLUr4}Tfq5;th z=A9beW4+IiZ)`)?*naM${m?g%R(92`C%|WZ6rX;tnr0cw18T15 zUuFr-JYUlh^dFXS-zHmfDfeo{SByU{FeCkJO^v`@F3-5q(*;rGfD?fIvcgGO?(S>) z{DvVnR=1jn)9@KnrBz;WCzF6rgb^HwggJqk8oR+oqpIAWJk5lFEov1wc=n6oy0qD^ zqdrjYdUO?M06bpbty4_8si3;Zw?lY*hpbhm@hza+n((IGoknZPF{H|KHtpXZtsu*t zZ%J9q*^&Oh>!5U_YD`x*Ita}*CnrMd$eFS^lhTkuffsg)H&r4S<~NJ|2MyGdOSY|t z-EJ8|hKIAF+aT({>#fZee)yRcA?iUZayM8A;yohKyHpJBQgc?=(sjI|a6kGhM^AcR zg=+FwJ5S$hQS=CSeIgjGeqF?5zLqr#W3AM}1K+u}B1OyMC7fXc9{zyU-x*cqS5=Hw ztGoA$*#KA_>J>KO(Y%K=p9;fP?0jYTx9lMpbzM5TsUQGxLf=S2lRe9}anMtTlnjn{ zM7V8Rm7l%i}t{56X=fYOZvE=Fm~>2f`n zHYT%8S5Q@Ok2zca6K4oYCted9w7FQ#_vED;K4%!8rmfx=jCGJ5DBoHiYR=m;}{BJ|I(QD%Veg@2!% zR`<&ecW|O9fwjgaP|1|6st%|$xuwbkKQkNky~L~m+(H?ZSk>;*>z~Z;p%jvYND?E5b7>)Mw~@kZwsVZD@jJHBhy?h4E5e-$9RzseKq-8O zxht5q;!B5@Yg(|aZuGbg)iTVZQdfJ1DfZ+V&OK8u<(TG6h;o$Q{pMH#L_+O%7na+* z`WeRAK_2C(mLu^LGUkg+5-%%J5Y4kKh~!#RIG?Aa)de(9DVU!DTbf8_QSKg6Sqwn9zVJ^8qw@ChIQ@A zT8Gw%@rP$#rVEn2MFZveAn0~pKv%z_>J?0-{84_<=Q1efTFLhVug&uFz)>XsR;AwT zCOJI50-GlRmFD^!^LI~BO12*cXND0L$5Z@wYIx1vVw`*rN5V$Kv(XOjWPxN9JiQS) z`do~|ra5rY$N3FJya<3TEucUZmxUD=Ik1W8umy;pkxbw3afKGV1(qmv*`NF%dKr@s z2sQN%8boV+-p=ake50g`Mii%_Dq}ny{+R1hn))7+&kWhzw&l12n#mBUG(vS-fvz`c z?LF-{a^;c1;^liQOST8StGH=^GgV)_Wjz-Y#w=V7MUnElY;)=qBqoVv0fCmNbt3D$ zh$3(k1Ul*&Hs;(bBVd_7QUSk%Oj#0^ThLl6HhH!OEo}ym*B^wrpkDcX+7$aQzCfkM zD2@02sTPems%BSgP>1BGQ*oZg-<`=ga4^IkH|!F>IC~`m?GK96r~7;Yt`YQ+K7fNh z)=eQ#ubkuu-dn<^YJ%Ck1q9fP0r^1{QKMzm^9%EsiFEX3lnRRb2=_zf0ib4L8xo-@ zl|IDWGC$Sua_rzb1PQ%6{W|Lkb>6!^1$$Vr9#<8R)Kv0^rvyWW|3g?UTn26z&bS2fI;w^3S=`B6CWlHN5IVJ;PHMpyVey!f@sQaOO`R0sQSh`T%k zw{aQNPG{nT_O=qI1>Wv4G(-)am zZ&CoN(%e*o&4g;jcr6>`ov9!UFK zL=tD+-^IEOVs{i7M)*tJA0j*E)QxZO5y$BO66MVW4R@9aqlw#&jS z`3S6#DFO4z(>~i1J@{e1?`+LkCDxmJQ^wVsa4YnKCjP-2rAEp?X{`urq3e*p{+uyq z@qzv2Bn5{zMWP}+WhUd#7r74Ix*`Ebfm%F5!@~+4p2M(Jm?BIz)xmoP#d~xOFAET&IV%4 zYTbZ>e}vJ82!9+YI_8~60psJ+`m>WqCkOFu%Ud-U`yidf{AH;A`BJB4!qX` zC3qjW)9^s~d7#s^1iO&g85JqV$H1R)^+7lUqq>0lm}c~PVQ24r zT=@a{V4KCgi{_2plJDe@=db`=yX7v^QO2%u&dDz?m|j7aT55?W>Tct05p+}`ut~7q zxb>{60f})l$nA{|SDPWk+v{)tSsEz>j3Lo)UxVgzXRJDY5(aH2FyWYjY*kd;8Iv#y7)mM@D1eCH|$zfW3d}{oPAEcC@R^{_*-$1fx_oLbw)$cN)HdJKsqQ89V9mMjrEIRten|2@xn zi->FgfD^VxW`rAgrhzXDcasL;iCha3wHk~dyoG2X)G+#XtHre@Ifm(@Ftw8I{$lG-M0TqEP%8oL{EBSq}>v(dycJg zGJpu&(6y}WH(1dMGmpIMBcr6Jh&QlV#P+&P(4v7rvv8M@BHZ?fyGI{isk(}s#HJeF zCKX;SPfDqrZ8g*S76Vwr!U9%qbmus%N3^`i|8NrDJTBl-i@uLil(3`4Etmaj>t9{a z7eEHDaW>HBe7O)WFHd`gzT0vQc9OD^d*6U%rd!)+ERB2E-uf>}x~VRD#O~PH|9-qD zFFV4@RI-@*8ogstU}(Y~=PmsJK$MHzayF9bDB!6DwJI`35P1;b_2}$g4oX5B5)JA_ z>7hKia0$M}t{Z~ukE7IYzq8RbKKi(NCj=w(d9_qTlm<)vnjrTeRwStE>CHX6l=hh0 zcJ$Vc8FfhSkdY*(>j~2^iSTHR)_S+Umdf&f-^~3I{LPe3=(^)pR6F>!zK7h_vM$KG zSX2|*x_-)3hGmA7 zRHS8kXE77|jgyj3EXb(DWa86|r?nElfwUk6(Trzi<=Ne8r(n^J0rEt07-?}1vfXnvcHzKP!$|0gc*f!e zMJ1=?Lsc)YSPCCynB`a6JvFiI0&D5ZoVSh{qvd3J_QNF~%N_E_!2GOZs}Ij}2~9nLYI{b zgmqLKiGV9ierTGcAAlu8tcdGnZm!PbN?1pFS?|Cto=DSYLHS zR+u2|^Ci@Ja7lvayakxNvO5W$22?KAt04mpS-bS9rI0-a_qC{oM?IPJPKQnL_V^*? z-u~0#|2ibgvO3$Np{&z@E1hk5oy_>UQ%$6sido}G8)6$^E26kl725OH0m#C}|9Z>? zReE9`%o|yH`7Z9L19r_L$uxAdx z?65ce8-;lcQD8(}(lErmdMgEY;q@(zGC~}$VO|vXHn{OWUvo)f>Vd_v6#rS}`mYw; z{nWM#BT7-EvXZOno}LY?o!;SB!&H6N$;97tJ*=n8l{0vwm#uP)VFNMxm~0RHZv*3} zP;d9xr>3y_afB7>(zO35_j*pDTM2&n4L3HZDe!o4N0J7V1vzii1+%Ig0{R^AOftd* z+_wWh1E!U~>{I zpf=YY2J~b_4fTN7-?M=!;$$Ne1M{P9s+nbD`_)iL*G!i(ol3V`(fw&VWa8tVHOO)V z3n2y}sSDwsuzD+;XJOzCl%5JE`WPc4_voXQLG)&R7I)#h;(NxwQg7o}n0WqIB=0u|oU~HnSXS)r z3HEpoe#|R>-&|IwKHXZR7VJ?C_&@EESqrOBTC4T}1jIotl*(-CFsm*^XUkFG5(9BR z8LPn>cy}a+TqobvyK;Wev3DAXKrOaftB7vX}n6^5>CAo){_|cE3Fs`OX^{B&8(P%gwJO z^kPY5k5Sh`2_d_h?3vRo>AJ)#A!Q1~aAn?xpXjb8&d|e2(y?z2aA=;eVmadc)A@7K zL#cJ)=7X{I)uvT(Dp}s{x*ivT{jcOGMf=)6bdB9^AuU8nLfr*^ldP~rF0iJ9<~wg$ zY%muEd$p`$lln*7~xfLK~)vM(8QXG&Ebu1Z(v~(%v2% zz@$v9Z|UoET2L28cKOb><+^@veFSdQDX}bbfh%bLCk>V-XD z8&eoXb^sjq5FQ_#5j1z#*6Ozjrqwcl|6wDS5dv(lcW9x0P}^1FDM^^f@J5cMmikY# zsj)!6|2SMxvNnBp@cEJKGP03?zTL zvQqwSS>{&0V1GM5b?*~Pi9QBQZ;m`#~;P586 z-%%V?QCZP`y6YLNe=YEo%fK`TfmNUNsowIg(FDFycV%eyJMH@)^Sbecx`IpCW5lKk zX9%G&#Lm=1IZz^S%b;zv6doORCethA|=*Y@2{zU}%gOWW)b?^Ea8}6pNJqRJ^E7>Kb9GSfKV` zklBd0zy*E7*5?+a&5qLvE{T*vROBweT1}UUhsOF7#%pf|{A_D|mkeBPS;jl~ZATra z%YQcqhf|fd-CV4|%4$m!ByuQD)~HumTjK!%6cJY8@usRAO>@3T_>jk=^1nX*51oW= zbywSuk8SZ8&}J6E5`(L8ANl~+o(%1y?Rmg<@C?wK%`=qTE1&qqWCBHL=%(9I^4>Sh z3)89zNO<~!gJm4n2ueLJxd=J_SL<7T1wYDK63K&XR>n1;Uc6r}Bf&cm>qRX7&I7Jm zbW@%$wooLf)Gs{6Tc1W0T47!FhtXz3xgdg72TX(z^aGcqjXhiqxcNYOQjc;G4ejEn zNF-S(O}-6q?%#Y(id9s`?lnt`z!r1T4vcS?WsX?@MXg@jcA$r_GR;@IjYwB~ORs z=MpErzyD$|WnUAu)r(BK{Eb=suljgscha{&7|9a_W#pY*WGK3){I+MR7KV&Ny`!yj z;<9L@2hRh|+EA%)1mIjKY+?&s=fEQs`f7~hz{bL68nWeX@c1Jnls%>ep8 zFf-ocaWJ6NTCezEjV1HQ9Q$js$CB8O`?DpPlzFfQ*FqBlV17BPkO*G45wB{O4e_p) zeAOREw$tM%k&ebC9Iz;js*x|(1a6 zCr$l)l(F%wY1G?n(UT+vVf*nnOeAEjC)GxATdeBE~c9~)Ou!HP|Y#2aO0AZ3_lhtqPba#JAt)nrW!~SL{iRY z(8rys(VQ$iGg8BBB398hh6mt>P?OqRr+!MPl^R^1lI$c*vKg%IDKkQHgC_$*`(>Z< z^%258$IKXuKD*4ogn)p`sHELp4b~8YR*A|OJ7CQ-1>myK=P z8{5vtwr$(|V%yl*#>Td@vAMBrygdB${)P8$*W5cZUDda1YO1E{O!qmj3Bu_o|2Sk% z(YzP<{@EGeu&IJKE4&9)lydEd-|Nx9fNGvyWRv#FL*GDgl8G-r;5+)v#S?y5e650j zfIwL}7+d`JYGVNcnx0_gz-;^_|D_1>^ZPCc*#8{=dH$c{e_aD00e~+h5EKCDw+9^T z+w1;c<4XsGRM?%!?fTn#zX=9G>~_o*zRf}b{M*FU=fBP9%LD`)1fVSWON2`8EO!&9&79q z<{IG=fe9oH(hir46AZNvwhD8IMGCbG)(l6Bg$_gx72D~56eyM~iS zQiq8I^M!*($^eOiNkW{WNq|rhtg#vqWLBY05puC$K)4X`Fw0mDx-idZmefIgl3szym4(02)Ea0POD^{Jvv3glPcaNq{;4ETbSJR%#$&ARE5i z_pA>A>jJa};OPWH0(AI-!2kfDeHaKV6)Vsl0Kfw3LPC21O#w1M>|kL4ygJYl07V59 z12|ZR3Vr8-5iSBi6AcstK=T5f0Z0-+t?x{Lt^jCqpe+Ev7U&N^46=xV2LLrgp<}QC zh=IQ%aKD3MgeU_5ia}@q2*yBF06;#-3g8Na3G)O14MMDAl>k9<-<|3DfFOks$5;>m zP$QHu@)rOQhW70O@`S*~m;#_fT)&HafIowMBfp~~hCqc`{?{A&|I`1KC-9XtGtuUN zv>{A!y9G(e=plGK%(m#C-a34oM$*)5=^CTZrx-;H6zChL7z*w~%4Xg5KA?g87Y?c+fYvVrbmxtfO0h+@_o82U{i!}?5bl7&6J{m!?*jukl?~@ zI(0Nq=#PJTS={ke77W6Pva{p`cK9H5anmt=Pq8c^b&ge>)SJ8@hNZtcH24hM2>G9y zsBJDI<>HcE^GjTZWg&^rlliyuVa5|-sm17RO_5d&+O#74JIFkvu#M8q5GIgRi5h49 z&N#jwbdkRavK27&(?Zv(TtR6JoV?#YZHXyR6yyjakE%hYe_U+uU`Tv{-(nV|)5vzaCsk3Vd^BN z$5}Yc418nsR|}lWFBSz}cZn@|4T|J*BE;dsoOa9jVLcw>w4OP102}XlJQ(zw2#s^PANBkAigLG*E zwEI&#t3Nek|6X+VWbf6uMW5Dk8{JTQTJ>S5!)r~*Q~bkgHF*8Q;a7q*DF7)488(3i zMy;z5D1$W4R_#d1v<_Xhy%!P!(1T=3Px&c=-1Gn6#2Q? z=oRe(F(6Tp8nVMd2(KezJiibm^V!ctyv0F73pns}xq9Sb_VTh$>n32Y6?c z8c?D?kSeoskGLGa5Ua^-@i8l9!U++paeLi*v9!9*5vySwW3c0eZHVsV<0V=?B2g@)}UK! z{kZ&+YUlY<52L?h$S~meXsH?4M&@WvR#+}o2ZMEDq9YDk4u$>8P|>JhJHvb$NOCNr z571ecQRBv68^2tkilw16qgT_(#1aY6v#{{Z6#X2V?D~R)l854(`iEjK+6Lxb^LjRL zxq`*tnAFh@NeH7xh@#C`o<(L$*bhFVxsnw z0=-xUC(}INgR}E)+!^0bo*CcLv1p2h93I3Hx-xp3aHxMub(&vzpA@jEM_{_NPa^mw z2i7lS{oG(8JnNvN+eq*r^?MizM6ZEwQIx2^q}z>+6{JHoQ_bLal~tA}k@i^@G~G+WMuf|%+E_b8CF8QOIT+nv z$4XnGrJ_U6abY;@$8QiaHk$6!DmzlV;4(xxdOV=!ESm3I&qzXs8L}pJ%)#`T)u?e5 zf=ngk&xH+!K~7>Hqn&SRDbGK8f{fb0AHfYH-Q3qP>IrrnD+Hv0LXzRchjK-#Ov3&J zX(=FISTU49I= zhKT#p<1d-Rr@!eOXI~L6%mqIycKYPqz~*T2g>*{%}w z)DWP=KJX^7?oI_f`ss)ZXAIS78cqY~7bl-ttTD%_Qh{;HpG|VA1n7ef(~p?blQxPs z4duV;$**nTYpboiMzmya)HTK1{T1QRNgFMt88%V7Q_#Nvyv`xHh5OtTwQa-rR0r?u zY6x+L%+%7JE`>4S?GvH~@#{&D>BACP-54%YO%a#~H)|8XtRW~8?pa&Ibt)zA5fEM2 zRlU{YVO2x_uqtv^%xGZ_kCYOc09eVBiexb-JK=u$vF^YAKmYWp-bKNkn{B>*p)R>q z(XnHd(C_L2@SGzK_5<|3nHTxaI6OdkIpf|uqI8?EI+isT= zM}0yT+VqyKo``AOVGI=*O!Hs(F{Ls#-tW_0K zOx7rly3dt+tOL<``9ejEk|2!TyXkgeI(<-@$a966Adhke&Q40WrbL`g|Bma%n~B_? zN-B!YvD-#Xyr_47_!l^l9D^9R_7Mk!8U#aaSv{ERZm=Gu#r14b;&7Zl52SlV9lf1T zzTb)}da*`KD#Ap(^QJLZ9sa9ZeGYkTYjT^@kPXJMv57f}7u7vKo&=eiSNmCYwhbSh z_Iv^jdkEWRh+?4p#Ri6*Ddhe(N$G{qi=N@*nr-v_u?YpKD;z}pxstPOEplqZUx(pD z>0<35<@iQWGB2t`Pe$kt4!5|YnlK~hbRj5sY_R)bMzT_vhIzjeDM#|eDSNR%p9p`i7{&)P<0I{~7gv;93pCg^7 z|Gll2W3nu~AyW%NS{ANOPn8IkD8vQ09VNTnrzi5+m8w z(V=W%JtE^d3mx>DHl6<4GTr_N=;BB{ekQWxEr2a1N^^Xz^=-^3nmV@h@Y*}mx~9@E zIC6&8<+EX1B_x|;g*|TOH8t=bv1w;dKr{=Y1&WE^z}cl9V&TRwLh-7+>m-nlY8ot_ z^c;c4>fnlIgeNpR&tSGDTr1K27iygAmS5SwjH4s}^4^5w0kMq|B;$j6V@2>)^Tq-2B2W+MbTK7?NmJrmW zx;Yvjf;y_3EV4kA2az_{PEm@DdD)Quq@)&$7Gd}7=w=|X|ERPoj6h`sj}^R%KCbm^ zmK&nvtsc%|(sohmQQ)k9nN?7_Y>A&x9arMfLl;Rxf#W()LQLa(>7}iJndvwWU~*qK zk=Rslw)qx!rZ1!}e;=sF+p~3B0rsqEm}NgwJ6K$#QZl1aoEaAebkbC0vqn$Z72|FVUv}b> zSnjt`&3VBVte75eT$v+4ziif6A(cvqtQhvtnJ{-g3g-O~TzQm71TS2F zt3?sMiqa%Rk7*r}#ZS6Wm#0Tlfj#U5EI8d~h2Z8gWG#i9-ZektF)y(-1O$fhL7?&m z>|ZjL&H0eDFM%3>A!3enXf60UuDSt1@);5Yd19snEk;}B_igdN6_&(ObBGJ#ISrc= zQ`s65^8UE^hnH8oSt)W?ba?o_H*r|6m0b$wopm3i5TIUpM2hefyoS9loVaSHYOy22 zxP0mP`Qy8C!12h7e@qWLsx`|#yvE*}*I){jQK|)J)&+mrcdy~RaU(3IKCbQjEA=N6 z+t<2t$iqk!w^Wn8JM{&UP-wH`Fh>$r34|A4?DMt~yoT8t_dhB=S}i@i5LZ(u5X;9P z0mQHAX9{1sRnTZ+Osq*w?~7aE5fBw@v(UTnDb7V9!P*GEcrUwP;A=%@<2pqfnMiG3 zK_o6Q6+$i@0Pi)xA81x8cc(wS-u!Nb13>T#kAC()$iGzpS-h)?D3_9`DPKV4!O#Sf zE3st^R@6m&CpcJ)2hf7La(BGpnh#7t`TaR3B0(mDb9fdjW{;(J6-9AZ9zn@F359J{L zbEYc<%Zdn!5|+rt0Ts}W>T$v)hE{W{>+~ya5gW0gUvX6mZ;a1=o6=A(o0dz+iZ;g! zwjWQ=pI6~J9VD7>Fg3XlhM_+-hl2l7uQ?N<^4nIr@+!q!ec2n&)I-c6URXJv4J)n8 z3|@66%xUFMeCR?-S6z8wxm)ux#rR4~yjwjF1abnQm=fKa8SxIDw)!Rn*XY80Jyb1bKIJ@MJ-RtZlQ zkka(}@&NBJ`FZU4=lRlid(+p5cyb4M|LCa0`Rtdjj^O8JZ9jXuisqgzgtgY&xPEy5 z*-m*||LgH;T2?g^7lOCqwLR!F3JiybWVl!8pgrMsQN^J?k;nQ*xq!=f+wnhw#f9~C z?{SZm!wL>L%o9|Ruy$6pA4G3KN?zImk6#d7`Btrd&jD=i%pOcNl;a`}jv>-&t{=;d z`zI1GC7Zie{H~w8pk@jqym;;ph#PW~$m4qjFkb>9>KFn+9_ZU#ZvveOe__PlofrQ) zo36bhC_PU3q6Hhrt4=8t{OVu+F^)3at&RSkCEawK z=GZ8A*(%Cjp1jqB8t&AuH0fRau%4%74vps~Pz~d9yq!l;6oFbn^-Y>vK`L9}`(&U- z0Ke3uZDX1&xB05x({G!HOFrX}wWO+((`rxOx!zFz4&q$Vi}?uYM~L#*(gO=*b*yh- zaNN=_hkyTXJ;)QOVDOcjrQQrPC5`*;nY|49K7JwXdpc2DvIja3NfUR3R^1=Mt!u3< zbVQH<(>v=U3`2MlZpn^k$!JI>`Tej`O#i@E6$ri|3{=Z*h67EtLW6Sc2@(oXW*dEx zcT$e=+i1VXAu(8C9LV`f;c-a1v##!^O06gBFQtpxh0lMUt!cB-$E2h=QCkfUgG#EttK%+4`k1r`xY#>eviKGdJ@bYSw~ZtRBCDIT6z zL+Xx7>QPe;D0Y5riSx6{g(B@#S|hZk&}9^r(Av-3j~^b9GsBQ<$?Bf{R8Q(`DadM` z8y-G;YiYd9PKze9$nm==P&X3rojFcXPlkK@0C06L4XMbA88&(!ip3i9d~u9+2D>_@E-WF{s#TweIjrkeKgVuIeSz=xQ>&8D z4Xuq|J56&DO^_MDpiAQum>rhnZng`T)6RJ@sOmcH+EZ~4vS{;^*ZXGz=bIt6uLoss zyR^9_C7VkDf{C>e$1bulHK`Bz94gmF@xZBTEo*%#o?L#Su{Es(K}Nx#4?&=vo)rq} zv9Rdc(0?KR4)OxtVRWER3etUGwIBL6M zT9&1WymPMyBb8|3?jNnceQybqaP;Gvl2n{Sa=0>h48|F64C~9;+7qat8o>| zoJL0IzsvM>XnBb9B?m{3R}Wde#gsBO7JT)U)~!)mW%M|Acn`i@^Z0S|o<0~PS*MDt z4jwAlYNlX*oy>)>9o=Lhxu=IP*-aKzF&TJTuE;<52orrw;ms%H%|+%4b;0Z#W!APA zSh3!dv^lgH0)6%`vNF_%U%t6Jyqx_MsMn;GnGJm*G+VWz4b}(5EmUAv=vQxj;^Jqd zIfX4vrZ0Kz8j&W5=WR=#d_V1Tt_4&tL;BTDxiMKBqpydM) zyIs4dYjomkJ5qJ45(BRl9BEDzopXz`;ppW`5(YgpR7F_45Yz7hvN*<9K7h zQcV@%h#5Q~KPCYsEj&`p_QaO8%<$gAqHJLqP4VSBu)xV?lib@mf*aK~{qWXnL5aU7Ld2er zmaNWj5rJxo7Lc8^ywgcrlLYP0fSPtrgq0KLKmpANberlikWMzT*=vvK78HU z4Dz!>>g`_9u7!#JpLm4PoEJ8ZUL3503DjCZi%15f#1}(LUmUL11B-kh+-gI?&;8`O;-H)P$~AAQZMnC@7kti@!Wm7c{h!_yC?hZe&p4m>$Jqxe1v2v zQLFfDw{p42z!X2jAn_a_9%irx&=Ok@DCatEBU%m@Ki?=RG{0W-65C=~eJ>7mjpYQS zC^TYuS1*S)2QE<|(A~z9pX5!56TW`Url5qy>I_rD94k_4)j{ErU?~I&vFZjovnZ&q z`iKu-POwgeE4HH>`>1tkWp7vF`Fr5rQwfUq0B}DGd4GjK2O83Yp#FwhUI`Ij2-Ynagpjld8PG>dg%f zba`)oDcUlcAA_TyfFwTs+UE|MBM9*v$T7|E+WXSj`1Qw&!A@TOpNLLbkLA`U`huZ^ zZ8)VdI|3qqCtaj9#=Im2dSFw+@FY5ZVGQnY;~^wPlcnF6+a4A56-T@ny;lw?tYeOC zN_Ln7KL4Rujly1-(;Pj;8+}>Q7bVs^+K8X@bx`VK_lMwHs;Q)Hen2j1biLS#R3=}F z;W!a|(e5j6PNVIs4o+O9nbU0hog?(3^CI&(*HitFQoZMN%<(?X{bLHB1!Zmy?)A$p zv#9gb(OcZCK3UL^D)Gp zzL)BGd8E_!c)TxWkrpYWOd4|fu!~+3J&w5T9Z%g_V**>Fzet@pK^ie;dc|f%+Rhp% zYB^A0=z{g01gVxD34@LCVOkPQO=|vzb(n%GGnT{;ISWwV+$XlD@wkM=KXzMwh4UaN z*MAH$_`dC3q0#asM8tDd?ZNx76xy3?rW{D^-?~Flp<<}FNGk2$yYO%BaZZ?LNo^^E z^!j?2zBC8BDj~kO)1J0w20OpM?h(X3Lk=fU$ns>}yT4(}FKwz4R zwq5V77pnhMmWS}KL8HSb=^9AH${0~IQsi*N}yjUlh zdzhZlgY^AeGgpDpG_Bj=h-bk(7Cwtn2q(t+B+s7(Y`3o>A^|^D8=1lOu$EDkt$AXn zA8B$0KwC&@du?29;BJU-m_R6(f-LZb{)Mh?uOz?3`!>IXG8wr2k*fGBSRZJ+6hxHQ z^kaGga-jezqo}#$6nwDi=_fS#QKDRBi{AY67o(ezSF4s#fBKZk^~K7NlRo`l?*7LI z`2onu%b_3<(~(8Vg$^oWZbP8wr=kYdSko|g+(-2;AHr3|ir_D#&pMd zUEPA>&K;az2eoluJQ}ut5It$h<;F>O>*=h&V3je5Rb#jhBK;8#Of(L?6G!EME3LPG zJE3*S6@i&+&&J8#M0i1~WnYEdH^kkqnrzrYUwbCx{B8;k&=-LvB@m)jK$Jzbj%6RiVw#b*OPez!n{~2U) z|4vaK<;oJ*FydkUg|as%9rJTC#p!*nq$-ji*SIe%{VV@19H4CUh5VW_Z0a z<)ngNM4?2HZJGMAVT`kdG0dYG>~p~T=xwr$9OGgaipb;BkaU+!m>}nPw>xpO7ZCkO z0oXtH<{Wl5C2od(HTi1xq*N<9$D{^cu)Yfkfb@;`mC-0PGso-SPtUC64x+6C>2HUV zs-a<`(;S$fQ&Ev|L3G=0JM~6j%e&D4WBkxlHo*ZV#@2T|l>DK1A#DZkPL^|zGS&C3 ze0-G_104xoxy$(LE<0av$)8TY$C%^L&>t9UfOikLxAJ28&xu$YPXLBAJPCp|zWZkS zkJy?$b5tK;nw3g-uGY;0tNPhR7>cRo-4HuITz@l8OZN!_lxzm*Oj{A)bSKsolrg5uC7H@EJM zwYy+b!opJ;87lkbg39Kc!XVfo+Mtlu8xY3b&@0RMs#WzZ_V=@*_cj&w9}+XJ=u2kH z&cO(qF*&&V9*T}_$lm*ccqPXYG>UmrX~@J7huGibq(!=bVb*+Pd%N3e4e)TAwbbsZ z3+GsUs*91cVm{Zd+ywIl^f`uuD(B-bDF`BVt(fQ92tBDM7 zVqNDzCAS^B)M|RrrtqA!eZ$D3xFn)9+69?ymLzokyR_Nu>LKF^PjkM+J7{xOOqV-6 z_yi3>vJ{Jdi1X(nj-R7XkrJ4|MGDp&YaxkIEloF5MzCMm%6~Ib2IkuX(EC$fYea@O zI}GDck@T7L zeZ)3ZOmikO9??M@94x(}s+ta0$HFn9{1h6NKmO&jH2!(w_fDVIR8Y z(Y;qllhY(*CK~_H2b0cr{DcCh5T2V4@^~`UXW8QbtRcl$~gba3wuIkBRi!jUJA; zEJ5ai*U`BVu*kE;o11Y7Px)y}r~1kiZjQ4J<~i zGvIyjJ|EyZDr_2?;G)lyU4Rhuh{Fc|I!!3)7QJwTq($70P_Uq5y7}i6J#-hE(1bDu zQN*GweTF6Mu)7-v@zLW8%lUyw5j7n17u#F$Y$y2UDzjdrA*WK96FOPB7{0bnU%NHt zjMK2qeC-P!B0@x@Vj4l^ph{B}O8gV^H%Y^9=`cLbvkwbS8yX60n=`6TtMcBiwCS4Iy3yd`1@*BE!-(!l8v1EAQ&ZiEw7`$l}_gxbj# z+6x-#CHa!HNK}$v{Atlj++Q;OA4#7FLOPOaVAf!*_ngRm>9|w67$F-vK5>5dm%m)c zIc=oN7dqCV#pM2~Qm88VfDv^+$A-j}n}hpo5hQYihodoCnW5`bjF|mLkK2Wbq#mr@ z2A5D(+fQo)0M!A7Z2h$X50R1$Gf4Hzua4*}LkL*skObbY#_R2wlnmeeHwjQ!;wzSz zs~Os--RE|x$~0=?MpRNZWvp{lSVf{DH&kE`K7t=d=no#(KY-y-5zd+Kva&d| zWIov4Y|kN)+Q5xs0nF*@A($0O9jnhN|F~81t*5%)=`TMqKVCL#W|h!=;i^9*8Q9j7=4Zb0RCFQmzHN2ZtCShPQ-;j?k^4 zS93nr#W#?8HY)kj83;yv_Cq(ruwVfKvlsJ52fXiP86Jg?^g@l!Y|%N>Zb^@qs&pMFB? zmH%r(B(M3%J}DT6zAoX;p*G(m@dP=GYMpK0p}>z=7Qc$s30FED8Cus@uASe_DeadB z_>qaRQzlWTu~~Nu^X6&#e!}`SsmT9iEi}N?9D_hTP6wEO1ku#YrF$qNwqm+*QoHL| z@I|z_(ytkHJ<{1X+h!Kf{qp#TdHxXits4??`>-pi6@zOzN1$k>T|tN!9gd&yR9tfVYdsbFL5OTU)jd3whK9eQQ=i=r;@m-J;I}|M4IsbpJzT zwO04z!hTv#-3bv+>t3X2iFa@{ntd5|qd?nF z^ULRH@L=yvZKQ`nOIZ$jd~t{stb|M;3)MX750RTr+i`?3pKQKfef~cK3J{iD2H7T#jxHfRtAXj|PBJH`vqC#4xt z_THcE4;eK9VtrG(_&6wVoR+5myUyul*mUHYfrl^PRmn$U5vi5J+y&~k81|MZVk6)& z=l86*Y`vf3@Vy>+x6%2XY3<=*tysXT`%9Ypomfl`G& zoKPS`fr7?h-;ZjJ)l5Rh0uPevkwm<9oht4sTjgaB)_T7K${|{nHG1OaIM^UvSG05r zg6q<9<$>Y9`uVC*g};~B+9J6eD*ll9no({+{Pb!i^dtUPZ2Q&;>&X%LGaW~%$TB?- z5Sq){Y2wJwMNda#av)p_f$0L<9%W2Dt`ci77r>hso0<6@1_sSL%ziC*L_sQeZzA6n z$Wh;(>n@sfXSIG=HMFez!%=G-#ytn&AC`zThsH{>GWQDp5ge6xoE2+;q6IFY8^RA8 zwLUGz;ZrqipWE6b#GR~m=q^eZ)wgkI4?g%I z&{lAw$;>YAp4Ze(?q^&FZcS5|RV$|3tMW(QPknV!2;^7=HSM6&4fxq2iHYVycUAKA zxwjqh-b^a!5R}!Z3*<32(?Rr=n1%+JX%FU0y)jegBr~#tQ7tQfFwV=%<|!2J=-Bvk zqge74AMX%^EC)vQZiy)-pAi8eP*k(a1Y4$O_8Fua13V|P*Dhp1(wm_Dkn}C3EB}S4 zZ-8S>TA$$UTNkoAB=UCj0SwM6Jr9!qhRGREc|mwLH+VYcPtu4R5Dgq2IZKI-+9nD( zPx#cx*)in--#?VGBRV8`847EBDU6l{Jn>3QAQOAgAGTv}w8wl{by5xqy*3K-6*My_ zW8O@~%BdY#ynxdEoYYZCi*t1t;{k4LHIE2%s$XJ~0zd=OeExrB?7+qx9I&Qa z1C#u;FTqQSx8lg=0{L>-OuC{w^RBRWa?g+QC4T|vh<@y`!$lNv8mP&Cs56Q+JA(j% z34|{#Eg)TYjM)?~xkk4${Qcz{{Yn&^#5Isc+z_(@C--d=NXqYK3J3!SSBZmAY_`Ig zQ)cgK6sjk2wpjqrC%-RRtXF^}KCi}(WvMkCNv6jBwHt=O{o-}vln}Oow|(gRs{wf| z4@?nmPR@oOK*q$I)XR&Eo~{JPI2e6lH4sUtYLS3KBEQ1A&92si9z|w(9i3JXJ{Z#E zk&q4qQnr$-H_31FFt|jG*oR&RL_lP~iQeS;{1lL!PCjALb+7ik=9Wnn=im^B7xR-r zdlk+7$_P+(7Hlz%Mx&fIp;7~&yUGC!$*a^{rW6O+lR|*Dtw*z_TFdFT3oPMW?B&Ba zqDu4Rr#KMPF6R!Tmz!rzNoPm#O5=@`J78Zz%L^c4QJ@H{WLro1RFKnbA{~a*uVg3$D zOCJ}m2%wAXt|eX5@>%{Qz13#u9z*jGA`%aLL5VoVA3CHtvZ?a03HqnYB<`^!mtIkKiu3a@oQ&RL*o zS_0!L7>psY3cE-sGRAzyBV|?kU_RCR%O^ySgFCF{&6YAtyUA@=UWnMrinisrN(<(T ztjl-Z4=a#01pL#u|K~)tTF2;>3nmO*6bBkbzL3d17%pm=XUTCKk$Tz6RfN-T1CJA} z2$W$aFtQ|3^!`O7K5WNv!o%?FRq=-n+((a zg%=tfAcf_lZ4?yA6CZP1P(5_%NPQ@&;XdMdv_>pxG`FIxOhmB*DwF-C?k2P1U#KiL zxoMte3t!B-d}+_b6OBq2jA4JUhBP$vH>hSy)JP+B56?f9U6epW1R zCqyj8J{IWOSp>UutN@aFhos_R5gqh@Tq|X%SYQ0j>0jB4B6%v^H2toMDpRd9cs=IqJ6T=;+v8)?%Mn=JZx(}`@Z?H!?r%YO8>kv%n020JZimCT!P zz&LJRHu8re3y(q~=3I(PZTE54xp*@`v#pi6py|lE^qLj%vhVpaSs6rcy&5UwFV)ee zO4%d=EHabMMh*%}0l0x@k znY@iP+{|QU6LO4ggx{Tf53CM?t|=h;Yk~ALI8iuzy#2Pz2%c9B%qg^umsY?npY3^xGZ&A(lSG!=bimHt{qIwM>4{@qf znDw4n)wSY9ARP<%Ua|!5U3nv@ugH!jH{{s*?mh`~hnRhjD=j)x#@FfPMXDOjrsZ|z zY#%4*$_N(wj^8GjXS*Lu(NVHlz~XFhJ|uLYS|-zrlohEG1OB(s)+RTO=_Edue}AuPVT5K?<3RqvRpM}W zjGwo0KZlY03w&=__0x`S2g}MIs%Bn(=T&Gj2mHeQ zK*e4|9O8)_-j1m0DGevofb=`wqib@@xM#)v?lRqgDCWVVZQEi4e=vE@pkeL zt!e7xN6olp`+j}a510Fx(9wuNwNF#+^(2+ryVn|Bz8Z6pB`6QFwveG;rc7Ka)*o>w z2yCf;ysa&|*&%BVkoaaDDErV8Sex$u0`luB^yTDUl|Q+9GNvHkUhmyibVvdpxeFv( z{DPfIP!9YJa9n>AVDbB-fiA_HHy+Vh=%DODV}AXJ z!cP60IXL2iMCKnp8{H-3lVIcis<^ZLU)hSO}s@Se9uf!|7qCl)r%*!B8R-D5(HDFg#W-b3| z#>wO5u+d*2=XJ|DkN5z;YP%?B%)@i6cKBFZu092b>(*11zC15LD}HDbdW6@&YmBN; zgTxB1?imrcfINsxE|8Qpor+LOv14Jgf=kAdIaLX=*c#A^Qb;wHnv6J{eX*Dc&P-!U z6GG+EIVCO=ZDkbqcsm4w4x&03Qf6=ogjKINuOhB2E}J^L=#*9Mi8A4&RE9`q%2{U> zZhL;SE~d|WWHcC26r+A)A^vz6d4FftwX^$%9VNeEM}NqPb&yB;MccBi&rSgUY9(Pq zCBK-eBy0j*mSW2dC6h|g7dZE&Tw$Dy9MHq`bj4xZ{$A$~m^8@m_ z8O1o#p=Ad?CPG%?(F@|LW*r^6jCX5d{F#{!8Bw$}6_M_Qxu-4=|& zsx#Q-dcGt9YN_f2I2$r|+B0G_3EoPFAs5>%!g}p(`$+jcgi~mX)5QIqYhzQ)GP=>Z zX6*~gB}NLDiwI%u?73iYUNB!U3|HN_KJ4BRKy|S77su9jzLYK$yy#M6Lr05QznREe zEVl=lHR4zyhI{p=*Kj40M7JZH@S}*q3Ku_44#0@jEhh4u+#F57$53s#4_vnXNbZ|n zW&e0%x?*z`KT4gF=n&|g@YN*N#c~6l$q-DcfEIcYlYHf3t)F;&0PDmxB#H;H zfS>sbvMW_Mj5YkjxF&=Fmct?zu)g_xzLCQu4l4qEzr{VqWDe-C0wbiGTz*&AM=QKx0iw>UQ-N2_!Zp808T~2R zLH@KFP_}P7oCYQXb=wG&CX)JcSC#HmQoA){^*Ov8<20l{nBnGx3ed!zw9~X^LmoWm z2=FcHXqhTv4dF}h?@Csq+dWQwuIsA33!RX0;un~J!rLzeFvwM2#=4lVXy#{eF^sno zKr3NVNY&rcA(D;I2TDU?A9o+Kor9Pqr?f~)*HsP3{a|r;T!}px%~CF_fa2IqqI~*< zYQ-bo4%Y*a9q1V!h1ald+l*cbIeja@|s zElY>L=arbI@&v2sW|Im7CmlMa7c>~Kw#W4Pjl79`*7WKQD(m)#o4#NzCC>u*^-vNRGw&iK5XMj=HLxYG(ITd>j7+W(tepH9dr**q03%{FnaAhHPb;PMq`) zqfYLJ{j)d%z3ivm8X~Q^ESUN%e}(ikODVO_Lodv1ndMD~_#gkw2H45^x;15<7oj`p z?n2P7^eDr{m(Ou9<={f6JMJf!iP!$FN@fA6tP66fsgYCPP8S5Uz^niI99LQvWHS{i zfPb0FAiokOXz}&_?zN1CovT!SQP4pLX<-cSHR_PD87*x7*eECTC`zlo>I*=#xHt&V z3%le65-5Q7Ys>3>u2*-NB@{8GO@l|%eQBPG!c;MlC!CFPC}$;38c!;6Zsfl_`CRR) z3=;GXr0-s@MWn}W5u(9rbc@cw8&@+G`&=B5#{L`p{X_<%#*5h0t6qw}D+xBXL)OeFZ!jSFoKX1@Mv z3c?YZEUixiSfgsW`^MU46L4TY;Uy0GB4-!(#Yiqh(iv|P$g2yMOEgT2T`CB1Ry6-x zVUuX;MDo-*wm*tj_bA*72SZRwg_JRc8rDf>$FNd9XA?ve8dnk)y^#)7Ehi}23U(H7od?Duwb2igw}ytl0mtdBZ1S5oS1v=-J%;MCjxFA4k<9O{ zQX8YiwO161Sdjz>g4FEiM9^)~sf3*fGw_j!fG)nL0PVOqt2jR`Q@3|1UmlCR+v7@H zF2`^>Gupyv)kT70?l_6x#z->&JJKN=EqW=zTX4fVVHSXzB6k7e*kdjT-)a7TpogOP zr069GiO2d+OpMZPZWF{XGJJFD<^z{4BGY36@JeJ(7F~YwG2_tf6U!WRr60T*^ zN(c*MpiogXUKG@E(j7AC%%jqA(0bkXMMx#rDikYhz?>Bx&t|q^J6%F%mg4V4pS5>bM~Nn8D+j;VE_e?AM=}bV zQJi^H;@TKIcz9MXGgW2dAkuXboYkR?s;J^Q23^!Bu{?0OrAXJqVrEst&|mX+TeJ{h zH#{N+-Z|G|T(n{!K|+R0ixVbe15eh972~m)epttT-+k&I^M@+DsP3@xz;kj(?Yud0 z55EYGc?Ut25fCF(z%)+5j%$RSJa-EIiL7U6>T%D4u%}|JLj;aT$L6IAqq+ZNuXtfX z&*EY$K}>q_(kTlDL`Vi)_Ux5cTx|!pCy|V5jd{)M`XyEq;h)xV7U4fy`RUvA#TE#7 zk(O)C>1VT#)>X6^ZeKSoMr9f?y~aTGqc}AnNyFlR+WDnk z-%z`j3cU}8r`0XwGGn@a&Mh&N@rekUFSn%~m5v>}d1z2CO}WQX;PSf$ogB~pH;#?) z@z5T(9OT@E9=^N;q|deSN&G?&a8)dc6v+7;=+i8VQODY~Tse@F0{Qahfmkb4K-4xo zv?AYzPB5-?hUZH5CKA7{`k~NQY`P_p2iFcKJsmZW7F}3eP<$^1Tsbs3aC(^+}}LU{{icoVlY@gRIURo4(JyIBb(b4gnz5QSq2xUvEW)xOmmTtKjX zgXf%O4x6`voRIZ+%_Qw7HBqW|D>x5!B=W4@1 zH%mGAnNAk}VE!W|qF+gp6C@6Y2f<$pvsa+3X6`Pa-?}F*EmRHkso!8^98)^Gh9h-t z&cLBjJfu7++4D<4tt<^GuaDEcY9d4X?Cv?y>-p11%QiHy{8m2|mm?xv5Ty;NB{MpJ z+^=1em3P4>ei#lx(d&mcFd>>j(R}=SF?8rOFaK&qV)MuR+AFN2eah;i+0sZ;>Dts4 zU4gZ^g#N>H&dNKeE9I$44s~@IHso#1yzY>caeDz3uPZkp?Fc-f*yW67=VNmMsxYu# znV)$#?A)%(ao#!Z);ZAfRxcJWlv#S!qzuBNsjoetF@NY~C1QJa2=TK`On^ zKh^0?sEnt1e#mDhlB1s!@%d1frr|G>zqOx6u(NW~O|AUoCRVr?(=WpVqCO1yM>aUi z4G^&)SJd>({#c%7>jt{md7RU}Z|6w53ceReLXR{dbuF&~)?=h>!n|Y(vs|bL7Z0B4 z@Cev}D3O5aH*hl|4T!GHX>5|vm8~0QUZvrXHSEqGN>xfW9z0Px5f>AqpbZ|qz~XoK z#tKn~t_&yc!cB3;yS2^k=j?$T<^+ltj$b6ixE^9V``;>bvwB2SsYQaHe!y4~{U3uf zQe!L@C(kK6D=Qi|UEo8ozT6r2{Ryo1=tDw4f<%l+FU*vk#d@$^6zM{YorEfF+ERx` z6%jGI=p6P{+)4ctf=ARbqOvKm?gJ5Un|=z{c})4m;3<@#fO$c_pH+s)jT}}zonKO< zl!nJiKU5w2Q8Q5byZ=7Ng~{YO1Gj36`xT{e5^nkK8WWsrpWTuvvzSu;+*OEu(-ae4 z{DM!N?^pDM*%i9|3QF!b;HylKUftPe;JyJe4atd!rrsq3eMB$wW;^D5dpgh{HQhleCR1PfY7age)iiu*^Y1hAES~j*;`6db{~uU4j(~v-^auT(26Y0NYk?=`dBR zj@OQNcqFz@DfaG8k6$~Z++@XTW?epzwZ4jbB!(kJgWeq1!e)9){=O}6;*0_xjCk*W zaiwg(@PoPirq7Ov-K(DSujlV-$x=B<7&%;ZRCh7)4RH|h4S-A4Pf&99O}||tKWW-R z2YW{i*20WYJ_jQ>At{|nEIIGMwNM+)iyt>n?jf&j9yYmZVEAV#0+8m;F44OFN!JX? z(P8CnBDJyRwck0~UwS0^e-zY83Tx|&4g(4de5etg!Ekizk}$R_8s#1Dwh`6+(Aro} zTzOl0@jjgSey`j6eV<(GK1Nsd=|)k8dotT9?apXuiE+MQT$d=LPKNInKs0NT(V}~7 zRICC+Y}e*FTr(({RJe9T$Q10dJ80-f2|hx)YXQPkzE?QE`b zO$+Y9qEg|IVMjG>LoAQgRqvV2(RM=;2Kr@LDRt5J8X=k4Yd!?`50i}8>_Pow{pqsP z{qumcVCmmKN3I_`oKN|Mpzgy zLG=QR4CNS)b4P<9Xg-_q7tCtJYBYz8sT>NV`Dt1IB#I&9Z{tmT7)*mh$uL6qSqwPUQ}*aE*Vj#BbEHGq>9li8cs6Tyn>0Z z26)>8Gxkt+N64cWB@~k>*T8v;eAx+sOQavqT+h7mADGguqM|Nw5%`mVaHK~77*zkMT8%U zs9mWU7Hx7EF~34Ah~LzSobgSnWz&Oh&JC4P)Kvk%Oxx2(z1kAKDa-TR!$7K-Yfgri zm~t?f`U=*D$pHO4)qwFWN=wnOcaHpSTOnk?bTX%l#cXNvkH}P3G>r=8{A0WqfEKtfuZJ&@y*vQeyHOHy6*@81~TDyz&D0c2~N~N5L?6B_>shZ8O?jp{R zkJmyH3eJ-7RR@`mYB?>lz7-7Wlk1E;_Gvy_`-i7WvW_nQP=VWzjVK$GRL~fp0cDo^ zQgbRwa(#4Ak_;iwuj*=lwxs&VKUqtbjBAsZ1_oEpo;78e@ys{dK;iumV z`oWeJliSCn0CzTABcaKt{L7HutGxRHBsLZmfL$sSwy03>JF9lW8q4&UugnTfl>n#+ zFQ#HibIAse_|zr8vxPtfPUl;xA-bwoH1R5S`t3+#sd0#5N28^%DQb!n-$A_8u!jK? zIDANV%1Qt?5?R!J=XJ2vQ?*Q>uco9e^zxLnlY)Ov=*22CpRT^5zyVJnJxWH&P52X+ zC~Dwbg0nJ1dPmkE@)i7w{K4^NMb_etM8Im5dm#e3*-IBpdI=Sgi*3XJCHE%g9AXyZ z(JB#Kkd7jx9FqHPmm(sVB2g}JE4`}{%NJky!cLtZW>BQ>9YtZF32TcB#PX5T4}wrR z8+#gQSCfgcT%{7%4-r9~Aj(cqq#^FAU)WzCXE zLlF)JDH1nIS~*y2(qP9WNEqNwda#7p>Tx;2hfea^b-pml)WKp7LwX2pKO=7WP(}d9 ztdy7xU!=ic695y(PL(83J{3OGHJiM=olbGyfC7Kr!Qn}%VT0yedeq&1d2<@ zRrplW(UHDGW8go&u8^lJ1Zb zIJIaaFT&A?j)hb@R2`OYzQe55A$6&qe?dRSi|vCb;xqyo9yfA3-&)pY=Yl8tUS+9C z^vq>mjA?|iTSWFt;Ry1FyZTF^N4R~L0)dzSkzH=rN=wQ>P`JQ9ZVqAta2X-A!|3$4 z2g~Tj2gTg*)BN_lt}b30HDwQ)j%3E1BCGaHsYd9CS&3lH&5Y8Pf~BiLLU=YSzDr<3 ztv$p&N9P9w^Xl3}u=#5=@C2+UFXP>Vc*7)1RJGo}l7nz;217*(dw7lBXq$KsulW|@ z23yaKKZ59>DziAhEpWb(uM&LMxI}JUaI&H;%vjf2QxL8aH(ECGANjAv|+M^97yA- zaaaripT1z*xNZHfIeE7&G5`-q+5ROxKQAer#d#%O*<@5(*0Yz={duLeNk=PRnW5VE@cD|r#W9_39HN;RAzxS^a z4>~$X8KqD#E2H*a#X2Pc;w%xPgQ5`Q<3O+Pz2tj@6I?3i+~TW;ll8Q_^YIujX1^`l zZN$ilt?&c|Mbx}46yFt8>APE-7<=3qDjuzWnU`95`@pi*l`jtaAxd!~OLD$%7Lp$& z*gwlpU{i@(NWePp!cCx(=q!f47@ni%0bqVFdN8H&!|lUmyaavJDQ<5M`324}5=e`= zfK1uI-6XhQLn*G|AKvOM3-Fi307jv!(1@R5R%mQung2}fR1j5*h6MNi9EMlc7_~1O z4*oYRxqz`T&)*4Ml&?igFI>(|FD-}I>0w`90C@#$pgI>=debquEY`D_L4qHGQ7sy8_-p%Nb>quVA z-pcY~H4`*sOTMPy3tSSe#QuRGkE@8dKTmuO7AW~Cb7OZ00N#J&ZkHRp|_IN`*f zyLscUWf>k~I=ywhayuV6jjpJfufNN_Y6E)1tq;hCAt5=1JIlQL()dB9)l6^7CXwee z1zZ*k+QL!4viEV@My)DR+pD4JsCaI8>n(gA{+h^_ylLI$H^MAP0Ld3Q+yHxkM~AP~yjyzq^W!x5Lc83YCckf6vHk(_+Arm{c|aI-{AWlm+?$uPz-_Trls zKr;fRwXvIn;cgwhnFCVaR<Dwr}=pB zzJGoNjdaa{vkk9+3l$vg-!5?YQK(I=)`8oKKFwaxUi6NU_XDgJN}iu<9UkWE0*onV z{SIkNdsEbZ7Mvd7l0SO_xkNKaQ27C<;?`bP*5?4BcAE)*ugIMT7VTOzc&sW5!e9HK zSlUCGE`7TmpC6@-^yY@VI93}OoTs{bvI2z>s*%f)n6Y%9s;jJrH-Mo7cmz(WPkVvy z!>VNy6*srFLxw==HzRZ(Ug~;nES`|FiEsYR4&SY-9lx|q)d7;D1A~Cy1<)LwCca7^ zX7?CZ_L(9)_b%gm$`YQ@N!ka2-=pRxZAsd0BILpyHk5iGZhseSsq&X^%-0+UP|G8A z?Hhj|VPciA=|PpQxSa{Si#_hAVXw0bLzJ>?u3?CYoGv6716{ur7|Mnv4KUmPX>+e_ zCLAogw^Y|d1`tnd9Wme}j>L~JOB#{U5PgHWwrbByss7?>bd;N;1>EXQ*QQ{D3|gmu zZab;YGE=ic0~d_LV2-^nmo@MrcS0ueqzcGsS{n;(V`GI-y8#F*O&2gNkH!@1$N8SD zni6)zqTp9gOJU4QeZbsd3)Q9cf2ymk;sduoR1ZMpy*+TGb#834hDfs#5ET~CNHD|^ z$5;95depvYhPlk(FsMNQNSieny<_jA#6uC&hIO4p0kM1=4Aci^Nj~I+Nv*krE#;o-4rAS8FbSR+e1wGq&|?wwfPK1^xsIxKnuevw zpM&bofjtBk??#)hBIX?)w)@e+TL{}na)J(1%FC1+4mfZNODrkeE}TohJY<`q7DhPc z8BXp_U;RMBSA@DuERx1Cv85*Nia6zl-&{SM*@loXhHc4}{qH{^BoNLCGWmagsGmA8 z@DsnU$pfh4vCxrtv*>lK08C0o6HkK@aDo2e&ye}v{};}u9g2=3RC;!246D$b8Y4rDAVlXbdkhHNOR1* zFKAWUpuVlOF7FEQ-JKfv!g%gp{)u~NT+K=9=g(?{(D#kDvHrYglE8_*q7k}DJ0;39 z{fdLNA>1+|$Z+6^foffX)FWrLWl;lcUVM^(KfDZis}g@W*!=3Ug~r-Hp*;wmbQNS-O; z!2B}JqDSB&Zb`>r9Cc7H*A9Sbe@W4eWA-s+A#CUfO$sve<7Fj}(ciep-DyAiylsfu z8OAaS0~01HhdUPN?5o&&kv)F(1@VNpRi`%xY~(~MBYWxO37 z4`Q}wI|OH2RA*F1Efd9Vnk}EB_o4gNoTVA@&~kR$FU0?cSu9!Z+ErfOE#UeehTrzR zZ-CMfKw@z@kEC*Wwx{hvvfFiWAKTs3`(`lG#fbkaf_OS3%T{4rfH`F8jd37i2G!Vr{+J@~an* zW9-I$FK2qJ5~0g47O{fm-VGaK@LIkwXwQ#iO)@sJULR|6o=-kVBQZ zdPc!X{cznFn)Pj{05d^+pZxqbiY2UWI{5Os9|QMoUrRU?UdZ0}>G=lCXbl8$)mEw{ z(s6s8yxR2!DghmHy6;CukTX(n+Fa3UZ7b+9?%uU)*o$$SR7|Wgn`|6ZWp%#ecZ7SR z!~__doU@L__Jlu}c`a+?^5qDFum#LCKnp-IPcjMT^kBB;m{IA(Z#MzGgZR%_V2g}3 zxSxjX9a^upXP9omL}Z`eQPwH>&RfErsre`(+Nv~G9W25jAwni3nP4$pYSiS& z8*)Jr$#!)8P0E+>DhTl!5JL0A5O)eqAnnt}b3*as3y6D2-bz;JV>Z-`Uu{0q-S1G1 z8yc;F5lqE&3~pA?vvF|xiDE(QTRm)@rdY~g&AD0vnV-f?2SsA;D7F#M$a?3wB5AE+ zHZlqv?Lw>k7uR0ONsx8CmD(FW%?-)W$?{jp_R*>mQzg;n!YF9RqODq>B9&THmo>1x-o}$; zrV2a#2UH~h;q{LpRQ=4Y)Sfa4dGTL&9GDHY?Pn++ap=5*%o|9(xI-v$ z&&yN?h$AnRG*@R9S&l$q1DhSumxPqK3uc}UN(xySi#u1UCH#oi80n|b9Oa*ulY%)H zqwz~(p{%Zqo}w#&@V^h;)NjW?)a?+-m?cm@>H6W|hH`|A_qokK-)<&#k*EJ3b~@ z*&4Z8w8uJ2Ckt7zzg8spr2ei3v|PW^Fb8(2~~LFEZZM2tuWI%{QmEK9b!JI zFtABKMor@O|0o6*CqXIAur>ZuyePCK?8HUJK$3<2Ji1)8jFso5KV`AFU1u}Xrt%1i zE@zDNj=6;)^)q@=3s0%Jd47hriVc9cZF;DEl1bJT?2+z3nzM-TTUn|N<6y&-ZD`|2 z2+v@S&}FeK z42lbo^SsT@Wnh_i;H$WOWhDGUt;hT@5TYMiN&9WXtnQN9`Y}5Gn0^E zsrjf)DZm!0S~^dp{4VYlW=C-gnz$biPEL5fZ#fBHJo#hDw8X9z61IpGY#JdBgES6` zv~`UE82A85kDz$srE@AIa;9^XceFB$g~BJfvO(xkhtVTN=jy#<)P=?QZ}T2w>vtY^ zo}yOjE(~ki&OusyI4f&0gCF2!t+~Bv-oS2Ckc{AC&3UkX?!&%fZY0`xAFY0I>pzCN zIYaKMe1lh5=W8xSYNteKt^m0m`QKi7?V$RU)XBIvV|wsV0aw_&8gE>PPc+i!dZ{R4JB*)x!J6jN2L>~y8ehD{)(>&3@iYGM=f zDM1a_aPrj|$u7DUBIzjQshF1~CPEPbaFtm+K>J}r&FrqF(F?RE`3DiXv3jrHB>oER zzdD1Ye)(6ga;aug3}7M(-e;!jg_1T%cS#iQ>)qVu*PmH#vsY5KRwC!l9D53Gvq+Y2B~Rk z2`E8XPY!MYkZ=rNh|4nyH_`ZZlWBOVkSBkuWy&%2w5;~$+bf4?^`S^nU#}(SN(MaP zqpbQSpAA59&>CN6{%Uj_SK-coVdRrTqV3?K_Tr7Sr6i$z1C4Gm!s9$k))isH9!W}U zi!<3EgrZu(E@a+Mfl(!WXQ;NPC*P>9(vdzwNpE0ei0TvCN04ae$xVF8EqFv}^-m{OA`;Q(g&qQ~ zS^caQNO^kKgx5i`q-Q*!v!7SAeb6cCg~cM#Mbl<2bhe4TXI2L=sj~%z&!&4T zfMEVp>-%Bz-$KISw>%Qix*yh3XGlh#sn^$QWrs{VYC0E4d?iydO9+0U2bd8$1UYO$ zdF7AEOYb|Lx{eH?tTEDswr*`|nK?DIW>gI^y_vy+fwIdTw@wb0=;sYJDQ@}J=!EFz z&y5-DHI{h9Dj4xrF5nDPzpLw*bHi=r&sm{MS18CDF-swdTFMLEM6SR-`-FZW0ddTV z7stU{?{hW=T#Sf}fa+obUxCQ?Ik?=sil`0<$1DI)qoZttf*z-L61-+!96N0+!^KG> zGz!2#*t}0!-F6Rk_4!69X(57)n97@F6l|&loz?x}v@WxVi!MJJnR12ttz{83td@rf zgYd%&GPb8XWl0GJU{!Qx-uL(n^Pi^dG1P$*8N&N zD9A)9P1wR6e@(HdGw|B1kwS~I!YZYUK9NeW&>;F)M!bz+A`O1iK`YuHhR}!;dKf-dH@+Z^DOGmEBB$n!G zi1RdTr}UPN`i>&L^H8M1UVa!g&9$*cAK@2dyaXZBKts#6tE)&vA`19@@mdG+oNiO% zwqGI7mD#IkNKV1>`b2}}^rr*j?yw;9XBS@pE5yt;_E(SB=3fjeFfGoA#%)GxF$U)Ysx1~V6r8K^! zz|s{FIxIaM;T!N3G-cFKWm~df;6KBB-TE0I;_?*22SQs;WmqehAO`Vol8XJ>gxCng zQn1z1czT_mT!-hpqR^FKSn?~IQ$~HUmZ}Le7fbBeN#MW>24E~abp3P#d2miMZ2Lxe zTLQkM?hE+T>Kh$e?#HbBZeSgG^z!Zbsi?iBRH!J0%!jZ5GQ)(Jm+IQd_!D~vRANGz zd+}zly*~B;a(0IMB>b2;s-R&P#07XnzyEQt>wGPB|cRq1;{ z<;nzLd2(cV@kMu61<@%#&UZQl7VzT!xus?IR4lRqw;fAc020busFvZWI#fp?1$sHh zbrcKB+jL>R;ge2p5za)CEQHYCZlqvE@Ys%t42hM?WGK8M@C4^QSP#d>Z5Y{}yJptz z7CL)RN;KTql~ZoGU%d3+7fc+$A0t)SN)V}* z-yyagvM!njeIMgt>+NFH3cW9s)*n`vQh-tEojwB-5yAy#(WGJr6btmAxH>h19N56@ z9sR^_PU*v4U{H9v?L%N+?Rn^Z58obqy3`$;5d`5k_6wLFH_1R&g6v-X$B&@Mk%IP> z7~oH2(yWuK8v4ta<4kunYP$NI`E>9Jx0vR~*)f?WUR@ zTGJU{b1AU?uGv8I`!Y0*Z74u${z7GQ4`Y3kyNkBRwijaa|9Vu&A6q`BjiuWqqAcmu zqOnPTR4tN1RvlUH0>(ztKY*=FWDri?& z#ObGpF5Wy4F#0t1)h=9>I*IhXVc!#Y3WmDskO2HXAm3aT+V0`(VdhkmtK3U8iM97v zBtZ2A%^RfRFVEEFD8V=fS{ZF7S3IXP-p9ado0z~nZ)Ja`KOER;EN~b}M})KLYs(hT zU6wAEtQ0;@B`s~ZjmHgmvNadTpb^LRvtZKlpI$J+X%|7=4fzq|jGyP}Ta|B`tBe@6^hk;=DQn z0vdYE)tS^sN1#v!dt;NgF48UB-iMWl8J6s}%z8texvPV^Wa zp5rT@!6Z5(LkHXBTvu)VtJGP8%v1LAEK=gzWl?p~*jgiL&u7#rDYrcFY@ z-58VV5uO^@sDg26hh7jsZrs!yomFI5Z1PvO_*@P(GhUAEd~*^pA*`-AK&7sV*a(8d z@2=D3b-vbIsjKYkAbM51Oh|Mer*XS8o7Z}@%ka#}&qT8-G=b#TKpwO>+eX$I( zofO(7vq1M&kz^&Zpu>>xmkunu>9TE6g`eeC5}waaNxUZ`M>WB(uoz&foj%$mSO%d< z08e^4+C#&CBs;Z0wj2r+8DcF z5n5g4-p}$THHk-l$gv)z+1(64Ij^36J0JIBITr$UE%^PCjAX&QE^7zNSF;wACR^jI}^u_fq&4Y$|c0S@11JxG6CFpS~UKsK?o8x+({r zT!@rb3ZWyUH{-%mFlR8rWQ6(0nNNJK6!gA-j^YtXVSsjZwNX7zTXvmp2A81hTjSu% zrX&`O>V5t+^$!0x-0l_84g4?ScFPSTpCPT8S73 z)yCCJSb#AZGCT&}w6TV~eEX1OI&9pVlLw-W9FMyw`ivR~T0aReXh9X{RRssh=G)y5Ef^J`9AKbB9)X z{6ebhe8$#7I0$ZuMPrGn1D56BNi%iYQ?31=O%V$hazOZ0G|1~z zE(#TsV*Rbhmz+VInbM4u@Y&or16*mF<3AYab30_Sz=DY)b7bDh*=6h)!oyJnYqQP$!27 zFA7=3v(gh_0VNUC>{}7cuC&uF&hyt8)0qV78&$@UI;ba=JxB|gc(!FOpx+H+<3rAo zFneD?qWvH`k_xgBv6fA|zcV^s7N;y~u=$dHu+lz;6$O@ot!>s>E4E3F zvmJMVF_E;0S%b5cc9}5hF76!^mD(GoW5_pui+|8T^Jjy+*a#fhg=1#>Vftq7Iw}#? zDtB4ldR^^bvIJ4-I{lpe<~=z$vKgVB8U}U7q_H2**hS3|;?qQN0g+9Or1QPq0^P+_ zHa9}w?McM8ns!)zhW?jDfzN~BI0z$CaX*Im6iC14%e|hya%FUsSu=z&tF$bDqhKWO zLl>Z|phY_G>at59ci{R21Fmdj@mcZDl#}|(@2dr(lt#;D1nyg>?6SEu34KczzN@Ao zQUsXlm1Iym^mSV&9~MTQTRNA46bs&01Mb0Ax7%wV$)5q8vnjkO6o{)wOWD`<7}ojH zyM))8qX7F*Lf*VlEz#Zb#t^_y25#J)+xQq^AJy8g7U2dh8871(KWphhj98d zjtqxbni0*aM;}!I-85lxm(Dqaw({C^c|T*X<@)Rvo$DNIOjQ!2)1-%QTR+9~Ifw>$ zH2&=Y&mjvbW53AS$E@jom-^%6?s9|F-rWa>%JCF&Bk>x5yN0e0E#7qBB~pIE(SAs9 z7BN^u1A52JQ&*03r{bgv0`d_nsX%4BBkJ675zQ;yuYCImxw7a`Fqf-7)psucA*~rB zM^gs6@uI)@03)kmHL8H{UaG4!@m*9Xfv;$#XcTu);{LzNvYb!xrtB;KF8PX`03<8E zxdVuU8INMrv}aR1o6v>3Cwdf@1_B02B@<4QttSBR1NaAv<-H984{w2HZ)hG;tU4wkudBaFQ&aFWc-X z>TS^xRs5hKMMPG?6Wr+OiRF4-(;n!ke+KoKr_-(;nVD;Cp!MWF&df2Qbugc(<_L|2 zDnZaKmfbbt)sfI>s*Z7Aw*a^-UOv&}h~XL97S`T)WRA6wjC)*X_k0nn_;hVwkkz%z zx8E-MMb<|M1o3XM2XBYndog#Gh?C*UO` zi!5X#m1Zo76k>Flv1z@+e$(Y6&JHd*cE8WNQMii+sg8JyYj+jxUKld(^K%`l#yZl9 zTm2n-dVZ5uOQ@cdv<;l3tPLJ)jPt|;jB7R~tkovsQR6&(!eq)WIp6kvP2Pv@wzZOo z{x$GwuZT7t{)T0-8m%h4wnsO$1Qh(m>9lyrjWTPaK%$UCh>EVkbz+z`nH}SPr^>b5 z$TX(Qt+?}EC`9~R9vISliMc7c*lf>-Ub?abpVVE);la z=<@=QyQv47IwW_~L*sewpFLVKRo8YNcjX}ga-Y~U429HypX?trsW;{18Jb2vDv$ji zHA<4+$mJAz-s?Ror_0gon;X92y&kV$?s5uUxVxo`fM-h*yl-kflz5lSvCmqlj_M+Pb&ZlL@Y?98uLf zixY8Sy@g_kLSGKxxn2G@5c9Gdr+PQw>)3QRow9Nr?$V*SPWl9I?|zswcg6S1)ci2XE<~5q}O8L>+3|PWr-37w>@5SYIl8d8E8AQ~4js?lF0ic>HI{V0Qt#sYDgTT<5 z2#Llt1_)g|i$Ax&JB$PW8GBnNjZ_^?(N=+fuQYk+fk!$!JdJGkp=G#P@k*bF$?15A zy0(WnI97C*s&hKIw2B>(>fjIAEM7K0OkzFyYByx5L~O-u-21!`L=Kx*SNUO~>WV+Q z$rTBfP=BB=8JNoFdFge`LtRG)k$()pgt7yxK!z}tZ4ykFv{-S@y$Bj2kmdb{8d>~2 zQ#!V?4j+-s#JIQc&FvN#aMhu&z}Cf8W&#ZniZIHB2%|fbTkqjTVeF~&588b4#$6BB z9=HqkekTZ5l($t-{$dd@r8OQqM?DXid273Bjg$qy3;q;ubuA48tJP|O9*kF`?c_Q2 z`w^J9QQx(aO<5w7b>2OWR0VE+i394?9Mt*Z3=pCfk?@p<(;|Ik+t*KpNB?(wbsiX1 z+}&h8bxFZD32%>o~0#naQQcY&4G$9(e6cBBQsXpvGK1@$`|tq7VZ&VG ztrbEov`b9u6nz0oZwS)%1I|!LU2{2#i6v{CI3B!l;R9A})Nx7qWRTW(mdH|&5_(EG zeMS;90xY~jA}KjnW}C2=nH!C8*Tl{&kPgE&do7uzEU38qmuyBZP+rjvYRm?!uPCQ@0qpsH1k(o@$KL2nhnlEPgA`_# zpJkJw@S(xanPfW8n!=1?wYT$Md9RQ=>Ud)WZ4uU^=#(M}+P_Yo9;e}lssVY~dV5KG8hQfcX8;4 z;lZe$|KupM(qRf&7e_&a*eQ{iode07N_tSjT;$0 z7=w_KKF&VOi6YWOUy10oVA=LMULyv2!zH`u2XoO%IkEl>00}35K>eMC;H8ab=0rR+ z%(n{wfkmj@Zt9Oz?RlPw3ULzybbpv8zj*@epG(eor7mp$BAF|xs5hBVpPd-qbI=K5)pw_jXz<316Jj$oN={Xpa*60;o%=+j*CZ6XN z^9$uD0Qjg@T^`S-g7HgaChqB63^+6`hKbx8Z5stCb}_HQIToxE6JrE??Mgj1T61!Mm7b--!HAIND1e4L?pM)gpsLdPnm=KV>#ky? zp9HvAr&>JF!Lj`exNWMe)5+vl92j`Vzwank8cd|U)O82<4{FOcQCV(nDq*K#yVFE0 zezR(&j;0f!UxvCW;Yd87}X6Bo(a!ndbn&9+)|Mu6#J4Xs#5t)}a;8jiM0C zOlMMH_gz6~zu@M;*MVlQvsa&Xn$09zz^e0ip()%{GLq;0nC#F`^ho|}5M{#ev}Te{ z;8XBzY$LiN^jw;O*r{d^1n=+_>vs(AyH3y>BgsKAa42!KN2Jg%vhTCwqm{@vK7b)j z@P?u;{4ELLEfbH%IQL0^zXr%s7_V-U229PMwI0YPuR6(3qmoFX#P?_8pd4J%=FYB9FQBkKtbxEcZ zkQBnd-Mf^_fDlH>x^~{MT<^-1*Nh-mLqIF9H-*@}+QimsGr}^LtO$#h@He;}oeeUf z9a1226Bk;{BXZ^=Yh?(WvLhY*`v{dK_8NH$G!$wNN*ov)EC_{X_&>a`?JJns28E>ssTVx;%(uWCi;Cn$fl?P+O z+fwM86$nd;8Dx>KhSa6DQk0`#ZOie);AmTz4sF*@lyR;&8j2MO#dnZt+OOJLNrd%q z`xxNIkOC0=6CyawM;KGqn82f%7G3{p*-bnJ*uyaIP1kUFMrH` z#v5pRolM5miZWwKUgMMf21vJ~bi5TzL=4*hx}_)zP*r^P0lPP>kkIgNoRU-z6fy=qNgHIzEW7`cUt zs(iFD)nYxIVxG};gd>xx0VeRP8`7)i94xCF@6*}Q$mAZ2D<>VJ>mwX0{6F=uSme{| zCyV7JkMbKCj2zp@C50$6M2{7Cz*9miLsN(krasJbX3c2P=6?jKA8_*AH62#(N3AyN)Itf zGD#%RI88Xlo1Tn8NWkmUrE&BtI;SRoY!@>5iN3i;;he9n5tJSGG74(ALwhTKMyA*+ zSxVHh=Eq1v$DJ`;eRJowy#$K;OZCEM(Fe<^IhC79)Z$N{Ts9|mC#LJym}7@;z2lTj zTy7yQFd5LcB<9gsm|Hb3G#}fv0S32PYO@rnf{T7qlg^EzMfy#nNn--1ZT_vJgX!!3 zEdt}ef39~Ze+8U@trD?ZqlUcufm+NJEIK2I#dR+KUOgBfGfv}gM~*CqLyv2@h2#8^ zz(A(2FcNa-!y|)rS;(GC4&>!lZE$Gc&c;970P9j6LC|aSdgcDSdow{9H{A6c&fRi; z@OG&hMkr^u*$?06#8w@sMa#y0PTHADydHxPNu}k(-7VFx{CZ(pCMn1eDRW=AW~7+} z^oh~l<%`NmqtyIMwECEQ4#5g(CcUnxh*R7PRv6O4NO6tp+ZqCPU&vj&;DLA_f z5WlY(qCfySK*qnCRI=%;LXT4wxkPWRB<=vLR<7f$#HG`$Na!)I&yi<(q50Q+pzARi zy$;)l5C;GY5Ck402w`SU$OfRacIh*5qgqUtZQntTb+vm(SAb@COG}a}H~glWker_I zJ;cbeeE^NJsNG2Nizy8y^f)GJZUNxY7?`)L6$h9#HGxQ;-h1`CVmaxo29=!D70;^9 ztFL+uldK>xpqEr6uZUU(>%;ivvpCj<3b<}OzxPK$;I~6Z1H%Lk?5Zz1&?=mxl(GXq z!XgfiRmF8j)bi+?%k}~4Fn&S|GnUWoV0+MXa|$THwYc2Zb<0kE%?dYD$jW)^s8u4# zr7u5*jBZy7a85qck|Tet@iG;0{)3$nAP{;kNMSkO6(*K8B|Z&dhL7KFhgyIyAseYd z=b#1?p(D4w1p4H*iBiym@>GR&gfd(no&#J4VZcM@@TWZ@b&sfx=(e;d95HdV?*8c3 z7_GBY63X)Lofs_(z|2I^cf$nbZG1WL__5U)>l?ybIi}Pu;gB$(o5b{pSc4K>8ezt&9lZoFM*l=3#gIXNzCJTvbNfxaQIjI+`rZY?vym9(=h zfng`0c>tO9N`w71bNm6F0 z(1zz&Amip6Du@oaF{F`~54{E&ZN0{yh5tKbT0d_aoP0f^6j>AFet!*xyC0r^?y8Kq zI^8H=+toynxQwhYsIAsbt8FDIs-A^x#r4V{jTPT&{)=8*m}yI(5+W5WKYgyN+bAuc z?#dV#o1`_y709dZinvHnnlI?A8zs9H-Z4Nhl{3ytVk3$*W_{2~W*#FM$~W1idQV=**{cD2c8?f%Is@Qk_$rSL;$=t+U6V zON}qVZu4=lI3YqIaCi|UlY$>sS_XQ?5X--KUL~+PNyAB|Qqp=G#d+soHhE)8pyquT zHF`TLFL0IG{487fhzDCz%ptpK(AzK66JhZAVtoz>YKW5AlS(_(F}HuQV_s^_n)`Nt;+A?I+lkPMd-HiZDHezt0o0!y6o z4P&$P78RMBqc}n^igEIOR2Lk3lMlELCNV0|f|VIyDx2+aYgPN=3=9MPT32M;>8B=l z!Eth`c_!g66MwCS<5lb-Z;f4fmsQz6ke4&;th}(2@Hqv?wCuXb$l8)8@E_h(R`2gD zA7yhb+b~}IM?0({-*XEm1CS?L;_uXz^`Bj^o%!7BTKdp*Hu_f8M@kao`2yOw zyE}(oP+U29q?Y9+ZOv0e^dsXa2uuK}CRhc)Yf?93Y7Mi{TD487xLF48TgueBO^}A1 zhHT5_(V)_)9IM+Dqw1nl5c2N{_cBibipST=MP?i=x;j-PKr`paNg_|)sqzJGlJD1d zq$5dA0`*HRjw9DlzdkpF!03wX4~6yjuAp-N#(ikAOB&9~~Y zp3IMWYMi|0Y$X2%PI0I zL7RwUC^CoS9eW4RjoIuYZU@;3+E0?DMvRau8)X(VTT#3JQj9lm`?DttQ5>d*hEH%4 zMVeW;ukiZ&9+ySSy|aAgFaLWA5~*-sG&zm#tC-8CqhQVx+k4_T!L1cNMBXRu{+$Z- zp@v;ve&rYPE@6Ws6Iz=X*iw^Zk>NPi;J2|?ln|^Qb5&;RCwfzvXK<18QpiL8Mv?e* zr(an+%f*z#S%UZmm9g@VvS?@OJWwNN@<(bM`mm_tf6gH_P9C4--NOS~V)MCdGnIig zx6^|Ppv=x5FrFFvjM|S~mk@iZ#r(*`J60zIwj$Un#{XLmE($&*(iQSOchbm4x4_-( zOCNwl5GW!%Eny>l$j#|q2vgQ3ov3ucs&O+sE-ei4j?yt)gdRK_%~qptTNuB{5VRUD=gR~&@AyNn}uSFjfg4) zmC`bVv|DJS%6}i|w{QFPb=ivW28_EmSkDFzx;gaY1OEQ;LfhIF=%-KNqTYd1%bwoG zM|T42-jbx!VOvTPf33+$*b9Ig|AtM(c4{WaI_*)&&)uKvS~as?D!}ilS98GcY=Dyo zT=kmChg)m(27;{36Wm7u1h>_v(?@bSF07gMKYqt5701^TA-*Dl$~F6gQd=sG>DZfL ziI2=ta!jINQXzwj{c{UcJ(0TzrXg9JkEDJxqm}C@x(&xlSfMr2jn!BYI!8cEA5Ha7 zm!pMp_HouDdye|->(&^pscRnnCQ9R(q^DhZ)##I>M*ghSmmi(GdymI+x+nLP4Q)=>F47Gt((koo3!!Kex`A@na35GN{hu? z?M^;wGaW0{3{W!bWwW`3K6nlUV7Z(A>_GmLj~#Ll|0AWlE?G}y7Y=JJEqOODy_z6| zNd{1=zF~Jwv`!f3wS@vqBT!Elu6Oh|ZCe~BiPxE#v%N-tBRA0%*Ow8&&+-C<7|HND zJWvkPfRI198AT1bqfFb9rk!$eHn|eHGQDSGrw#2yqms}m<{sH747#Mn75<&#f!${l zdrHf`ss#;?(T%LU2eCwi6j48Pg$Su%pK=>bpo>{2_0suQcuBw*&xMqHN`wM4O{1FNrE^3Am8#j+*kM-9?u@M4 z!W7hd8$H|=OXmUZoAf8KHMS*I_c|KgMSXvNtre)Cw>(~Q&rs^9(-xt7eM6hP}emfsnW&I8k&iqDA- zra+XEUksq~bMn+lf=V24aOwCb3Og!P#|wSPlLC!}eI+Bn9^FB7Zlmv$jl^Bm+f<_*4E7G^>(vk^m%<0C3>r`3GmK?CSet$JlmcqxoRt8KF8?tFuXuX`u zTmpobc#!K;Sj67mLdiV+2n?C$pi){3T)MCHw<(IfjSE0&qtlU4oLy}8qBtO)YWPqY zoVfIiiTcr?=H#j@2w;XP2-sK@j(og0P1`G_#_o8wIvsXe&ZIE_hl(9=s?7pP42|CC zV~+g2J=lvOZc$!QrUe+*b3x>Oq?W3zD{UF-x632U_`~$hoQz~7g!=h~b;~ET4fsyy z*yH5xFnN$vdJJZwS&&Z;UqR>)Qy43DEtEMezeWD4EP{eY57oru5~Nf@Zk^K+Fzfbj zW_tIbLvdoUJ8C~SH7frdf*yMoqHH{}q1;dP&?4u}NyhXpI(MzUwcW59a}ZqPry^Yt zb~Ba67b%9`$<2BfdFwuBW0ln_h};hupsly>UXG^p_!%4&?m0N~+NBHhbJS+&1GOM2 zI*p9Zg6Pj0lbX&D!f(s)4_&2l963$2EJYDmq3?m(0J|A+&r}599Mg|Ra<}!;Dp543 z@BPQgh>vmi6k>c_qY@^a)GkwE)As@ZtM$cYa~MVegZvKl)OkWWMHhcI4SNr)e4h1F<_9GJ<39% zS0`YZB&R*d`AX#aA-zg`d17#Bxv6}u4NAvmYL`(lQNvb2p}xt*&YJo&i_J+}WKR`K z8~3VrCQ8J3X6DP~(&%tctm4~ucHm?#aN?6zgoC80-5U-IsJy{jfKfr|fNReZaN2z4 z-MZ=(qs?of^d9NE)%Rd#CCdOw@G7sLo2rVa*pFy0#9DJrS8m-2FM5^ya1Bp_MsU_E zKqJL8ULD(*s45QOA^Pw!-CmltbB;4PLR5Y3L*oz&vfHkBE1AL$YyAE*F3SUM80_{0 zK{F0R>O0h5sr>3ykjJ<^!7m_>%>Nu3!pjAL22hPE3+$E+QlJtCo8qipvGPN1YwE(4 zL8ZR54|t7%Jn5|12|h?iYi=XFBa`l^qI9Ny-7WeK}%N8hTOQqQGfBMRIb!-3pZ$i zoietLgHcd>8fR`klmj#cn9y8lilsyWh1x|w~FJL zGgEt7Dy={iXC$O08ZZaO#sXgre`aLB4%(c3Uxak#?$|$0JX|f_;~W0g&4`w{3Da3x z-#%EbZHU@P>5v-t>Y+iRk`ltWi}YEHWGG}T6PW2Pi~(T0xYYvW;ES5^Sm`~8D9RX6 zX0hs_Pf|S5SK;EQMtFTkCBbD17&}9ce)aZQ`s^R`@(k76yGU}4W>lFl$b4*qeu<5BmbA|>oq(na?AsDcJdw?6Kp=s0R_fE*s!@M8BoJ>Rps;Ek zKIC(-sv~fqf4*<3Q*u&)tO0sdUQUd`_b)~%w6MHZ_AIV-rv1En0QckiK>_1naj9BK zovCnmi9Bv-lzt|yq2UNVVBBv8)uhe~YshSKduK6e;y~&>?b?sC6|gEKC@^P8;g~zm zHba1~(Yoa;P>QPmIuiz(`Ru>LY82?%p>YIr`G=6HAoT-=fp>hEn@feN9EGW!W6lD@ zI3y!_C@g7Q0_YmKPh`CiiLW;bgqEwH@6}6oa>PQ0@+A(uxkG-qYB5zuYc!oYXAZm$ic09Tjob*d~z+Yo$L_;QcG*WxM9#jtjpjSqBKKClkbz zL*mB?A<-0t? zSrR1@v>CRF4bMIdOPjWM;R-{=03SQQpE@_KrnOW1xt=#r9(Uml=@a!yBJACFH38%At^OC--@Q4DnHTgCwvs!Qb0RIo; z{S%U!;eQl7lH}JqN}-vJzuzZP=?N~@L^256ht3$orW+`nlhel|4y{j@P!+Sn=(vD{ zmmSE^cwF&0J<|Rpo>XHEPXr!P)gsg9$cL*zeeAO}l++%U;0xL1{uWL{$bFQfLjHUwh{PTrZMgxOib*gKpcbVbOIAKa9Qbo{?Iy^* z)IwoWiCeIEj^$^=20SH-kJg(=6GEQAM{v6#c@MgAz^LZAQS(#Lt9dLOc3hN`zcNhi zO4kUT8j`MY4wLcjHU6=SM~o>c2Zi8+>9R4Dam}9G2q=bt)SC*(iZ}c6+l5Be5HeX& zCwC*2hJJ(S61W3WHko^=u3sA!hs4WBdbb-^ljKm^Ugvb-icDP0Vxc1eWlmTFiOAvK8FZ&b+&8cU==rOT4yr>TR~ zcF0?G?U0U{da%yopo@&^$+L-@u|764H!tb;i4Q8v@f)xO4P_~|OAmQSM_4fUIEH(X zwKwfJ1*hktT2-Y2(ew-3FmnD32GofeZX+%Bd)m43 zNpkN`DOT`rvXatS@f0|i>FS!3x=4NS6(Zs=8u=Kx6VhW|*F~9nW-8Yn0M>mSCu_vS zYZ>(X=U1HQOEJTs?u_>9=LviefH!z(8?d{;WtnMJ*d+;*qO8*rU-kE^YByDR6JmMs zS=0jEs-NaYFB#YnPh~#^p_b81ZUPzeznL)B?inRH!0)KM>0}P~Fd>)5clQ?-IdjnB zBd1ymny6|BU7?$rM*?S|Se7wveN@2a`mk;fE(!rbA`B0T=Wq)geilu%ZH0L`E6mrG-uY%XG(3`BnpSkWX?cyqSnYW^<|-WYK4D zGRMR^IlKe>ut^e^B!k=Uu;tK6kTVY031Jv9@K>bNv1b#6c!l=zs5Vfvmvr?}_66=8 zT?S;CAzGnrSyaE=OJQ(s&sA?w`AspvV7&5pusYEL9Kr<$^4Kp88Xp39_)!+2IZRUR z&58CoP)8vY(hh1v3_gPH{bCA2@l)xB8#U=;-8;l|v7~^71S^`CRiVI`Dq_A~j534} zn2da3HAl7KnsA^&XcLd*3VYPOH>!?68}6G~3U#=@ewEEVZfgUe7eLnC;-vMlEXon;Bm`6#^#WY(U2!Olx%BB52{^g$ z91ALrV-N-zkqi1;c59bU=uTf)70PFJdU*h2mb_CATXi*p)$RAN~Al_pT2@}##twkjomMJw~+V3&(kX3 zPX_6n%rRE$&cs-42`^=C(?@hX*(`#U0hoq`&8*xP4+)+zg402j>z%<@?U7x|)akta zFeed5YX-zFn*2EdA6@)>>$-k+*th+V!;Pge4Mgyy7>Md zwYT6-ej~(t<7JzecuCHG+kkr2SLOk)2#*&b)MtJa^}qhDnQqa!F<6iUcOQ1(C(bz& zu0;mos+}{!%#f$7`*S8l^{u?*Kka_+7D_t+4t5b}GY~>)_n`nhAFZ~5bXvPECVwLO z(74NA&22q5mT;G?5_fzgG2S83_E^V>g7KmsypD&oWSWQf8T;s+$b}@oa;UTQoz)1B zvF|ilE4EGmNyyXBRytD$QX#Y&8juKF*>;*WP>#k)DucLqM^F17Qjf0R^YI&MM<7~+ z(%LXOL%S-bl`hf1$g7)XQ`l)6pJaq6l8ZW`Z>BbnY24mYH;9J9cfxJKFrMO zlz6?oZAsv>&27jc>ql43ra)vzE9!L=&X+Q%PomkLlsw7-{VH1tFeN?MRxawLP#T=e zdkR!#lWg>dzzNA?k#UNY+V?mT{k3gv_Va&km21j#U!QcnLvyqtR9yvfF&Qx=%_W%g zb^-V6qE%g3=07?Q5~vTE=#Re4wud1FM&Kmy_fFXybCR7Cgdee$e+>A8=r>@ zHOk`CQkz*QyuH&GDOf>V>E_hz)gZIfMI(AJvyyq(U5T3E;8>=Ym1k0>lBBC&M}A3C zI8cSAwtjis(^+Gp-=#i(#r5pBCjb#6(i1ngY)ffBxfbU%(L9_kb}UHwx3q8ZoBRRP zMriOiFYpWQsM6cZ2Fr^m@m$AKXsM#8#tKPxrfxuTIP$+q0PPSG@3Gd)Z?G>nXe|Lc zs)MDCQP6e&Oq#ypd4A}9yZSoC2&%n3lZ)yjCoB7`I`^~{wT%O#gbth zyfrgE@(SF?LT`u9Umj@2hZKCHmWAyM*P2r;0(9dEx|9@gfg=S$3AEq@Hf$^vEdjycfLLj@L)F$e_W;sFpI#dIu7rv z>BK=kXs+G&Hc5t6|9YluJ$ffVXO%3PW>#7Sz0P?p!4_R=n=!Dae^5ldP>y8V&3$oQ zP650twJsJ-lKw-aI0bmDxJ6fl3VP3~Swskg7+HIwF3&hN(KsgN$6fM|PAf8|D*D(m z(=r;D<{`jhoqPJnaegfF0G5g3!`7u*WBG{xar^>f9{O6y`+yJ?&Pj_x?^&^_ZzM$=BFzb)aSVk{?i zWu3=f7A0GUTnpLS2T1(fsCou?5wd-VzZ*-}JO$ zs+KY$&VlVkG`e~!NHP{@I?JR|*-f%#E{ZwQ4%am(6I_`&zu4FG36q zY*M|=iFAmXnC|@eK#dTdmC2B$rKH;@iZzFNlvO`?S4@Y5?{=O24!&XR!VG=AdA&kp zU5L2Qo`nIqAj*_gSp!{G;%HRgU1hVf4Gtgt7Yyj|B{diKlnB!j8w_3)mtKnRuLznH zKy3##{d*6eJ-&YIwWlRiJs#2k$PA$ke8pWBMBy>tf65L}iHgONSaJQv7<#NRkn^1T zw7EYQSSKXZ_V^DdBIL9xvZJbDDYHJQ7{FSd*42dKU@7rS`v{#p`#krX7bxLO`v?>j zN>7*iFuS5_u2AjthiD}$WgD;XSNjp8G!Jf87Ue<2#paP#S4Bwi=jtJRaX+k~F4crCIIa4TS34g)M;w<$F-iGOf2^(|9^1 zTEsize3)0Sm_;Zkp{@sbk}H)z-rdjQUYzaXtf4q`pQ4zjl;RA2x}@%4f^m4tUG-8_ zg8a$p)q%Q_=jDY^`EP376G7R>)DO>ft9HnPHb8tLF0kvh6lGOJ06m32*gZ0^GFS1I zeka%AD-6q4oD}r+{rk|@-nzUeb>4#+^L0ewECg}ei)q3$Z#d-7Rn;uEWrSND&XW0J zOGH;#yiKB=NLck63+5DF(7_%?^g<;9ocokTY*W`PN-39!+0kwxjck(vVJv+&5>JKr zJ1^2$L-$gL*z@6q_sXA6V(NZ8)m5bR7OZy;Q5e=Kh z?0#JPjrrQb`KaWmuFtpN)@@$$G_l9;U@fwpO(tQ2pbceFf$JG#`YjlvH&F z1aG-Yy!OL~S*crlH%l5hKx4*4nyft-qv)escpVXn<|XeH)_ zA=`3cRh}b@2(tm1EdP2KPFG57ZOU)sq z#AZY5)SBlyn=G>8sR95Fb=$N9&4JDl>*)QOz4*+%dcS1^AXt0k8)9USgJIg+ZYtSqr6N9OwL z8yzGOnck&2&sj1|5(S>BeX;E0=x(@JOpx&UWP~(lE*F&vF9|=#a1u3Ul`m2KjhbfU zSUXbX%>@=l#%&jmE{0ygJc_z}p|EwFNCsfZIr8v4iFb}F`N~X&Ub412LlGu4!Ee$v zTO&CvhrsAbV+DAQEp^?P2_pu*i%2)ZZA)lx0ImOXZ{8O)T#9~TSY*Gh)AaMN{)v0} zrG$u_Jf0ltdGW{h>d`TRw{ju*8$no8GSR{RR!0v)Slfgm5AW?W@}@FU^dv+bE;f(B z-(|CYEY6qh!I&tq+M;>Tkl_c&?7y^7n3)%*6bku)rL?Kqoc}@b{Wd@mlEMaM>H5=kxLj*;^7#sKg+$DJ*GPYA!3<-&4Kz%|WEOEEJ|qGw3ir zX4GgduA)#^&)+J4Mh0jB&BPw{H)`pHI(^xGbn<6WL;;o6nfB-wq9FOYyZ#^YNNI{SoZ`FdezeuUzN?`2A^DzYPyB7ArRV1zL9)qAMBj=RnMAv*xHF+?C9~?jS$1C!PG+*Ot;m0gzzHj8y z7|b1P{R-l^R-d*Rk}M@ZlR=$3qCadO zK0^|V+&Y2|7wB+}K^fAdo)#^pGMwAAp;`@8H_p5~;>}7scQuqCK&pRpYU&-lvD`Gx zdR%RDx4#f{_@x<+;13JtEMRF$6pX4%IYH2_z4{0}mz=gs*%x)DXgqVYvq==c#EZe* z76sUV^?42%)3!mA!0h}Fc?MP`(f!WWL-sis zAG+Rx+(5cxmy=Eg;kPfgh;s9;ywr#t^uWYzz}HnnaV%*kYBs);(@-<><_;bhX1U)P z7N88tvGReE$$mclfN&8ZWadljgBhBA>jGhSBE$wePJ_eea0~(=2G*{ z)TxIEp-2HX0xPVGQ~D5GHd&+cGyEhu~YVNqMuGmSD<$Db5i3=2Qu&Wr15NI9|E z=>zn=D%Cz!%zfqHK)vGnBo#NUz-=4l?t%XY^}Alpgn2)y{1Tg3I8Wm1j<*4U4?q_t zK1~hDlWHN~cZ%tFAx5!3~6neiDW3H(uOdr6xfht?5IYE9y|lAjCL#P5S+V;<8k z--6iA?6%_51H_m2b_!WR7e!3^=ddud!o9~kIDss=TBE9y4~rj*_@6i)a~Dhv>P_3QkQTJte0jMSz?+wmc=}^l4qNQBJ1p;i&xXI zt|lcEuu=-TYUF%gK(->sZ(@b1ZGM~PrV2~`tpzm$Q<>3rWx-6I2Cbl<;!poekB;_; zn=cUjixB7KnChXFmpw&+seafJp3%Y*H)u5R{ur=ru+#(6KqdtF)rp5IA$Wo-C%GXn zai=6QSQ&lFmyBe8-FZEwZmtrW#Q-^!V4r<=fxVyuHJEuz0WD*FI=V%nYoan-cZ?jD zOh1E>$n~;p)09i^+1c>CA!c;Qb$BsBdNs;?xrK_n@2b@K?!KGbQmrIPe1(EYBgOhi zN}gBqcoX{A23W(1J@>6+6`#CF&^g(;A&jPu!1zIu7&f6nB}D48JU4Nt^4UbIR!P((u|0>htxUPYX#38_QB`-JcD2V6f)U~YU z_V#LE4Jg>4kess!4UcOEASIsSB?Z5l;0mQe1z%I&jc)J-Zw#%X}{cPlID2U8nB$afI{Wx zyuDHcJqyEp3FKE`spo4QLbW$VWYugT`8&y ze#r){6S9nyOCg;Q(!(AI1J{(tVY5UoUV~{1&8%_*ujE*A42k z-_uhL{wy@n@jVp~)vS}8^MS<(HK@FlCmat|7spw<@N~kdVLd^K(ivUA!Oa_^hrVf3 zhUa=~BTu=QLUx&%*Jvu~B|IEVwPOBB!D$f+9jM(EvrW5gDuC{*W1U#8-q9UFN{TdU zBsy*}>>L|Wf@BBd5PE%oyjDuES|^R{u#2Gl?&H~HPrSv4!A#>x-qSEL2bc2rZUkj) z_^zs-&cWi;t}6d_?$VH()YXP)DEzt7t9W0W{Z-H`;Jbh`w5tohr1Lqj<=Qr3FDdgD z+aO}&Y*lk;9K$Aflaa=wA?Mrp|9OXFlQvmm(?`ISG#r<;^VfQA#O{ygT%psY=^G*^ zS0&sTVyqJ+Bui}xCDqUJ&@Yipma7im-dm%=I2aOXjc5mj*P1#@RAEVDr`xoXgxoDg z4hc&It3@bITorF|ZEk!18qe17YXkPC77wGm-G;&o5ma*>8Y_ERe0Wyo2NA~|mmB-z zkdzJY7jx_4$zm1W4|=M0pp@>peiPzL8T|-W?Q+VLp;?=&#CAJFioy+Wg8-Uayrk^8 zFeuaKQrJ>4P=Hn;89Bnll|t90QMy5ZWkOZB7gbnYI{zA&s->=gsS{~4KLLXO@y82CLQ+x+uI8}>&|@LqgV z0zjX)rv^~{rmf)Qo3I8T3CmhZuS9S87bcBfL>?0_t%~*#PM;YPJSLd*`#0k0r7ps7(k%?Fqo-DH9 z>=;sPug__;^lMl9eCofIDzIR1nSFZGoZQP_ z7NuoK)$bI%u1QluEoe^Vo^2^s{lBy;u0#BulyAeCVqXU@hh)PS2VjA*c3mR!3u}q* z*pfCPsCOV99;lA+$?(|KG=xOKQivhy+juOXaxe8YO%=hsg3U2vYHt$h@uYne`R4On z=b;IAEiXmM2R4%?99Vol_oQi9W7~q6+9XNcBm&YpACS?7rwW=HRXAkV3zt1-BMU|p zwBI!&Ps+;$8U9dsPrUa+qxtcr9UGFqE}*A`-VVS>8AOZ1P+P(ufRffp-Bz0oi+T^& zXA{t;%nqU9J15nicj4%It}N~Fg~wD_evR02C7e#+T4pf&AA}^Pq6Db0H~0=VjcdA$ zm4}U89xvu3cOjNupN~ZlV~)>c3=#@#TfAcl^Y!#sZ4rEZeWIuUhds+#(zdgTW-9N- z5^`pQo#7M9@-{wcQ*%xttLSqRReCVK4v}OYixQKt+fEp}nO~63E=h)8zVRk2VfRPL z6LC-s5ft}u$*OLiF8@(IxcVUbHXj|szPB0cTe~bva~0KhSMfOj^P#R#6%uTce@(dW ze-~rE^lz)jA2JMx3;vph*sSwvd*6usd;e(uNJwW0IfU<QTgy&-K<;ZCjC{Bg>^(dVeG8xa}sDIq~tO4MYIm z!OXKK62m55hGdbgpoSpD1h67O~!L9^QELmbO$8$&xX6CIoEFRxm5 zKyzLWtav88E@Zb3`^r(U@sdy|hybW0uf&`oQ)YFN(71$PP2I9Uy4v!?yO zXs};_0afgRN&5o zhN0yZKmutVY8XgE{EXl;?XQeomrh`^Co&f1TBILFRv9{A*_@qQ6Q1lK5D_@-^##zXsV8UIqoaTtKkK&C}y6rHxC?b{<+Ij@ja^ZbZPW%YWxR4=}A zE9!JFJ91bI{Iv@qR7G}EFW9`AIChRK;R7c)tK)+B>VhC+BSRLvwRZ@vR3q#O4ZJ<< z_h9;2?elv{$4vsWg?oJOgx&U0C0RW#mtujkID$yN8Z6XRbNFC)U7ct8;OV__ZXx=) z=I=%-ZMo#5LUg1i_ex-^0NxIjot(H?^^#es8EwQkRALYQkWh$6Fbcu&Y*2g`K=WTu zA)s|L;EyAx-GT=hjZX{o;(n>Rm7WFxmH7|INKC13RW)*uNnRBq2@$|ZtgDf{_+h#2+w|_@ZGz6 zrUxFVqJnryNArZz!}DlOpn)3|-~K(o_~ud z5#MoW zG(DTfW4*!DQbvfw(t#PJ?S0mi}zv-a27ZJ~gtc%XD93*l5+RzFc z&rNSbtor^@O(Az{l)wdK|3f0PT`5}b(uSpZx=L@uKzh^PU}_@Bz-UUK@|hVf!W=H< zZ69)3mg{`CRAODB@Ny{%n&%C)aKmbN=h+r=A}C?ksn`~2>@ZvnnXu$2#NB;WJga*s zjb}^$6H(#whL^AX=ccox33p)g&8~iMj&^p5^RNI=d3zqBo$V)m-SKUlS*|^Rng1$K zUt=k!-1P!p_?qcY@n9v2JVkhhqFD?!Oj7#)2gJE051_avZP(;6u6ch!ZjPM5AX3Gu zqs7e0X*I4@tUKuh4k9uZK#Hy&cDCIF6YDc*kXdsMCrW9%KSLSOFUC*Lby*?r_nPiP z|0Vpz)axx=`$&ti)3y`8FZPigCVq#np=2)L1GW!DZlELFZz;<5_$7FW*{$+O{F7>) zS&neYQ6H4ghaHP7oSX%3IfP>NKX(%e$4uScpjp>6Dcu=KxWo0iZKr=q2x*-2OG^t? zS6^yhF(gw=fg~^lV)d-;&^YrU=?Xfo-Qo3e&MYabs(=Sz*+cz#IBuDtKB<;T09NcS zRy!qovKSO4$#y!;zIQLOJ5xCZhBv==Euo1Hxix42$`>L59g?xJya!rAost9R&l(=& zhK>s>t(@2cTE?xgrFXlvKNSiSrznVq!r24oGFnfwLTrDu_h%EylDJdR;Dti((h&t2 zt>-Jw4|Qm>$QI9T*M%TH+uT{>xQCWP<_qiB=THvl0G1V* z0HbB|>|eZN%Z)nphcFQAR7?p4UBTq^XOX3YNGzOHOQTEaWNw$sSwuzo_zmXK&vcC?Q}~!k6T>Ol(RkJML*%IxQ_i^ zEcK2Ke>oN??n!+-3A};%bT>n}B*-WLfO3_q7YWY9TDs^#VY$X-2;?^6yU41{)3w?)+3XAL@LcADGSmG?ON4~Jc2S7vuLfR)@A0f|8=npHU4r5P0HAZEsFkG>1*z z19~r07AEh;{6$z!XmpZkcj;I&*IlHO?hg+<*N1f2%q1picA{q1Rq$P(y{Px%wXlJ%0coUa8gDQH0NfL8dF zNe{j&sE37A6}cwbOz5#Q{l!C4zF(L}I!Hm8uU=g$HF_xQb<@hsd!;RRHayd`r45*j zSXIA}=rq(bruRV<{EjINXOc0T$nDrU^kFHiaJL^BNh z?nZ|Rv(l+doD7Ue>_@33Yzb_I#j5j+(2E=;Tk<@vqUGBF)vqCINg>Ous}<Hi>e&kljC3@$RO-;OR}6z-$Zo={$d_0bf-EbT96?jWB6v z4?W#L(#EfWv=ng~NemG3dLFx*K5+qHV7@d0LCoQW`onO~JbT0;22+I+mlWuR? z%$R7=-O#rMT7p{DBQPYcC~ReVnXG_;RgZM6rW%xk9P@*GPx0Dgzs1x;!Z^c*EAzPe z`cz5b{yIY+F{Ur`7Cg4#&rk#Fcs9cw#X;F?g)~9g5P^-ba$us~EsV1e3A~u>U*%r&wJwEE) zP#scP@Wa*B{-g?SyT9@F2{omua$Taa6|SFX@v~Z{GiU{E-i0`@v>-^y?5(8df28`Ptb4%lh@TW7>B;3N{aj>u@+v)=fz ztPh4~*=)t9B|p1gPgP+oaH2-|0yO_=79nTke*B-U+4wKZoq!{@1b)w7(r7e zC*NI9XgmmVP#nKv^Tiz?4;;s&-0QSR&rVq5X;BV<_dm(YweF94%zX?PL$|P(PXKJt z2@N!?wk(|UIum@|zwruTl;pMW?L3_ounBuF2(2H&9T+8j1B!o5#=88XBx4Jzj?(%G z_VSIYSq@kTYi~uKPTqtJk@p(Mbf}(-ur@6iD9IR&)IBCn>o|nq6Ry`*t&@KAR~+H` zZk^NPo;UJ}p##E@trc0{2>sy{@N)8ZDHmVrfL&A+v$vln zC#>DAt`M@V4$k051LjfXQ$x??@aZJP5aoBz1|c1o+7an$Aghl}BieWZ7V_3rJ`l$c z(q`PaZMqy{WD}N?6q{93Bi9#50~e}(?SOm(AOT0`$pyG=_NJAfjYRCWELRhPg?7bx zze~JZRZXvua;%sbZ>lXA`Peplfy~jbBwb*HDwx&fImn~HtQT9T*)|%jcuAk6xv)jd z%pc^eoZtq@5VJD3cL71RFpyb*T&&dUDod=>_~<7kImd!vUc9&VW1l$Jo<`Pqe$C03d{~D10mE5a&2G1aaDEgDI6r|w(>8w zZ{Wl{+&|Xj6|yvJ5O{G}u$G61=1HB?f-vMG5b(2P=5mD8T`(UJ_{~t}=ax9z8YQFV9;ln&-3<*Nhr|V2uey1Oa@1O@#1Q<@+~VHo8V;zr1mx z?-C5I1(%&XhJsht=!Of49lSj3PGLo~*jqMZ#RRiownTMxCM{xWWL+j&mHVPnY^n-m z3h}W5c@<-vRjp`83inb23&-eB0OPDiY4+#^e+cGS$_w6z-ELac`ynDrd`bl)164Q4 zFDE5+rdF-6!pk%@#Por=Yoa*YT2};ZLh5cCGI5?@3H5ustF}6!rP+VW$;6-td;Ll7 zGy_}`iaeE%t@jyV0VxT-3pj!+hjN~h{D8>@BS5%$;rS+7t zt+Fod4_n*=`GIuStT2iB!L&u{v#4Duu1jKllrE9itQXztVk!Jt&ZsD6-X3qQL>}E4I;seKJuU zN~bIr>L2C@KG;RS;aX+(3ryg+eI~fE^Luu*F8yE-y9AWmI2qR@o@_=)R`jA& zkBy+#duAWrc!jVL)KZJpIkISLD}Y%!44>BHcAG0s3I>Yxs_DiDsCyAjvp9Ma3~oWL zTo|EZl5H|ES^+0JAu_PR=?07Uy2u=3kDjC_^sc3M&UW!g841d^cBk}XD|PfE!sa}1 z^;$bM!PtMe2qhiVB9f^bh?#KK34y*9+Nqu~jK%=V!wx#u61KK=RuFlxE0&&;?uW3)RB&E+c6XE>s-UE>DP1~03GW8}vS=|+ee5VK0 zLs=bAZH;U0bVmm%mQin%B_8tepQ_R{Cna}h5mO~yj6UDAO`3n30aUtmq`te~IkbJN z@NfEAINJe&(RdqG6}zT16}DqT=OhXe&h_=8`xz3(nhiG4DMjVKeK0l&)UtH%WQm9% z!glShtSWdy+#$c-7KQsGykC(-&kA7*KWn(I%d5!?s@PjEkgSyJF+;txylA)Xp^a}j z3G7JTlM-5%=IK&6whnyxug1lgaIbJYDq`jPNYjyCR@apsMGZPZTUg#6;-IpT~o|@>3*9NQM;4aF7PgP0@z#Yr<$-h^_RUlw>&ZilCLBG~jNhJCLVm zC36weV^5iX5;b?Q;dNBvHDzZJKuwmJrl_v|$T#Z`(>~ZDYZ@@IO&!nM-hFk=O=?O% zY#T}7FvcaeXdMMqu>k})|fv18>6|6kkt-f9yzwS3K;eDPXfeS%W9ojrNUJ8;xi z!PC_XCO?#myZ+`w1gi``8&FyLMNyW9I^*-iE7#@WSh5*Z&!Asydm62IV7z7$+5+TiUESuo<}7u4uW*$dL_C{aFN!pwJv?UTV&p8SNYtPl_T4c zm5h;u9_nmX=`iN%AMO75e1=}83D?3T?+AkVk6M^}FrB$;Djtxz zjG@Vp(7{no!ui2oA=kYiXvAcG9gL3i+BBBTg^*(M*`VrKb7}F1)w8pCF;5tEAK;6S zyG#JkIe%W;21ZTi);87Uhv_A&!&QJBz1$k8&|$s(66l@*J7qiWATAr?0OuP@>r$Ml zEXKMd!wj55hH~G!_I!N3y!a4Z@HCK9>pyHj0SGd~1Jo^CJ0zRENTa;NV1G!RhCT6( zEnhe9agO7m!e1#=&sS-vFN9MBkVW?Oz*w~RksB4cvHxOIAbE_F9j1fe7)m|P<2qve z_%W}TLQUjya{k^XV$-8s4RwQUgMLr((kJ+93Cu4g&X<@KYDkS_BhkG=S*HS?1GokkhYmCF^JvzuHdUy(CT7F=x zWNZwlkNTl{JjDG0sI+rAo%j(lroDQIC>X?|)u%TK2meMym7BkaN6D!L5-zkgHB97k z3QdMaIq`d?t#`_}RAoJIDfpItKeh-epSBCDl6{U6IycHHJE^1fzWTjzPX7a0H#6~R zjs@gKPc!LFkw3V?Hd~r|W)n8C0641IEae-b0HO7t7A}^)Zi>}Qhi=mP`@#ev6(>Ri z_6&~(Y;+`$HedN_0_kEHUEv6x7-u*%e$JU=px#C}i|W7?#}qh;GZ0rRJsAYpG6U(J zq^sCiMuT@|BAx$9yGCV&L~sr?W$v7XSoV9o1WQH{)~6~c7WxWZCnzifiZ}Pem?=dv zPaap(IBT#M1CEeRl{#Os#=5qc?Rt8epvMWzqNTXs3h3#GvIavI1a(w022rnBw%&6W zcJ?xkYyAsAop#iBOM#TL*Damk=^M;vVosKdWJU04W?!4{{OrIN`VhBJCD^iVJ|4%t zz{fr=KY!BGN&=nd3fh)%BmJlK31eYf`Y^*^mDYsc9KIN4b!0p|rwvUIU#v65H}~75 z!|fnxegR&R>Kk~jvlR)Ys?LEfB048o(dhO1Pw}lW`fH>p;3IcRnG=^avlRZ>Yz)UD zjM+9+AbHY+&zaQHY%-v*i5TtsUomarwfHv2^IL1R0($VW;*1FDhk`HyKqFvTnfe~f zjc1SAI>K2ee8ex2wtC_hUf*eRMg%U_n352^V7bn_W}*8dKw_D`TU zU6sO1IeD`^2xOw*o>p;riK{ZHj1Vwvd+#{IlDwy*hhH78(a2%LMXNz+#V<;7x(_FO zeuAr3`OjI-UygK8H)7}C6v}plfJ%s$!fHh4O{vn#44(EBFCZ~j+#Q0yW*+%6L^jKvSs1~iO=gQrV#pt z0Qg`RN7Q0%Nf5s49rz@1W&c!jggj>U)Ahngi7);Q{T=XvF?(8W>Z9c?JN;oP; zYM#`8Fj@Dk4PNqC<3|4!XTN6$NG}?!(_9Y6d&tnp#;uT$8=M(PV)>a;gwd|&Z{KF# zb?C6ULtMgI4(ATyb?~u=$jMlZ1+*c}D-xr74s%@e@JV4qr!q~QpPn_LM%{W-;}Fqm zmnNtEy>!Fgl=zj3&NWKP@V`cX~>ciWNzBFfp@e*+macKfy;k1=uMQ|v~-t=BU(P#c4dyi}FlrKnbihn=T2Z+(`rgtm`|fh!o9r}k|K zl%y|A1HM(_|0jdmiZ@&oQDo=LVYW=<@xq;5Xq^9k6RY*&NFn9G3XBkgKr^cf-qazD z#)|3=GZn@gZIv_<+W;4O0>7NKgL-T#HHS{ftLt_Q6S|pAKx%HUm}}H1lgJS3r#On>hQ6`Ua5Y4_ZuM2{Xgj(+j0E_=%dA+xp728F#ub z&Yc3O{6_-?fhLuCa~HCRv~Gf#zcs{b<-ymXU}rsg!N_5%5a|?zV1;8<6!4xKuVcmO zJo};Z7nH4{z9G!XIkvNut^>oggB`Q7IHVmaV@IaJFEUla@rrdPpeVM|} zV;6z)$-#?Zy9mE+@0wO*t)*IR_HQ&UlROhYBs$NrcsLZV=0hY3z#y-ZDjay(X)PMg z1!&H*Pq0V}&6J_DG`R?dw5TRdXvYVlhyp|A@Jk4&pF!>-Q2H>W5NWr_AJE)_K6-SO zec^%+YE8&~^9u`WrQHkL)@2L@;A$G|IKATmV-na%af73M?h6${jE-u7al=RiQ$i>Oqf0$;27O6!uqfbVirg8{=sBB>iyh zUv8=_=Cc)-5)hBg8;;pG|7Qr4nozVbuC=iq$XO_8ENTqOggmt~tP1M;evrofy1+EC zGEDr7uQ;}v(r@cZ~Xyu5Z%Q-Q8sN|d+SKO&N}UN0Y>k%r>goPgi}m3LUZ zH>A(}%zAz-K_L3nw$mB3vm7$(e2~?_ypp1fn^V+*;H8A9Jg7i~vSWQsuR@zF?rf#Yb-xYqKsXVu= zK4wM-3B0TRjnsACmZfIu=6S9Ll%ODKj_UsM)Ly}4?)(v52*HhEtIA7-D%x*l6>M4j zWBCZ~#P3PIA!;9=r*0V8Nv(oU6%F1KDcZ;}s;`GalG9ZOUS=L5S(I2=Shc@q!>UBscpwD2D zFd7BXPLMnh%%q<_w{Dnk(=>nt{+h%>JFTwbIp>ni5Tds1TYBQ` zi~bG)*lyH;D(b1f{lMu%4N9qUW94y$JxIF>dw7zWC?weM?PlQKKtSZRBjc?~N|Z}U z+mvVYzw^;#^Z%^0wA(a16z0}$1->iNl;JXS+Udl!~Mgm(|G7ZDOn+4OzGO5y-Yt_74 z><)kqKI=h&g0@3MYeotp2KrC{2`e|euY~>{iP;r@BwLfYQCbX3(>_d3b+9rFDmB;? zDO)f+CY}1&E?a;&z&3fwGZ;4FsF1mu;mQ#T7_y0rpf5k*0k|9M9UyLj~m8DNoaXq@qhPmDg z7Y=@cd#dZu{#l{|lw-E%lAi|R8Hcr_c$z?gj4dacAm;G6I)UqRCCWq?4hV7a_Rg1h zFHaOS@W98+o>M0vsa|2x=c~mQ;Ages!{Y2bJ}%Kg>#+5@ob8E3g8Z^AJ;o_c=oJDP z>TK(+rwYepDzjEMtGlYrseeuNC;1M(?ey_H0bpIxt=)vZo^l-$VS5f;`8m=2BFWeD zMG>1Go~D?QX%V98{}U#FF^Ssw*uzN)lI!iRQebCGir4%}QQECs+oQT)qEG7d4^fvmua`6rJ9Nl1Nq(hr+ky_N)x04J#9oV3~{)bknni+ zH=A0q$vEM|2WJ|WkSectwyZX^hQ%Ncp z`41zt*_yWpk>zwR6A=?S_`RgIX?D3%Z6L)@TAg{wni>VFZB}NkES#8-aZ&!cFUS&u z?O}pGm&sU%rkJw^>{b$5#Xr>JVpJ`bg1s%SWx=m-spEI&aXAcpx;Sz6W{R=y4~%KdTGiKWiWXt0L`WT3gj@OhF9u3JNeU&65_ofCj>H1(--Gam~^aP zp_xV2B^WPs@;=NwmsvCKjJYv+)bowT>V$fFz!)Y~K@=Ebr((x0Wc^jb^kKKvgFw=F zLd6pLH3YOz-Ct>iGxpiSfSyUSp>1V!2o^VOH^oH-;LpZL-ru17Tc@=9`>?i#=40YS z;kopWph}wS^|5+&RG3MuEy_7Qr%X?*s1#GM-x}i&wfeyM=mKOC2Ei7R-;J&>k9S05 z{i{-OB|(uq!HW9lshJ1461Ww*+4VABSGmOYt4_yaJE@Ce33T%@ZCWWw4t=vPuUt9b zN>vL=uT_+$K??EsBm^i^oo)D~L2myNrPYS_arBi3DEDUH3MO#rCLs8axyfW4t?b>JckwAcos! zXQ|IsCGSm?Q};JC9-k_ zhclqO@DZ}w^MH6DQ$}s^&jqvj;J97kQj4%fLRS*KY`a2Yhn{^kUEA}r*ex@6pfdLDkVoJVn zR?X)~Y*(@;fVOzbdknJ^khq;&pjA7}T&Fxyg8?$Ku7J1zp6g9)D586ZZ?_vW^c?k- zx5lLJrdd8{Vx2Wl7Ube@V0qX$EfeTnnpdW`LXjQFqBa+Khq)bjL5a+HCjo?h2c>IK z1b!n#457rp1^WSg$M+O^UN>~CkEb#trZ3R>uatNXIv#+x81{0slc>TMd;#XF6(=K2`=c|`F)~Eep7ebPi^h0 zfK_>QLep4#9KbPc6+RH~m9}$f5yue3FCw(~mf85ZDe|7X6LN~xu&}kucw+Km1yR26 zrlT9Ast?LEH=ajr;*olHK_hL6<`o?U@rttbK8;jJ=sf-L2*l^1F8z{fI#$_WtFIlK zlkT;)V)qFDe&wSr%Q*@@#z3Hto={^0ZNX)9@NUb-f|^$1zO!BOgdPm&55i7@v^!fv zGVAGa1M5J*Vmy8y%Hm}FFL}At7KK%%qiWGVOMOuPA8C}?2#?c~pfV**r>o=Ux4xJ91o6BiyjS==QQHLwlh~d?4Kb?8!mTbYu}Uf zsmDogfl$I!?~6o7=SXF!pc3sbSlgWbdByhZz9rQDFNiX$(^`+p?lH!E}R&iv>!m%uoFQYc=M+P#?h zA8XCO*UVj3!{cA-pJbrNfT~*&W6)|9%KGBmO{FrHrJ~4+60`LKW1Qz+F8u9WQw=;V z^&2Y6=%!?UYk$9s)QkTLcGu=m@FIWpy&U)5A4icTsQQA5wh@xYvPA1X!wiSzA4sy| zE#Aj>LxDNuSVQeoPTbK&O{dv$s-}(mCGfvWhDHWaGIwd-YC7C=O%V;a75-ouf7l?L zgYoMb*o&gppUjMM5*&#gX6`0VVW zkMpP$xudl5FSzjZV}>yGEhsYeOMqt0Fa)Ou!4UCkNgQl62y1xNXwc!h*TdS@gLHIX z){isM5UBPTG}ZhqjI$7}!sg6P9nwWP7*cY4bPcKu%CuhDVdu-Bza$(DSxjV#tyEuQ zRSjYmRKXrfG#UooLe__(WKVF%+b6Bu?D^0Z9xczjiGB^6Z+E`M?Hsg{!R`K7jg1fY z3qf07IG6M8%$Ky=2Zv4KmhlucO*k}3fVhb#d1ih6vs~msAc9u_ z4)FbQy1?>_K0!lXWHqzJ@hUFm>Bu4HZg3cT^0fo0&#nk+?-P52@oqW|drg#)ehP~5 z8U^SOin()4UGlB;XG|#wDwfUx#4M~{Oo(?mPo-b>mct1S_k%G zXoSt>cE_A3p)g(_H=x}Ycj&X}Qf6h!YzZr^&d%esfuX;<+^s^LCa9U?Td=TC!?04` z9w0!ccWcy#8!8hD?WUm@R~Yhxr3j-ZTIbfkJ$!=Vq%_F{slCS#UF|7kYAC$u@vlN9m+X`VJIpAK zb}O1=4V+}8PurKYVcTWQlJ7vO*Qk;&T*wGKy%jrlHy6{mDa8xa#Mkb>&$Q=EBkkpA zQG@zODj#8VF4NUUi`FRN?cin^IabZy$h1ss^9V~Io01A_t1ikkvjt9b=;wG5NR^OykQ8sEDwB&_XbNGb<& z+4=#Upme@M592VU@JRn#q%m|+OPaZAy}xHC@Q>{(%u|}@_NtH>?zXgW`qQR?ckCLK z_NOQR9S1twDMikt8rYvyRtJT;M@yhd!n~^PuY>6ExiAGmq!35lAD%N)m0Qq8Khule zyBu&0{5fAuGboE<0vOQZd18t%N-x$>WDK1qB!}y}aizH(M`1~StC*^`<`VRi=|9x; zHzwt~)?3rFLNDkKg!I51*a9FB7mn%X$#!rTIYxYGFNz8ZwIprsA!yt4)l6#KCrxP@j-y7>v9VGaGO>{vRU(dE-Q90jxCh7%i^NQ}yZ+6f+je-SCzRkKql0?!uggnu% z4>i;ydvZHJ)Y>pl)0Y0hbWI2_`U8?i8vSrT2CA$9({h#`%Ruwhy?YD<6N23&X@Zo? z27a~l(gQv!#qAC%BiXbfys{(K!DUNZBJv?BHl9C;S~HkF0U4`$Qo_O4!8vBG;=O-zDnLl*=l&MreQ<{w zoI;cPnO*Zyb)&}UHPT{$Bb0YrO>z5uw{y>2^(E;n41MGx93UpMF-kmQ%FS`P-I!XY z?<`()7U@?X>^4ZN&$ifGmQn%A-l-HV;T8mjqzA2L9}AXk_T>j^NEX7&-g!REm8Dqq zl;l$ZcV)N0V$FV`HV5vCAuATbbe}8#03U@|_rAkNAp9w@5qzv`oc9qHH$hVeK+ygMnN-l?!?iB*R_%wzI9mx2g7+lSd&zgNE~$-Cv4ZAYIj}F z0rR!Ho55GLtw1Q~^JqR>8^=<0j&rujnri2ErhpGyHFPxg1xNvPv`R<8lY3TAOQ0Tan1xp?{Y8-g&$DI!&J0tiEQhdLATVu z&|vWH4`IEtE-$P=7nThI1q9JBhyfku5x0FGdY3`-42SALhNT98&H*-!(~FPgoFf5F5(H-?}IEN}F9hISl*uQR{VcJ-J|0nwr(h zPgLw)AP-w>XCMhG)jO&raHALW7vewcgOH8-*6{@)RAA5&Fpn%t!=hLAfnlFlTg~kI zessbqA@5m;9PZIDsnIDtUBd9-BMU+k9d!+pvGbHfgyGmGNQP26NOO0!IE+21?$Rkc zraMrk1?!L;Wk7Ti_l-ug3X<*SmRMzxRl00^c-06G$fSO+u3Vs(It(4Xnxlqr>4l6< z(SZ*Vw%f|Zlsqxi4#~o_qMSRkP7bT%(}g4 z5Q((YOkd3gXG$kCwBl&Hf=x|khE4Om=PKQ=SaM0Y2PU6kNj@x6 zM>y4KZ2)EBT2<2wGt$yM`CFj70hfU}K?ZCwQc&1_{0o8AqvwQ2eK3yKbW_|nwqO0X zEQ;EgHSN^$tjIO<&;LO@VFU7}3v9EM+MBKS{IPrl!{XI2dtm_e#R|+(tn&U>MBASo zpE^3(dZ+dAayw)$XRzC9%tXQYhtSt<#3w+FhC(05Hklh}$7_5GAw&qYPb~Bzsb3wO zS?Y{y#p5c4iU?l{tLc>_UN??%4>dhqPq>EodGc4ki;bX|#p~TnQ*+MX6e&rgdpWXjhNNz03?z%jF=d)y|qQj{zeKr#A$y= z^q~P1R7JmX+Gs*rbWifi{qzwFEYARdDtD*{T$Fwb2882$3VM1DoEDzqZym5*U06vl z`))OzTR|&Bo(9fevi`*~3Qv5kpIo|@S3<;vwk#Egc0(fiA1=KVK!%%gQtcVEybVN` z+PM-M(-9NWrgU=rEnJ;$P?d5h1Wc0KxpuaqyQyzD9)QL;boG)k@{~Vv^+o69g z>S4bY)ui7@e#l@^ROr-mOi^7#*~J%1=DTsEzicwUUXRl0?JIcm$!kvA;qkC5ReMA4q69HITb#wf`i!YS z7Qvm!_*~VIIYkGsVbdF{yF;Yh1{wEZa`uzG^k;&ogb`Mb4p(CfLXy26TGNScNe~;b zvKITp9!4^)gG*-wR4r?;JgmG&_cVW0BmKT9S=bE$R3{2HI)g3nLT-xrE$v-&cjIVa z8bGm@I)xjAUt2>cz-PxqN!0CIcTwJu+H3LoqOGzy~=J`FJq_6^Y;^Sb5Mb!_*g zpci$9PaC%gfPJ(CH>@De=TxFF@j@NM!VO@VO0cIOz-7gFgw*3pjJPlJ{nm%HsVhmF z)Jp9oX2w%>W>iC`4d=eURz**)n=n%GUb3jb#C8rYB7+2JG}cL#3739p3w#uKtEK z_^By|?WViWktEoR{ep&+*OpoRMtmU`Ajj)OTHb4}ALl+M;M zmNiU3Sl}WuTHpar8U^_uV8Cl4UznWb?JIgEA-5;P2F0CR^z+qb9Q>%EOeY8gq$@Vy zA*FdLGwJkn2k8zL^IjvTaD92!i^*NYtX}F0_ zeO9*AXV)NAWTS$i+0KDxk1khb zCUfK^mKVRcJ}yX{++d3>!0xgi*;Sc-^8L+-8KVR30U^vn=ZE_sm7t2*x*w#Skwf5e zD?SIkWB5gwICa;_O|{{F@PPDa;CvW4_odvVQ6X6pRN|FMrvE)wjThQNIEmLH3#R~{ zUmJfAMv^RWuF-C5y734txndqkGXX!y^!1`|&WDGO z88Y4w3GxFf+n+~1y8;D|d`Ljb3I_IK;_i#a6E&$pqMNlRqg;`m>-=2Y8p|fnlx+*- zZH^w`OMMNUwrKk)Y!LF_j&UX~wE8jr8G5KYu)w=%wlfK_dZAPZM53YQn4Hb3{X)nf6$FlfDz7`*ZhAW5!{X3TdsG1HSu$(31PLFjkRm);7K0(H<=xm?8X2u6 zUyuJ}UsVBFGf4e|`W#9u=Fro$wNC(UNCZNYwFf$@X%#oT5Y!)FNqCiS)I@;^_<+%R zNLZYs0~9wrBY5u+t18rlXhK3!M8tZc)7M#AmeF4-$@S%ZMJv$kM*lbFCck$wsli60 z|dAJ;_h#T}p z@QA-b%wRV_X{jF)2Fvxv2ZFcNo`f>5_rEhx+G=+IwC$y~G!8uR!#rqIH{N^=s zh26{|CVrzHa~Oox*jUHoC`gkrJLLXeDV^$RA+1V~akXy)j2?{0Ec2l%Rmd17t zIY`-25uzF5%8D0x!0(t*yksb6yX}vF!Q2KEl>ih6|4AC4t9YO~b~FOCp7p-$vV9J` zg8x&|)E~A^2BA60wAHrE^K}YSpv8LDECJ>k^hFEWrBTTN+}72?_4=uinK#;3s;@rD z0CbiFwsD_Rx$%1n1V@W z;g2rnV1fiUv6^oO6gt5z(bN>gCZ^Lb_s+>&wY=+dPiWfACT%H8X+_-VuX873YvJMt z@$;!1V_z%Zn2pgh2rq;>Yn&;i5r}QAc*q?kFF&_Op=iZqOne;Ye@^XKV2ROr)$}eX zJ(-|p%;2Kv5FjnjkEPQyNw#M!WfKW40l|n_(`z0>KrA_~*RD2t*T!dH7|o%_DrQtD zUo3uI8(hlEk&65!E7LYV0-0yjVG?w z3>M>yXq=<8g*YsrXv{5oJdVc?S?d!ISfjFRTLHBt8b(3d-vwL%BL>#ef5GjNdl8#H zDRVBY{Z_2KH zAFqgb%_<1~`Cn{cb@VazOSI|;*U){d9J(?bx>rgWz|<89nLPHL_Ilw%^$YdSKgcpz zx>F+%Za~KC0y+8FW(G_J?x6S`8uRLJ>L4`m^GN}-uV}j7@sM}b)M`+i`=JVCs1Ziq z1VF+B1Nxwh?O}rt%c(n+36d><8&Tk6kTRrXW({#$vZXX;3$P7^1s)W0^%Z4 z8H|M!+gnrVeDFht{iao5?mxWc?OCDxGw~`^kQbSq`yx_hNz5{(NJ1#tetmn#d~kY3 z1btY3WHs4I3d>)1smi)=R6sO!emZsjP%Wc->aixg7xb&}zi3bnK#a6+3vI?`gk#Vm zOHqAjEioVn#k_lrb|w3=v5OnHOtt@eS-zs z{@j(SGF}Sb(S)c9_CDV!B8ne^(8r^oxHz<;WZ4mJS~s(v#cV`>kC-IelBd3s<2Isw zh4utoz4o}_wiJ!h0~#e~r|2|7tVS&$BlDo!RxLQWu~uHm*~38`mtD`6mj8a1QNT;i zl5OZ@zPKfCGb}8k->I`V-&dU_vO>o#2nnQhFOxJ3ck%eC?ep|MU_DK$v)dY~a)1b0 z>AxxQVQQkB;MT=k$wd>8F|MJC7sEV`t3|gW&&L)MFBiatbG%4rF51>W!+3p(3TPKD zr%D@MYV2{EH!ELx0`N$;<0sn~!ky^SH)ew>TiGIHnxi2h?c;m!sdF4&=AAkN%y&+k zhG^nOpFBPIdCS{FX9l&Yr_XNtoJq862XrtZ?fB1h#nn6RCU!v*tJv#&71>Wg!f7SN z*&VLRoGb6lI1t&)NDeMr{XKc&p-N=V^W8^ZEA@~PizK8+fX9y9Q&dB(e#~M;=hs7c z-K`%grPpZ2%xnwGWnb?4X(Bw+tI6|EH=6)Et+H25K$}xY@L1vOTI7I!BigSP8-7Xn zaMgEkVRg@Z6EPK0NFANJ+jRz;g+>GgY=X7F(aZN1U&HXskKg3jKrRRyC zHDz7d`Qu0lDl|nkVkG+Wx7vSnh=IsINcNcaPkvhq;jp+xx~eNwKzL1KfW9627@++aHN*TK7ZhC>RBTq z+_IESFhLR~wPDll);+(l4A^_&=AL z2YDBckh8`SqG%PI!!Irg^WY?mxuECAHv=|fNl#?PHZXn;NaV6A@Ij?fAjD`#L`2?osG(2jDEctR;-N$*1nBr~?uqE4Ps-on95f(>d&N4!Kccfsa+Ryg+o z=j6rXkBlq|WO1derU;-tEh#r)d5nl0i9*ZZHT2KZhrYyJO#4+Jv%R@io^Wz8V;WH_ z1UUb5#xrMRY;*U*Nq|%odpw1orQ0D0x$)gZ1ZqIRBXC$c zxzf$-Aoc%P&&0>>UhnSh3a}?H#M@kU7q+k&UyIeE?mLyOypnq{YJQ3oxQ%>fH-n(^ zsZ2<`gJjiqX~=8iiIt@Hg)=v_(>*Q4Tf_q^nqRyk!sGkZeu%676GfV0??RS9Vc`P` zu}oGq0>*kPiF55$v4kjX7oK)IDq5tS{rD6opvA|)d!SeOz;qyiLY|cl8^ItDi;4dt z?^h)d^6YsNO_@k498@SWJR0mlHc>1VgO-q{P{uqC+-wF$GlkG-*A99kg+g^C|9E>n znsrjwMHEu99|V{NX9u)dhLt4v(&iBILu@5hnw+ErtVSgcWN}eg*o>E^=vNeq9NvGO zH=K+gGs(74Z%&nrHt)wJOwC?g*l*s`laE_v1CP8ht(!oqO%TWiR_UI=A2JJHv=F8O z5w)ijF!WI+?x4uuTXc1^QLqVzDp*C`+knwBaEKyNye>A2jEz)GuBz-Hw=99h@Q7D{ zYA4cy7bV4__a&#>4UrjU)CNChn#SYf{hT61U4Q>f+hfObY0u(`M#Q>oKhhMVL=Y8e zoj#dlrwCa?$IICWd|iNnZh-U~ZoH`H?OWd6$A<6(dz105{xv6j7f857=-EK*q~E(c zZvC5aWilTWTduFvz>V>0Y$Wtu;Sd>qXh|N!bK3RN7Y)cq%I??ke-@3DpcxF!a0fp( zpiDz^f!t4)O`Tx;yqL5SHac3AUr+*rvpjH5tcOouCnA$dBHJxx&7A$llKWm zTTJFQbn%pr3$bXgj%glmbU0%d{a&?U$pa3hD-d-_BPh6e&pBl8g=BPygMjn^{4uJ- z9zzWEVukbRy#|l=XvcY8fg&FsbqAMOv|JMVop`Eq$V67=(>S^tt!hm!GP4Y;6*Yob zndk*P{kl$>jTEEaUIv^ZwOFBVUG@zS$7E)a0iFT=fBhHuL!YNZ$#~Mg2yB-?8n)*5 zx;+}@iLc@pf6l8#WQY$$%qtryMdGZ%1D1N!K~n`Obe`y;bpXSsCH@uDs&&V*005^Xr@JGx!}ig3|U zM>8I}fil-!M{2Z65EqWu1J2XlQ$h-8WHr!()Zv&z$lCF#*oDZpkX-^HWD88m&_#C52&hA31H1Vvvj~cpf^3Vs9>4=A0LM0XhC;;^fF9%{v=&UE z^JtF)D=LgDnCWO~&S0RQJqAUPpNp92Ub0jO=Nto+SWa(-gyl(ZpC-#&qsfFAgchTY7ClDW3;wKM`h-@zwb7Btz1J#ZU5DKnDj)*k;*LP4 zz51hdv3K%Meou%&$WUm5wKM&{cDLRC0zTAu9@B>iZ&RQ?&6GeIs)D`9EeM%c%f4N( zh&{G;zy+m8^b^!kg`QU4_(b;3_DadiUKB&WFUV`SW$K}ZXXHtVJ>hpFcCiK9+xm^` z&t3e{pcya;PFS(5n)G$ey$8Yi@8-6^^;nMSymFtLG%SSpn&v#0Z9qgSUsg`|z};C9 z$9QA47jR}@;c!&43gwiH-i{2#MVuKEHu!03_EzV*|8gdt?*!Z4pvc2-;!V6%hOLN8 z>~AyI`c!cJ!6I)bY>i>+l`1`eRRUup9&~>Dq*?X;DlZ%4kbrhRcP9Lmm+GTu@KOva-P>rH`xz~lu!M{{ z50#6FjZ{|(wi`L?1E(kb?DmNkM19o*VokJ#YLQ!6WZ9%03h$C{0llA>1C#wgnW1lQ zyUr7&9Ag4i$y&ok?$Kg3ZA;m zc)LCcyAW*4Q5g^n5w(=z)*6gdVPTGqlgi@dxc*k6mn)N0=Jw>Ho&`vKa3d2na79*&e@#c6oNOv{~2j`wXaqP;F$qpY(| zq_bn0-3dI}+HmMdw_A^yZg%pbl8q(OKN3(>@o}C3 z>k3Z}m3Q^3D?v=~X_cxpGhXbwmd*%z9nE$Dn(IUcfL#WMJfviEvv8i6!cy}yuFa!} zfKGN88is&KA7GNg2qbOFHx3XJ6##oO9$8vVcu)EiA$0u;(86{VrWRfCV5)+f*0E2a z1!Hdhmd0E5wftH8@lj~(9aqR|Ncm>SMBG9YiOT&|y2b+u8;O9E@?@LX(RFc00@&k2 zgzWo`dG%0wZ?9G!)_@>m`18MM$jkZI6u~-ITrl=;f9e&^^4`$|qwgfC_ z865kgsLy@9*hq;)fU#j#V8V^~9+P$y==74jv+fXtNL(kVJ_HSA8;VsIZpNrSMki*D zSn*Srg2WJ6*tY*wXo=C`{{$^g!eh37`-;&LvpWs|95w=LL-)sfIe9@NX)l;QY~1N%i*55NmYHOUmV`f3poEjq1KkpMq8CNef`}Awvutt50OnmuaHJs z8yH;X?E7tBBY;*FdV~4bxNdo}wwP|ZUFP|g)xHe45x>&5OQpP3LSX7(9eEUI&9e~X zmoX?TjW~a1)>b_npN0YEP(VS%OVqO20>n(yU@_hdVWTMvI+;vpLQezu;JW9@W=qU6 zEGL?k$*l-txX_euQo)b4cp4HQU=l;B^v0EuIQ(F^zZ=z2B}faaxFNkK^DOp8Ar8RN z0>t8+j?1%w-wso(&3Kjxo=+LY|3H`AB+3^KS49{^}ILVr4uyEbnI@dNP;S_;CpkAdj&OTxjE zOf0-h*ZcGJVP$(!nh#*N4jr;=yM1z=@~+KHFM}B>VQ`CP^nT;w9bp>2Q5y5#c&yL`!1KTi!ov=wk^1vSento0Q49lVIO75i zVE(|Hv`ZLvx$%9J9B!?Iw{0E6eu)PIDuJI5%@R5QP4jtXQoyg8raTd?%Wc&U%XYM! zKkHOPo8&69F* zojMM~Url5<I@Qpk`wx|R? zC!vQe(>%@gftt87U>AF`g{?J_Dog7otOT|}PPH6r-gw(vf-cM8D(M_j2@asuIysZ_ zh6gTZ?)q-BM`;Q|eJ}>nzeR4{q5mo#0)T0DCmb)>%3(%Q&95ezE#kTrUpS92hKO{f_k_m1*wQX$!ft? zGH%agBX}@gd+`&`p;1@AlBG)odKpkJI6h&+!|f)3y5%7j`o!io%;oZD_9C5CBoBbI zAqWhUM>C3icpl&I&hRTQ`^Dwu*&~**4S#p`XEdNYhwQt6_se1Xh+J;oRM0i{l&)Gt z^5RCsp)fSk+4VkNO|Q_f;;27ouFZ<9KcShMlu^!Qwtd_|j{L*?M4Q6P6A!ZA9NXLt zvoN=sjAjs0WlZzrx#JzBq!!B|w@$!f9*$l-XD(V%8ZN+!Fk3@A6K;_^#l}H~TMo`* z(i2)g!jyk10mr8Ut`yY=%ZhhxU1@wYYaQ!09977>VWAJ$U7}-*ebe(&u@@8G&sM|Y z?X*5F)-~Y|I*4d!0X&c7=5I z4uQoEOxlP}(F)mA^ukEHW?^&V6m_E{1lX$Ehz9~Jbt=6_uY4U{!{t5l)>WLwbqqdt zui0Ydn!+X;K*@u{=*2I$pq>x_nl*8d8$0)W=(!@cI!o>iVeT zOc*W}?MH}3Cq&L4cH5pkL42Bk)?Jq!?g_$EF`w3CsXaDU%MSK=92k^>za3N? zF<jzF37SKQ&A_fKej4>V;wWs+ z>5l~Dqr9JKLr3SPFXy2OzYK!mf4Vn#3-cfCdPsKq3ecf(QlYX$1FMH@cH3PevzeNb zI|SB7b6I(0=LD0yg+j=Fng|>2l3>bVEp)u&7hqK??y%AuUltqJz?qFh(?USb9cv;>8hjAqKh|sNv0_- z&3HogsR|qlcO-5G4l5(suGCe!^9QaEl{08C^ zHA?9#o18YoxH~R)5zMA+#MRV?oN?cXPh z8+T515?x8M*xuR2eGim_%il=!NDnth0`1kARnrPEWX9rdICEgC=?d>%I8mNm1XH*F zBiKyXvans2oZ&!&m|-aE0o7D{;vv>pQAKfqC(m#NPY6v4wh|%3cbIYzGBe)HFvt*k zpZtI`C#1-LYDG;&h#lY_l4e6U3cYvAa1H zwKP{e9$nggL3_=kRS(;^-gemVl}S=#XkZkZ4ZEHD4>czmZiEYj?MZg?^`T_hY=cCh z%e>KR?Gn%HJMsu_KrV&2dFy`p#p4&lbR`A}FYgKniA7(?{*8D!U~I$*-!*Qsl|(fG z{_-8g1ZwP|nLjB21HZuc25h zh*d&cRksfPyCeA$Nobm^W8(`OHXO{h&Y%wUC`e%aLwf%mrpL^R>rnrKc5Gkypx7z+ z_&@8ll#Q*3Ve($iS)l%ZrKqKKl+CFWthA_6M}J_5oONMK5e)l=EszwfpHxrsq)kx( zh3SEfzvv#QRU0tvVl>rFKk>TUQaWxi@mn;cbnTWZCzY|UD$J^Bs1J!Ja2ifp)47Qki^n2d~ zTiKaWwIMRH1itq|E`ZNj@58|w;}h}Na0YO}9LW30X9Nfk*yzGmQ=-?E^=H}RbKF|~ zZayi#@RZfNGDXVCV{hCg6n})&AdkPji0hx9`YV4PT{!VhL(e-}bcpj(==1Oo<%gT* zWIM#Au7AgWg@$(ftqqY4iGPjW6~t!R4;*DMyAzR!qYNX=D(J=lcT~mt`fLtamxgjK zxig|Zd9c4=s(u57+4G>{@DeBxOF)Ey_X$1!Hs`n-NX_IK__!&j8AaoF%l~-+Lk78a zGt?5f1LWF{kw?r9O~rAUQfPNDS4B7A?iKM;zm!y(o<>=!-!jyA9|7O8QoSsGiYonz zJYlYUyF;j!rm*Aru!)slTjZEhz*?4Wt#cWRM9l4v(zwPXOlH?xi6f~BEa3&+MG`0# zLhvspGbpkSJ5?vpkzS7xoBU#>qj2X#F z>vta{Rf?3IreXY~&F0TdJBZ<<p#UhIepiT5D^Ig z@nl?~kFk|j$o-2+JVIzUgn5#qPT3b;`HZ!eSUFp(AByt0ijD+|XjFo+gFX4(M~MP# zIDQa%@<5Dy!U&R5Bwe_}d`oW*%CO;xL=l}gc$i)eMXJ{@{P$2&(XgDQqk*CphxwXd zK_)wCt+t8z?EfH5F`auEu(^(0MFl>K>ly8wIheI*FG(#MyZ6n$7T(C1dJVP}AKz0f zdXx_}`||I3K%d}ivKi;P_fnAlv5bMfwWS`%Gpvz(z2AAR=iuhh<)h5GPrHz^dR%?F zpgR_nA?X?wIqqZ_+cSlne_(#!6FV9^l(k0X?7}~m-5~khYe{28V}h_5rrW4DzBDnDpF){s$8xsQ@8x&o()d7fN|px@g4S5%${b zeeHr|iM_gc?U;Cl*pg_VB@~#Eg9s@R!XOzyCBG_%R>D=&hlZvl*n5hMy%RKSP_v(J zVy8hy;k`9zDih~y@fPt5I5VO7=+rcYe!JxuAv}lbTCI(KoCens_hob4ztvKL! zqVc*3xANsT6!i@w%S4&gJkQG-0cFymf}%@S6$eqNiiq{VBwx&0OWEj4IV3+ro?OCS z|7~o|^NGq%jJAy}<7+TkDx>VQ%CLV#%v2ULrE#Y6fZTp60oznr!55EZWGL%XLrm zDL8nDR6aJJGqO2RLXtC3`5pfUVyAaAuSZ!)xI?vnJDv;-tV;sZ!bpPlu`lhq9MCh4 zArCq7FNN8gg^L4ue=C|a;ozyq?hJa1(`yr>9^q0`vX)*60z8>JW)|ha8{6(LjKhCC z!K)R%;{QI`&%kKX9y)x`e-PzL0;$Ynnk;TZ1CNWbSC9_)-gf4}L^N(dfz!zN zE}*ITKd}O1(O?of(i7^%)$IZUEoLEcT?toXD#*(PZRW!V(&Z6iFab($w8DnaEGu1< zy5n_>W6CklPszjBPLEN2d2Pg%Ujes88C{Zi=fU-&CsBC&L% zPv_Bt{kG(R;$t81#Da(&TzbLyeh|_6eV64v*#(Y|@xB<5Y8fPppoq&W3@$q`mCZW$ zh|7vogam=BJW?I;bYXzSc*8#&3ywH2a^E=Zy{E%q?C@_KxOc34n11X(V12f|lxI70 zeql$^FRHAQr)cI+4rYm;D^OH$(!nqiNeY)e%j~l|W_I^H5tJVk5Jp@QgY7?#4E6|a zz6r+1Ys)cE---CW$MKSmMt}PC>-AgKI06Ic^8g4=-9-# zzW!Yg6UgRq)Evto&qeHkwt={f#m)E<#oXRkY#~k&+uG5KBaXTBv$UglWrBW{7;uh> ze$wC=u{|mjP5wg5bk4!~U21*o30~m@Vwt2|MjEpRZVYWyV8*JY33P9CRZp+@;Cs2YlZIE04ZXDAt zWl;j`s9ZGxjzIS1X zXfak*37w?6#u_dKSQ7tUaC1IWgBAF`uN5w#*;}8&3t#^WMo8ORygGg`q);el`!8lL z4)|^7mB0(V$s<-jSFr`?Rl7Yz=>d5yg^v4Wm%I{Ax7v4~D%fV4ARNsQb0(w|R$8=}b_jiMy`RbI+crPfBNvxHw<(j2vz$U{o9)ltf74&wl zpad&_B(>t0D8C>H-$yi3Y(KEGA*EGIFl4-gQP8vZ=EJlhA2JQ3q0V>-zd z-cl6E4Frk*W_ThzmOjksd#!lPN3jvJ8+S&aD3D|07g;^S;G;og2!cSR%J;2+etN`kzLk(MD6DvZ)|x zayfjUrqTF=RVQz*Ea#1``|zWWrwys~5Zzwy5+(x4#umuVP9#7>}Xn9#JE**`tsbi}<|7Bq7)pJ)pK`8U$B!+_@ienLcFJthFHGDm!YDci~ z`zE1@6S=)17~wB{7&iYhF1vWjP_k<~q$o|3t6zm_CaVixU9%s&vzh^fjJ$X7XFG%W zy@yCN?>q4o=Aor9+5aTd+2pJQgVl~9rJuI@vR>>axlUFn$?!0FN4D&*_b_UtEP4!k z;3+sbyN}Ie`Y$`8BR)7ctvUCW+r&lQA)0d@gnPzH2Vwte_yv{>tPZF2`~M+bRf;g^ zV14T-ySn8tD>0<4k()*)vBW4u*XX`UX0y)?Sg&-9EmQ1c`QX4;K29jj3U9H;KSlCs zFxJOe)G(e*{wm+mg*nw|6-&}9Bw^-moF{DnJYO0*E^UQmV^TddfR0FU$&~#v58!IQ z7Z|96zo5d;#nz|;p@H{GJf zuSd7_Avl91k0vRuI6G?6-d5W2kJ>$VBeUaPS2LPQED7n;Mkf=O}Z;Gf*;nNRzMAnFbj9NVFdFY z{Qi5m-PVW7cWpex?+TETBOAN~Djn>e%)y8&P~=CucsmdsT1^o<%xJW0>@+zYNe?2V zAL|W4fi!Uz7WDZdmH&44UeH&jjDrMuwb!(fl8-Eh$}J&)c*Ihb$08%xlRTgYbz$Wb zzowzbuSj{{17!ehGsmjIbgwOV9J$_Z7n=*{YOZ#dTF8s-%$jHy9d)Rc&h=2pKw+(l z?M+ZiE@e{-5|cK`s;bJw+i5Zy<~2PzrD=NjtzOd?4-4@H5>-Ks$K_P5`fc2|%>F`o zzcOlf)(ED)@XKqHTc%vdED_;-NNdY2!kA<*=4Z?vFbiRE~gdB@gF zaC+eH4HeNzl!zNiw~2LWSvqnU%FQK|YoTr*)xtnV{{DGBvM6f>GQr{kwGb6{`Ko}F zaf^I~r!~sqQXq|?#rhL@6W2CFs^Rl$1V47Rm*2q@o<$hA*q+R)9%tL*w>JH~s{X%R zVY{7$*%6F;KxTUAqFzA1um4e{*gH%Q|4{StKJ#AY17rqWEKBp8bmKvG)3{<=wzT}=aU@~ z$F2fBCeC@7Y#@LQ1ZWJp%MbbU@ltA$u5dPFakaY{cAsCtupkx}hrXRK1c-k-UV*WQtHdZ&7CJ$ zdDib)0z3$>Ea`A<4y<(xDE$%k1V?K5WRlkg{f7~@PS=X8|95)N7lY(4NcYk^N$h&B z7J@naL$Fp8AC&$Z)e}^9dHhyI*nNbPimQx;U=TFOJnuci+28?(f7sB@uKVy?(&~is z*Q~oPlAY?tQ>kk<666EDGc~YLCxu|_w@1Rh^I6Zm6ndwIE`ftaPcp`^K8&v+lwY~Y zm@GN6yi!V-VN1shc!mwg8cA?eDu5A^r)Qp?9K?+ps9kxQc2Z0x+A7A7+QR)i=ux5i zEw%+kRPsRN7$*EX8H3FhGufQd`PKbh_FoQN)Bua8PO5#fRa61(?=JN{k#Kds9N+7} zv&A;0&F!udHb{UAI`eY-KZA;yIjL#2HO66Cd&Nt1G9q1QDIT4@oA--~j(1rtyx4od zom3LEdo^(NQpQ)idZqnw5D8l7P!kjH=XMIihLEvqqFOb6YEZaBxh_Kdf_JoaFd7MZ z>6iFQ41upLZ*lphnb8PH?GbZC5X2eD2+V@gh->;~2c6U%QI9TH)LlH8)X1Vze^*Bn6H)d8lVFWV^EcHj+<2pk1ml>nMqOcN0&r%(B0enE;9cqh|qfT(}@AO=ys}p2e!VU^L*eX3=`>He*P`3BtrrP8)II7TR+tT|N(Fr{JA3 z3h`e$TWm&hpYZs}CY7L(Fkc~lq|STkszkDa;&gF@1v)?;UGl`D~&H4;*cWYT7s7eeO|!*n3}Mzc@Z3c`Hyc?>}^t`#{fR(64lZa(Xz z@?IT_Z}}#tmr?;GrBy68#IP$O6G#Snzl&{P^)&`fh(27=$4%|`b^vf`F$TNAI9z+v zgf~)-DE@lP_1&3%H9 z^!l4}><9%b%mjz8RPwI?^D&7&jlOn$qD9%QgpYbk3xDtW6TSYve4OPKh!g8gM~>F~ z$_r@zGmu^QHbB*qRb5irhw3`CDmxm&uFFNTMB65-lYdO1=wftYJ?W3k)3qK3glTfJ zz!D-g%7X<*{sEu)t%HgUO_@I#aTZH=wC+oi7(<_A5|79LULDc*r5iv!j}JR>%O6`h zGs^AI$IOpRr)(B`j?Lf`6R%ga4OBkKHqJbx22KryX-1~CZ=5uOp(8M-bW)sy&b&_( z+Ixk>h^KrCDf4OR`Dv@H@_CP!OFCB-2DYE<&B08DpzM`Nx1J5t_hiD{#1dQJ{;zTw zcubMB-&*G(;5e(Q5qAvLROYn>eQ~HIzcZ;-Amis``R*o_#_2wA>D-3@Xc1vloEX3| zoHl!ZQVU@MP-xB#|!FXgZON|CsG3zSd?5!QeIkGF0F$U=)rVG75xc`GbOU=BQ znltYPgM2s z)Zxp<92PbX5MbW{gAbF$UW&Tm(rt-YNN@b5%gw;ODh1pCDu}SMxt^Sm$*u<@&&ex1@sNa~_GRo}1wC{=&8?)9-6oFx2Ip6CNeY_W;1Kdz#k(&7$VK zV0rKch*whE!aVZP^J3t4*Wm27{a>~4U7zu%pOpZt1$$u^31ptceL-~VD8|GXMg3lQ zmn|7AcuOIYy#V8l_U6{!OQ!fmx(n0ZNROt&xqgGK}MImzBJ)-Yg6$tT`Cx;u{~eAR;~ zrH*uQd3n4TX6xRz+Q`xTTZbsrx|{)8{=B6rN8MRs2^a0TF{AH#Iwwdw`&KT&&S2Xp zh`CcCC>Mvh5>l_8r>O8F#S^kr#}2^o)YI_fS>9pS-9pQ1;>tBO5*4b74b5ZP;`Vd` zi9}CU%3&ry6i-l5MnU9TmO~kLm2b#XpPIRb5z7A`P2#~&sz%7SzR1LQm@=d{iFp_t zNZxC)!oZk89~W&~ zEe^pBGNUNehnweP)rQZjHn}vG#xUdm1f+7xYqZ(%Bs3MppIJ=hK0OQ`GwX>taO6Af z5PZMaxu;(HV3bU8p-;ehUW|HhwkgR`ENdH>I95WBZCT?UT_1t_;kgJQ)s^)GE=C-7 zJW{9WCNy~}1Mx`Ch#ra<aeCRBbi-ZdU`U>7mPOQjui~AtL)XyKl79xwn$Je zKqduDnEiIBZ(1S{kh%Xltmh=!q8&%mKo+72xVt7Z(WvFs$aV=lty$b-&=1jgLrvv+s;Q1!bF{G-3Vd2_mZb4OE{g8v1(Gq!P-wVVYM>9Rla zjvi`%CO4Mx;Xb=GZr@jEcp1@C*|JvLIc=eL)b1h6MRjaa5Nkjfaybum-wckI)(Dr7 z^6P(Ae{PZ&47{UU@@>n7Vok*sVcMeCT>^>o)QV7eA)id$91%m!8%uc_uFhxnP;VER zZ{M35#jST0XeT3REf(G+ArPwc4cK0!3+oezHBAec%1mMPE%4@Qh28^hU$=zRFUX!O zNz1vAv!3MsoPr9swxN5`lZ+Zm9q-D6``iHjfvtTN>o z&=p|`qlMzymBK{>DQ>~GGj7;|J2bGybhdI5y~~6v-0@~Vp5_zt};YQP``oS zLc3hPT?>?XG;5q$cSTD~KPH?K6Ut?cb2I?;>i}>!8e>>EpzC%Z84pqkgV)ZwuT7sI zJhz%8mfTdCgZt8Oeeu!_u(Ye28Rt_g{|$zvlEejqp=yLgkKda3!0V?(D)Zb?730?V zSQ&DhH3QZYotHVl@jj0F{2cO~`AxjjXp~ULGvR*}`%wD9o9F>ptFd|s2K8OB;8`$_ z`ALPtu!GNq-S0||LKokxCzcJ6dYAw~E**TuYUJpZziYN`ia@Pb!*7qQ%^7dFgqWV% z$x#J)?Q5EujxgL$ZN8m%Y(V6K85E?0cdb^O>&K$n15rOc{F}Ds3 zBP%a8-HGLxinMiVL#Y=it;!ccD z)2sEx=S5<)2LZ0&{72K&4;@4#$9?FZpE!hm4z=jO{o2^Xa2Y6y-X#>dXmXCF>d9Wr zKRZOMhMP%Q`Fsl$!^zufiT4gUSK(|Q@mZ?Ys~mzjP5D?FrcXIJywVHv)e2ND)xJ8< zW5jtWNnONe@aFPd0B)6ri6uI`Azv#ufpVJ}e(TnRoM*vD^M*XpH~6a5WO}#PDziS* z&C!t%NvIPInLshgP8CDxzFBB6ocGdh_xr;OB|I>u)X|+p)SqdyYAoCQ|1Hj38Iz`^ z-O=MS%lZ`CG_r}0uFwR!8f%WX?wE3;$|2hsxj*pV?S#Iut~{}CkdSv|cHJ$h9VPZ~a%{-;39=Ws7eEkbo_g>K=qviT zS;D3#7{aFImuN(ws!Rs~Znm9}Ph^T=su(OT@-@2M(zigqpmH$}_IRXSG7(iO*VM=* z9e|`XPbSfiRnqdZ2DyWEQ;AJ1Lw*2C>zk3CUF!x@B$h^?7Ritc*T$3`(-A`?&%{VP zE`wl41Qt=7%bKH`&eFHZKa^}Nan)8$8AFy8!*GcN4&%n2N*l`})&5e?tJ4o$sO=u@ zip%D#0L~N7cQkx*B;GD2P&rbKA!pXDor3*Q!4b<%YekDC@sXZ5IK~@E(3{vQ# z%MUV`Gs_@#|8UaE=bdoRM9?erc6P5-3>wJ7R{BV4 z72jxxHaLVuSnT@s-bWXBBvktEWhlAfn9QR@P#Ii+AXmlj7*eVp*@(U=1gX$+Dgywd zLp(CTD@n53GrJvEF>Ruup=W8tNbB_rlY6@5r(-hx)U6y-e9yK9g0nKCw5rwgliYnL z+U@{@5>3aR+|FRZ3Dw5!gLc^p;xS_O=U5mcK8Jkk>Kcsy0{MzH9c!y=e)L>!&*=0o zDmLIM6qamWKPMbi`I0wy1QsahY=}pxBkl$dKO359$fGGQM4hH;dfdr%3+D^iJ7td) z%Z?R3<*_hbGhxl_LDoy7Uu2!SoPb87I|fnhaQ6PpWX;N{aCwTqngT2vfwfbeA1~%- zbo$oA5yN4<7y=r(WEbca7Z7f8+yOf8uqdMEhIbzvA|Xm(K`#Ez9bW8x^wYV7%IdpA z{QieX4=en?`vd!OX=%JCRTM5$$Ana2$v^Na zDW0Zi8m!;L(r~D{4%xE$tcSbPy-NwI|0rg2z5X@!pJX*lo7V(@E=Jz2zOQp~Y~#e- zMAfGWk&kGV4@MT4dGC_}3{~Dk4u~2IHDtaaMft<{aIide|SEbK&D#qbsySfxX9U^i-i86Q5B5blIBny%k!{g-aHRr{as zS4CR;gM0Y(6Rpg$qlW~`E2$kpX*H!yR!a8-+;CxB`H>nE<$u?60}W)5ebgLLK#QV%BtP1Bj=ro=>EpUh$4K z`SMW}i8#6(Re#k&m^23DjgZic+qHXZy^O3$jk+oum?KTVhV%l8R)Z@D!DKShh#FLRA|=BPR$GT6I4F8nm{zEpnth@t(c(=?X*2{Ot% zV_fMd;DTS;l5x~E*>Q}ecy(V49C->UtMHg=_AM-0D?LARz%+O|!zPr{rkt<$tsBLjkbzCD!7ONy%u3FX>Q zmO-7YY+?E~*NwyRTAMhN(+UXWBKg(LP6M!v%HuOtdUr9rvYAFqkf<5B;KuANggNDu z-S0cA(@0<`pYU%h@K>THT?DqP#q2`RfeD(mH0%(|S`=7X3tV?C3j$=KPTkM+kG|>G z-g$(CBxHVkR!~?$oU(F8(h}uoqSv8rAWNx<~ghWg!#+_5S~2TgTo;W!W;!^bYrYDJPLmez+r3o-w#@q=BX| zcz#=h3ou(`EPt6r6)uqnkJ(GywiCS7rGSae4zI%HCSP4-!mzBj(I2Tf9LUStYBkCm zeKSPzA&ylNco#r7 zIT`fE_)m`LD_OM*+bC>C!j9m@pa)?>d1Yt?cGNDoc(5&9+PrF#!RQFSk8UARN<-37zYA_++Lve&WOs7OH-Bpn6VQrS(B(GBuOJs7C)V*ajngw)dPb4 zluU)zKBU)T*{#|wv*X8b>f7aS(trKpe2`|B{d8b`MEUA8OMb43poS~AKuFISr=uxw z8qc#b*b_sJegg8SDsaV5qW$+0!0)QtqOd#Pz=r&0S2EI)O_hz?Afg$9jk&ZevXkH4 zM`;F5kq{b&ibyj8jBTh``aYk&mw)KsUPQH4)T1vqJBWZuq|B9a&3%EpNuF*`ACGesd8EKF_A%lVJ^PrPu${Y0n(Sg-b|_;J&HcQ2k7p> z=;X=rV)C6&G8C=n3|5GJJMgFD&2x z9`Xi9zJv5XB(rmos>RgDw_ z!g&69&>;+U(>#?P5r3(I5S$6SQO|ZYZ-B|@!TLII#wfitSAW7ipGg1E6dG4unpAnDP`5EAc6C+;aOm78?58m_x(yLczX7wFN0t5gLcookUExF#m13sT z(n2#aq{wXJ_(ex?Sr7jW90FOR7I~iJX9a*LUvF(|_=3|frtD4QUO-J^^Q zn2re_57=uqq=}n~LAkz^+aT@VVrBnR1@k}S75pEM9FEH5YIjlF*^y27qeGOU?|LZ` zlp8bC_ss~iv1x=%K1d9iGs(lhLiu44El#NGHvSdWHNxm_^r$GwNI!RM;I-#Dpz7Od zKZ4(YAFI;}+2|RW8JFvsSpBV_wqKK*UGk|sKBktdNG*G35Jip8TLdB;K8-OT zo6&QGO+oW-oO^_M*i*L+hriof*OJbTmka+R%dFAXEmf>cE-@F*xd}^z>M$*D>eGY>PO5XFO+=QzN(g*x>sC@R;+3=iq<&wMJ2@aVH#?Q@sRLQ zOj?KCC>`lwoCHJW{EbxwMW1Obh$_c7z(D&~Dl~iD*8Jw0wfI{%ov!d;pNBI_OHPOw zWc+}T+*%jbdg0opGeR?9W2ZPQ<&{!Ne0uTTIP(J~K_mM)37jPxz>u|COKRJ<+Y58< zh87z8Edwdy`R9_owcBpcs&M4#BeBtuO8$U&-m2n)IOA+6wBmaL16vchMX*&cvNq+* z{=ziY03n1WUBqfE+VjMjf_ArGh^Ov6g}?xiXS6Na&=S*q)Cr z$ej<0ZfmnDZQTRGA2ijR6dN#;%BY199I6i*_$OY^VeadF7@N{f32{SMni~}?!*n0&J;DfoJ^r-h zNp?d7JJvO^bQK8!=hc8K$BYO5i(-96@>rq42U9v8XC96P)wf0d)jAN6k0yz6k4(`I zrSzY&Wu}tcyVmvS;i(&RaXrxKxI|9&A~h7N@*_&Phrt#J%b6)`_L#A@+VbR&UDXPQ znPYAtP(HOFqNt*cUKamosCc~g27}BC?eW1H9W7d2Ya(~hwQm86@<~Y!oErL)px+&Z z(BXaN&3n}?ot*^PjkpBohQj;u@t|aFms!B1cIEN&N>vRO^ zY=c^i5YBHjMT-@v+8RB*qg0ROmoNFO52vJ#R6{=tN(nx zEJ1Ijl=z1G0Ya>~-sj@Xj^8#;tu;YA6~8cgUn)isob={}ch9%P7nlj8H-=ZBVysF} z^nlvP{7C`nD){tGXVw)z;tw#QM{{!sIc*4yf&>Phf>8q_FUEnu=`B;n8?0_edZ?9#6i}9kI^;A2K_5 zXAn3EP?}ZRz$WoGZEA~*Dg%(A)&yJo$K=F4Ekp>wl+eS@Vx%py7f72L%zWVLk!dLn z1i3(vHp~m;J!x5NWcqbmhJOQ_eavDef=F9#}yHwC(TA@BMbSDtIHOV zCPLCJe03tEW{zWaDbjm0BV^Hh(vxr&AR^_Ea|%N zY@$I<3#eF+WHLk<4j<4*0RJK#At`k`YlHUq+P%4cpNFC?rc4m1vlFkmB&%E#m%LtQ z>Qmxf)SLxen+9l?H$U>9Bb<;bGb=K3v3Oqov1jENau&AaLYofS)8+bh(!lf7!&7=Z zU~8=B0Ca+>qcztV=@=}P`CEk=U`aW1Sp|!HDPcUL{?>@Nq3R^ygVZwVx;3SAgeK^3 z5A0T`8${)^i|HJ7P@ex?b9)R>btY|l6roEmICxae{0d&Akn6Q9ka9?g24fJj*pSil zGC?7|ntmH8HkdKa+z;>4U}%5`!P+FJaH#4D(ESb!o~>$)RxbNo#shK z){qQ@3~onr_SW~5mYEvDU5FVpDJ?H=Sp~wI4(}t92B?atve6IGwX(|-M(cSAkqzCrL}lv z=ae}GJ7l7`Q~~oj^r;ZrZ>(SSc{i^;&bNpJ$XJ)Xq}CdU%m(T=wR_kb7eOwqz^b3W zakdsh!Zw`JSAS?zeTP^=`8@|<%3|2cHDbb880rytu?q%C#1SvOREc~bzg?r}hwg-k zU^)p6=paCq#~+s{YKnR;GrZ=P-*sS)M1up8JK*Jk!7u0HC$^ROGq0#7UOYxH4jWjt zaFy?G=AtnI-?3*5riTHiWoX(%nYX|F;;@u=%oMk2W$*mJOqC6y4JEadI7w({08c=$ zzmQ~z=~F)bad=~RptL-5^Lep{&OLk$SdD1IRFuKOxMlFNU~-f`5*8j5^N+XxV`NC` z@qO+0-aNPe1Pc?A*6F-G1hBYyVQ~r8CZd3`zFP~)An%_0EQH>)P=kKDy65gq{OERu z&cHm}c4*ncj%7SB{1#0j0+hx_R!DO!f#vy)X5pZ4`*b(Ip>@z}X%rpplR}%W8Q z-H)u+Pb!ke_~FadyzFI4_Hlt{28vZ#yiTIO{oXeg1NA3b?l(+gUywS9i0(pwC3jco z*fabo;H`qoJ#?}SUbCGFK+T2bnu0HipKllQ9-zrKGM|S9Abj(5G2$B|8BQHS;}I4LaP|kgK?=ei zP!pJhuvDYjeQpHDQH!@*5?7$Ji{)v){kIiF0z)W1zvcXT)7`o2j~N_N0q-oq02P4f zd!O(u)X2IoVyn>z(q!0{|5?xt2o3f6%uSoDiLqBu;D;+jD_+L!V%rbwnS?wQEN;H} zsvchtAjcVq|tJCU-sOD)2``m1*!8xh~FVyKmVl3OUYTIO4l_=NM)>0^isfLe90{N z4Q|yU9=f`?yk4d^+C*<5xmEqei~l`R1O}O6@vT?cZu!+db#Iqs4|w|76Pd+{&+@bN z>unElsU|@nmNF>Xl%q(qin^$PdQbGnm9pGZYP`T>JX=!pqqrzCg;Aau<;NupwX!xb zc*qM3V3jd}I3RvM1C$aM-ddU+UHH7wrp`1Llq=_`?x6QX0BRJE3K{m)0-!F=FBZCu zu8t)UOzldP;5Pu)qt(3IclZsUIm%!xy|&2ZOQB&X?1y)_F(k*>@1p5eo)S5-?PWpi zzw3a)a+Z(}+yredIE$R1GFs#k+HaH)S7Jl6AF@q-Dg)F#UJ|?d8}57wS3Nf9`bU}N zw>KN63>=kVetJb2MPBYxKfv#%TG4^tylG)D&|T|pQoghg79arJ4@cYiyocYDC_3mo zT7GHuBU7%(snuFRd7_CqaDq~}$Su6hRre=cHIw%xXXw){_+~cWpb$E3JKpMD8=bzk zv+;R6>Vf&sMcv?<`nKns5h7C(?d+e$Z1!6a3?>`eQ|MF2iI^pETC`MyLyTs!kfEUn z!{)&FxPoFUh=3bAVNw?9E1WLn3kmWTYV>MPW_;6}S&v7?;-Vuzfg44qJ#I*uK{;W5lH6!-_5b)X>1q&y(N-4z#TjWQGBG? zZoWVeUF?R!I5}cr2bO4)`@Cuaigco7f|36pp9`JH{pKJrfbE(y*kXuun}h2M-^j13 z94qs7w6ne?sRK*yaWO=lOuSX#tGFTf7b~5<9t5 z-7;%YI`cuIpt18yNQY|}W{vm9@YkC^qBd#NKqBu;3I1ak0HD-}X8T?X2WAG=6#T@W z)vG8wbt6Z1Ejq4MJt*Tt*)r0{lGZ&5k$}LLhlS!T@AMk)@@dm#^u_i)7mO< zB0M@-Rnupr;N@XTopT$IggeJi_lvY8ozvhwtZH@-Y5SJdO9KC4l8k+5gzXyRd>X+p z&jO}=OBVV`D8Dvz9A(64>{|r`nU7NvHBMXrzOI;F_FA}4;tqrZ!X?Zu-)}N%Fdn{V z>lJF`)7ua zi+mk3(l5$3d%F5&r9Q}bWOqi5S-k-KQ%AMW=2m8Zn5%_iWbB6+iOv^!+1BY$77eF0 z*HsA5VShqSz?fKG(+H4!)5}`6b2~wS(cvp{DpDy&eBsqe1EQyNK^Im|tL7ELt0tc?jxZP$XU^>}Hdz*LK105Q z|8!p2Dvj);z0FdKtBuX&fd#2nXd{!>-X(K2e)(>`uy;nQZOunzl*hM&MgXxIE!qT$ zcOt|DT&I#YRhNPIHGXt^ZKp;J`W_e8@DM%t`w{>NxdDzBuUNQ^<~#$ST5d^sG9}8> zX9^;~PmA(v)Gb4!19v6!TFw{DocU^?FWN19d)j*5_o4JF2~??BLCt=xEHP>CkWIjd z$sb@F#0Zq`B>+U)o0C?=>EC>>dOUV8MxZCjCAFC-yOr=bXrDj9X}Q2|BN_fTobTwH zcdS)et912NBL4gGK3Z$;X9*XH_2 zmdVN)DiI+Y{CJFI1;NyMnbTT-7Wx|C zuP_MRg*R|_00v0FmazY9Xl#9`@X_gQxSfFheYpe_46x3FHI`8n#2uE2?S3JuNI{Aj zFcr*FB-tur)aNa=e7C)UQw`mPG`;}0D+ohBX>P%QU3z>G{Kqh>_-j97(xV~qKE!4e z5swS3o#}7C+xqEVk4M%AvdHQJYUlhEvKg(Cq}HA%-}UZGOeWY%Er{E6ccDz-L;_9D z+-<8dD6>)rr^3=eJXcKcht;Za z`Fb0E_su7HG>f8A&?QCk)9Y;DKo;lyhtPn`N_Q!RcJZ{}c1Oc`$!Y9&k0G)R^u_S} zdu0!6|3ccXYw&FE6GC%|@|v`JwgfqN*MR>S)T&d(@Bea3L5~NC)}a>H#-=)F_u*}% z5ui4AKRJ!D&kQO7hhks1pv3F`a18Cn6g4*}Rdw?wMMw}+sWRFgK8RLQIVUfaaK683 z^WpO|R{v}8wzPB6Yh{VZ>5JDBM}jgCk2i|T!-d*|)G6)3{Efz4+TE17e~gE0Tm0qeOUZZb*{Uzp)}4dtV%=BxnGE&A4C z2o6ZyW5p1Bxan=<*y=~(S>;Fo3P2GI?_C%{L1hiagnt&>eih(^p9@@K6gLZ9co&YY zmk(Tb=^AT@NoEg(ZrxZpkU6qDT3&ZStNbnJ|5g%XLpjWR-gS_Xd{(dRn6bxXy0R}- zM@qKj6BlGbgzvUTjm-pJxSq>B7oq9!oq5XK!-Ui~Ww**cDatZ%kjGL=FNxsqR*gvX zqDngb(9IiooBvCN0Cy2rg)2aWC!t__Hl$MKQgxzT?0sfYT=qsJucx(EU9K&+-T4{4 z(rZ?d`k%LWRG?ES6A@t7wkFGX-6AI4!?w+e;mi&;n#|q_F5@%s#PgFBTw3#|L$E~jxKge%$|swkEKL(p_b$bI zJhMc?tO7iI)hXQ`8Cot1%naAO=3DFP>U-+%_}W=WIIhp4iQcS z<5)-Ogv3nyEG4+2zIKbL!PheE36YCl$L6`Gvbg^2fk{2Y3)g1=;|C{HZ)ffnLfu;F zEM|E1H3Ph@Y341@RtL4}oUZ{>N=`cQSiCN-pfP`ek{9&KiswXvGd>6e4H-svWHEus z!9NNnqf`i<_Cq<{L|udX63!$k`-F?o-dq#41s#j_i7p}nv}*+`%Z-d6Wnl(#{O)h!(@hn&t*0TBF1H=CYLwWSBSu&C;)i85$HynMazD`y-ffZso$c!eUR4w zGpm0}K!`mhCnkuO>k?!5KxNSi{mc^tV8e`?BV$}s)rMeg3l=h#G@z$vaWH$P0aY-~ z)a;5(o*I7WDmvMl{Dy@GTmZW|J-i@4>05az_qZPxR;i`|ySu;tmS(=wQzv>sOisF?XT6(m<0A zph&+Nr)6l_XO6kBRe^On&U$?gstCwP@^qDvGedjOo_Nf;E z?l+0nJNeHtm`Usz^yXld8xD?jz`+mOUBxZa0}%whI~|;kbZv*0CQfRHCqhFF=U7bL zZDe3)QV3nSzvVKC5Q_5%J^|}Q-5wed$0Wu11hgr(k!_{Z|6#AGU&sU95mX^meT{eG z!Z_d$;oXBD4G6ZcCzSZ9~ndZVZxiX-(VXWGR5hItFx|K!5D+ zMC`;vJ@s15;Sp1ZQ5Tn^2-VCAjCNIx9-~wMZ(B>KIPO5w5X3r$6TpP^C9xBIiR5qi zSGkFP$M!1zBasf{6hSS{N|PgbdHem-=KP$@ZXTRW1VEx(pr}f+PXQ&9bQ5t(_Pmr9 zWT_t@-h}gOr*K|Sq?>^Libstjt{7QU=Z~#6oi8s}1;P}(&Jm=I86d2)EJT8FBfCZp z4HV<+;CIb~*NvAQN8q(*_qar|l9oyQdHHKMLLVDUitL()%#7@LhZ>xspnU6DI&Y|b zSW>t*$Vh)Oe6a4WK(z@ff}&MK;fA92Lb3Oz!-5(EX6?PEBL${XJR{V9K_J{JF(QWm zV!oC-vm(7DxQ-fYZqz7$t~%lcX&W7D-L&lu7HVLxulWU+X2DkZeEw|SpApCHsZj7R zTHQURYeaq`1~a^r(}(xnUTsf|7Y&IIlhIqdLGwas@^lvOClIgPy)MRp`nigtb-F#} zUB1>NOR{$iPY`bKz1q7Q|49sF4~)6>GLvP-a-Gib#kHn#K5v2Ve8A;MDF=V*o!+nJ@^_I>%_Ek^_S_^H4+1Ut(QpDX2ryxIz$(2n6&x<|K1y`u4;~)VSbJ zN-bE)u!vP@^tt4B-FDgOiSi#T^Waj&i#=S73t_V+(Sk?O?&fg1!|QEJI4#D= zo_o?`teGsbgM1}uxjZ|;?%Lihxxd1Ro{B)N zWS@OzF!cS4-3RIN$o1D31CDrl*!6q32{JG{SdZedYn`mv=-O`h*R-g7M(@64lnfwx z96^SIc>)aL4|&}~0#O+=POc|oN)vw|Jy-3RkdK+Kvj{Tj^-z_DEB~8T`12Qm|NWhw7Y#;K(`24a~ z_&Z$%Y?C6VN0Z@cdpDcIk9%bU=!Z=!ZyoUQE%KmFM*VgL48u|N$c9kBQKB5h+d*lq zEOQi=qv4-F=$}Cd>P&EeD%g!S-XwKkK#Z-}Fa<0VvnwB7SsI}EB{?{F9 zO#O1eE>}1ygF2<3KPpUGlIgV?nHj9>=d08t+kEx=13)SKHjg5#UC1`sxNZ9QUdXsA zb=y`$)svWISm!I^G57uNtQ010Arv0dBxA>yHXT57X_i3QbE)Jc&HKQ}+zBG3Vfy)aH1>QG$_J zPH4}7t3_Q$ujI+EG8Jj6BSk;3 z;0gE|-Q#LQ`(iP;ZRS&?)Q99n=Yhk;oR(V)?FhDnO#u>$fvmrBGIk+RSo!EI26+cn zpwL09Sy1gTBzOjs-EC_tN$ef_iM%vbYg*uu$r9TTuf+i z9M+6m3ZVO>mvQw76K}9z5|^hIAIJeB(Q>NsyoxYy>`UeoL!J+;%w{|yP-Qon2vi!U zWo#w3>rU2)VXltz9?K1R-XkeX&8$dF;SUDH-%)ZlK7YPC<>RTxxMv)gUGkasKm$BQinXe$Lj&L`u{Y`++ zO83P+GW>Ol9HXs(I0te@>uvH5K>ZeaxL)~$Y3L50R3cBbD;=)AmSD(>QXm|yB)ORz zlnS5q#Of)hz$yaO=!D8jN&EVqI8n3@M?h0c_Ic2jI{XQ;>hk=?KV!Gfm*&0}Hp-P7 zJxznCybVW3qotK8h+r+r( zIG)x+7`P#Rw=fop2AX)0aqBip6g~%G)Xwl^DPbu}1ojvNp|~WfCdrgF2#0+RRlktA zWXIa>Mb}G-SBd>oQH$EEySKut9k592IvWNs)d{jqgP@^X9W3QNEcy(Ua{?e23xl>q|K;rKp(W)Jc z@jz<``=xMCIp=WZvCfMUuPIEVdcf2!rGAY@f%j*e`hv!|1%_N;9(X#ZvW!;8gn@oT zLu-5f$`gdnh2mF7=Wjuy5~tsUyFH^6`1kAOeAAA1N5HJuJ-rUHhmWnfa@2C>*2rcK z#StM?@8SUJE!6}lBh1psWx0_&w3C4_L7Sl)vsa8}9evB5UR#On!y!v)d3mECMXIxa z6N`&Ee`}7$&I(!dof71>oUA^>I1TyR)>;?f(_t|O%c{R~_byNmVm zVv`wpd01y$ig{>jm*vXx6;DZvav{(k>BpHN!9QZz1NIE%>~m;mXFl!XB$&9v467MM6C3k zF(yht{O@KC?^?n#vr8XH6K^cKtmg+t(?HFI{U#%otdS=ttTRo){Oq24*%O+^$ z_|fCg1B>V$8v*jB9FaYYEeRb_7$T8yyrTi|BDeSUqoG4sgeyO|smXF!EeSd@L4{OD z%di=o8m1P^w9|!AV8zTB=q924syHU!5?dzug!NOdJbx$ z*iyPfW=JV*)K$=df5=UEh|Y{!_d?_aB0}*nl=WmV*cy9$9#RuYmOmSSQkXg+uoI%Q zQhq!_wtYBzo$l|DRWHhOT$$CGPpEQK7HzgnYcPsTox2?FugFb^2ti9N>9@jlIGqrvrwEg#+gS#_7vSyEvr#4efwBq?Y6 z2j7Ju4H8ct_!4{iM}3e$?%E`#Qa}ef^{({p$25Xlp13`;G>IS!YM{3aXBv{(ArHCT zlljqPDPGoonz3$Q*1r=NkaAoUM~WKG(wiCZ5R9bYQ!8tnKgT ze%I5YuUA1*Gu3sQd|8(qA9sCg)6$t;M~&X<(iJkbFEDVECaapX)t6>GBMyZDSTb+^ z>_JL)B2V!TF;r782)iAQ3Dv*0TF?TRbBvS}({S5!DKo1`yCxiMaljcYSb?KGBiiDR z2B!D~$m&HhRw|z167d@+M2!287lEoz2uK^Oi|bdxFi^)FVf__!T}sjCbjX9l z8M5Sl-9U_%fhlT-4T0jN)~N&Ia(2nl~;r?+rW)z|_wOYt&_QQgp*}~{5vn@c8 zCnYVEgiNv(9zei8SHUUn$gG_S)F#Zfz(?PR*M9FCh~>HO`)&sGKbgUu1+vM_xP_|x ziKgx>=8*t%{O~o9!KmWPmE2Gx+q>)`ecN>m?qp!}l`Pk~-c+STebuk5Hteot&DH;Q ze6AUxT%5%4IRSlNG38pPHw8eb>45Hlo5(1MZvc9gH7+M6^DIv>);A5YwQ|@f5+3Mq z4wG@31p5Nt|0>cT^f+&HBHC|=uM~0VVxw2- zvbFq;pv$=LcpLUgBx`K&7F`~Zm{*7{YwRavxZ^Fj4f23^K3hw6TJD4K&;NVzY#bW9 zd&FW}$j8h`CN*W&C+;rH7Qs<0MQS6(SwhfjTrVtznm>Y~;Zc1oqdP|zMRIz(S`krM zk5&J7yn*Ah`=O~YH%v}$?6Jr7JAcvyPh&NG-eVVliGkv`1@Et9juCl6xV(f-%IPuJ ziC1!D6nXn?@HwcQIr)O7j@rj4aBjk5|7PvuDNvK$@88L0N)ilH0E+DU+%#MA)MPEh z;AT`)R<3HKN35xF`hZ2`(X-;qFd-~rlunH?%>%*pqqFKT-t6tbGsqTK!F{p{qb84Z zrmGdR!w+aT7oqMD@~jx-B5C4qV1{Fm=8o-aSob_AQV5TWKU4naY4o1Kuu-8s{Vk$?@>VSS^)88=l@8*FG&YZEzkg3c1^pwU|YR0wiES>?9pA?Mu=2_Sbh)UM>xM4ns&OR;ZhW!kD=*{b(f&0f%JV!*h@YO z<(^8YpyXpNvo&Nz2cE6()8D-fDp-XEr5xj-{>g zS!GBxj7_gg<)Sr%3A?!}0vY+sxnsejp>}uIN+B) zmDWDjVVnA2ZNQbJj4|2*NucAjU`Y{8VCaNivQjcDeT^~_;BdkAsuEnw2XQ4XSxq#s zZqLSS{acvSs3o4foIPs3oAa*6)mw9y7jBZSgtX8{u-UP$GnVc{J;hXNe`8s(g{P@>;a|d@%aC@OD{pme<-yDielQ0^e8ep!@auSqEbS|o z>xXER1=Ik(eV`9xv*}*015Z}V+Nu^kKJ$1@VW0aVG38CdWE;ymEeqtTsbEvE_BUh` z+T6?@Y}Tp%?LEB}ak0W#EoJG{b%$KMZ=*;9^O)GMKD@rzStf=1tH$s?*i!nct^%LI z6I@lPS4ymBQSqs7wObWWorh0tVhQ)sxP%$UZMs$Y{aRO~_+r_f0)S5kEDr! z1HWkCJ0Ai(|4Yees^{NcXAP8Rl4udcjh=}+p>U{6N_f#ukfWU~ zjPuGirq;%rBej?4ZVqQ1;;|S|dC(q3mt%utUUNwET{eFaIn+W2HInjLW zwJ3SEbLy^|g27YMOB?em&i<@*a(0dfyO88CG-f=+Ujqwt`~c)En&84TD4 z%#}9b`{4|aYMqpgi{+CG@~*!ZBEo)1Z@DB#s6KYdlLgs?=Y!{0wkK?w5}-5XD?(rO zp$}AGaU!CE^cB;w#FoBOoZ*=ey4bPV@3T!=C~@(7N_OhNa!#~IK!TB!Nl?bnW;1vX zG-(lX6kAJ)1#tg3eq7WtD3lnwxJwyvNbHA|Rdgzn1cyv|fLrS@j6igDeKMSgrOz~r)51XP86exzkWj#rL>B~$}3i>@9Kf`FNuK|L@B z3qU*8?-&N3isJ;(>GW~1K1Br^KHgOx-<3=jH@&=9{|u;Djfr%+cu5>Q+liF9HZc`; z=hX9v9lJpo7O!n?(#+(as_|^&E=k9nb$5o!ntwi$A@EE>?zaJ{ejXJzZoHUf9D-LW zuuI3e1>i+HtRzqx2)PIuU>sq>r|Wa8s=>1X?1LT+xE`#bd=K6d;M$P>Y%NSqmgMUQ zNAi*uVj`Girw*D;r_luK;wndEhX? z9^5o;_jC}QawHt?V9@!#OEtogfWSDW>sqHa*@A!GqH9F82C*n75W4(p!<|>tgUQt2 z4;54hJfEzTY4{xxFlO(|dRQ3j3ggwLVVMv)qiZM%U3H#EL!)=^Ep`CyT1UiWd^f{F zX1_vY_@2kg+T!Bn73D~mK6p0=qeg||&Oxv10Owa@jPRLKaOp=a=6eAqU7Sv9z-SNB z?YQ|vub->vGf*POY6mrIR8WLlZ(MPPweszYXChVM+Ya4@1xz!WV2;t67GnOjq8v1n z@b|3n4lbg;6E-iM@YVB(DVJ*+gNVp_rXpRgk;+WE7Dh#nn@Bt!U{8092l10%LL2nE z{b6N)B7MJy?Hz3yze9+oM7ktg#fvBA7VJh(!mDZ`!l=)4ovFZkd|r zPH_r&Pj@`65ebgr)Zn+R)u1=x;Xu{PBuPNgn^>dB+9n%+k@?e)`pb2YCzFG1(GjAV zag@Y`e(j=-;#wO~MWvFwq3Lu$M8-82M=OTC#vXRQ z;BEs6u_v%YJAlCNSHSOB0Py}u0`mGpl;d4~U^w#2!#?LRwXy?IlY^1fykvj5_N@S0 z61cM$r&MYh=No7u}AL3QygW0b~>S(A@`Dy&Z zN89@?4j*RHODQlB2p93t9oc9NS|+VJr3uT3XdDLLLcE7`f;(p*kim4{I18m ztA-`qs`!iEPUdX#jCm)Y7M;p4+pB<0Be^Kc#OfR}Tmaic^7{6W-+U!LWPn?lJ0u25jfBp zNeCAfcr?4-80MVxRA3z05`7;W)pIDD*@KUkxXcFi1ZhgU*Mmb|xf8XP#+GkJDz2Z^ z(xXU_?GV>&1=RqUoM(bB54}J~_$?tvH!2*f>ajb>^OzF@BDyyae|h<1B+weh$(2~V zGt5CU%88QT=DTCVa+N-xCHACUD9RcTw)t#kc7MY^Nb3Q^rKBa-wcsF0aF+fLAGgc` zE$nfH&VoGqs)h`#?WyYjfm#=j^ss-TjZj1o!`b+xbd;((?>GFX5Kf(ilGi(^<5s{S zPL7y+4;M}z#_bNEKZih&o36tPF}0kdMFZRq@y;y`+RfZblH8$(t`=W6FE2o_2lT^~ zI~jD9gQ@zC8Z$G2(>9CU0N>2l4@jUH<}N;1;*&;iT4#Xig)(XB?QhTIr9!@S5TWUu zWuFib3)si2-HC{Lsn7F5%ukO<>`lJ{?j)h`rS@Tx(oCAtw~C{5bBMORs&9vn+8BB# zEbZ77=>sw>K>dJU<{jj@3Kck4`{E36woUyy<3#u_p1fmM8=kMthXMS*j*AXhgc5%l zKwNL6E~E6dX?1l$tF9iavBMKz>{J{cgBV0NTCR^`J-!P1{IvmU!+^8*ye&+J(+znK zw|>=UFq@KshzcfN%$eTP(%&`BR(b# zdy{oU#9zeF2mzfy&*^3{wNEUlHwv3n`Q^LDJK58`=2JusfUW;c&1FtEF1WY2Tdw7( zd}l$g$}@HnSnQk(9;%J!jUa{YMGJug6qcZDc+18u8DP4VZF^cqgUYY$$yJ1riZHHfP#2M93IBm>CG9t*5GLofx9Qjg*KSH8 z3{KIfpPaIy1)|sxHiN+!TyQtP9rOz^0?7s_cPN_?kDR(#!^U#A(g0vj+(KiiOe}|*Hl;+}cdzp0 z+}6HPoCe+Fl3~`+YhRglv0LP<;9P*Pah|lVX)K)ijy_=PZ)kIt=sFF)Zk@ax2LEhM zv&gh`FIkv^2+Ny}zw7>YU};W~(YPP=j|OK{e5i~Wkv5sWx$04+`lqC~{jL;9Pt8jrASh79HV>Uht`>BtkUhdBsLmk#wk?#CBX)4096U zJJ*6B6&>OUpjPrxC^+O>F8m6Ca^53;TX>& z;)3ypne2$c^XQW?4CgQH%{`|&KeJ#TD+?hCf5=Qm>dqlx=)6q%UKL@Ye+DCWeg^5v#PPP`PeXx0ku7`&$rz7x)sQI+h~L<3u-z z7_>6`Q_j`8U78#;7hJpiKdA#NKro9!q+UR5vl2+Khsj;?T;vX5KoU}mZk*=9RNIWy zo&30GT@I!0;{BaJ1(Xc0YvJu7lWxuKqK1sqo;_yO`g(mAA9tCP|G#i$F=@N4{^ zvGc4h-j;)O^Qgng5}3s>)FaU#lpdFR%~iC%JeY8dhTEY>ehNLTx5^rkieEhPpN~Bh zOsVBlINAR*-BQAk%)*F^Kv$fqnGkWstLlBT6&c3sEcDW^y}7mR3#FLg$>VNEnTv#s zXtO{~>3pt9$sLNdt59|B%nqXlF?}#j6W;J8z=3cbtc1V59WUDJRu{*$^>1?gh#fL; zhk(GSf!?wJ+n1m!_#d`X;&A>q8vNFAD~W7vUK|=XUu9Y_dM{5cahPlGpt49pN8<#R zuDyEoB%QbqGHR6*>$zmibilJO{{WQbe9-JnDdF%JG>3wD{f7JL;)!A(ZV*UKzA@^X z-Wp`mitH1uC?0}8y)|85X7sk-uU&NRTKXgM5587AIF$o|A`VduxKJC^-^}NWr=Zj_SU6O_XprqLdJxGs8wtU1lP=}CB*x#8tH(#02Am3ovq?> zykT9etok+jVQ_n{#~cc*{u$BpD`3|Q4KEj(r6sQWV6_p_N#D$TkVeldD~Ziw0_iOH zmygwpkTZP1N~IQ+#e@vqE5qH>~#hmvI8_PdMQspmNVX!kYET zC~U6sgZ~jESY>Jida8M);sf6VUtF=%N6lm)oFUac4e6UkHK8T65jQ&Qh-_410E}|U zOorv~Z?r`OkP6GysZuVGEAdtoedxtokQk`sFPsbvakBWe-FPX7Wc%2wo!)RgH65>x zJT9DbFADHXqgbBOpBAd{c!3i({~Mh0d|~hHY2A*eKe1Ku0E|*T!}oS#BhV&P28IrX zrQKLb8=7_%DL2W;=S#xk*rXn+^!%Ji z8+DZm1dkZ-MVraPtvC}VKq~#I%sSI=z>O%~V&r8ky)Fs6jmo*!u;EP#g&03ehjzuP z(AMo!?OSt0fKZ>*MlJ{BFl@f8j|nSDI)=<%?;#3*&rY^#c}XGT z49k`y+KB37|6$4f<4n{oG>WUCcUU7+N^Dv`= z_$q{;{TDy;^CsCS}sV8B0|mS#Y?n3^KU`39FhF({&LJzQJ+Y%@|*lYkJo+h+fN^-rFKQ4;wk6D4F37c$wdgD#7Iv#gnaM)^h- zM`DA!P)*?B4+`HMPR7x<;l3FDPC_KOxwKtlj8dR}SpNX4GrX}ljm1L<7PhYvksFDz zo%ry7o{__mB1+TrMd=WZI~Hoq-cz z`LMbQ&Xkop2LvJ{)+C5_&pKPW%asZX^ekfjall)yJLwiS)4 z&;+L-U$}|=l)>rtHiU;kTwEHaj-Pmp0_P|c%|6!Vv6mbhvw_CPpD$lGfGIyX)*=~k zbnKb-5&m8Zw!djXTxu15UkYk>N!_Zc*{J)zbyo*TE1HeIf9&xK;!P9(MN_mIgKa-o@t;G+}rsU_Arr zE_(S4y)&eUNLe@HrmXCIccx=UY=CmX&pi6=!TzcM3+GXLmZ0Ov{v_p-8;|tmt}+#z zG7~C91uhE;G+Q(I=0lp@(M5r~xvdEvNF#~QMLBt^r;%#4LfSD86Z;xeJBVs?mVjR}RmW~qw1GVR$y+)O zVN_zxTYifRYkGgZW<^2G(-{z4?m@sZL4?@JCHaK@VF&doDV0M<`8k*Lnz2fRv^_-z zk<*bp1bHOb8s>Vx*9+)ldYARJ^M>Szj*}6X11yrYJqg$Z<_$Elz}d1}5b^qYo7IF9 z?k*dC>^(J zgc_2QF_x$I%j_Uk8;;Y&ERh*`HJC3O-;8z&J8^d4x8zT`wg~n@l~FX$BqaPcrfcqb zV=YSJ5T9eMxy63sy_)HzneKjApuw;`ha!e&;Mh?`cuRJ~_2_+$fWYMt#0%%36XDsw zqBYhyLYZ6Msq@%q9|y8f7gRdkHNNO2X#Ug4!zZvYoJK07P9bT#ewDjcyn$GBdn#Ml zA`=&WY5|U@=j1-YWa@ZJ?E0F+2YnhBJ~>xCwW6ERw#J21qySne3xo@w&;zkk8W7w4 zkEP&9@Q~8sA1D=*WNJ_{J)EbatG?w%{>*1Qu*vrpTQ5FS!T)Oz7?FAFrk&Jn!Y=`& z@k$I|Nm3#IAm+uV)8jKG@nF-jUI>8G1hdfg%O{d-T8{g?5%jUvsEp}flAjV|Vb`si zwat5)7YLqa6wXO|XLZ-C0@Xpqp}BSO%+SjQstAvx^5po>;M$xA!~UZ znMPdMIG_{@DM$Y**u}J>7o8Dx9rIGpIH1h|S2~lo{)e@d)SU;9HGZwW)O{)&9tc#B z+3ilZP_^hu$#I1-KH+l&+4$ zpF`&uUjh%*fyZj30X40GhFqL^!O9*<+MO6yH8h9i+8CjgTaPgVnVaIm-#zR;u{t;I zkt|AKVl9;Z|9*uZdH!P`kk7T-Jl$efh~*N$zuF7-Wl^tCLoIN3sE- zY7$%J$0U$8@?Wht^f$|4{u13fvIxt(`YvaWuuw6#yR&Thmsds$rn>@pVf@ZAh@E=T zD>`hIF&`V%*+lh##L`TZ_XQz*mWgig|I1PDwR)<^awM?|xy82PHp(P6>N5XIa zX3L^U366=@SDe$5KtT|ph&}>u!eOx%4Hq~BtrOrIXi^?p{rVu&$hNtQJMAVG&b|PM zXEb&tC$ZjuHiU?H&BmRrLtSr79nZZhB|!6_&*#4gJ^S!Ghgo7)jruu39!GhHgA;<5 z*xdwg;BcsBI`+=4>@oasA*57PP^Z)#hAjh>!Q{0yo1{bHleE1h2KEN3%&Z^nPRDvP z`~gAs&bjKbD8CCp*}rkQit=8pi~gGn|>%7N}N&?h7I%M2ly@~H=Occ3=4 zwR@a(botH~&dA1W;hi)BfRlLlNNHfi*zeoSCp5Ntx_EIwj|9J79RvLtkus0nQfHa6YA!{0Nk88mSkTpW}jv%H)pFR*DlJl zfUUR^`#^N)TnEvq-OI`5f`E8|ahn|KI0h|&$}=pOX#X5gCY|t7T1ENdp!O4o@}CjQ zbS{_#UE?QzLvz^;+`HU1=AJNBa^(m8E8CtyY7>5hGS&RIkMC*6eE%h*K zCCR0mYpL=8I7Wi;4KcdNrOdAIsvJ(IcY#P(4JYcAzj0qomRQbMX z=B0~KfdrGOJ$^@8OQSeN>nWt}MLUYY1?jNCcsRv3SYQKAW#k5DmZ|BEYCC74@+n5I zH@iU7_};V-h}{~c_m&UE9gf+>V#Q6?q^Lq!?+E!RqAGp`i=`vWVeU2b#)|y9sA|M_ z&qIP=R9k=0Q= z&CU)ZT-&sngl9p`GP$>BG({v(iUiR7JiQ$)5n!*0c_f4od)4ldu>5>@TNsg>L3c`E zg-0K3lI^YeC-p2OJc7|C<>9^49{i{w*`n$TPGqaSCgmDoD^}Rx-KlW0^w(Qv(y^Dr z9^XOaqnWPUn!Hk|h5P(kS9XdOdH7!vMjblFhJu3?uV9`AXKmd65@TLo0WPh@rIp$L zZre4V{mc>A{{`N9o8&>N5bk8vG?L|K_bjYWF>xVx>gcdgPZHhyEZR)el$FWn;(EnP z&_EFu?ALG%0+An2eb8e&W#a<)3<&tK7V|xYIbr#!xBYG}4bI1B@)tSHi~i11sHf<< z56Lxiz2v=wiyCL)n{9La`9H#Ta1w4=Iuw3u$ml>z0>iJMbgnv!f}i8oS6NdDx0;p% z%!Rp;!;OAt;)gJ#ylu1+7VSY@yQLi1G{Bx!BZpv@bz_(lju%0T>#V_PF}W*R!kizK z6H`?zQU8W^v!E}w74)BpO_62w?XyqiMexHsbarKdiRkZfsJ0poAgO{O^C_Oqq+L~m z>}c>;I)5f}iCcMBhg+48atk;0-~)EcfhE6z;Qha1h*~k^JL%z)QxM+-W42j^C$0Tl z2w1A5$lD!_G#l{xrPINQ=c}cdjWqQPj>l}CHcB5NKbm1DrWA==(FD!ti3t5tNjEEX zfVU`-f6NOS6!Orn;8n0BBLkNxG)(p){eap6K$Ucf2#Tbgi?X2P@jxC z%tV_c^k;IDXDBqFB4^2ZC23kuiZ`iD3|<6-9EB){lEJm&Ls*}(wQBu+4Yv&@D;fU7 zx9M7%Ko;S}hiFYYuFN(*)7GcRrmBmiG52swPQmpg8I|YX2hNz@iBx4gGEIPkl@;t+ zdGG4W3~{F2fWYryz~umyM`xyf%@AR!73k9(hYe!j0E`|wLzT*b0kvz=N_t%`wc{Iu zh0h6oY~uoxHJ#=$s)B6x@vU@F0%1wzg{0PsvFkp>`Ytc~13U%c%Kf{&Ta`u1pa;z= z)nWuvR-sA7&xzIF2c@Rq5RSx;XOCdy`q77t(D3OE{ccMJH>xou3r*3ZEmQj^+RQ5# z+$r|D+BE(4aV~@1n)jdWMOPO6?Z>R<;|#F#;7#U35cdW5Fui(GS(UOUmVQ2U%i~%t z;@{_=h7J+ZP%%$|)megf?GER#@E1nJ-^E${K(O9ELj-$zpJCJa@Hg7BJb0xhta?{! zsz?n!yEVh2V0;@0ae#~hC=zYB;82PI|7Fp6u8my)%Y+xs%Mh;T4>hxKtg8{DyK&RT zTy+Py2m*2_n?I#aO}bu=niZMDhYYZi^SY=u-23sVJ$su=>PdzqZxrk;Fi`dHx~5lV zEz9c+xOabHSoV+-l%!IdoLj*yV=oZphmC7&-8%5Q%l`C{VE(e4kXG8(Dw;W_c*3*M zCJ*O~sSPBIRtDpo?W|o| zPFQ@tmykj%ND&WnC&*oipYm=$@fSKH{n|J_Y!cNCBB*}oum^Vut;+-@3aNeNVU+Mn zQ7VgY+KoV2VhRw_2OCykoQmmQx9GK`C5#o6ugvISZ;U}&|=r7YxU@}J` z5C3kvxzJryjHnOW6ImuoY$40LLUN%-wS2!v8kNe=$FXB`r6&o@OA+;=_#f~|KMfO= zxhUMYGvEbC`>+vFMM(%>Nw}Q0;j-vcIW`+AyEh zWDQ3mRC~!G76O8uZ$Vy*1{^~5-JWjaOL!jc6$=WN6|+U{dGr`5J~$@o_o$CFh>NSB zVa4H{RF%|hNjb#nD;XhC2`xAXHuJHzroo;KN*kVBUgH7Ga*LCM0cztQi-5$tOvp2| zIcU$(d&y7cK4m*6JvcPpLWX+F(YP-y5Qs~ukDtS5?(o_kl=R#z(whVB-MuQNdb`H- zhWQqQlspCqWGE%n$z-|)m~%<9L)K}OeHOQ5AGIavPMni*q=rRKu@aY-t7SudO4*hA+Hj3S-&_<(~3?i(H38a^+_@x21h?SGSIV z`)sP&fl>pQzeLlquZL@4|6gKx8Uh1;MC;-J+h~Hn5S|eh_oKy|M)1-NH;|0j5$ZX4 zOsX}bs80WJ8>tZ*dsA4? zI>K@N+^GmFDE2khK)K56t;#{A(ArTPA%}vTB zAnz}YALq?C)(rsuRTTY>dA6A^urDVx$uJYtm;WE_W}~E24wdD*uA6`c23I>+v6({^ zdMbUYG#w4;1i4Wy_?_FeRNmE|Gt^!r9$5q=9yPb)*a_vGXMd3IO@`d;W;^E>ktFMSqw%Oq6?}V4|l3rN~08ZhuS$$C@(I$Rg z_@-+>#(*WBZaP~QlS-H_d?Ey0t7?c20`_JKyPC>MI+#6xMN^pz)3nZ>a^Tv&b5K6# z7$h#VXfRPHd3|bRpU2&`P3f-Z>T*Q3Tx2ofxMBok@xG`TByPD5Y1gHvvS&MWgI+f6 zvI9iJyMD>7gA#!=@Fz`U6Q(s#K)6Q}{WG|o*C$>0u6)62V_o4*M zR)G_85J&FrqkL4oQk``&(@tjjc~Pwxb5+u2AvZ2tAUnRqutDs0o@$oH^zn`MWL}j>%9LZg) zW}NDGt-3_L?!)N)n^D_aW-IATMlT$>G_pnc4%r{Cg#3xRI7mweF0wGYbXq4*8U01l zd`DVs&-zueFQK3tRqhT~GuWoM0d#8gVAA#@glw|&zogF;3ZObAgxqr%3%{ecy~|F5 z@{7v(*fdtOia$OUQ?zBm^t&VML8{wTh{b0XPD!3gV+52xRO#;!W+x41HqqDqpJ%ff zzH&`U*-X3tG&Jo0cHa*h_w+Cdub}GLp$uK!FFsh&hUxM|w@7BPvLa3DY&MgiN6!0<6+%$M-;WYt?pAQ5BYL#sZdkX*i}dE4Eq+! z(@~KGV`xtO^4=u?;LB~m#L@|gizJ6m%$nW5+ql{3rQ(iIel$Dg~@ zIE`Sz)_4!T2&__c4Mrr(PS(#zDL0qHUAZyGEYh&yULYKmxOV30V&HhFwb1ZeV1uds zYU~9^k{M~|%76#;mLe;ud54!`I^~Xr%DRK~Vh2B#B?E)8NcS^fnCET$uoT_KnR7tx z&RD~+ycHwmXJCgmg;s?$eoq@PEgEu$u|#wU(U-}ztIf;@RNw(Fl@J*dQ%Lytoc6gl zlK$&-Az+;J`-4QlbM3yKPO`_6yRg!!jd10R!MSQrt`m3(Y~l*r_Mags zDV$YOMAxmM_CK4QawniAc!>8sFJQ-jH*yh1-t=SggI+)!=gLQ8v4L$b!QQM&ajWhB zhe6HZU7=f!g!|7Sz2VpXeq^tgY2H2n2G`6)G5AV+xN(0WP+Rg-B|cw^(P<=?mtFd- z7@{MBx{Gs>bA`?YZ-f6^+ub5p!`1~YKZn)wrq0^;&qRO?d*uBT4*4(fNN;YW6G71x z?I2f!l5r$M^-yOOMnlWVe5vB7OR_3L@DIgkuw0@=rWGsV#2J-Vk+^K`heyKXt#{qK zfcRD;*n4pBc=>-+&9}sTQvOV1DRK>+OcuZ_Q}goL@?(7QpE$k&6$g~1{gczFc-xQE z8K;l6mPh&4DuOVBX#Jw7-dpBw0n_XFWR9{Qg$fH|lFk=}`kME{l59%8KXMKkv``9h&0gv&*m8k{+08L_B9(0w;egul4}HWgajPh}Vw#eO~ddS%wzF zE>E1t*}s+1q&ndC(x28_%^uH9^2cq)+{Jya8l8s5u53@iF5|7{qU&T42s)|NS8edvYu<(r zrKBG$d)iwoPQ0j_yRr9&au##gY%3SG8}9+?$_|0r227@`>$y8IHgzTRrwR8&mjF~U zh)wV+Xx{`KHCatocg4`)wNO1!S6uFmD1Z`Z+!!{D(H!dQstLDAaehg8Z0`H=!Xn65 zElwiz!;w8UU527;K0dZz2tbnh7yniP!10A{i7Td0=$NA0QWanY?AnMWjCX~V^B`pL z8j4;w+l#Y%yOG=M<*DPJR=_Np(n$(QM$DI>!mjvk3}o6A%RxbLfaG7Jz-=?>&!|E% zM!#5Y1&Q;#jr%;s$=p;>ap8Z9BX=Z4gPjI|gc-ElaRuHTld&_ht$Deb$)6LzU>c21 z&tQ8&R$}n1OY4j*0^;Sfu;m`gLcH8J>jc%m%`nPp8d+6I^bTh@Xcn)n#l z3dfI6K-NjNLEgpIB7z8g&gx3XJWOvd{Y%C@l@5$)5$0a*hA>K{|I z>4S6B4t*N0FsmnEO4{9I8N0|T2}va2CS!tr$Df7fU3Hu^%gplP!p7Fik8Tz)%0jR+ zj{U6fDV`~ibZD&8#{D;PJ1h>hR1epZ89nUjM|Ar_wAO=yV4gaox)@h7#Dz~*XCQjL zZH(y9;OyHGND!Uhg?~4}1t7}cmvBRJLr8meyk-HjgG3D$NQ2EkEu>)!*h$Y+U_|N5 zfx%KRdRWSZ+^s7d_Vx6nIm!Bd)99|rqLWsjAeV_g<^sQW^I zi{+%6i!;EtyxXwIzvuNS9}QuxG#vJ$-7F`JW@(e!Ayp=0lG)ZiRmk&49s7Mnp3*g# zOKxDl1 zq&~u6ki2ZN6ajCRuyFf>Sh1!zU&Z7B+EV_QV-(@ozQ;Qa-K4_XWQyJ&jitOs&Y zlWp6P%Tqf*SY*Ore1Dg&u>cKTv#92BvnNvF38?X+R~K!89asmTK18=DkI9Z^b_5x9 z@8nfimH@3F(?J&0Snq2gD`uDi8NfVRU7ELd3xzO-QtdNL*hZtMJectE1b*=6exW9P zejigrJ(c=D3Z{jrXwyV~ubEutrzAT$7)Ps8N$9PA*6gz{;oDSe z38dtL=-VOlX_V%mKpn>Mr`GUbFAUeQ2SN~h8jYke;bbX_ODXM45u#m_uYe1P7@(`Y7=GcQ*H;FW>f5#Qnm zBDwtNUi7Hu>!rvT@`;m*9EK0K2Lhc(tY%A9Q%;r!HU+Gtma5aO4OohkG@!P}auf{Y zdq~8d^dcay!Z>}jN7KFnk>zWbM}WXzYe4UBfCn;Z=vuso>bMxPpagTe2pWWzGup!Q z`P-@HXgYT^^d$BzgkGh}CmYMSr`B9z4Ni7(-!4YEP4-4w0O#p$Xf(axmMkOOjxy(C zW_IuqOzxSvxtR3xTTb7afY0mvn-*9k_AS?N2Z#tqd2)T<_ctJ!aEwbW@A?gx))8qA zRn2^K$YQJfrfWC^hp+hmKCRYUb83QJ9dJ5rt{Y%Jk^&Zi#8n2+V^{r*TZ}yNWYcY` zvjfv>vbfo2tbIiVJh`O2PR!P31$cC%DNWvpgR&nd!IgI8?8fluv?z>)1ZAel!soRl zZovR*DE?w%N->oPsUw)P)S{$VJ>PFgD^-GdaB#&to{FFs3a=#s40kQ@sq>kPW zRET|GRwaH2s227HE7%m+cMs=lN(>w)U^3~GcZ2ljYEVC|ty?FT6^XQZxwCD^+5jFk zRMiPn1b5Ao2^M{zyiS2bNZH8rqb@9w{1e}w8zSLe>UND|3FbkXgs((7d<<0z#m2!q zbc_%!77tOM<$@86Nk>)dsZ%~Z0(tw*ZJD`70QS3B+M7@U8<5`lzSniObpK4-KC`bc?TlFFGci!5qYGlAj^FQSIGL6s| zf$Yx?5XcbIP5&8z!M)HGau3oPy=!Nwb1K;f z!ZS>W!mGUumGDIPC(YjETGW=|>?1R>uZkNB@1DT_9L4h`S;p(53lnlD%VHnxKwglA z{sI1$N*m*m>26H)4n&##m-~wv#h+~dKHKwNoJ|>?)Bx4BKBH3W;qA~PhzNm!4m!tA zg889(Zg_bUw=V*8f!~}c>gnV3<@kHlm2g+&IKOM~ERU!A{Gwz7S380U4Tc=O@5#}9 z1tg?C#xK zoYUEZ|1Gao>YgSSs@o8V=lQmHA8ZN|?=gYF8P-Q|IYrmg?kt(Vl}D>q`dv*xJe;I1 z^UaRBXuIC~>2Y1TcHZ7jme};WaIA^8tTqHq(z;Be3rC&j z`1|4FPX6H%xFyy^zeoUr(r$JVJ0*aRyeyx zGW7&S<7%I}x|?%}{f}=@ezz0SGG^27f$qjNW`aJYTy!_}|N zVwZfx%Jpdg;8-AObK2}4-AF5)TJO^A)4>&`p&2( z%dJCWFt=A%wzCoP9+HixCGlqvTuC${a%PpBZ9eu{`rodN3uCjjXO^RV8Kt!y)umw~ zR~8c&KASUm_mDPJ29VU$NUA+%v|+#NL!|90!vN%X)@PKI8q9>!+)sPsNOuzottbJ} zt#bhdKw(bPBwpjth?tHFTY}mtvU`YdD#p{jZ`q35bs~u{-WutxY$MFnDKtxkLu=m( z?=?&<>d~}_hCIPj>HW*IjP~pLikKWG>j6%&eEmOv1rgs&SV+HO{{fUs17A3=Z(}I_ z5N6*7X#Z1cbc<^~BJI)Q<{MgC(DQ2-1!7tOwGWD7zd6VBT1L1Q+Yz#j3mL?%xPHk< z+E4Hc;881};8f2FM2BVQBU`)*ZxPIq97wv@5Zflpj=G~to6GB1=i!s%qEyD;w{x*J zPIkl2k0;>ka1QAKj*qN6T?m7Q(ul>IQj=m>Bs4RpA?rg*Ga1AzN-zN<`3NmJ3`8F4R@Dz`NrdX zf%LVMfr6DZT&IqcE}1c&?R?F7gCscvpmBg6g|zR^vMPeg>^hf6DmOmWgNv zdR&N>z#|*G@x~}nEb<_BFTIB|;ttxo!}fv7Q$|dCG744~++Kwi8Gh&})$nq{uzOuO z40U;vbF1D@j|~T=(O(O#WXX&C>l_AQEiTq}FVk5ke>feXtW6(1ubTQqW*}#!HXKA^ zROfdF7Zp;?TR!b#tfsOm(Lf&-s=QI2Z0P=#+7yBnb)=e^0+9zP#&x}3VjTD@h5|JO zb{|o%eP;5ZWl^>jzhfWBOH1vmHXYzSjD|n&aP1&2=VqMw_)h$SCa}F*n=I zt;j2CBeb~KY6x`$;TfzzbUT306B6u)P=`{S7MnHBz+#GPPj&m7(qK;9|#lVX_kHq{QF7NY}IZbf7uEX?jffv+ce?|050^ z%Ae=qhy+sLnUO$OiZQvv(hDLST~TYPJfF#eRIx$MZGIw9Yb^4Inur3<1;H^z9)@0L zzdE!bpJ~ zf*X_^IT=CkxLbYNVpOT@`ag&*f4?30*={~SQ>ZdeeSU5O9HkKSv$$|K{Z>PHp?W>l z7A6Nn;CGCJc$92v5Y5M=7%D#)^d1R$2F{ZMOkcY)f(3)Fe)FJ?tww9KN;l_O&V=0+tjjZ1Nv6&I`-o+|=&JZJ6wZgv5(GsG^P9bM8M?BG zsglm47=-lA#K?}9p&t|M_2>`pkd71^8O`L!8P1*2UnB5nIqyMx{58lk6;V2|!I>#m zowo@OlBrata?>=vl*l7c!NPmy@_@kaMu7GQEY(^rkUQ& zsc=(3ORKPjdhxFT#;EnMi!fK*Sj05lJ({6_BK&t335~m_3bOfqsrlpDrnjR5bryHl zw-PeqS>t`UO^||ssQbUPcI!C=+qj;+40Xi6mW+#FKuc(+6SzvPUPWK6=BxmOVoeOm z%1Nr`gHiSEFba_KK%jg+!~lmF5kEa%v07Wu{*K^ujL|jDD)l5F*xs#&gwU-RVMPUsQD5X zHSBC}?N)Wx)6pWoFeJiK%H)B6<>DtDo?b~krVK|laN&0;aWFhss@ElGd^Qx4WN9Jz zs4pUaZkD%%Wd-<&ojHDFGt&a+GZX+dL@)~%RvtXFpr0;=`L`3KK@WEPELOcuCr#Lw z3sUaN)_-(a5U-o)@Qo0aAx+egPW~T72R%N0RwUXK{9y=0LYc^g?#^%jd7@4YA|2{ur0#9P zgom}Bk)b*0oTDUC(_G6?fvY$xiogro`)fDj#&RK(w=1zDB}zfZ-P;PPI$-A{K_%@B zMea}<&(bymOT=HrJc~q0Li{_af<)Fc2beLO3n^&6A~y{0Zne`oP)S?-L1NoR$V$fLL@)Ou3p3()pt{^iPt4ki8^Kh_{o~kLt)nT_6H{J?_uQAY!#+lHiOp zqQ^->c<2|n)p4#r>ta%Rg=4OVP~vFB)CK z=tO5!OLjgemL6*Kj4(&yXxuKJnK7F%^ws=AX4wF>f=V3tHGi@>G>FDSvbCQ-0Y1$` zsAHb8vD`gSocw+SGJiMWMBNC0?FS87TFzk%{E$UfIS@GSmTLcN-e`GncCsAf)l7 zp|IB+zEwM)snZK4Qc&N9DEe01gx8(DV@=bCEqv}v-N%dJ;Qj(_81UWbc6xFK!(ZZJ zugi;U7bdJ*4kIwcRVI;e#n$)f5VNw5Y2vx-GOE@@ktv%dI`HpMhy=_ae&g2qBk4Qa zIs@Qy=7|lFDrXxA{&hpr`~m85LfKqLjaqw#oTzox=Z~-(Ju-`W!&=NpM$|Q_7Djv( zJ3zInjuTImtJCezqIal&iXedT{kX{Dq?}gg0Q6wJe#V;}{|M|;m%6;xUEqcY`=dq% z^RJZ{Z{(EX=r6pW;h9~hpl^~udms!YHg+SeFMaaRi^=}gus5Fg(a$ECj0~B`G+EI@ zGctWf4r|beU}-N8n4vAQ2yFNw+8A1S=Z!%7dSs9j;&C29i*{~RDWjT9AtldX4jf>! z#?9sSo!c3nB+x;Y6K?w*EdWy8EgiH}c00iimP15?%C`FfE#zJ`fV!e*25O74B~-2X zZceZOy9l?^pwNI0hBWP>GD)v@UI8A~Z2a`_=7Y8b zle@lrey=fYPrT;Gfzkb}8p2C&B?{8j_MNd5Wh~|N-6T33uD)(wY-#*~4O)~ZnNbRc+fyRm6XE9Qiv_v^f~b5gbAv}@{a5ffZ>BYQ zd8K7>{InXRLHA6OMsll*t4bOQs3)S}$g7@4l#*vXxumYP*JDqdsgqGXhF(_<$!z-5 z2_f1}aDLVIqK+}7IWh)pQLhMw%MZH2jKkaFOH?T!EyDU0AvLIA`0fvXDP+mG*}++6 zFvH|*GxIbu6cONEdrp{`(00KM$8)y1D!cnYd8ANjV(_OjDKqo&sxQiCTMobkwCKlP ziP}Ea!(O=Z@nYtmBDI!>%fN@o0(d64GqDL`GQ&bOd6hA^}z^rp@BdXumU z;QqbD3xRL>;LI^7;AI+{kGJ&qP)s^x93Wt}o^mF9v!_-ymy4UfM2+I7cBTuBwTk|; zjy3X}t@If3x>uivJb{Pg$C4MGU@*EmF=m{8Lc4|3r}JKZBp>&9#5va`oo_)(*DZN9 zmO|_ui-A3fs2G{mu16g&_Ck*=WG88F1%QtqT@Le)UrV;c2ikIk8>nUAEejC?To{%K zdhna68ly8&*V(-DJZz;7M%&})EEai$NGA)IyO#eSY0FW2(H83f}4JKglzMiUq%qPSB_BZ;6}Un7aa`*0??S$70Jx)q;h{T1pogB@QYwH$Ftu4d*NQf^ zBk$kpx#~%k^a0LtnEaA-s157pH>W^vOe)GqbqrE^+z@F_mD_7U`=&GFl$n>X5NaH% z_r3Rst3_!iOZLwX@g_Zoek`^Aex*`QI3(RnwBM!igw))QAqjS4zbYEB#=ZBey8FgS z3znLQLyI&&^;4;3rHNVg2;SlCxrd}g%g4vJBDPbPgZL@_;ogX-!g0nSxzkEq5;|Xs zdnC=i0MrjESVF!Jb3R<&Zh$4@Gaj}b_dp0eh@*;d#3CM*a&5P!*bVr9&mxPy9odA{ z>JWBO5o+t3Nf%&IA0F+U13%X?6gx!MPxx6pXwrYA?Vp_PvuYa$HdC>10R2WWy@-dB zGp+6;Gu{`~gzj1~ZVtRwCsR{$S9_-0Dc)Gmtijur&TQxL4bVgi9%0l%{`g>AIk)p# zL+fcwFDON!?mH&7_F<4b*2Hg~ChPZ-*ifhNzwTBh$#-uJ;-F-kumD-`P)s3l^OF%2 z!%~mVY~y<(kb^ObC6?m|eE7&~0z9juR8DkU$#)kn@@4;P#BYtBSCd}#xw#zi3nb42 zY^D`XV(F;p^(+&{$R~fO32k2bYQiH~`^Q^e&8MaUtWO?GjoE;JdaJ6B=r%!_s6!?h zIv9IWtZ}@M_PC(1%Y<%?$>)T%$ErywnlIkJTMQXs0Mzt=g4^#&GB86Tg9>kN z`N%sUk~#V+vZ05CUVHA(fYkeCYuI3yaASDt-pf9s@EkN;uw=W6qbV&+p2B-GFCwq^ z`q|xTCrzj8!{O$#&^xQOs(KM?q;c~bSi9LpTY<;6Gdqezx;yLOA*v;@QW~IN`u@Mh z$C3HO2*lkOcJmJAjvxOP--jBl6d04l4oyAIz5GraP&hUq$}?>M(ZFBmL<{ahlGfNQ zpj!A{FmcGo$GR(ul{**52keTJHqk!1Hp|`<))J^lkIlFehuY1J zeu<5>KJ7@|eVLeD;d}e0Ps32bpTo0O39J2r?k3`fQUbM{Cw$0t%=|m=atZ$3bSfb7nVVSgucLG3;&t z|4G?TFz{^#V-wY_TAz;&U6w-PO`*n+yZ$6qg7izh#<#8eoq!gAKi?fAuH{uGteSCO zYqPv__nq%5VsQBACbFQ1*eRw%*Cy9i=bxXLJc7pE#`Th3b}Aw2&6|shb03y)!Y?Ux zN9Z=3&#UH-$~{E7EU28sr+0?#vLab7>fz}m=s_3z#h4Ma{6cc!?u!!RV}PXuW#ftr zRtVgJ2*#=u;KUwQh~PQOUl$?-Nb}gjqgNgYcsVLJTDTu!H%a}-ZA-qbreA#MvQBIG zvT@scsfiu#lu^)TqgOzMqlKRj@9opVnoE{?E;sm>2kVsOeWP~#tROHD-f~!k z!U_~wgr5>moQBh9Fm*{%hUw?uEcS4 z|0?W5mIVW7fQ58l3nV4~dkCP?2|1^d+Hx3r8X+)Cgk8NuZA*-Y8s0M0dy7k=Da6OsM=~OcNYY8 zGeyc#?;leHXUuI3dOCWoR~6>~+zB~;Uguv|iO>>-sxNey`A~-S-;JpHCuJdF1lq=L zpb%4|esMct!e%UG{D}-)!EftXzR~e|yi-P}1e;F9lC?JfKH_jNdpZ0A5%twS#`oHH zQW_>HewC7Rr3C`lh<(*3oViYdKNQ|pSDUL8)}p}0!7NbZhZn=8pydxm1F@dv;+Oq; z#0%Uc1KPJjG97c}xYoO@;l*WT(3Lo&#IQ7?ihs84YyK`@Ey3#PjdG+bEj!(3=h0ds zS_2i_#D-A!8_+~}ac343QCV-G)MnJC&+rf4N~+YFm)CsryVLb{0j4Z)VTkz}QSS3` z^kfC#Zyk%7%qqd$mFMc>@MB*QqDQRy!^>W;q~kya-n%qFN}6;zN1qezqVJ~Me86DQ$6#P>f0ES$7MU1%1lkPOC!s(qMHd| zO5}qp@cCJ;&i=uUBk5oo-zBnm_TspebCNPbSOi_Ettm1?t)l=}TyPTCq@?rw!Ltch zw_v^{rH$keE6Cm+|1p6v64dy=sNt5UcXv#_CpiYk&2-M1Xxaa8)&!eQ__P&{2G~Ra zilM-%7gnHBk@#vtfSPGc-$v};(h0pCPl$%lhKR6al5K#J6SfnonFZqDFgy;e`hMY+ z8C;fDcHtG5J>yzFsnqA+9Ate&j!&#So0n+!w}vk?FWvG7cUTLgjLqQj6>oP< z+qVTe>#7VnYRIWrpQU@73ZbBIy|{4VVDCtP^1rXH%1AmCGQO4|S* zvcv$Kv*!LNqS_WI*bRXSY9-Z6t4PEUF0K)lUOP>4sgbQX~sYhI&6-Ql%D()x6mDnX{{*PNcoeQ=&wHS0;H69GQg(4)g}XR=6edSEVAv|@gV(L4xdM02g7;^a(`*U9jurq-aP0xZO!$n?NjmudEz9G?JnHK zaHWwa^NXILB-Izsw6?r1I?BK91Fy{tyV1zJVr$XdPDVR%kEDlLN|AZ&!-ae1& z_H2%a3TxmbC(4)7O1kv3bb03<3vqsk{lYsa0P+H5iT1>(eoa6KeB<<4Nm%FEt`O6- z0J857FDa2g9P#;Po+AGN+p3WUcEToV^JQ}utL3;(&Nym4w>qQ5*r+BQODC_yiB>~( zTb0r&sk1__LAU7$y9$?2hPo+6pXqx}Rfc=rh6+1ZatLz|5Sl)-rGFZ?_O@=8Q1gXE zf$3S0+;mItY5)~rw@QoWUKNI#5!p)lD=5PKzOB2#dcqn042zUYG@6#djrq2pb|X{@ z=P}ntTZ^5jJfFC)AkspzGs8vhP6mX3OCyLYm%~vEBUX3NqZRy#vf30H=Oijq<+@X{ zxj@MdDskxj_YBZ7oHpCzn6A3`jfr$JbYISmIy^a5e*8lcj!XQSEXCp;CxZhEvEsERHC`ojrezLaKHJNP=LVV0H>*{(*XnA7~SL!=};D!7EC!umo+`b z0T1c3K&?eybW5^C{XJcj=1@#$3?UWdJeY!k-65 zx635IxTe`v18Th~NeT?AkbqC>mcjo{DUZ;6baz^Eb(Jp8>xj5$U*a$(UtPGmm(%Po z>_#S?`?h4!z`Q(?mS_6r$2KzEw;mH_@9`rj;44U%o)=tv;jB!vQW%SweIn zL9?GfHE|6#y)MadpO|Gf#k%dH>u`N#vGrMBAA)4HNV-E$aZ#`H@3snT#H(Be#cyT^ zV?6D=U21JEqoD}T%cGBSi3tywQf*+7bdOm6^i{pni7W4zlV*)|!aBB4q#+u*ly3{f zUFAS0NbiUG?U;^i=rd5n6tkw0tCJ*Ag5xF8)0+!@7J}OI&s}{MM`5N;MfSPN{^nTN z^CF~a+{(XvK8xU6CgVWF#?!!5?>Oy^rypr|5@i|}@4m)Z@C7kH-XZ@DS^g=((p+tQ*KXRnZh1vPPWCAvp^@h3Dno|&LjCJLv$zJ9=P6_Sr*CUT? z?!aGgZMRkf5POEO_e`HqG=`S?ak7k4W+uqFYJW@OhbjVzG#)0AlbzhPAT-y~Z<(XF z*qv+(1Rg+O8T>TEITi3NeL(JZ%9$zDL}nH|D;F%RnRn`+GXwBXusiu@s3-S_PD4eV z`&v)JI%5AsuCB4)D4HHb?0|s4>#!D}zT0Moh5E@=!Qe=z?)wF$?-23s#}p98G^n_? zg&P^y@F}JG!Frsz4aYuO=a~HeT=UR;)es%FPF?s4R7IcWmp0nm5SoH@v3F{^+Nx)) z6|ZZ-E4F`{->Ab>U+sl7It=Mn!s2}AVwtD<%b)UtRAo6QaDvkj-wi;vaynq8+Zu~OZ&?u`<$RBv%mPN zXJnpF8dMzxR@8s>C^}<4w_K4uFZLW*wPmpcR)E3I3lFas*lX|IEmHJ>>Qr@BH*qTa zID$W`-jq#8{08uspd(P);O7(*ncR=+6Ke$UvToC8wKM| zt@95g{^)s6ry0dMFE8bN1c1Qdz}SHnT*bAr+E6=Aksv7S+z7?}Z0Az5i37p}@w4Dp zs{B36+RVSEd9(T<>Z0E&(hmSuK&ij$(Y;OM6$==LO_EhyQ1+ivJoWQVbF5%HeCv;Q zlourAZs_GStIm)44C~b1v)H?6T^&?;9#4BU0hO7Nj+*r;IWw-nkz%d!n~+H&9|v)6 zov;#$&rW3(xlcHj1HhxDaWouVS!-1sA>}*=6H6x#FhR zt{lj~a~@&c^;uzeVJxw?hsGWZGSF6lVv1n7?DML@+MBQMx7++=>dG$4;v^kZI#fmp(>PxO#3E8%IEm=fWYCv;V-nWJ`<$fWDeE9S^$+Ha023! zcybg-?$=cut|+cR$MCOV8)jw3T~x$m1;^N!*1tlILV*B*XtvjI9KqbZyIRThr8<>W z=5*22(X_#KkAky-K`$tqU1pL=5kF7#F>l-Dv`nNNHfKGANmODI%C;d?)AIG_d-A+d zr`Hai7*CQhT4AHvk4t!#CJn#Z@Tw!OmvTNn8~|8LF1&bs$|$5_4KqOnNSUj*)}CZv z^)lNhf8X0|6g2|fYITRqS0s?N`LYM2R!@h&|1d5%aoWEQ>!4ZBF!GsQkvtTA0vIH> zz%>RuGiz`t)|mrJLo@v$IP}?mTV+YXp1y~fX7}6HJF>>Z{n-r#lO>7M;Re(?k9;+` zv~Y4*u<0lG-sbheNvRn1R}2Jaf!t(xgr$xUt?*+^k>|xSMb_15xm#d8q>^y_l~3Qz zxLJ4>4ExhgVd~9*V>Y{Wk8~dPC;K^kAg+*p<@Fr7eO)`s;X?wZJ=;v^5f1zAb&2UT z4!gn{3Xu1OXCXH9Xv6ed1fHp>mg!l(eTgDpnulm+IcdM@KlW9LJ~v7F{6I3*sBIQa zY>=1chT@Sv7_I6$3q0%=Mc6EFdq>_w=`4^om5J%^{4YSara&Fh6}NP!ioycrQG7=$ zMp>21=_*OX*6a3n;Rku$Rh!i4(5gGG4xAF=e7-SAUPEjaA}<8rp@32^$Ui!e{Q#z< zV)RH5FEvN&#~FNPio)!t+xsf$Gs>_`W;#oM+L~q~{dAVeI%=da^5@%!Fj+>TeJFr^ zR`SB5J*k(ySXB_BvS=Wv-?&o?lb#YW$hD~|G9?wCJNpZT;^#g)fRiFiDANiqEvGjD zibfR=33O4fitlk_5X-swx+M{-EwlS~{Ss-)KkyybFK>mpZpVzC1D#v`3pv$zjBQbT zW%bC)m~Wch<`YzWkAT4Apm89P=4p@%9FEgx8d)J(owuP;|9edq={KWp#)-V%fbg6qbr2jDo<`^6q1#i|((a??2-va03=a0ZmW6I$^Lh z0M`(y@qfYJ#_F&0->7Yw3Er&8O37`x*hm;*0C*R%h(_Ry;#9mQwEtpZVpX#RWH~Iq zFCq>v@=ibBT{Ch#`K!L_dTHzvrL*OOMko5D+0rk?_2Gdc$N**QO?aHN-$`jiH*ouJ zV)~cUe?fe4I8{=BHNvkKbnbPB!y^AZ<+y}?!`1Fti9RXim`Hmf4iRM}%rUSo>N`YL zf~e@RbjmWHpEKbK!b~%EUX8>*q5vdO z8qo&t#~0t~+T}3}&k%$c*lKDuj6tL4V=9fw3i@pJpC;Z^1DQND@=<4e%aAS{?YZ8L0n5 zYri7!WB*O)p2qqc-4rcuX3Q7cw4Qm}Cb%@9X8wGHC(=KXr)^fi9W4_QYN)q~7(Vxk z+8)*!o8v59Ji^X^uY5yx#O#R(cxxw_4O)c zalU+7GUO8-j9i7Z()9(xI9Py!b(T9Z#&1?YP03P0+o6Hyf-0LRVl{fHnclclxB=CrXXL84zf0- z7DreK7x9Go7_Pwg;xl4X{mdq}zlm(;vE5ENiXYkQE}X^4@^C8PSGEK$7sK9%I$VJ$ z#OFYn7Qiey5&w1A5Zq#x6AL&PAB)@=&6%^x_fyM62T{?)Q%kq7f!SOefoC46_L(is7493{ur$L!nh&ay1^{hhz8)G zcOB#`m%Lo$fojM@QnFFJ>%yL8Pn0fTFf*ffne<$##-W-qaCX=n>xEl?A%Z2Hf4qre&BB<8=9JgRJbL2fk? zYDO@i_HWu<(oehmd^}U7LFSjCobRH6xsr_4NJ!U5nY4`TG;M(5 zB5b^E&Y&8O)yCFJGj@C!-Lm(XMghBLr%ze$+OQsVfIUW~jkYA9AZFicRMA#nN7Lw# zf8Ri3du0oX55-22^6Pv(hx^BN)a%)A6StdtZh96iA!(_^I3*duN>D-G3aae!;3X+K2WIumoY(TZ9g<3YLg5( z6LFz%WOE5PKpUQTiTbqJiYjJ{8jwsacT|(Dw@AAl`oR%?| zVcpR^Iz*Z+WN!Hhc@+6Czic|LM{Oqp#>$y`e|;RE1aNhZH2&(-H1Siwas33RiXRI? zuv?}&Nq~UBbLrF%gVab1Y$sq?Mi6j*mxo%{k<$sjk@Qn_}^DKaa(1)fszY26t>M44+@Hv1-DvBx9=Sjl$h zv`Gzx<{2#RtTN5+oRL6G?-jV3%4_fE()x@%HGonlKR5F_7Dp1zYky+qz4)P@N#XM( zzWxIkuFl7~Aw3cO(7|-)Mmy9sle6uK;!(4-V~I+oz^Szk8K*Ewy%QaG#6pJWI+BFR zroFhg%Ff#_{RrG7aY8-l{zqmh9ux<2<#lFI_!)Uq-bQa#X>yl{B&gTxbZd)r*8gT$ zznpL?hSj=2ivhb_7sJE|kCU+Dv-bt}($-Xpc`LcC3o4Z8W!^trG>6Wa-v2!d>zGLX zswcHps>GFvwdtq69TMj8p=ZeIB1kyfBJf;SA4O1#A*oe9I#yv}T=i-U30lXtX# z`y;ob&zJjlh~8%Rd-4UH|7EI$YQ)j!vO=Lz%)qoSJ-7#kS+qKekq8h}2Zy-q+=BmG zxEGL-tE+@lIq251Ei4s7F68^A;rMj`ASI{(EI{*otQf`Yw^v=c_q;gui#;j>rV+A^YC6q# zX6!!%(?5}>3sWR$TT2*xyNEjI>#)6YJ}bE@%vh0zUwZpAP1}}YvljK3}>d9=1L44EZt$}qybM_jNu0DbfWVZhSr2C^QogT@dd+hV)3E! zZf4$OszQTB60NR}PbDG0ZAgU=>HAEL#Ly9HB{cp@^-3OBvW6`v_Y-6>(QJ$!lxZiP z)dpTNznpVjIPD<~HF$Cp{IoawJ2KV<_=_HGzIF)cN-8(chVxgJJIL5pDNm+8FmW_3 z6jQC^s9PUm;)V4%ZS+%NMebd^tjhGFm9g^XZei$hcp8?2hDG*4dI@lf&+H;^Kk*tY zd+6Z+omlu1vM0FUOS}D&ZN1Ycl=z2}7BQny-CvI_H{qm2(>pPkHI!nReJ!3srQ~1N zJz1rTv6A1La6n_ys<2V)#AtJR_T9MZ4N=08mhj;IL7(-(7bh3#Lf+!efBevZco*n3 zq0Mo{6q@}frjeY}C@GkN(g6L&)Lo9+J1>}D80R|?a4OB6*-#!*V!O1Ak8~t_B~Up<<{V!Q~7cjLgxt4f9mK{AQsTM8Y5!M zATmg3nuR77yV`ak>eXjJ&?R#k?lF6+EE==})@TfBR=y<*Y$$Y|P@L=^mhvZ@0yJCp zLdV&AH?hB~t75-go->z zk2PrOj`eo4h=jkMB~_W|mDJVx!c=}VU&jV1U@j~eFQB`D30-pJu*!Yo3*i1TOjXi% z`HoNhPlyJ%dRLtVU7rC$FsJs(%ENiaEIooEEIJs|kiGDWP6YtZgyln$>zpdaD&I(; zc9Twk6BWM z;iOrW!MvkHXUby+=A!hQ*n$C$lU#(H7>!6Pz18|k1tAGiJ_UWuc8`eldU=q2oyp82 z{qZAKVR#AN&>;h)qJa6#Kp@T2&~kT$w$)l;LMdY+T9%h^OdTkBC&fVFFU$E1EYS~+ z^|Cy3Z9MFsOU0fe2=MYlC~}jYFLwllJ!E;>lJ)X;eq8hnaF8D5IlV22sgEb}){n%n zn-1sh)A3AupUtqCi~kEdIX$0gV<3+h1}62E$!(0UgB?b#huE(Q^8+J5vk@XgT^fb` zvchf-BRar^s~>wcuDd}{d7uS|k=H`tf*n$?$3KieOLW7Oi_w&()C~Jirt;t+Lw_X6 z$FL^$am5`bkX~Y-m9^~439?5!#G%Q?CaBC&@dRJxWSR-yk>j3!BX+fh~gv87`0#DW*E63?u zRfOB_tI7CA*LPvEudLEybx@jRML4lEr9-z_8M`G^Bt8K%sFT0ldQZC!@kWaE@jnxI zIb<|yoKiM*MXn0x+1etq4m%2^xx@L|Xpk_5(3IqCo2W+OHByY2F@yE*3`UN)V=nCKRV2 zq9tBv)cg{zCOz~3uuMmBe4D21pk*Nl0j`y-2XVc9Q2G6ZEoZB2Bzm`ziA5oEl!8jsIJL?gVZbY938}JF)HJp`im)7xYx} z)GoAMcl_Ce`55_xc)1~!k-h+j2&;fsx%_{l@%${PLjPCViH2;c*w$UGFqy`ILTCBx zG28q1+uc^1P_}eTLq=Sm3%CkY^U>kc%(Dz^TJXxP@UgUlIl7m&!J#RPzv!P${d5${ z@$(*utHTfKr!=+21|W$AB3QIb#w`obN_T^uEhu;$=Px~d`SGx2RI*{%5Oq+ zdY9HH`qy*q=+e2cGi?Y#OwXm0&&w`|mL}T9Vj{Yk%pe=j(K*9kd8BJzZAGS+b46dL zsdsg#wT=|VBxau&H&Z!CANOs0jqce|3tE@Ay2d{b39UV6j}U4xOUa`2hn|JiPkiqK zfMCU6S`kCQKxELU*aZt)B!p2nK>WJiDIVs`(U(wvu{Efk%{p1f#sLnx6s+iK;*KO< zW{OuLNJo`FH$d446R&O+%vruR)nfPPl4n~LQf4BaFusZ~DIDjvdHbO0XQ7jY1Wj+* zVZp!seK(v=7J&&H#~(2vRsSTH+l0E@23vUuq}_Zc)1p&aZDn(3LJPK>x_FI>aQhuc z#kZ8Pk4{Ot<34&W9@~;Ms7%t=rWigJ0)bf+;(JQdS*;cDY1R>!IKU~n6@?$vs9#Vt`vdP-;bzt8f(dEj=S7Mi1xRA(STCTtO5|d zN1!>XSia4!xl=bv>49U+VFfc8yJI z*X_C6lfD`e(WBDs7EhTui?^TfPR%jt4vVbQZC&w|1=B?43t1c^YTJwDcru1Hy0);t zh@2e>tr9{ToR7!_v3gh zxvP2afFPx~idDx}F*Dc3&23D$I8l1#-`$}UMF+jjMN1Juq6q_Il6k-@sy`B5&fyp< zpF2tAs>=kDskM~UgZ-xSj?ZfRrG3oIRy+1^$GDU)sKsePE{U~ve+XqC4F`%Mj6+VH2R#x?nP z>x6b--rpT^ZGr@*16>Wt2Y2y#1-zW4S+W)+gm84ZPzybKsM~_oqul6`#rHdnH3#-Sr&R94UIa# z!RD<9Uwl0?s+Cbm$*7I)jZ}IpJZxE_qROG!YjEXJNq;btC-OkTTGX`2OL|Qeoq^LgH z%&^VQc`xAi8uB{A6(Z!h;5CWOBl+BIkP)#G<5&SK8aJkT+rSX!Nxe-V-&OCr3>T?$ z25xj*Et1bJ~(4=(LaV#7{_eo~jEFi5q4R2k|<@NB0hf7L9)Yulm^)C7n5E@S=G zObiqOlAwP+2aNkn8qdl{6$QC!1FAMnNAyuqQ%je+exz_De~Ha;Yu(j)quZ0| z+hcUAafEpLtLY8h-XirImVdhZfbchl+KOvO>SR%!PIy(F+WJ!t$CQ%#M51HCykuEXxe z)g=i@*Al%$FQOwNWLC}3zX(Ec07kNB8AZ5|?lRjshjufnkHCyC5cGlw%=@WJv`<2a zK>|y|=+K)N`F|@2E9cG!1=r7FV?UpM|2r9tnbjg#*cidM=_o%T>cI|#um3|`I=&3c{f2uObuV7^!VO?fZFV|Dgez-m6#Cx#xv9RsMJA`rIICoX5P5BR$zmK@ zsgjy-D};ccO7UxZca^~5I^gsI??;?e+&NlLFu1`Fx{X1DWH}=~-t|(`=0+B_6;StW z-E3a|%ElY(H$&N}zQm5i&bZBO&5USTg+LujDhoOyrWv0TU$V z<#8?oM0a;?X-dyTl)ZycNXH22**lG(JoaecYpX#)7;1gLXHk{VCT(w0Wm6aRE zYraXew0OrMRm|^8C)qg63aN;K4smPMA$Ko20j{8g#|J4(+wa>cF&9f-{q&z1fPkQM zpz?VI4%Nxm%M;JQcT(qKfa$>S9SLAK$p(AA+XFmD;iOfH>&9K?1k0CsZM05iV&N51Dz>HO95_r$_jG6s>NP#u%pSqV@LH-R9SaH5k5#c@C3F zNgqi!cC)cE@2)`vwiW+$hc(6R`{z>IX0kQn%G}$0^}SwnOS1kU&-(!KbRFU#o}ROQ zn8M=AX@G#ha-;@e-vmbDPrJR7fhp4Nz9pf)t9qWQ68Tad#|M6s)}#40paJD~wKQBSN~cXuljS-%MfL!Zwn|PxN{}PQ zRYyMDz0B%^BM~Jzt}&P?em7V5h3x6SfPkPikiDRWwk~;vMSG>n?oQ%N0t>BOm#c3H znVrCQU)x~3iCVC;=vf!6M%<-##lb1-lx0Ik@3aHe;LUMnPP~CdzP2}C5YP# zPnr!_(9mo(nhQJ_PlJH(Vp&_L4`-ULV}%@v{{t;q!*~E{?2UFtVg*Eg9 za8=tm2vqwKBd6f&gB@OpSr!5fjp}6SLSYP;z0z9c5{y{1iDYm>E#_Wt8yCjx{1PuDb7=mJw@I1RJO$Moj38ft!7@lyT zOQN)uEE4rr?QRj)1S~}eO~u89_L_*~yPsi=?Z5YK3&MG#mTq&e-FgV(_jkK-3X6i% zlAv+}zD&&W+8HOMi(+K*kbC((6{Y=Z_bET&2uvsc3e-Qj)EJoWU)g4~8seDB=ei9Q zI?%IxkEfb85l&<$MvDaCiuYN@`~Iw)E~Gr6)M zN4_NiqyI=p5-yex&??Y)=4YXKf+MLEltE^y@>(c_x0@84%DoBD^^u|Pz|F&)Of_G* zrZx7`O!a0^E1W&0>p9q}Pd)z65@|UG?>pd!h{}OVvO#uTzM#=QNLyl&baY!yrM<^S zg}dfWZBhEC=g~w?P@?bzG%Nckf$$QHksX#phfr6aC37dzKORR&Q``*c{+*th8e|`g zOvqUsOs5ch4qXVWMe8Uc&0-(rcB&;qS7*6-lIP?Gp?%dy(8yi9*0D#i)w?PrUZofJ zBdp9YLJLg;XmQ=2(286*V^iEj6m|wYbdMvnbo>k}y;#p*3`1k03VF3!a7j9pW@K=+ z(_ECe5iOPF7zJ@^3!4wUoCpj}jC$kK_;+gM$Iu9(Uz)wU<<>d|0-*I2$JK`^S2XaS z`r6?tTCTv(GM7UqhgWB9h#8D~Z$+@`tBH#gtL==V_`xrSsVCJH( zb6G{Ab7(S+IeE~)*R`K91iv(sYm%fI*~U^f-ogGoY)>IH4^7M}nS+)Maps!0XRoE? zZOR?g)*kB=$4md2o;s@OvgJ>m(_0OFnVEU&;1vjMl)=E@XrdMWa9lLk%S3ujf1#${t#2 zz^{OLmuVf5B+Bak6%~${5b(n5He8>4yXXB#of_tG^J~(^xByvvw9S^8_ZoXb;P#3;5Gk7K`tttK5W{+&|f`;Cum}Sv9 zkZ%6R_XezZ{}@p)h^}7)L6dM_n&sG36A28SZ zH1u?I{CkRkpi}_ssmQ$;SW@c1?%&N`0_cEcq?a?(UmLU(Js{7k#-f_zf_1c-&STth z?Al^mV26=iKoreBK|TLph70e|di3%>qDIVBu~qC=53DAA_m<7|l8_?$sNTxl?Boy)A%4Xek{5kgXC%>#YoGm-zTKxIgixU-jbmOGp~BSA@ze zu|2FW$FQ|1$!!Tr)#XVN|HaSQt}6rWM`!mB0g z<>vG9&Y}tG==KM?OR@F=wK4B1Fotpr(%eFU&tF*@{|8x1IEs&RnwEb;+;-nd5%`!< z<<~w>$Pp`wj|ZNF?$-(1yvi{Jc!zE7LM{j0$MVj&BkG3grplBiy1c*2#UD;VB{pkH z$n^5ggABOJun6C7kzdY+B*%aC_!NsHw%CIpLIb-~^f;#-x#9F&ipG<8cs2dn?O~@q#vd5xnN<*V(p;HI%%)Ev4 zORxrs(hS|-F8F+gU8 zkke$NSpC$TViO??z8pIj-el{p!AdPE$2AAw zz(O|XH_D&M$(19I%tL_0=zn76$0+(}TQ`$@wu;2!;v9^2erNGrd{=*(n z%b=XoXxZZN^l+`WSQ^Vyzt`xo;~jmiN%;1jm5n-(Byh78Slo?RFx|_zC^*%PRwMP& zK6$i6=5h)MbcfX>-;@qIyg0pExWDeG%?GaF{b6wcR1gGR`3BXyXY5-i!JfadugfjJ zP@fB`{|5ow>Ti>u#w&%L#ts%dS)U_%pmbzL#D3r)RlUo4tb{64Z`+L)ZP>cg7pOqc z(;Fz7g&-0P$lOb)W<*R)0Z-6+)na&_*-DE>aEI9*@_kz8j0*5HM~N{6r;xcL;HKpd zh=&$5KkJSWmzFsK-~(i1we3bQrSy;#uh`MF!1R(#~*$uxUb~?lhf7 z;2uG!A}Ejde&=#kV~Aq0{F+pwn}2T@UGK0--rS@3>(QbRDh+W$w)+8PQRiIGCbXNf z?66x{+31r8AH~>}nz|pBSEUlrJxOk7t1d9HtN88kAR+%IM6=4$elxJv_9w?(M@Q*M ze^^Z}e@}X-?}!$6-b0sfM}*{K0=Fh0(s@<@mOZ~C-RT3S5f?*^WSA%+A{|hGz>)?i z=X4`4=Y)d1_xgY|6I&!qGvEXDYIVJIWH~x$#!t_;L8u6flv{4F4LAecx7#EK3dMU8 zBUK^xuP{GEnWr`x?WaMnj8^y<#6}57#-O1$ zzLmKam$9i#O$$`HXdjA^dIf_xXz)8oG@~{#!W&pyF9e$CJ{!NnpZ4?QJI1w}bZfvv zE#l#YBkiBb7$xmBg!SqLpai?8qliu9tdMyT40)*^+Uo-bsBTJ6>JunxE&KL*PriE) zE%5RHN{m=$d6;|;`g;OsY*4!me)};P+k!@qpH1h2 zW|Cikz~LZq;I58?CGXgxZ=3LaZ78D(i*sL%^Fht<)n^`Z8nMR9_wX&@&=IF3oJDSH zx?DaTH2ln=gDL@K?y8elkJqO(@pAdr#9AYVFb1H(U_y$2J(>uEe7~^!njNX{#xNYI zQK%iUuBsR&VjnhzApG)VX_G)tb_3lXx`zS8fNPZl#2*zo%01K}^aLaM3KZ27g$F6b z-5^nDjj8ONIGy>LOs;J2-7OBIci#*?V z%+GN2e({q4xyPWFdGe6JUNSh2pn5Aj_6U2=@Ht#yYx>ax8`PR<^k#XSHRR0y8!y(4 z^H(9MS~;*D+ICEChLU34J}vXvjF3w*a`&ydfMCIP@npt__UVahzXws)pm zFG_tlJMS`5P#uUum4k%go=JFDCl~$W zN>{^xA@~H{j<6u%o<))yPm?>n7o{vH?t5J{MrVIH;htEBwwnZ_D~+9$hSe^d=S0Q0 zd2y-PRBfBeA%Qq|Yhw$UuFM1U0UedZRkHK5R#-qaxb@h5>sdsU@Jw$SH&Ns;iW!rr z)4tjdJen?j>URHHvDVrBNFD!kx%#R`aR5jzKf}?u430VvVR!K0%X%QG5webzRMBsf zGpU?1n75?tW-+>()#b!z;b z&FWdA^wxtBpW#0KxJ!hpPjq++ly`XR?7D^9W&Zz8UE>`;0qH;E0Pk6zN(4Wf-AA?P z_YU8=o`Z8&`)g`!At<*PT^uYpGM){zOyYIKnUP< z`!*m=`eQ*b=K5uIqrL18v)`(|{j-|QY*yvCsy?A_|gvugWY5}&}{eqT&%~H>03$=`jsHazSTEV+0Y-HaL>4V7BrW9J3q%?j-Z^NWj zx_KBAWqhXIt0n&9VoMnDhe;?82u?-R#OlT<9ZJHQlS$=C1k;t_3VNuyMvY4|ji*}u z+3v|S6@F-Ac3!yjYoIBW($p3|C0mnu%Km{eT$(OSHuv>ULs=1ifQytr16brawf+oA zta4S}NB=NdqkAe=l1r0RhO`i%knt+^2f*S0?Kr+REU}2%^IP!TKvV!U#0k#BN!3%Y zzx7-OlMmy0u6j9YaJ16W4n`Y*8W7|rU;&N%Otc$I8z$1kSQMqR1`y~XbSLIgBu~;_ zwV+@$@t%6o1_;cmY(*EBJ&d~ULz+V_{Im+RmkfayPn*AVSIV!^xOg0Sfs9C#+b-Mq zkkd0QO|UjY4gkwRJGfY3Pz$d{KeP(Hx-NnZ6^P^N>m=%yjZEEuIKkj7^sWOcI3Gw> zjqZTU4|#-wgKv6erkC&3JFi6N3e5DSdtN6?HyFg5myk!%WI0$Xf4!O%C4pm_Uq9Nw=oY9L}x12w=C_QRU z3q+>I=EyB0SQ)z_Z2k09x#Fjyghu;-z~f+XK=C<9E}+AHod=oCB5?3L{|7-TMr{|R z^QG2+orGRhS7>g#Fw}nm%q`K^Z3q-G3Cd^=e3flJim_E(=O#w@cU#T zW4rzK@9#vYsT!)T3RXi?Niya0AS7O_o9hI={)3Dly?5x9ic_U^*3NG*9dKWYL8@#; ztj5aESY0#?*AB(k64lkCCm9uZ)OuK5qJs&8p!*L$+|a*jI{OA+=BAv z@mBWTX);Sq+x-S$>VN9%TX$M@$@u76Mv@GOas5}-^GV)hR~)_UyQ zu=NgRQ-&yS^ctkoV`KGtF_yeG8j*S5<)$5uNR>z!&FhU3a#bc3@N^VH{vIbv6d;q# z+zO15tJAG($%E}?2#=J=3{K(zc#Iqs8M}(Y=``XY+9LWOAwrMxk4N zCU|hkvy}h_Or01Pvbsox{gnB%<_(xMXq*DYe(1ThIl*SAPNdz5VQ|WAM=7_1_`{w4 zVv_?f-?FfoB2$-OD%-uS)9H+iOeMl#k{%+?t&?jVdI2MC!jDS2q7=bNV~^$k7&5YC zsZY8~(nrw!`Aj2m<#aVavMbpwJAKiSqP!Xrlg4*q>s7Ao6CkLa6lpu!xfp{3!vi4% ztYfw)X3?2&@`GN?!_>qbgqveG*Q_F~QgiV53Ocr>W)MZx4GL4Cwm9kyJ>Xx{#}Vk! zdW~wXNrdvvr)OynU6nJmLD$L0JITVNw}9Ai?Mvk1u1n2~+)M_dYj1y?@=)HiL)ET2 z`4Bw+XwdBbbRh}wb%c+`iYdbGiYv7(L@k`bIN~vN>n~1ug4GYH68Seq6Re7|I#)Wp z4`;*hz_@1B9 zIe%6jfDPKz`~G;c_LaA- zr1$0EaTPdvlb57ULN=bQbPJ4tPX|m|%PZM!@=|Vgz6IX@CV+sz?#TnY3sM^D!5 zP`61>q*;_EhiZBgyULkv-^ta1XV;v$W5ibo_JySSHnrdc~4ffqtX zJIDGi@ReIX04TcklwKetP31KT=zY0xBKW%Ei9c&PiCEsT^D!Nq*vemeWI2WC&H%mE z5Lwmn2;N@^G?KygowX5yRdw^NTnZv%g#{^mS)%bV?T?Ic){V8iVdIS^vkvz>;LvENS8+%XzN zNW4nuPb>d%8FztQO|tXAmwEAr&YQ*GvT?UEij-j*v$5!9wSPngNXc+6rK*W>zkeMi>`Qlql#t{ z?h8749*0MUx!8tupMd+#YCjm%&FrQaLbh-Z1aex1`?j0qI&soTQWCi~Fxm}?Xp(N) zP|)m<-RVe04o<3uq0`cX1-Fqrc{O}8Bm>9c@~P>TA^n|}7;gQ?dm+-{!|;%u^`4Wi zU-2wNDAYrI$>sH;-j)aUw{LOoXE zYIF=@zY-@K5NA*MfVd!aYsF8pgXta-6*flx}z%Z7k6F&g*+1DRD>bZA>YJ ziP=oqI+v!33F;k?@~xVlJ2djbq~t6G`t`sL_2QXccO@|GoNAQ>wZE98mV}za$_2)v zvg`rBn7!+zf%f~dTg(&am+O=1C?^*pRx~O28*)H+?n^<8!3_@I0D6oJ>iO6h}UN#7FO)&o2HjWk+1}lK|5tTpC;Wj+KJR0cJz`cZbG@#aP;>wIi+7h zS`-G1pqZTmyPm5?bMOU@kEwz&w>+@WiWw|`Oaj2D0N547*zC2|neo^d;5w{7S`}<; znu_Ine$+N{iU4CkoWId$C2dQD-8PwUvg%gqfjZTlxSQ2_;O#f83t3QVyFu%QagvdH z?-IQKJZUlNkQao`dWNQpsL15vhS~9;x4djdlFw*~{JrZ{?2sL7$Yh^K$xy6KP)F8V z*eu5$O#y7T6i`N z-NPzN0hZtb-Fr0M7uP_1VSMe6u^Oh}+i?&w%G*lpSdBnq_MGR4OO~*iFO%%hH&vM= zDY~(KcYoP-PXP7Rl@)%7n1NE0rx#FaDm+fU8+-c?syIo%YI)-;A)F>Q*9GTbULP9P z>hNh&O(m!_79EfwA%K9u>Y(^SWJbMz!dR?H1c^VI<3JcU#VGLetw48u0O+ehc$Hdj z#&bOGf)9+*jBC%LQ?FTbG>d#i0fF-sbXMKBx`m0th~{xjbQ5q|2B{iBpu~v8VEKMp zt%<-hW~R(XYT*c;!4mo!)m$M8#B~|SXAEKPN#dcR*JUUbHN)hsUxfC`ZJ5t!?p@HC zv6@NsNUqM|2*-!|8Q|oa*v8>2fS!qH`CQY@sUz=*^*I`6!~le1#`SEz{>5ps-q602 z8HYNSmR+!DZw^b>Nhh}sy;Y`38rPRm5_Z_vTcv>}-UazET(ynOmI?00Z{*QUU;ehrfzR%`Z?Ju;r93bFHt9m%;N2@()LTx>fblDZAP_;c3$`yy2ezXu< ze1R5?#2u^^zFytkA*9@U1_6@^bVn%a-V?8}QX%aV0UuN({|J~kIXgWu)NU83lX$S} zEkW}zGm?nbBQ1;JDL&TYR4YK~5kQqd)>lQ`3jxT#;y>%|uy9}jR8gF};d{{9clkLg zWyYBKoSSb^CBE@o6qN{6YB@b(hztAy$fY3lhOdG5DrJ?k2{aiV({Yd5en~ETY(_kN zZ{OMGsggEvDMVsQMmqhy*y{t>azO})5hXz`YRu0*bPpmB+(_yN5lQslt)#Xu$yx5r zELsL&&CQ$4BM;!py;d`Exrb5^g%1d2)5Wi*UeBRV{ z@&`;;mRc{%a$NB1IdfC}%VT5y^QvoT3Xt{syiGHmkT>8rA5>?Ra`uS> zeo`!b4-Om^s^4SuTP9_G?TpVKvD>S^wO$K*Ztqwm8GPY-yR9^^uCrYm;D@>#6%dsN z8#H1{IHT}xtZHmTVhX_qFO;U7n^Ih#WD>&Gvf_iqO?5|1*U(LXbnNDRXDq2Rf$2Up z=&y#{Q2|UQ$S!tBl>69h?rRJiaH$f0+1ihG4~qb|Uygx*7( znEy`;bUzcVeA$(Pmf?>nno4;izyLM`nsS=8jvo9da*cOJ-wUE)adff+&(OyX{;}ed z@_v_b(M*96L@u@S*R{g#Paq&+yR756aeV%|_Q)dNFv~f}#l1qCvaW+Oh$rVs#A_i4 z5ykdxS=T~_me+%9KGfXk2cxYh8i2t-jGKV0i1pvj(lWkpi;N?pM$fCLc~j8MvE1rR z=b3dlC)Q?Ey@j+(zZye~Oo%xx>NUM{9T&YQQ=>@StoT~^S2 z?@=HZ3jj4WGWiO>!xf{5FBOfiSvS*lx`_V5V;;u?MorEqTUpI(WA3zimJc2f3E!qfohE2VS?rNo)55=bfWg5_^tuaYZM zzy*JEjxsM~>U+P#<3Y2SEyZGr6h(d^;rPFmgrGmVode`#?+_WF=IS{pm9L(JpFJbWpeRnfxvAhzX4q5 zBPtb!9@;^__CKWUX8M)LB2(TrPv%_rCAuc20ae9CwMSSo@WjWlcoOvq(nPvhb(NOM zNDCs#1jRrO5*SK21yAQAX$ye;U6N%h$17F(>z3Papsi0G7p2&7LYh)%-OMo!L`4s2 zZ(a8X(mdT`2S076<2h%#=~KS%+VCMyKrXUU!^g6L8PvAV0BM+i=@?jDD-hRCn$A@P z&or?8k(GFDI5F?zU~{8TB>yz2emOIMKO=d(&&Kk$3{_FE%ydSey-iff*l7t1y7=Br ziF!X#RZ`K>u9Q$$Y=4>7VPYRFCsdkAy&cbC@M;EMgQUc}RYgzVR0tRYwc^dx`hlMs zO@rXb5fHO%L_sQ!ABz88+6zvm+!B7|+oho2@-m>c;ji2zs+%^pgBY(((zhU45xb91d%=$KbJqnFZ0lClJ;ZGzvX4tFp9zAY@(2W)I5HsDQ zol*ioU_M;ryH9#~6rX%30J?n6Ar7lg<~QJ=!BjElDd8ILHFS+cky}uVgf>IJq z9Q^3Jqg)dFfzTy7jX2O$t1m~Fxcyu0VSO29f$OXL$(?qlEzVr14@k+wO6FVG^GRVeF>l5f9w%6e(mDE$K=xgn}X zfT5L;=t+k89ypo@yOjp3BCsR~1y>{9o#p1Y6pYK|{ljtCL6H^MA_P0mS>OxB9*f!= z^Q?U8H#CSi;s(YTCc}4T8p#_I#m~*dn?8}1H)lrK-EvJ?$FNJF#Pu46!y|S@w?76T z50e^2a+D=ek7C&SA_UANz>$6d6ydeAOuk3O{zHD>@uk;5Mn7L|_>IjCWrKvtCS6WB zU4)?b#pZ|BF=}vjhR6a&nQe?ctZtuV<9EBE^$8h<;d+u-s5SI;YfTNXAZa(PIKP?w`8csR3mIn{th;iv|jI6p5y>w_O+Z1>Q2a zw>%Dm+RcqWrFCRtosenBbE*f^<9qBkv&t*Jw@$`INO2URQVXErWKYcTw&7V zjO)4{^d+=7zKawfpYD8w`A0o(18NCRE!nmsqs@}7ltIqm9gB|=(JuJQx#c82@L!%= zFIpHBqDWhDu7kFaSHYMPJEbuPKqUJnlB_Pprw8?0riJy?$nC4~5Y(R%MsIx)&p13b`dDvs^$)y4rFM5$_oU5S~a3w>N4(L@J5(|0gVB49Gs5*h8tlW+CMdrt}k z5`s;!&(72=-fX*#3;;1_II7XT;kCBewG}$BqZ40!|48Z-Dpv%3wqA)ka{2yq_y?zW z(|ib|gcsNq6`z!$F`I|z<-`P9Eqe2+f7C)OXHQLFygV~(1(iRCKQf3+IRpUL2{P!B9Lx!8vS@D zJ+^s}-2tS$;5@VHK%ncg4otW+IrN~ZZkvMd*bxwdsp>)|5C~hfjd)(gc}qS*M+QpW zq+G7)m!uv?=(tds1w|d2w=AOn9^mu9U|sO;p<|Pvi!H)H|9=Kfn;u?rZ6u{!(Wk2X z>{R~TF~b^K2c2)~AT!({Ww0OBRdQ9fZ@`gB*9rw14zK&&5X{vwE81H{yag>YtLXIj z?SB7lAe2!sD}0AX&#CgsXbP@gFpmFKrYml{425F1eSNb2Q_@h1zC8JpaY>UF!!CIM z#(==7fyV%eVqG(9qbnvu*DJmNblMSgC=j$D?8ozOz3vBTK)#`xom#VSo_~m+oYrkY})k31jPagY{=HM z3!t(`*iaZDT-+Bdgxp6U;)ySqE`QMA^`>b`F(@+K_3$AG1S^{VP7j@{+xqpG%BB6g zP6PpM+DTVRVd*eyU(Ggthd5)4Ev{4D7UjQftn)xwA%-=d@QQ~U>x6i(g*KpNfO8-; zjPe|pvLyH_lfY^7MpWOx{+DNvts7V9HfI_X!&P43>Jgnmg=L9RnxC1)O1aSx^KN-+ zx!WNBeG@IrWJJm{JTiiK?V*pqqyJjFJY9I*tx*@b^JLgAovUnGxA%YzRMCBII?w5f zZCE=LH@@Uca~gAfpayOEW23Of6xNZFy^LmG&N+9Hd3hrQnSBtvk>BN!)MO^wCP$J( z^K`p2=Y5+j5s4lmD8l#IPGnVaezK8tf=GabE)Jy?bCb+gx4XX4v}kDwz^`dGqsTA& zcND0+DCU2{ywqiRlJ9tAWTud5fk9I(K}p{fqhC$KYZeMYV~sRMJ1^k$uC>Z%)_(FA zLID;@x}<0GIqJoXEi=tUq(0vTD$oa4m=zci8YPv1m=kF96r_whx7g0a5Ty}u3Cbp* z>7H`#@v+7IuRSC)6+y+D*-XE@O*>WIbS2!7IOlb|;rNo@8F%SBk`Z~%^QnFJPiZH^ zX4`FzYa9)G4u5&8+F$BWGytoc#*B!zifZvhcg@5%%rz4v8!p?>z2*%X_ND@e+`wL@ zdx-^@E%iR*!z0$jMk9+=?)h45(L@boO8O7{z1e>POe|la5sA~oc*z|@`4JwCbxa0O26WfzrWpDZZ>4>}iZ*An|c+}I1a6{p_Y_Shlafq-k zj@D@(Tp0E4?M`4h&dZ`MtpW$PCpiDUXV*=vi5JT)f5^T13$eN_4J zGXUOujT`sx+b%f|{_;^deuI?4h(o~t9nKq2_e#8;dc2;unh?BhZ>Fs?d=CjdER;a$ zKPMyEM`^LAuoXw2LowYwGJ-gpU}{5=54C)_nTPw8(6WC~q&gQ>I~*>7!7gXO zq&;+#gRCZ%H4=X?6q3ROBKzG!YUMe*>yNz;p5!!tTv=aQ@y5?<$^y@||3_u2UgYZUhu{baPPCY$XQfqiwn_#fjuifgnuyp7iD zC$=EkmKQ#>@oLsvccXStYxWV+&oc_!lchM5`qzy`J1hS z&Y~HU=70MKaZH|Cpdk1x1aRBq0 zG61G4zU}8F(m16xfywBnm6lYHQM*-}z=?M`d-z!rKnnNkH=bSHT{5DnqsKs4-Ie)K zfY+ItR4I4yD=Ul9@)F{9wiK=ZE(3LffFa5fjFfdKqhv`DYf@ti_V{bz~vCA z0A2ezhL)TCf9w=a(HJy(&Vkopd&^luaMiMxp#AHM-!ncD9}_QsgWU_gJ)i|5H}j&o=?kV2@DD&b++66a+ zF}j^=VL92!#4dK`T_Qb1R){0p^=K7Pgy)i%10ipkO$aMhq!(l8#WlI`wgm4y&}!so zY2YS?EYA*l;}~RG`M+QU`qQ;0d2u^!@pIjVE)l@8qGELCFt*3aGoCL#SGj*mf9B%p zNiKZiZuvJhMZ%{1MBgT-xne0ED!UnP`)6q)7(7gK(yWMxWQ{}SuWRb@UuPj+NmPuM zUi|&GRcXgAN+gNc@v)^s1O!$x`=efa-CQ4D#|tA$FX!R-k^QXMD_E)#>hN}ez~O-M zMbw_wZ0%G9aA>wjY&yAbgy_)cb#7RL!) zYMiUael}o>rZ29CVF;7qYDgXrJn6p<2o}@GzqJK20f`Qd__HYTDDNq=^5CH*X1 z-_>{uH#}uJhIrD=?N(<%%!WLY?y26W1V>>P9X!@Gzgwt8ORR=hRJLJoT+O2ObRf~) zXxyf<&KvXRhpTjSWdyu{*p?klF#lWKh0L(Ts`Twq!x~t+2>Ah@h7bQ{PTs(EW%S8& zxCG$4Ol^V1b%XQ~jRG|0I>YDN?mkHb`!aarO=xW?_h}yMAxe^NOyw$ofWYAZoF8P9 z_i2Fc{{Z1Yz%|R+-;7=0iW5#GM@CCTn+RY}E=L~=3+ca8FWtY~OIs(+l*&LPsf)e+ zeKS*SpyAOEamxG>e_-0aj>wl39OR&>K{^stGJw9@h?V&*Y>VE*-wYfaO~3tr@}o*v zB+5XjwETn5Hk@)f#UCo{0~;yLEpXWv`WUArUONj2yx)z5C=wDrq6iWP0!HOvm1x_B z+1QfRYJ&?6wyR~or}RG0$*p=`;EBa!_?8_wXs^(qYL#gV%ae3?f>-i2Z6Y)l2g;hm z1a~ndtQc-{5p%d5NNApzwcas8jFWUYycXzZ;P}-XoPT-yY=EFn&vc1D+Pl@j>HbK^ z?T;N^3f`>KW!nvbkGrgjjyeg!)rJorh`Rk4bMjeF^RHgj0oQ<&dLNR=S+c(66 z8wSDu6dH+Z+rwPF&MuDZsWh`{_FMt&T%#WAH&EqXXpvoJt#Yj8^wVY9nVh*>(0mKj za{OU0qFO!#VB%Q=r?^BihHDVZ|zu$f(x z3B3WxsWwrfW3?%#IqJ$M{WP~O(c!^|r^LRY^A(>0~PDK$|a%&qldk18&z z{ui1B19K}VXq^WD^V2-lFdeqX4;O4SN2{W+{f$Z99zO2i3IjYw{1Gm`b>+UDbenpZ zcUAkvdR5c3e!ZPEO>tA)*(dT{xy=JS@xqk)S~SW2s1A{-#qvn56BG5GK${s{W4`?o zn#k2K9_V*B_at`l{IaW>A4%XtpN-qml-RYV<(WJn2NVIaAnqvZd&JfgoX|stqg93O zd`SirQcLn$x*@(yF;s%5%n~UZ?pdxMOW*Uk|!Uv0?&MqB6C4peHE<>5^!v|yFxgw7VfvgnO$oe;u5-5?Ee&QD}`6*Ab)4S|0-^T_r?1fuO?SXNVxmf zaj3FNUv|AEjD6qFedY7Tf13%mYIEn=kgSEb&u#7xm^6&>rcbRW0D=CM4|!FW>eIGR z7H;L=%kMzb{7E>bnjn)sTGgu|Vg$lBkM6uQ`tmmF$=Sy${K@>RQZ!6o7R8o;U{axW z(f1SeyMs=BR`2XfSL64`lAnh<6-)W2+<3gO2mR52z~cbSWwrx9t4k~ZaetBX>O2na za2HI5S34~eA+p)<&4B=fQ9i$pQ;z z2a=2858-z~T^GB>yy2p3cg7g-l(dY=1MxPawOo`7CtOUWpL1Z*x5{l9_bKN4Bx zSyx~t6ZqCnfU^+MN$rB%Y(}jqaVB^3X6za~4=06Pxd>}%#a4%go>uZHiuu$36(Or> zYOA+44w}mL20kj`2+%SYLv5gI3v?qC)cPttVp;`dpUCKhBb0lVvC$QbQYe+jiSd^8 zD3;%WYyd&Pm_?)HEQDLWShb=$_dOksUO#h3S|KY2XGxqB&3MNS8_zCrCfE9oB07&FI!hPS}=*|a0b z`K~U$F3^TLI!+(iU)%{*#BB9WbsLA*n{jsu>j$Hb9fK}-T$;ZLOo_4rHKIX5M5#k> zj~%pWgRtYe=zV{e#lsyfC)=l^@j54AaN8Zv;g%qu-NET5xA5;=GZvs_gvk3g1F06zYXB|>5q7L6HQ4Rwt)HcR&(aLz9@b+^cM><0P7!hW zhYtGNt0b%>;`uf+_;~qiq=FrFoDqV_Xkc_-2xYJTO2k+}^(kB9-0xoYVtZz}D$s&_ zxGia<;E(5g>voA>x=W}QU8U4p%4>m}u_|Lx6mr8qjzjTy)&A2;j#mBSDJv#>sgIJ6 zp2!P!NnA`9+p%f}J!?ij;AsR%GupjN z<;3Uq&BWx|j}2yg`y+VbbAIN=gIi6VA&$zAc}aM04hIZxGx5GV+NSQWBB?l?9S%?+ zztfy81+YwN)&OBK-hj{#R$;42Q&36fCwvCmLM0&8fF@7KG_qI<{KwhG8(_34zY2T* zYDbS?hzyDdJvUKLwbm6%pH!`jB*o$a^C+{({s-vRFcbaLiHh$38k;b6Ubbvwb0@!u zY7_3G(qpPX#4UIyC-jrN`|i!fk+grR_&;tEkVmC-24-+Mpue+@wq%%URG(fh z7ay5SZi_fW%$%Ia!FA?^xOE%LH+WXLd%c|vd*M~WV=xcE=er8{h_hnzIWmf;eu_TGD8;wOF$1rkiSm)?=U*SezOxZkwW|r%|`Z$VzuT*`(8Wv@#(_enYAo; zq^6qlCtRzymgi}e>!NOWF|W?l8x7C3~tG!D=bID>W$ASr$w<&8w{JENc0P z@3uSa*x;yHV?xN<5nIESOaEWGn7~MR*_AzhQ(*>`{<5$H!anaP=m90KI_gLYiSP)Uq6sh9ht{h;Y>V7_NR#-MFcy6uZ>$ua@QSIRa+(qb9BckdPrq;SW{Zd^x~ zX!GxP+hJi?26-y1V&}i*NmV=|xW=bqR0AUU_te^^q2kwkBYl}WdN~TGSmV_?-L5sI zSnoT)8{xCN7g*hFfHU-LvXk_r%+#l)w8^GwS~6%`61O5=Eh%j;yJ`9+Jv`G))CQpk zkf2T4)kkg?m?~sClR|+m&+hRc^PucPU4jt&MBYwPfWYr+fZp0D)|3}bk6WhpQ_Qnt zC-B3SdYOzwd7(_w>>VxVC$U$&5BA>arQ4AP=SkY^$|P1g`7YvJPTa&jR{sH#=7n2h z4AmJ&Rs^IrvP19^US2rnh;i+Eas9Ua2@k7UO}Yj4AF7K{j#NxG}tHEuF1XHtLvP59$;r6#BHjI{jfCC^hNi|oLf2@{s>?2|h=5$;J6A&q8Np{c_P|8SvP(e~-@nB6ORx(^MO^n` zgOE%<%sie`*#qKFeGIVO33^~zxPsAe(R^byC0@$~ytp=LKMTLp+<@5~ z?hf4`>Hk2DNU%eI8NM8%S;JE-U?aSNfGm;>C?u*>Q>mAWMV=Rw8PWNj=*qx>bkDz zOb&r8;zjm%6!WO_bWQBi$QYu0cg@X|SYeA{9_xV(=ddi!9 zrRQafpASs|(fRFlfK~l`GA%^FPrR)|>zr=4O@)gyB5im^q&C#Gk*r!t=u)%qc`u}K zbbY#Gd1ceh5xBCQHR;YD2HIiQZu!k4o}Py(YRRFMU4RJqSb}XFWC0xKj-d@ zOKc^+C?QLHVkfYcb(PcRQY<=Y{M1T<^tH`;b@*aKLl1S!eU7*KKVhV;6nTh7ta$BT zsoXtGPE8e52+GO#NSCJxk2O>=(bDEN%nR1rshH-f#Lxb++{IlAD=sf{jR#XZwaG4! zRk}?Afu+Kgo)d-ou0AcWR`ET?s!Y74F~9?!<#ecr_Mc~B4#kctH;qE%l4%kHHkubU z^>vVhcV)HZM>E|zx0Q1|_O!xF0;;7ngjxG*Wb6S6?^IK`VDmc+%aETg@qZzg|{6)pt`r5e}`E6q*sc^LZ zQU$=-@YU(kCE4YaU0|H9lFd{+uEe8$dBXAObqa)S7iSsq$t z&6)w_NA}d2{}|gZ(10kbq3mi7N@n}0CeHw0{XX`(ssxg2CILxS;f@z{tTA^`xj zZXWp~`-788ySGSK;6(e%yYj8m@y_!i1cZ|GoW8~xfSziF^}s6|U_46jQX_hXS-&m? zcXRa!QvGC2?1)DySKZkXu>-2c1lpNB3by9c##i1q&s5C|!Zy5nW7Jq26a*028nf_16C86UWoMHG2=-agg)I2rN?Ok%6kM4EW3ke?t zowdL37aLA)od!rGVR2Ae&Q|8X#)q_*5s{93P8qF*lW{_D1Xrd}WCiskw;SIsQQvb~ z&+<35q2&?|ESov?;q5-8CY>??iNM1;#8WSten{yCHTD5t6fbNb%1OY!YX#!paZw?h zcyr0vxdSnNNObr2Hv6N0z|~T)unbZ0D}%hN_xDG#Sdn zPR-r$YE!l8mZ+21kTBDqpf-PA2lhZ%r8nX^GfWiQ68ACzDk(y)`tU{mbQ1aN7Cx^viv$OLjQj-Xm(a%U;e@()W){Bv(cjif4lkn15HqQ97Tkn04CuXmv4KP#d(-)i;zGktM&RKtjdQ85pYc z)3$F0EPjg%k@yMdakjdgy~g`j-RbO*$|2O^U zZk+Kq42$e$hY_qoB@(=IWuE{_~SBc5QO!dx!4BjNys zJZJnbs#x}gs*5f*5k8-ymd4Ij4kG~iUK2n=z1pBFnl!+p0N;n!0$|7KtNp}>ROjGkMe%K5T~r!D#MqUpq)#-V!w*@qi4yb`uCFJQxNVkotr-GI_et&OH@%t~1jV6{*3mE2N!cG~oKp z>J$7pj2ilKyIre7ORdDE7TIHFE*EwW;PeL`=$pQCV#YNX!0ZVY_pGAR*3iS{V{J%6 zYK4-yzL%@P#*I&kgtr}hc(bcx&(jK-;#@=^a`HJYQT4IA zqN6ygIpf-Bi(DO_(NuH}0QA4(txmA5XJHOP^>Svh#(I`MgqFEft7_GAxP7p{jH_I? zX!z$p5u&kaSnOjtl&!Q1hwp%U`LQzG`}`#LSRW^n$0QtL5+bFKPsF;}gZ0yb*6L|( z(`cwL1@j&mek)>7kQWTw4~z|!IYw8=HheGj(rQ4)br?20=@Ga5TUYQ0N+B_L+#>NoR%UU&b(E zYlDJq2o7W+2yvOsyR9hQq-^3V+F1vOHb5Y}B+&F}GfgOi!cml-93jDjCE6lKIUB}k zcW8TZdaCqL3MKUAVG-kofHd81mj|StKMZN;35q0}hu1btsO(-u!AB#rFLwV+3(Z7+ zy~72IR?+xbwC|7VO&6w}SxO#Kho{F0(zmZ_A0Cd3U!xo?kk&L~$Fa&0&T*53)x7l& zGR#xxZJ_7vF{;hXKC_4A;q%tQ4tobvN_C#&D`GRKVdcQe{S#@{FIRUy{9ugR)u8Ls zBPB~qWWhLInOmsl+3pMTS$$daT=8?LIAgf(T^m2^U1MI~xIWzenH~2Lh*N|u5Obb% zBR$OGGAY_#RB)(79HwvO)LjeU%nFm5!hOF(*g}Bj)B9X+O@FUrxVyY8N4Wszyt++i83>tRQ@2-GxTGDwzm2P0uVET{i>X5__TY{ zB0h3jmU+d=)w#wYPwNB#DI<`8VPuq0vr&RK7Fm&R@+}30zkD(bBq0Y(SdS#2pGjZw z6}%GzMzGS8uNURXyH`k!Dd>GV^r=a@+=l|B1^d}ckQ&YmC3!VoZ!Sx{3&tR3trs;n zO^1M8AB8+CgGAM+Pd%9bQ}Q^SsZIygkuD}3i$1n&bi)hTDrWHzWQ^o;5b!t{Nw8S< zB-d3RWU_wwC##@|cyP|0?@UB@K9xsp5_~{}%|vs~FHLoJCG>h81vZ-Bta@PSqU*IF zl~Fr$MG}FpkF50F+cO)m1~>1+UBt#%1Tc=>MkREu*gA0395Eu37->70SWyKIqm&QU z2P(?R08`9=g^1^T`2tw<=YX247|{5GXBI~%$AOeE zONQbcO;wdsvNgaXb;R7eWXcfYQfSe&=3_f&&By3Z7h8cRn8Dx#E8E%?3i4Vkb}VY$ zB1!nnG5b?X_Q2jk@y{P9xb9k+>$v^hFA3GVQWN)@04UDQA{FZzr&Bx9T=#%~A81_z zccrYer#BBNGmT5=-BKAB1XUfrg3fh-z~f*}@bl2K=JuZLYHJ|(iQPu$cjw;o-S0QB z7O;uw7z3n!J`|RxjDTx66l*QRaH(lnRgwm0)g`O+?Ea4LrY zHVwbw18;N=vA@=gZ38OYgjVX8-Ak!Mp3#(;Gl_%817qBJoN+cP4{=$ecl zL5d`ZgQhHL9LZ-f-l;k!eShH@Bf5EPHTr*F>!ALIM8h;G&1H*wEO0g_yP4x*YU@_; zd^DKae#QFKFLnl&tQh_egp87x`?&OTc3)IH(%ZhbP9K4YfROBU08~6UQm*mXM6^^j zx@mV{na!ropX9j7n0*!ueh2zb==Bjw8CzjhAe1sds>_#4E`td`Wl^yW?sd36!h&bTZa;SH^79`tD+8eltPbp z)#t6x-#Kk);-Zm@kO|PSXq%v7Q*E9MK%p2PtFXszNc^Vz9N_&tLvlg573xrvYMB2F zn5+FXm8~|bxw8p6hDM9EgY#H9!uj^WIs1qUzB&+rf=<5oNC~37A@(xsT5WdW?<%;k zmP9CK)U);{ZXJAGJWO=K%V{dXe$V=hHWu2c`qhi}vat!k{(f*+i23kuJ zuB^@}kq1Aq;V8dKp_m4ieaE1WBRPM}gYW1g4Z)2pd4w*hj|=WIy{ln70^y!mf)2wb z-X>oeITWTp$h{qsC4drxSf1xRx{QpG{SzKAxFV12BbT#nA!l@`*O50sJMpp?))@vz zA2uc1xd4);@9llkz-lhe+#5&+QV^sRh+S+NLt6M=&U@`s!Csw6zvY)G;r5I>oJ?RK zP!b~FuMPF^(wKe?z1+LBAXpv*B{w5J*2Y(!$1?7T^~7!l7=VDl?K}?B0QP79a|q(G z>M$duTK|bY*-H6t-*;*RzzvJ}@ELbKgG~K-F=b}1_-uf%HAPCxyehJ*d`6_=$Gef6 zLgnoC*23^%@ST#X-P7h|^Qbx2upPMECd-=f1qHDWRej1BQ7StZW@>$II}kie7FJc- zhW8JcCwm-ZdDG>bA~igm-pvKXv`fy8bnSXG5QWuJK{A8;ME8?bXVR0X&f1>=vAk}w&L#!MW7Q& z8PX{*XJfMKXGft$C^AYvtd=8vZ-!1D)rL>@#jIwy1o;=farR&LNp(^z%GH1jX^}G> zM>6iPE8bcvEvCOA0Hie2*x_fE63v$#fl8&YXtddoG{F+P9(EpJPsI+b8RX$!ow%QB zVI`Efj+0)2iUjdcHh9%P&Mn=qSVLvOOPxpz&U7K9hsn`*iuEGQaYNpKY&W<285Lp# z7rE4b0mlaW1XhboppZzT1MMW0)#SP3SiJ+clI_uXnh)*| z@neVp(bzMc2l(%^GQ#wSQZFBZg;l?#}?3|9OX zlF5k$fS^dn=wXw<>b$P@u>WFATd4Apikdv!hrpYgW0=8wyZ|=0(Qq;l_T`t33*gL} zhn>0e;xK*lzVoRdJ6bK{EraZZ(iiH6D15^cWC#LHOcqwVzdR6l@=C1_!gGTt9=-VR z8|{!CFdMpDAvp#&#i!)Cw}wbZy2u>}K`eI9k|DfZ$R!qmJg; zxbrOuv~kUu_?Xsag56a=;x3zR|7}Be7>!0jMuZIXv5U%abU1YlWYbPnflg&v={F2E zJcE!Fp7G{z43oob@MGX+3mG^a>jf^@Au@5$Riujz=$JDWnOPpBA>@VxmMnr`@pS0Z8vPv zt9VgY^HRr380A}xJ^KfLax^xH*kV8klGwAaY=(s_Gxzcyh#$BtnHux(LAGj9kTbFD z_u6u10npD>8nX9xUMCm#=(-0;cZe7#CM=bR!1%$_h#y86fPld5EDq8Dd4DER_7_+| zfVAdcT4D7r9ICK|%LL&`8-d2dIc zXLe_tD8T)D6%KpvVe_(i*)Sc{0KSW=YcDL?0`Gx}HAe(B z{$)%3@SzM%+Min5u zSjqK=1)Fx(jB2Ln2gWY<+vMtM+1(_Ns(O#Gva8xWV0A=A{|hhuoD%ntAC62)1PnUW zv+({$o>o>dq}Niw&?hkouN7i7kj`g6($yCPt|j7u43Qq5WJxLV*&%FdSpAVh>}0(^ z8&=TKj!*cP>Zh7?0BDv+qt9rB;J@5L=8ET=?J;q$7u5qEn>e~4%Zoz|{XPo@6*jR> zsDHQge>9DmD#g|RBhIq$FgK*<&BwSgDmpm!c{qx5OD?(Fh|H10Qv`|EoUQ*`BHF!Q zKGEh;qoY9d1FLgpre=J-RSf4cJ)MuK6j93nW|DxDD`Lgfb8|eEXnM@tIjlA5p(sL$ zWtm6%#e!vXekp1zMc@9RK`0D~G;?I5M}Vbb5Z>tLjCl8e!0zGz_*Gq5!0+1>Yp>V8 zD?x7fLyT%I+!owX&i)+x$A7EG&K{7l70_~I02SJCIk9qL)@wu-soC@OJThxRy1e70 zh=O#dxpy|WI?Yw}&is83iggJ&Y)M=%g{HOmo(Yf^Z&GNq1~9bz6OXL747m#CRG@ca zfVv>~IQm7J z1JqM>8YOud@`y-D%;EIF6hrPgIf4ORgxkLC@kymBMZhT!F_i*(jHf)cgxni6Bhw%l z$R7;IneOJQ89hMp3|s~|x+irl=$drKzp+Hr{-Z$iv>fb@ED)?fvC;bN2MLm#49HDq z+2d<}R7@HO_=FA9#m&)}m+0F>GJnQ?r+U60U;iph)B|Q0;hO>k6h5teU`@OMx1s$tK>oXSRtHk(- z%RzTUPE4db_2^~S61oL_C;v4**cn#S$IxsPAxIv&Q@5XRR`&p5nM5hY&x2F<3RdJM z>!4=wpZD|3+bZ6!i>bbVPt392H`e*=tkC`CwnOpSwfbFNL9n*}Q)Za~*5V1zv6rkV z9(XQY|6Djd>2vH$N@iJe7{8?@IzX9k5z5z(FXygob+dRyPDkhZs4W!i&aX@dqlhTpBv8Q~zQ$N-y0*Di8;Ep5UG}u3~e}AMSR{+sWE7Ijf!s zc<^#+y&MW?4)V2S6%X=_V+UdTYYp^Uz4yZ@=S0%jr^EKe$e}LisEfjq#4&8F)iS zA>(_I4>mjkbN>@VZt?~uQ-aPx=7FsUT)wohd9lRES?LNGhzKIO)6=@8I?!|T)LIAR z{TOW^;4BJilHj>HveZuebiHvA30^F*?N?v(t$5y1#j6aC3_)@l@jySZnpzGGisn7Z z-E?C6K=ZpT$gaFZ-2g*CyuVF8#@X`T)dNzX5~KeU4~I4+qefe%DgEiVy0YLN0vDjvXn?@)XTajXt&lUd$sWDX&j)@+I7_Hsm(WRg@~y%0pJAx|^6ElS zTBcS_^41AP{F=pHrDwS3!F2U2efJVZh7}xgu0==ZW{m;{hZl!_^LN zPaQev;^zrRBXJ&kX-rmy?BU3j|CGD$w@HY46o+&Cq2mWcH=yh`~-eRfhnL$o*y&}`>6 zyr8?yN7X?~q=+z6+yD+2RA|0=DkEgRLcXm{C*RcZ48Iq`3S^)JZ zMGd)$oSk{2mw#h&*^#*o1lqSPZ0XSYL(dsXC^vKCsVOPsN^PMk8ddkX`dJbo5Ad^n z|0SFVz*mLQ)i)AZNEAG_I zKFV(z;{FQz@kt}CJnJjTRtGgj^I;W$(1x5fxZey#gY`tpBhF}_Gn@yEbNLy|AGwhcZjT4mB;h2FCAacvJMf|NIn%7#@rBV!Wlol??Z3}0F#b7F zbtMs0R`vpjy~odw)gwp1feSCKUlG=j+HJMml)REN5XFp1f+Up0_ z5zRfx{T1#-2AqUTh~(aK8#xhld`Z?eKERbhH%u~5Xkj)_60}kJawF`9*otekJ^d+@ zMY^=SfZ zQj45uPCaiIKp;vP>v2l87nk*zM1I~Kkjd8z6m9|PU;|Uqv@1+upd^6`5BZDfvBZ3w zzu2=WVo|gdUqqkt#KPYzWOKV8f7JLLanRf7BB-5LZYF!8(%k;H*c*OdT&!R`C6$Ib zsYPGurGDAF+K-hWa0*v8ogBk3*#;W@OKqjTCTBspq@KlDaBpG9qF(^QS+<^5{@pTw z`LBl*aEdzwt3Po*bnO=Oj z#6DL|Y|wRh#pRFsT#TAV8cQluk?N*T@Bk-tX|fOy#*NrJkbNjC3PURz!#D1BR9VBl zxXwxQNNyaDA0&s^`GUU9&`AA1o~Q1%(3{6Nf6TLL8T(iZAd0PnjZZqhas+Z+89!cV8r+m~)~B1Zt^n(J!)!JJ++hWI zOXN>2sL3b9O?!16yCr5?<6Sx3@*XZwk&XwFs}%8BmGo6;jor)FT^Xhgv4f+2y?=et zc@yerU`to%^mK&wpoJl9(Mw}%AvLw*azAU#GA;&h$?T6yrGfrmMyD!4uDyb8w^80> z@@;JL=)2>4aG_7x&}2>_aJ(f$D+I@11s-=Y4e zTaaj+1Rtsy#VJJ=HraO>QrwXf+43AzJ5DbEbrRzT^Xe%bi>XG?AkeN<>=D-iQtjGy zQ2kPW3a!v!Zu@#*l|>A(BbU_4_EY#$P}8Ti0zmit6S2S(%!$aYD#6xe@&c>Q043x_ zt+Z3QDtYyic3+r2vC2t@r^I6j&_jno%*hmdfG?U#mZ!qc~WyJPjQ zP&bB(vEy$!`uU5G;=NzgtdJAs9mVKLx*6l&9V~0Sjmg$c9CpzuL7=EKp>!P{C}t5Z zm^Il{vXT~G`q86?P5{?Hp{mq^0w5VEY&a=x(_V%cKqu_0C{%#p&xfPeg(ZKOy#1iA znB$J|sn22_|A9Q6gl%$H|8^Uwu06Jm`M%CWQ9$@_Q&Z#g9|LAKl9n@@`u;hJZH8qA zj4&_rJeEJ3yyQ0mK@S#PxQ5rj_8vfS1G4x8Wc#%iQDCWNID0rRUW|P3YcRDxU_*pj z2A8MwQq>&VfZ-P!y=U2%&kMtq`h9xy9F1A^Qd?n$%2z9Wz>t+2{ZiE7>yL1D9olnvkUugQIExgR9pA77juA9H!0J`+uPX_O$kD zf`6^k;%aF3jzrQdgYVQ}$n7a8t?-~vM9a9C5>->gt9{|udk_XvpB?2A{12%(k8KG|vw zw318)=p_cq`1b?e*esAjIO5J?fth#$O~7UlWMW_TIfoBgqfQ|~(w8JI@109v-6AdB_agC+7T`xdsd__WRKqB(W|%7mVlwgar3 z{2sR?XR`$Pwe}mD(euc-XuwS2b7%=#1UcP}g2YY5K-1Se4V#{Lhgmc97s$H&>&j2Z z^b0HC=Jzx87`MXSus+H*3!6QP{2U}CM6O&GavQU%4p$S3Oz^!+h5cg{$g0+SWO8G} zFMIWx5qm5mYC63cNlqjj&;uD`b-IC|t9{0XabG;$;xY4Y%1}cKv=-n0Mt`_Bst!;8 z7H=d(Og=~8{3YI}DgljHxK`1UU#iUwJ2aWrtj&+zoN3vuhDLa}Tml6~Z;f9tt7uT{ zmYbRT&`-?ep!?`p#k}Ow6Si=dRJkd=S=0@U)<}kbH7*-TdXdSrLK%OxmD<(+2lTLo zl!N~X6u}NAe@0qK*Ya)d5O--P`9&fn^d{%J#6h_5{R$d+rO^==2C}QTM4A!ZtP)do zFK>AdlOpAkV3i)Jdp|wY(VU#BHz*vKIE`_AR2LI8qrJ{I17`$=_q0LLoz&(KGR@eE zZuE2e3nyy6%}wYiPV_!4b%@gg)T;W&%vJ^pFh8k?O?&#~3d98^Q>2{Q9(g~q)+!z7 zN(GQMZW?p}(56u86b&@*i+Jm{fKQZ$BN}M&fZ*rappNDwC_)jM&32ZJex^}X5+I9! z!0&Xx?{5I$z7{o;^w^t>_B+SD6e7yrYmUSR`SG;vWO@=1 z>Z6o@Gs-<}5*B0p93T$`(4aT!DEFl>PMES-)gMAix9+T5d9nDo+rl{aTR{v($I*n9 zVDjJ1kK;hD0=dGg!q)uE-8Shfd~PwvNng^=uapHX%w?FZu;uuP1Y>zqiv;b_->??U zowkLtUBX*86jr-k<6E={B=e0ww4RJpQQGtYf`Wg0Rp~k}T*%*a|4{T&<@B+L@{Ahr zniuLyJ^|zu_bm$wFU1!!VjhqYau!Ubrp<^9KZtTI$ug+LP~P(z4Wd_b7Fz}gyzKS( zMF4n0vFOLxwWknMtp%9{&WHpc&v`)gtj4J!MLk$`Wkppy3|ZVs z6_3n@K1-P_6zh91&w1t~u0@t9^cp&pshwz!y{l>N!GrHO3G|^p{E~8mwCW=B@3tK( zS*BbLvjWbSqY4n4VODi*oSu}*wT@)O9-2;QXg58wZz|)xHyX_HrXG6bcrVQ65 zHr3y#;TZ~V0PoaygnHY@GxZ(Z@p=}An~=Yz0&MKq0g)GokwHY#ryU(l9HH}d`MOXS zW)i}}0<^#fSxD{b8?fBq(_LGbvtN9k^aE)csqe&q_)TfS8t0q7dDDm?tiib<_3P|> zl5#hxL%xL4zaOr|%fG(0;#RdaKQ5bj68c6ig=#_z)x7sQ@TCk0 zT|Hy+e4gXXEpZHKST$wQLbS1y>Z8@pEZ6BTmrnSLGQ_hJ#htDkAcTk10n935$ZzKx zipbWE(si;V(ACa^#-n(akE8mLy0xbcoDX&B{aJg?VJDc;yokNHrIa6{@3eOb*Dil( zsD+gwaN0W3GaQaUT~evlf!oV}AAz^&b#TasrjGv^v(wx+1YF}#@%B~TpjRH1Q!euvJ|_uk}xGZN*~gjhNR zl&+3W8^?mPmQLjJN+w|4AoL?*%nt#V3VUgEN@T`AtK?7;g3);FfnLm8fD7_M(gx1c zpbt@FmuKhr9*Gae62B8Orv-;GdeT`I1Py265&JewgJ+e6{hnQttS#0mY7$b+Z8cHvl6N_*#JN)bB~>~O;cUFE&SYWu5yneX0a8=e7F$~Yp2^N=&+ku z66uAz>qw40G-b8C%sqVW5W<~&c-#g81$C#e0{6Hg(ao;VQ4nx%33+V!p);R`xUoM5 z{VpX1Rw0?R50W&#zBOM&gz@Wme0R}W+I#2|WN2_{AnI$pW6iIFKv&$$Go z?&;OTLHSs~bi*<9E#uk^;_T%l^(8?(rPVGLy+G8EcXh1u&g%R?VD^zG`2faV z0Elk2naFOl$h||@;%m#Um8{xI%eT#6VCrbmVKLJZui_GNgnuf>ul9pjdcA-@UZdia z`qo0Q{L~<|NZwptOD4T9JDHa=++{W>y$#Y|d58aJ!e$5jU7VDd*PfA#AgG8vYJnJ9 zQ5)1`l8Kfqg^Hh5ZP~$x@bErHq9wS%8VlyeOJ_`ge;bleHQ-vnwjCb=3%WV^5wRn=S67I=e#_Ly;9%$=z z-dvtoF+t_5!)05WO*5KxuzF^s@8+_(TJsz{|0pkiAMO@FC+X3e?NsR3{YGF;+SX{g zBXlDyAgKzskGa5H`tH68iZXvpHs`?#EYPu6X+{jdTdY%~Rj61te zVHgkI`D5tC(>N{D;=B5O!c$*^vku zMS?HvVdTZ`)hzb%$tor>4T^Gm`IL+5uc=4sqA@1uj@R}}Bnl%d+{V&Q%eBBe1tGY# z3`o0N*4NM7RFjQ`{f)Kir*Fz+^49#zjqJ_wNMlvZD!AEv_7JTLYM6}<^EMB;?RW7Q z0`C-{bmdxyp01FcZNYeMa-9*@Fo+Nu{o4Zi_x4ElKVJ1QabN=qRfA}!XXNpX<9y@o zinKx*!sS?Uz@!X>+BaUC8}#Oi(7>!`tmr<$`>uypuc6}}5HjN;Gtp&2nE|*BN%a&0 z%)%q0gSwoWJy+q%-(Re%8ao$>OOV&n1(U+VP;npn<@?TMQ-DUfLkP@Al!xz z5m^Gjwmt2;*c}vo)n$&He0rZ};t&l#5pUTV(O(Z{%JDoM70bqofy5p+PSQabwEFcj zdcXxk5RJGgY8fSVK-m{Ct}CI&=HD{~?2J>75sf1yfSFpIYu1W`#RFa{#p@{7O8zwGMBq*9y23TxJ8}8);9?I>G0m z6C(KoMpkfA1IJy)kHyA+u`L;}lc(C(D{Ob;<4S*=R?Fj9ho%FgfxTPC;c#^eXfjwbYq#LGaH@s2`2i2 zhR)K-u8>Ke{9?ToI}cHv=q`06=yM;cxq@jww^~qu@SQk90%9M8AUuh-DL0`h_tLMH zQEm-&g?g>i*9B;A;l63{;piI9S5VuY1)k!>_Bj6qc+#x9Bd-a;o(Go5r>(+jsozoo z%g6#njd>dS{+Bclx~SXzqfG15auoC*h3!|`r2%>f!@3b5XInYsK47d|M%FPdkr|u! z^QMJ2HX9OZzaZHV=|>AbNE7G2x7NXeA>rivEb7UU#e?<=Ak?{p%QK;Fs&Y)kAu%;q zESj;n4D`d?P(6=lR0C0_OMxzOQG>hh^jWjFZI!R@{$-O~3fYg${R4WXDOU3NDiDwp z4F@2%p+#X9R&cBJ1tC~v&@y!=NC%GNUankOh#;gF7J4?R*@WF5=Q+CkdnP_~w$G3X zQ)xzEwO!(Rw%I~z`@Jm*EN~dM^j$ZKU{~@)-EWr80 zl476QrQvus+U&S2?tTC>N#-qdNRIjJ0g-t<^TWwuAVDsY=xTO%u0uT11|RH_55=|U z8a?0du6J8*6S<+ZCwTCE-#1k+u6c$4M@oyYm)iC3z2JmSl8H#NNOB9()5E-ynMQA$ zOC_Uu)F>2G>)>~+eP2RHzr#kmlrW!?zas&$p}7qaOX5X>WBc)fS9Bv0gWZXZ%S3jD$w(5c5qE#d&YpAJa@gT%>rCI zwp7$XAu?2S&VQ6v1v zvi9`YpbH#6!%dB#TSvr4yC`k@m$yOFilrKObvZ{0H*Rk|?$9k*w?d8If6*o$p`-r~ z0KIb&#l!*RCD$uVsP)kgk!*F#B4AS)t?<%tyVeo!xCk3QHF8+0`?l_weD+%hWsa z(7kxeaGgh-oHU#@n0yfXPkHrIyH8)OglFQBXx|HgXp^VDJFWuhw6Rmr+45J+=8FTv zg15VhYytkrFV3*^VwE#6d6owC zlZ-5+-%r2x$5`$=DNW5q+b|(dw0wBZZhAeuq7HZ5=01wVecNRAt+mci$IfIVlYj{- zpA?6mf^X)0pQOO>xCUj0+MBo$li5Pq0_&SJNSH5V3K}ZpjMGU)4g3;1_98}QP(>q; zE4;DH5w4VMm#lpL*wpLVm=drl@1LE*ra+FzupwhESd*$am9pDj8S>p$Yz(MOX69^s z7uV}Eb;8gNqcYzWbkyVWRFt|~fH8BnGg4xJm>gV)W|7tYKs z!oG&DQ@e7RfC{tU#JxL(Q_4~NO_;0Uz>bz|n@<9)A`vwSJjkPw_P*y0q4jJAU-RJa zqR2M4MdLnZL##{2E!U(|R9JHhC0A4Lt=aSv{|86RsX;Pm$}E%Agcxl2}Y z7w~ylHH7>}e(&qje_DS-4f0DwqDxii`N7*U^kTOUabOrB{BrU*{oODgLoB%5#CUx2 zkj4!t&9mD}qD>JE1IGb7c}aag{y+#@oR85Qh4K8dOsaQp98;OOLYBeqH&%o@7VD;n zxZ|1yAQngt9SB`=PSMa+hEVLX3sA&ay%)&QmLE+W_fB|XDEj6F4_rdvPq>qnScBB| ze5kYF+edO0w6g?bE80i^VBB>3ez7|KV;B0wZKfgTbz~DK#`FIy*Ih;P0wC>JdQ-Z! zlQdZ6ff*c8!3Zo1aw46h@{hoOV22q7ap^?`qam!G<8W|8Nu$KW^XTXUEB)j{TTwlB zDiF`6WDf;T@WKr$WO;&ppI%e<|3BW)5Fg)A%5)xhum~Ph3Y~Ipb1HXUm_BAT1);ym ztD<Da?55c9hD6L9Z1ZBMubMX>YH&o(cQ?k(6@JF`c~%J96( zfRN;XIO^!Ak3p>4xhl=j{E0qpj^xc;1OW4FThbNfl~>@KaWdteuZXyMfl(QlbvQ#A z)|=(1v!n$yd<-Sz9G+p~e2bg}lsYIJ$v&d42Bab#PB9?5tt2kbr5?Q-1$wQn0&b3h zC{u^~LMz&$nYTLV0O(AvM~%$rH)EF6fR!UfBu`Nu#75nC!qf-G3y2P|-q#O706fC5 zkw@`CD0RGNJ(OJN6CAu$@KL93k)KUM(BV8@?b&Zu=ZUtQ$Xw>ADt}7qv$(A3sOmz$? zdYxKZ-8V_v3Bw(bv)r&j{+KsQP%4*W0xFP_{LbHegsoWzbFF6nqSSUTB0Qvfd_-FO zoPiIJa~5Ds2^)m0Pq|`K=*UYXs6BV(Q|C$`MS9IM9^Ra5Yn#!3{2uh%{xE@)!)r`@ zf~x;Xq4)Nsx~m;3k@mf()OBlK`sKnOHmgv;dqXSFp*Ue6KUPxfXw%05_afJ zd-|K$bw}2_8W|$M62~LC`6DyCN=gRazZndZH&= zJG1)mz-xA=-AYX2wxDO4`nk>~1lZ41RFo4?v*r}Q_&wy>YT(~hL4?`2o-ecoe(1Qb zG+Gr`lA!r8w)p41SjB@<`3f1n5GM7dl2s7OJ5@FN$EN>QxyK1{XKX#0!b)BLQ8Aa| z-Drsb-1qc?_SBGd)_5nJKdNE>EPS)hy9~=y#~^E*P{U1C)E8#F5GPd+_7P=r^7n#D zTps^h!oqbpw6m|PMs?r zXKNz2$cScBI=#UTYpcfA`nI$Ze;SUn(pafiJl6*@7m9j z0xQ+}T;7-fSr1CO*BTg*^ykxc+=0cW)TMzkhQmiJ8y5CjLQ%-|0U977WyHq;gN=p3 z68~@DyIXPLP4RWbVDPy}hLO)p3MGbpwq1YiAo16#8d3lr?2AHvwyYcM)fWoj+T z&%C&(a?eCV%$BIkNI&c7xY0Bmr4zXQunky(ixrN!>O=$dvj77zwsCZ5%qDobOdLABN#_6t0Q#u^6iA@1=wStw~3t`Ru9P@eDM0o)PtV zo>?_Q2l$heb0aE&%b69`mGP(5TQckjlqPTPAB_P(V$OaSe`d>Rk>lGdqa>RDK!pm6 z@x=2)dG%ah$joq$C^F>*h*|3QrxCh--|_Rvzx~UxJ80-A1v@Dd%FMn`GW@};x5w>y z&6p1Q;?NQv`fuPAn-nqRT#ve}0#0YLLYW*1Bb4hJZsIq0vrWJnp~t0g9=vnEwMv3V zaR&0`i3Tj-E^sf=3oNt6)GgZC;geU zkR%T#m(ByCXL$-_%mWvZ0BugngwwASwRPLsN6~X_nZqR$z*v$q{cL#0{SS z_NLLOzuZzSl{|XZP7?rVN?&{H=3{nT1G!5fc@|16bGO`ktcA3d1(9He|dm-5VpbA zq!lc$*tuI4;J?^sV5==Z2GvJjDi6ra+YM(SnMgf*=C)+j48FeWmE-fiMOzS*!6wU7 zfx+ zw$e|8DCjyf%OinanT;8QM5y=Y^^>cw@Jqy_V)o#wCp z0!RRfJ3qT%2saS=PBl?n+h#92_VF_RaVz~pwO=y~8c9&O6uhN$pbuI{L4zIgUTU1> zmuRdLjLowuMcb3O=-V^Sfg#5uk=U+t($>&Jl*ApwceX6-)uOc;QMj0Qj*Tj7=NLeY z;?d&P7!BrM2Fks_+0{-{Dn=A;3WFULnX|$;F)yWrFcHy$k87ly_YP9riFkxhQeZaU z&*m2wvG;3Sf{$xo)JLkWk!MX~NX^!fi9HZ(X4lL(g~JCXRk!y^-(lsTMt;)n9N{WN ztVIZgPpsV|urS!VaHW~?!v?MdCBH~%c0FiOjVhh~j`W{oer8474AzSe_ZzOs5>(Z!c~|Vu!$(7Ews-Kc!eM1|(t$gHw>K-Rnr!{lwR; z5rdSo0fWr2A&Lgh>SgXX2p4s=STvt<#aC;>TcDqMSenP6qC{pKrDvAOb6T z@WmB${FeE-c08D-ex-1ifJ+{|D0SM$VLKlVJY0MSHsJTu;9f$Dw26L!JP4|GNdkp4 zd|19SbfuD*xTe5(Q}I@x`~g0&fbZ;n-uYj$RPTTG6;kMbF5-`42x9%eo@B6H~Dqho?h{a{>LT1<(|-F&iw@Pc0A;i z=M>v&k*hZYYQU?UZyd=N#UQ*}9$m%~tx#Vhvq2cjY`!;NKVP~|IZT<&bDCbCyy-3T ztn-0yOwWd=IkBX>X|UD|q4SyB79aVZ?&E!i^wHUz1hSDDZh z{Vh$|(ixe-&4i%$gDp)4Xq635wD!2Z=CJmI2tmRBWAr96Ai?XA;!9}<#GxmN1tR;2 z_|n112PMczaMKjX7FAFe?_`aApX{^sikSZV>3=cT9iX3cohBX5OLcSsog#a$`Xx@riRWxJAT znQ`9zZ(FXjs3mce%9!;r;A!Qz7ZeT7gqBy<;h%NZ5l>50m-2HFb4yq*P)wm81i@?4 z8>w{No(#Mlt8;R93pTIOC%>VC>`rbai$tcE2&@W_2#*RIDoZc-|5B!ZWpayg8pLlv_&%}J~ zb23Eq%o?jH{}ZPY3LL@zX&LKH`y`e$v|r-re=A*Be7sk%=6m--%fvWV4p*7?26Q|N zqVk%D&Vs{VyCwI*d)4`p@?b0U*9_+m$GGv-)40b4D9*KrPQ}TmD_mr$3ENYJGL>mU zGx{<+m){ezFGSc(I3Zs+qTAIxq6`QDL10+2S~$(;84V!=^4UBA^NgSTkGa&EIB2GfH$5VC|G4@_IhUnBN%rS>HPH)KX^T@ zg02d+?q#ardI`<*62y8RGLXll3Dg9$x4~B*(vPuNF143Iu8P6vV`!AGgQ$}tQUg<9 zD?US@H-O&x7XfB#FGxZ_D4d2i&@u=21@^TH=@ z7|FB2DSi~;?K~t=I8tDi&0u&X$u~DkFzlR3;Em<?{Y zA+^%=;<2|Qw^|erh$3oF^sa2pD~UmC0=z8Ms5Y?H1&qnl;TveItzxbvV`u)_!;~F3 z5j!*2GEILL8d~C7ie+*C406h$sZ5109Tka_t?7$xhSjBi4k7$#6Hl4TjXCBS763%7 zXFq3i^~BbM{t{F8 zZNWor9u4dYTzoy2;6TRR6V{SJ`w{u1nK;m-p(~_4YD22JZx|{dT~8Fb-(zCXss}4L zCseN7qEBxILxC0&^(Q6u-~7;>TzfregsXQPdi{lc$D#+o6$-crM{z{=S z2YfQ}iyCsssxJy3tFpg;De`@MQ?C})kk`1CH^W9DSy4I}FB-)GV2c%GJ)L}cer>#a z*nzBh(bM~4sX1Q(As>raM=l23xF$em4Km?YCdEqH-%{^aQ(&%qXx_F>23+swHmbl| z_X)KuKXS}pOpAh~+rO236mvn9539TwHBeNd$J)J@Vw)Y3U9*)i=)mK8Pv_bg(BfZL;_PuuF5s-)HBQrFHSY3e2oE&I zVur3b7ov_{F4b1+aNlXKQn~ZQ+|NT#3e(9>j_Ssq zS5gn|a+uOr>i8`=F2p`JnZJ9AD75ub)=hLBU&-FuG(L#)TCG@dqOv7Z8Bs0Rd0sLe zO(+GFrAePQ`vVV?w8sFG(!QJQ&m$iE*n$%#SR zKsZwCunbg}AWas21Jt2f**@~q@GEu?dk=o5{}%GOb88E&9U$(=FkIHATEWb~I=ASh z6M2HdzX_SKEah=kk`mq0@W~PeS}4tmJMg20ri#!wy$M&*qe`ZA)9-gFW)YcdTsLrrkIch+R z^Mj2vyIt*!T2BzQ8i5SGMHkG{p|&g44Tq!uMM0a-`S|*0YiS7pGNPZ=Hk1XEZGK+~ zuaz9(r58A|^v4H}q5ot}%MF9`N(*^$A|{-8f8woo-{Ftv7%kee4IpYHEOZ4<#4G1R zBW@2sS9<#b*7u{i(@G0WXC6NCZc5kQAlp1|jLKicI=oS|IL^|?e@)n@+4%ER`M=<} zm&yVmHXB`*97T9l_b^2KF}6QSQ-!O%es&1g_~c|J{|f#6%W!-ufa1dW4P1GbsGlW$ zBev%`^wB>#6@Hd;ONmwe@M}JfAvEIwpP{#s?z=!-WN~fRwqHzIL`2JUc_WV(HXZo# zzH%)t*X-%Jp@^{_@&cnReXeJ!O>GoMig7PJktbtvl8Es{NxON7!_#x|7q_03whI4s`AHqO;WM*5Wb6KYi;H7yte=$A&U?@z&_E!*=5zlO1?iP zplhg$ZedQU9Hp-FKoW#LkjvYHeX2}9A;omJ&PWC+K?@NxURK^Js$iVKVK2gwSKqi* zw~m;Ycu=GpxwZ5?9hCs8K=&Xw_xDCGF;cLMg#T`%t%~GL*A_==z<_-Bqx(KJguf9f zB?Nzq9Yr|6EaToaTZX?QRsl$~um|aW4?YKI2X%}=#9Y^|+ht8>5XBsksvL~e+IMNY z8hJ3N0{=jyIqlWR<+ka#FH5cr$` zy6jQq%3%0u1CGG>J#^Ph1=4`@e$AH3FQ_Nnnv;`sbNxR890hvDLakJN`8U-ht|^In z7y3qt#IFZ>`!gXH+BtB(nmxY0-mD08f*Mk*%cK%E24=xiv+O@deOM}uf$A&>8gqGu z?rHDO$=DLagjSsD=W_0Xi-80(eXeo-K;KUzg_dMPty+K?Hi^BW@$D3D9wpI9=l+pp z29K7FUX3sVa3M!=A?;rAjmC~Py_qZdNT8Y^8j;aAd)M_6RUm33G?xnXYt{~5P>&e) z^@s~3`~hdb%1cj3j5KN_1WR-oK}Y=xuLK!i_bBN)Gn!;knH=K8V)hty#T# zMFKarg^s|GCO00WM7%d?Pjxb!m0zSm{phco;)yIu(S>O6j!q6mDdS|DDfvD8I)8Cszsci95G&i zouYw6GUoJ!Cx1ChZ0FqZE)`4fZFyW#26~-hT|}gb(s`NG)dXScwy8E+Y3p9T9B z#MUWmPK}y!%c}KxWAuPtf%0)n%#ZWwKm4ZYipnz)5MgeDkR1@)>;2F zTF3IZ?9MfUr>^r5g)D8lCa9T;dPAxwd{RM;jLMuhQ&mAl`1e&T!T4NzEDh@`$}uRt zt-W)l;gg8y5`pv^6pUUx-4);;{$Boz}gN(MCdp0Z1&=gH|%$*+XR(Z3;trB`vY zXiSwC%CwA z;oM1WXSxSrvdw`tS=Pm_XkciNWmOj2Ier}dM6;4zOfR8M`au$XIz5V3y>%0{+B!)} zD8nK>yA2y=zdX~FEXUz)P0H~tRnA2IF!gFaEg22By@=7r;X47eIqKw3iPwvXnB0yd zwdmB#>%yvLI{y4KDzQAemcJH#-Gz~SQYOIeQ|JeRN5W7@s3hrQz+CK5w$gkt$m#5m zu`DtG)#KWZ`I8?`9QTF^y$*s6&pyVDC|s@aZLnySr`sf<9R5hc-L z!p-C+9%e*H`b(k`f$_hrJeOp2UG^cvhJ)R}AhooSlscMFK%lG^L}Y4ZI#vx{uhQ5) zGV96YS|ytxc`Lt7OKSuZ>1BD5^^gNpbeJU5!-#?@C^6NTlH75{rVFxS31X3l$P3#) z-FbSdXjxCt?(O|(&`7cN;$G#{z<|P2Nz&21k@u06{ryJ#{Z>keN(x1pMKA_U-^9($ zO1IYk3?Xz}&83ra&h$9O?)W#SGnes^xo8C>V;V`7yLb+uVAisIj6y89aL+=`mbhBk zg1_GpNUGx^Et{!|N!{RMCIgji;d`y(KLL78`3oJqUTih(#=ho~Q5jQR_ zu`6s$+O2CtZs3^k={4n(WUH2UfyIL)@2C66X9++?DU? zxhZ^GTTo^2WPHCIx2&Y2F>VM*Ti(ZEdYvO{Z~U$5ngfUVkIZV(QLBQau9?rhdv=bL zae|5NS~zw@e*Te~|2WU_?mSbnKD(Myl*@`|X51 zW=uuR%Ekk-d#7HX|3Qh2#XQO=5fRj~hO$Ws8t(+^I~l+-q^R;+cBtDUdx;T~<1&J6 ze#)}_K210)ylydamIPO0$5nV00BK9vi%Kh$0KTJqv$xF8mp!D>3IY=GlvRlny##zmYGnHsLdc zBHRe4vm%BfyRvN^j496d&)xY*CJ3c*a?#u~D>YXrQt)qJIuMwiz5}l?hJ)@|0oO zb3(`xmeT(GVnLFPt)Y2vXre&T+3c9F%>Inw3DjE>`- zUQaxE`bd8Gwe?x+LwxVz`390`@o1++(Q zoE_#y>9aJ8*6~!_Szq{qjt_S8r5tDwvt{pfN*l+NHbarFxj6)1YV4R3XJNP4mw*iJFZVPg#Wfi4bncpKBe*RvECk82rP}UT+~Itg zn&^b{*SL2<2S3!|-*M-Mcb^X$-Iw zx!3nqHiFYma~CA<_Z;wA{%uIqWiPzh{L(#Ay~B)pj78*Rj-?QvT0bmU z04VC7q{Zd(JbI9^WnvMA7-R?mOt9_f1Z$sKM550>phjU}--X^(034dGODm1Du>B+G z{(#xF3SIW4eKVdr$nsdI<2Fj)xrSPIO1lwt@)3bv)>Xp-9kqIAhbr<8-WB5nVumai zpeFauTDC?@U!OBnl^t=}f2rw~$jy2vo(+%LCTk$&?CqTgR8z~h@DV`}>j*xZ~`m3!u>T?`00 z+rG-BLE8Sxs-m5Upds}HZYXPSDEODTDcps}((fDWHGSmfwtwZ^)lH91+)(G(n&g?r zOlVN^diJouZ;ZzcT;*Uq=@sG}mx#+&|rd~i)RZB13E93y$rbzX< zice42-|U{T=u6mHgAmm*k5U`9$!`+Mk5O1^7Oz+GS!?plNMC&6+6>?<`HwG^+k>g3{#Z0YX zMDV+vo*jz4pZvl<=*jBvAUBy$_gW@iTjpuTeIZ?zv~IcD==Nl8!cv0Ql$%pL4XKGuKBTG-th6-71i>iB zGu^qpQ&^Q1Ba#g&revx62g!!tt56uSy6zo$>g@7PpMx`AQV)c$o4B>9kmVmmh*L-| zP_db=)_r?9{IGM{a;nEQe$PvR{4ja7?uVxHk)q*OF5PL&ziGL$dzo!U{LYkycBa0@ zpqO(WPBOuNQ0B0@pOluUB0715k|wVWv59*1U zn(AzZ;oGozIKJ$M%*5+dH&usM=X9=~99Qd-6LyJdTDqlc2@|(r@bcG{U6*wa@9)}e zoNCdndh2@IXvaI1nRK})7P%{S?5}3Le40|zbAf)i(Uy2tE`JK2a^Re>Z13XvF%Q>P zjz8b)pZP-RX_+-ELUYUfj%%d>B)L?rQ)l#!Mr0G%Hx;iJdLP)dQ*Ef5SHW`cwcL4K zM|udm5+)MeWp5>2c$jy2e)6EiiCbo`D{vT*IzsyC>)Z)hvG$|o{NMX?Yvbw1YxyT` zLU*Z6m-SwsByT%Q*;c!R-w>TJDr3f##+xyU4wFXS=z4LnY@4v;BJ*MCv@t!Zbs>k$ zpC=tSV13S(6?(E(+x3>hg`2mA2Ww`I-uR(;?ZxhZ}8@qL9 z$vbsC(0bKI7Hn(Rj@EQ zk0%!0-=T?=_GlKKihQY@DZ6{)iD;cKLUXHeBX!?QicTLtboTiXX6ca|Z@2B|bgG{1 zmJ9jN2C2Ponf3Ay$HI5_xp_)%kCs>6Kf9}{c>i?|^=ooeL`bt5sz`=&rmt9Pn5rO? zI45nZY&WZDZn(~dx1V3#ol zo;E0;#K&H5aokCSL7X^kuJ6>{DTvI-lPeRtwE?y7F?}uI8%Bq&K7Q52wdSeUt9aQe zs)^sRHy1+5_@{w~yZ!1kwXbx~EmCgM9JJk)wIoLQlodb9iTbeRk#O5?w|M3`w>qV! z_R+VpU8RoOou=b5TyINVJ2tT~zx0!`w|SNMdpVa|$BG6Y?B4X*F|VsCQKlfJ)?|%x z%LZep?y1VK0)dR*>^sr37i^I)FAUe&cz$6YudTwqq&N5WB8{6dJ`xWkuADM zarnc-vJ9&4&P|R~v<^?(HuK?F`8Jo-eIB+m$1+{B;#Z{UT$se4akRZoFJ5D_jp3By zqwi-g31RPkaPqUY5=2x9+P?JI?ex(Ija(`iWTxX0tUr32)a)UA;b|%5YNIyk(vEct zjk@%A^>iM2;QZQjqJfvr@j;6=y;ixQy?K|gQ22a(>~*K9Zx-MJ@8xHxnd{{$;tYj> zEAjRBU(BG7|5)fEC_TD&RI-(|D}T*)#}ys4N5%=#`;*jH6nCtt9LYReCm*_Z?24nM$V=Sn!S7)!e6j8Dy((Ud{ z5;0xdIr#KpGqp-X=VN13<~~{dIe}_>Wygw(i4&b=uOZ|!PRDD`PfcpE*St-%b3Zk* zqUyY6)@#O;7w3G0&kLK%{H7ngtM0Jo8N4qwvapz!wqN&y?hew}mOXY`JQwqp`u2F9 zPj5gg6r2O6Tb{Ag?l^d{Tys?u4maV@i&aXk2iO$}JNGTkH?2z#%jRb93EJ>@<&e(T z<~i1?4~y&+^TsEw*{D0pw#r`X*&B=a#Ac^OQRY*fG==G&H47K6o%PnKutTq|*El*@ z`*J;(7k#JL3vOICA+t$$mtXo-YRmhZUN6!wv!}Pg*{O`@RVFt!eR5YjU0_dmQ@#F^Z&tJoy?J=EFwp008&-AX{x;J0$>{V0hF3pg;G1k=<_xD2QrHN z6JHFPa5nE<*;v+!5S5zp(Q3h`*4S)G8}zI*zj^fiVU4#`6!Nya8|hbn@yhU-G3~8^ zai!y8j_Hw3E#@BPvqqxSb%$3vdjjI$tT)9E&Zt;ZT={sRS@()vXD?=rzq8h5&P4|g zW8&7sRkP+M29syug~f9PE{~><$_9s5{H7hV&86DPl@~QZ)0eDJESTP%MLJ==;FF(j zDJl*Ao8trQQ<*ZQ8y~kVaM_dY4>hovizepB&yA2C@hE8UoWou1vrG_aR z=dEt@bJdeBCuw@IV%}%8ap~QW3u|IWbbC3TWDV2cyuYs<+4)Xw-}xEmMqbUEq1WNp zk)zZ8+~Uz^&Xk;H+Xu&rX5ae|qPL-S-1)NPJf?DT^NhN_WS zcB}O(Eh|5sUmAI!I_L}C?~L*ri`g@%vKcN8-g&8xmy@~=;OW~E7oI*DzcpWubHlDC zWYJ2K$t_+xKWjv+26qaNI^Q1;<@FdmQJOK~v_cjfp2O?dy6@di$IqROmr=@%&WOTk zCP#K?mGHKuA6<3MY1_xe2GMS2Czig9>%5d$?xXi<^WGXE-$lwb#N(#fvdpR4npux) z_qA`_J2>Pj43%pgp@XMZH*F_>P&oHkC5J3`sM5v<&(tlpeQssP#&Sr}$TcyE~f!5k@oYfU3s&9SN#lGfhpef(f zI*PdxOx2<#+IiA%Pkk|^PgSkcTIKd-X*S;V=DG+)vm1Y$*%mLjZo4&ID^N{utj_*B zDhJCveAnqRd=A^ZJICyeU2iK~SCXqp9c>l5uA$w#X;^bvQOz-r+p~mCi!N;A&QIS) zn!01Hc1@^9mD4h`{?WcOZ&TGw6dNMiY@%~4d&X`zEL#7{lmn9w)&PWH!mi;_zZ~}KKAD8=vB3r((xXtRZ~l^ zJh@$?FTC?X}RdMNw7)9`-YuypA{dxdY+-cP`6Jw2P+YJ2Fu*-G;1H0Cp^{l z!py?O;^Dn)fAhl(gX2C{9+vO?x~)`vBV@R*3s-ebd%NlV9H;hGEeG0@W_OHQ;+-+J zL%WmZ^U9ooqE7V~Z~HxU7J4IRCiQ%IER`J(^%Q8&Z(V+LcEpSidu4bpcK(qT z(7yMJI(PmhCDT<369c8!zGD-{=`L0W9_ZUDF)$Ryc2VcwpYk8Mru&TC5(%#H+;CZq9p24X?lxP?4!WPrn}ujHl3ti z^q;&h$G7HFme;K-|t z88SnL@Mk2BYgiCC{L|@6@6aZ%n<){egPm{R%G=FTil4N`>Jp+~mw&B&gMQ(dMbXdl zwa-FxkN3`%o&H&N^UlFr?+56#wFtt!UfHzd!%Ir{_&+uKz+d^`<*2L5K3cN^R)1)j z9RXiHxr4e(H z443R<_)_&ax?;SJGSNmVdcSjB-QIChEz3TR_EeVtGD+X}Nb94+QHo^kHV3xGH_;8VbSi6}-?+)m@~rn{oGr=CZo0j6>H3zAT5V~YI97r671gvv<{bK1m{yl& zP?q>Qkbdg1<9*6<$NlQi+^z|yi}M)pt;O=@ zJu6dNU*DD;bq)7<+J3iNPqQ4J4h~sRdbH^2^?WG}<#A59TTY2qg9DnT_ZaWK?`hU{ zD@&)gI%r?`ZjS?kX)kYkm0L;KXUMU)d1b1*oP8s#^bZeUl*(pv^xhUJ&_3+o>&}kr zY<`>B6Pnv$SoHj;j?6)gQ*udK=M3i6JnL#Vdq1jyo0V9#XASxCRHD)25hLdmZm!mi zZk3vzDl^`;&}QoU8xM+}rb_F)GC91&rsTp~`FqWr%XzDky`0RRscB@N*EZ37x7%fs zRoJJ55oWhi%G8f4Z}t)3-P>O1zHf`kFZu+Hfy!S*c204~AGhYs_#>GQj=k=XS~dH< z+s2N!U3z{M5j|2bWG?+29%;Xfgv(AfUVlKR zg)OINdRs2fSim-za9_4;#Phw}?c2R7vgdZD%55EaPZ=%mE}C@mTC0D>(x#?U!(W#@ z>fT~Dnf_=zJ!tkimFyUSU+FwaHtTK3=1q2c7B}6{j5;c{%;eao zh|JZLW}V}Oc@Nw9pL>OrDVoTE3a1g{cQtBQy8D+-cNEBbcAZNP-|Swz^;!O0S@*gb zL+eZQ@1?R!4#!kvU*vwQ{{;TK=4pFgKI)p|==18>!WYk{?z>P1H;+%#sta@o4@vC4 zedm^Pe(G|PQc>RE6J^Q1#jDicpNQ>zXaa)i%$&2YY;mhsu#2h;%2_3d?F}!zZnt_t z4VPVUSDDpfrLDE>OTkg2Hp1rX^TWEfyw&TCSkwA&_AFiesKAbE^Q|seWskX$pRk%5 zB?yY^LH(AmM5H3qJ1r}EgO;nyn$)-4j@1zE^LsxunVPZ3Xk3B+i00@|N!NGX_T5xa z&kHu_S?ZRzKD(&!Mz8sbBiE}2@Aq@kFMWUUQp&tp)q4!gH`rfg8y|T$-fa8X)qgx8 zkCrPt+c4pA8MkU>b!FLxkbDyj#q=EfxQ2*g!|KMpVNzSqHJ{$D(!Sn$_@b##Gq$L> zCqPDL3JSM%I-2NRbsi*_&y-VkGN=-cSbY*)9Os5p1(eRn9$LZBpqv%GGpnYtw(l9T zn76+zZkY59pVpGHkkZE`)jIw%J?jKnMb{JF43f4|s1B=~-Y|K|!I=f)-b@^x9kWFb z_0B4&JfA!AiDBa#)KdTPmd%OLCpmcI1Nl2@H8#E?pJ66BUg;Wr)Pyv99x6GR2$1fl$(09-&=lpsnF5Gf@! z+^FxdQDJi;qXvQJrNIx7tHg%R!O=oNJ#<>2AS#kh<67D}*s$q5no(bhW@gEAuym!v zD9xxZNwZ_PT5{k0l^fML?nz zTDcOYfz-E$!$Wdo!9*Uz)~a+pxJzdj~quH)DWL*Ds)f5#n*2z&F7Lie}WGrP-TVv6(zN zH#!W*c5()FiSyX_@YozWV~*&pu_TSf<^fgEF{Z6O7YAF%!q(t5Btn`C1H6VE&w>t< zK}Y*@parhCtv!oPcjgNPeBU_%xbVP0!-#MJn7Uy>Sc`q-G$zAAtjT0?!K+GgG`6b) z&l2<|{83|lakKvj|y%NVY71SR1i0;#VA2xwpcoh|KnbeM<-lavD1P@ez{ z83zoqy$kj!`_nWV2AAh8l8RPNe^#u{v=@DOkvf(ZtBa(6s*4IZY%tB~VCZN@;v~(5 z$rUX#i-0+T@CadeNPu6IASS@jk1vc0h!pU{z*p!~p)q-G4s4ii;b3ED$Ya=n(G?5C z1q7^s_f-K{#Y}9tU^-vu7a8CO^u<}RnDDI_(`LcmFdf0eD1iVLfjBFY60mW_@**t) z&=Tdb1Slr}@mEh_Ss-QGfR@E#6l?=jl!{d$l8Q?u4}DvTJJ~81NF{s^v;Oz?0 z4Z#Y4i7-T^P$?)3g9#~?W5sTeXni0sf>MbDA_SAbOcu+rVvfBHn0;g_o=PPX$OI|^ zCay$G3G{#h8MdNPz%>R$vOAf;36vCz~&>U z9|EI`62KS74xzphkq$wkL!nZs5CI_~*oVS&L?twyNd3oS6KfY+6n*7lJ#0zzAHm|| zmGre9SR`0=EC$C~)GvgLLNH1tfdwIMQ&d8;ZWzrU46r#bH@GW77UK&B2`EZ9j7+mreqor9UGQn=`=Sf-Qfb zB1!|Rgyro34l~h-Bgu%QTwty6F>8h8jU}-znAtFx)^u>}_op!_))sg*P;GyP#^CVS zGhON7;Be|og3dDSS(bL@^gwVd`(qvM%VQ;WPPX6>#=d-C1{0cDGQeISmWk3droAn& zl(<}aNJIdzDf+WC8+R8LsE^b49+Di*0lY4@C4;|#J?xG>U`aXo*)WXPm*88xWBZo6>yB`sNitnpX$IU zRKPLA!=m_pQGHTL9rJ~J)9_F}-Zuc#h?p-F1O-}p4~&|}7xwko7{_(7buh;n;;`2w z2ttE!Q*|u;`+9)k3EmG!f(S5HBLZw{u!G`Yk}-j54598g-|!%Rei%+?8dh(#(64_S zjQffV!+i05uK?!TpTkBo`es0lL?AQ(E)4>>1gPoXhD!+X7jcP7#m+6%oA1Xb1^V*Q z{|YW4Bnd7d#9zlH66FV6f`CZ{<6-LG;SvOJiENDR!helRh%tD5goK^-U_t%HrGGsx zQNicJ%t#SPeUD2dDlmk8;}Wn+e&Z5`Y9dGiCu?8oPh28{T?EK~z$IV`Q$_NBic1KQ zN+J;mL;~1>0Pz5f`U;l_WI+8C65u5wW)X=n>U&(G0@#AU;r%mQBEduwL#I z>m~>V(1rnqgrmO0B_d2EQ3>E$3dIa72}ymAOE5$tlCWxiic1s%N`ZmJ_fuQ~3MdN3 zbs$^)uS^(&XY3*xV z`l=!Dx&z|UR}G14>1#;BMIfiHs5KW}_dYzQ-jh8Gs))>3)h!{dN4trGE!5VO#RIxCFL9 zY~ueTTq1?>iHuNJm>kN78KEvHmaxiA=#0MOXcp3+j7Z64_+Gap^ZM zVSc8+!X;pylQBz5>}~qfy97Kp{{>tkVa_43&j}2YkjtoFc2nbaGMl&9O2=+Du(GrP* zfSX?mor`U}3IrSBRVh?c%;2y?dqmA-}~ScDRY zL^6a2&Q;(M`scJnfhYt#;6RuJDjSHFNK|0xz`&14A!0L4%tGJN5=6!$WCVuAe$4-z zmMA|E)n7T6M5pF2IG4WD1B;}ei7={5QSq>tqJGJ_L=**uisJRYr6m|p3I&8dp;#Q`53~eA8&Cv9+ktp4 zq8NxkjNYHlB?=bK3UG={B!le>JUt*TK_JwRjG@8La0w6&hC?W*<7c=;rNA&S|3OqX z=;lvx36KbQ4}cFp#U+f)L^l{e!zCgWr65>GBq4f##^QnaIzTuOz$G$-^+FP(_f;$& zi2yF>5CRB+BVtfCATGg}3ycgxPeHWYz_|2XLj&T{pABIr!T`8L0^waSa7>~w=Gc%> z)ZaOm5Hg4#$L1Rrr}uqC9ua^J0T03|NfaN{xqv0uLoO?@WGUqBNAT#5uN zX7^_>~rXm;f+=NKXm` W8vAc{F-ffK-_RruWE?Oe@Babm*R`4e diff --git a/glymur/data/__init__.py b/glymur/data/__init__.py index 2bcd7f8..de1e62a 100644 --- a/glymur/data/__init__.py +++ b/glymur/data/__init__.py @@ -34,13 +34,13 @@ def goodstuff(): def jpxfile(): - """Shortcut for specifying path to 12-v6.4.jpx. + """Shortcut for specifying path to heliov.jpx. Returns ------- file : str Platform-independent path to 12-v6.4.jpx """ - filename = pkg_resources.resource_filename(__name__, "12-v6.4.jpx") + filename = pkg_resources.resource_filename(__name__, "heliov.jpx") return filename diff --git a/glymur/data/heliov.jpx b/glymur/data/heliov.jpx new file mode 100644 index 0000000000000000000000000000000000000000..c7d5cdbbcb82b178b8da6a2d5731f6a6c138877a GIT binary patch literal 1399071 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

w1Y=Ob1bXZYn;<4b?um`Ni*m;(Yscp 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

  • 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�v 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 &~`qC&>67I5}?Ic%MCA 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ⓈT>u1 zK|`;)+gn?lu)2s5)a6Jn82a;*TZET9Bl%UsP?y|qCnDTs%`6LE-_WMGM653?p=_e! zy8JqZ&@ct^stQG0t3_-ad@)2#v0QJ>N!Tw*XLUMc+5w6BVVGVa3YX7FNsW?XX`7Xb zD10B1I(jvcxkpBm>iRs)pzR_E;T=xKq@ku z`^A^fPKvrTzp}q5`z@HDZ&=8tL69G}$G#28URh?E*LGpfoJ+9ydU5>hoB~J0n|-um z+17moa!;UyP&BhtD!nS$)Nop{8#_2Y4eFS$U5P`6)>+~q!ULBew3;wC-C+;=wg(f-|O+k{CAC3gG~N* zuJ>lg*OY;&uLXYCTeyIX*KvrPN?1->KZvo^b@q^>ws<1%QgWf%hQkD1;b8*5vN?6L z0t-|w8~B10h+1&KdpJGwBDtNN7L~HO5qvpre*s)9&3+oLAR;d$F5A#TN<;!eF7bSM z2XIv$xSY?^mWVlMlUd*#jI!PQ@tr-d}B1q_R^J8+ceBEd!s$ef2ZM*mA{Sq}ZR zRPD*BNL;-5SomPDRZ0lyrf-IY>ZN|{nByY!bWF)Hm822%e%1_Q7X7n9nsNmB9zF0R zjTtSYcze_k$=Pq0<@UHj7lp!rh6MVXdg`{vF2TqNwL?Fvg7fp-J((hjgOfA%r-VO% z&)sh#U5q}2a^?z~MU5YwHj}=B++?A29%&&2#7oR5{y0___LuAHlvYj)wVS&YoPD@f z{}k)+3#E&+_B_$NDE*6J7kO2H2qa88Brt)&Acy)%F!Fg*um-|^;AFboG}C9HOfXwx zwHP)8i*!lycKA#Y18YX(9If#kPDRN>8rLyJ%NoS~UanJmw#Ds8!$_(&&jdS+$S^;| zlQ3Thb)5uZX;ak5Cc!sUaxre+fdLW>3!KnJ$NH*G&N>(Er&lLxMraQm8{@DGvSlGapaR;EbJ6t~cQz_eCmQ&tAV`E;S*%v$13&w( z(-^SqjAec?jkCG15c@s36EYK=!lh|{*;GBlZjO{vY#!_X_|A1|W&WMc5u>bp@LI?WCiL&KpM2VsyQU%2F#7}Zq(=SH^=qkMH?-2cyL6=3 zYcosj>J3X&3$Ou@c->^9Q|^T99Znn?d`e!O*4tVW?u)&=fW#l#%Utx3`(`KN zXhPk|-5tzR8{2&E`f6xPll+GthGvR(|2Cl&GSdcwo@-AaL4vs|IJ;OKX z#XFTw-4i-c3f<)NbRo*^ke8o?ds$&f4JIbn7cxjjw_Ud4kN#DLHBbJ;g@l)!q*{Vd z3{#X0DCiKTNBJTVJ+@>fr$gpW^VKb!r~rP=aTT?!@d@4uUh6x$nhEi@8RV7`AILQc zy^HtTDl!vzb6}Vw+QGt4_^yDKS1OPTo?2ycYiY7^LKbK9Ux+d_VyKBey-IZnLE9ri z!lFP5<$79jXW&NE#^>kc`H>M(HABqWPrpeVLaH51+4uM6U(-1k7(m4q=?WyMOVvFF zW?#3X53MP%xI4|3&I;+<#*oS|FXMw3!km~tCz9uZD)}sK@`lC1xQWPxzT!stH7>To zhNjc1p`^--;7^`{+;Ud$KTaK4-r6Qw`Q_8?dE=h47e=8i#32^A@U!Op`ITwc*A>>N zJur2D+wurT{VWSs_%`a7=lwcV6r=`nr~|WSpQ^1u!piBr*Vj6~!F1M^(vCcg=STXH`=uE78Vx@a;nY~cMfh=+@ zNw)7X@a<1uz4aGYZ9c5EDgO$Y`a!8?i|gO%%&cPS(C;+nw4l@ps>VGQq#3HPl|?f~ z0;Xb9Ic!zRNzpwwWnQ5WR1A0M1+SVwlad~CUemD1R15-vGkB?x`Kj`g@P(!>2-ij5 zZonN}1kmEWf(+*;52QrzvBc05Nrrn&MCSZq*zqN%%TR zet|Kzl^d?1LYxCRZxBt>@)E9>W@XUCVJTo{JuP_-XrHpk;z$GoF>Eel4!L>RBWMBz`GaS`FuJEh{hWL2$By~PB6syzo7s)W@)=XhURutX zi@0QM+DP-vSyhTvwY7wAZ!8c8hq5Xxxqa$?O`bt?1 z^Sevo^p0w>ndq@YLGzdQj0|_RL?O>_?In%%0?HYe_6zguZNShKUPBq#6C8i4fyjp( z-ULB^`4=8zw$Q;8yGj9eF;OmR4LkA60hXpGaH*N#O@`#$EQOel`5o8i3SMz7C}2q} zU};19JaMK8REbLXdPZmXORb~$*UF4sHWK34s`d6M$g!gxgW@1+&d?g7x({rG8}1FM zr!jI+;Rk4{Ic@9ymgaCp!N}QnaWpP8ck${i1`vva$7W#r;w+weKdm0|NZK|3X%qx~ zf8kHV>tvPL#c(+VgiMs_it|J^29Mmzd;J#2oVi>k+$Xwks!mk8e0Gfrhfou-jWy#| zy4d``c%9=G#I(D&C}xV5SmF?MU21@q^H?*$E%Pdtyh&cxzOm3d!MJm+n(u1+VM>@W zeS52El(PqfP{kA%mUtVkGrP?J%r4$GRyK2dJvPsbz2>&55lY%)^mBLJ#QQbWD_Mbd zqdC&b{#doK5p9CuDePEzd*R7mU&Xja1%+DGt%7toIrj10jVkp&^AmHNmaRZD0DqkK z^_zw}hMZY71{o}F0q!6u8$@620DIbVmir)^YfrMjXo$D8UW0 zVtQ`!HFtpHSJ4Shzj67`=~N{-R&bR6w|o2Hy<)*Ll8x`XKRpjQYk(ESr6kCccP_Se;~ zi|?A1dI}p|!jkCoiV&5Z7;5WqcXW!5(HjV?U`;`59DJ;sojF+7)kaJ?Umx9;gK{T1+|hRRuQgKU4H`LZSoM=i?mwKa zvFsOmB4y^f^<)G@m1x3H%gEXuJR3@2s>DrC>rLxVQsN=%xEX(h%1bjAOVJnI29^F6 z{gM1)$xq0q&l^dUG+p5qW(|gJai6=jBB|wAW{BGj9IqQq>>H@hoWPiTS=KpUo8!80 z)97qH^iXD_%rq5CTw4E4y--SWQ+v7TPZ3KT5CMoqn3n31>hK5%<+p=8D7`wT^(KiK z^**1QB!h=#zn0LV2zwv$`2{^}K_pUHC8|kT$ZIb4XQsOhz7PQsAw1f~eL z+~jo{z;z?2qF83^PrC*dRADGGfG_4pcrUYwbUEH_njv*riFK~KP<3#kIZeNfIY7N4MfCc3DT0_(wwADD& zKz9--!B%7k!K9<=|3|O$zdgG%GM(wk53K7fok4(gQIJIeVU5<=Hz8qupqbQbp;<;k ziWY<{)71p!P|b%dos%pW#i=E~U~oAmajLM`m}PnFb?nDEm=tietnIIkEnkr|bX&Mr zRjB*Hf}VoDSbEwwzSK)8dc`x)R}qvpuvV?xd@EEo7M^8`%|TRcgoPKBix@{qi3aS{ zzj<$4tk!OWR6<``=&vbK@lSrM!#derOo1wk+>9BlY1IJV>jgLHjo~dtFOr1OHOYw) z8*?6W4$m@_vT{{#S*`(^^&1kZEJ;!4bvaA|8ebOb9y3@((J?yYQ)5tbcFz_~e3}Zf zFCyo+W9Cp7u@mCG&@nEN4Sj8>ArB%Cp`y10rkH zi9!ZzQKQo`T5zFtp3`6P+Sr7&xo`=on^@nQVyT>XCSF*8K5C@kZ8pq@!1+$ z|3LYdBu^8E2Lg9-SW7`iIXXDAeA%OJZ%QbjSDg(o34q0NuWO+z;U>1PF#|mp;@RBs z+CsNSjkTeg%`LdWPyXQe9k^C=paHvCt|344_v+ zDaX`OYhTqKAk2HBbmVHo7q=Rp_aFH zs89Ezg_$aP>o*!PwELzBs{9lCuX$jM$kK?zaiL`Z`!cw2c2Ph|$mdnlv1-Nv$j)w8 z02R+G|2%LI9hJ)JRFW6sELT{nU*i^_AIg83s!V6j0>-^6?RL;vm*&dehn3xrcpTp{iV^E8m=`7I#87Xtd-w!Ry_BuFQ~=63#;hnu6^Qn0Iw}`#es)Gv77gT0z!f1Q>`MmI|?P#g8g@wa{&*$i0t=yv>Rop47$^o zK9-IN3nB5}S^q3Jx<;~X7*QN=TkPvwa0Kw=SM`#=cw&(j$;|u2(po-jsvp(h2Y$aG zZF`$uFivpoV!pbUMsZ>eF7SugXt$VPyG`&+ir#MWa?RFS*Z(uG#2Y|=*ZXZoORW;P zP&GL=(BZ8@z7BtW;T375*x+3b{p4gn3Tbb}L<)asI}iKAau^fCb@ZRvQ*g4@!a3al@wW4UMOpR`kK3 zm3fD=yGepY*iz@kZI_*T?_w>u^~KYCQd2%R4WOZN$>HVJG95Rl|8vy+GG|uQiu9y- zB|q7FZZBH!H<0$MPPd_{Pg^M{d^LeGncO@k!5>FUH$1fFr*=m~>>ns4JNbDM1^ZOMx6%mRLK)#ED5I zU}|6@;8NpSOO9s#j39xrJQX)O$R+9|9DE@U(>_WAqlVm1;MpP4N1cQv!JxBQ8H-?i~b|3NQ=hU3d8 zRJ*_#Sf8yLGu|ED<7urLH5ypDiT`GKlK6(doFR;~pgdH282XadAs@ju_hm z6m~qo1h351Cf-6ax}-uSjRZNx(yX&YbPs)V#=N5}s)avQA+l+gHk?n_MTeeIuF6ddG!!K)~V9iN9PP+&+9@cSZyo{H4zYaUMN2XnR#ido)h_-;!W3 zjq3j#;oIKM#WaCx`Dzj&h>^HZ!GOj+9dZ^B>=g(|i@L%^5I9T)`aPn(myU%|+4810 z&V1Q9rLpe^8ej}gnO{ERd-lT8RT0M+dRF|OYpi0 z>4@p?T(_SG?8Tx&EjL&TpwT^nw5M$02_gAIe=zy@F-H;WC0O#FT9rpF$8Qrt#sMct z`Zkl<^3N7Hiy51hpVZNvz6`*1pZ?i*OXK`_E8ZPEV=$=wuV(C3TQ+V!FWblv`9R7+ zH9H7fyx`-?*XAT~wN4cMaYC{%4fZzZj=s{~%{QTc0H~|pp@rNS19J*qS^r3iJ)D!O zi&;)H!M`ghBOj|qk4jt(N?gVbQrAOV&L#BTfbVPhOLhw2fFGF4R>0-H(em{ON0wr-MkN_s4^C9;(;l zUHdDqes|Q2YvOdOh&*kR6-J}tASu^-iW`QiAtxIdR<{GhJ)t><-wF8dy?p~;zHhg4 zLYtVdSiE9*&cDQHl`_#i!L;*FT*@#`J%|wAM`;d4ySNN(5k-Lm=9}T1E81m?^h|f%*61ABaA)505u|621jj)|oY2AH$PTx*{?+te1Xk;%569U3zSkzK z=3ca3G%(@e>O|BkbtoBY4K-6`Ux*?{13uc$>dq(!7vUM|pXWJ~QFo9a>?^o8yXEr<>?k5-7i8sp7E< z_Q@3?QW2aoE*D^-v9}F@qb}e-cw+bX7=LvPf<5kqgyAUibw|iyJ+pLyc5%UOB$7Tk z)#Js+5<)wgVUO&qUs7x`$A|;JRle?~R3A5CoVIlJKxh?rDhmX6ozcQog^8Py~O^)gnJMlA@_dmnX+Bh$J zHg{V~cm4zNt7+y3cGwL`r4kh8Ml_(y?`H+GL=pWp(By(D(y^h+;r*nW7k`+yOpL9A zRm_37<|F`(OWDp$w5)Z?x}|IY`(hkpOg-Z$+e$B+x-*vw-qIG&bo_(D)V~|M7-*z` zz(rC9>f)uRMsR2)Ofc~b+nZVgLq>z}ITp}HoI#`Vir~=;kGuY~BNb1&*NjieQanVj zzqo>t$GfJIwNwi~6hFo@?w&?T&EwH;xs92i!a-q5OkobqqGjd98+1|fX|X)`X_7B& z^)#u%1PK37r9e(B~RQh)-D*L7#^UB zN0Aa_R!I;*s1F!{}503fZGtAoBT>ANka~-kG%21Vt2Cr=wKPf@bP#3MiCxuh`|2HNBS)x{Xc)(a+uB$a-XfF|^n|1A5px<(kx zJB*khJrV){gi6^n6Z%1K8@dGlOtVf*)j23N(@=+Zadw#!+nXf0GTo!Q$Ml^Oll`oz zktf?pywV0?nbKA{F4#I{sLrGyej|vrlJ`GUBh^lZk8s)c$(?nGH)jH<4EW ziBG9g-M^K5CyOEVGw%o~86M541kjlhz*$LR4v*3AwxaZgl4eePhveGIVBqjbiZRIFwpGn1}%?E^>C z?f#vO!e@AQ)hm87l4TCPUO;S z-uL`b6NsNn+1Oj-^0n}Rve;DKTrX=WfU3;r!gnE#-(yA#vQ0o$^q+&iuqG(G%QJw1 z*j2#>-~9U=dHK!E87dZp?Es{gOWYE9=`<;pk?^(ih**Yf!PTDusRxU0BZSWmI4Hm! zRnUk}^}P&|)v}Dqwkiovxk;OXAv#xBAmz71Wv^Y7fw3t>_`eDwv#fjM)KF3jjK~qH z#aav-b;G$``gVl7sEwrQH4e!mFLIpYw6DP0l`r*^WD#~Xx0R(j&_*WwQK<7=PxaaN z83a0r0RujWb=27~bX;pzNqc)?28r=ve0-$^<_LLJfSWn;v2md3Q!3gi>IlhJy|~%> zsacabQB$Yp*t_{m9z@jcVsA7?x*c}J@|FHo1S%M;pUoI z7^j>{Sg$z?Zgd1jF)TPzfbkqrZB51&UiLMJJ|c5GQc+Z#1-A0f2=o{Qh25rRYPF&& z1-R~_86?r-c^bF+{2INMD)@WhePtq$j~lzKm)f!hd*A)}<7g~jP>#~FPoDr#P~sj8 zM)foQgM{}1eMSuWJTwRnRcip?wX5juHxg0$sT+5OB*+IkH-4a!QO~W2X@et}8@xt9 z(xzjsJzxo7Gf@Mm)>$c_ZXfO%m-W%P5};Yx96x!~v8O!rk@cq50T_}P=(WHpQ#(wo z^E5K|B}uKrk5z#)*THFv7Igtie3IS{iTm|Z{W5dp1z(VtMPtt&0_LwD8_5LsaS(9z z@X4XSU?dA6&soDdCioq#Z@qR^WV$!180B^B|4J66e~P)af1GmvcA8?Lag1^BO=UZ|P&8sRuG&zs<5^;D16roUhx83+e5D=K94T^T0gMk?A@+6&>k$;(p1X0Bu#a zotLg~l3>Ke*i#w132KWYy~ zTxcdJmeGh$ZZgIL>AR&&ShHvFSXIxnbT(qLOJx4`v5!q`8*twWYY)~ON3a)#4t4a|$y0;R^m3WX>-G0^&Xj!^We z)36bi9tyqta{+~_NOOaa`V?g4huVE3ySs@t`RdWK$ZH6m$RLuKm>fA6%^Fy_-ZBK5AdqgpuZZU@V zr9I&f_MgGkBOk7=1I{8M4r-h8w5TfoLgw|+t)BxV)1HKygy4i?o>x)AGVkGYr$b7W zT*dS)Cf7I8aS#jPs|97StjNa}{F2F26AG#^iR1U78a1>uf@WS{tmy1)#BE&0gj|2k1kMtci?}S7B+mp zew>Q{)C>eEV_ktB_I|;Q^{BeqTP6kud8);DX~N{zqux2jjqitH*}+(xR@33)NppMn zT1}@{C!;Lar1q7HM}MHZ6|@V7Wsxud8)z@7j&OL0Xbgvar@5z1Q7?4pxw(DYkMX}2 zv9P&b{9eX&0&#^bq=kE7OUbE9?9?n6XDB>sw;CNOXevP6Suko~uoswx(z*f3Udszw zJ+b0&`UxQ#MrO3a9Nb&Y?L{!7z}o=3<1_DCStBQ#t%FbEh7n*8FZ7|RAbkT0#V9M0ES|c3|4kV%+dWvGYxw38geK{15`N!?tc9fPQK*Xh# zQpbO^&8Fy~QLTF=bM}5JUU!hy>;WZbzQ(qkP#uH&0AsMtsurZ-;kg(4JE9YLdNG6x z5l1YSUdgzF`@tZCSM+W@pEOrdVK6QQ7$j`_7jHpp0RjN3BvM#VYR=ZFOl{j|Y@nQv z)H6oopkd7bWlI^LqTb&U18>*khs?T3UBSE3X6V+U{C<< zU3B5{+~=P=X7-@RT(#?C`yQ;vtD_<8R#(HTU`=~Yz?jqVt`I8ffVeXof!G^SrE?lc zbrLJ|$S&^lT46G;y8R-)|9`!q@}I{SZ*apnp3BHR4E5!%h!0~I0wufCQ*knE9RLes zMT|hPtmlPsEZ~2%Yrz7r5EDl1lzt!ZS1F)|0Bh$+=zkR@6G&dTq6mjvblj z*;l3lxF*Ju;t6dUIE9^*#}m#z=w)3%K3=&4QLz*gSqBxQVPF1jk7(fGQo!Qzwv4Rl z6N-;7|8H@89^2_lTF9gG(x#AnUGpHO`nC$i9?@%JCG2Pg!1t$9OP~tikrh-&)i_9kTRSCj(BEpIt+LZv6wrXZOr+ ziG1-nwfjS`sFNZW^eskgqUVA{@#3(N*Y@|u(C+xMO*9 zm|2ngpYF27CIGCTlTwZPKxgQEN#!a;5ElZ2u7CpV%0o(ZOc#ylx`)mE;17PDztQ=_RXQc3>6mNR{*1I4Hx zAG9P)q!dXg<3#5sxeTA1NzEoh#kVh#j0%jI;N*Xmcd;mpI}N9a(ZOQ z6maeJ!utrWK2UsO=Suc=xS0z(TaXq}vv1o`Hp#bfoLFTghdfh4pe zpm;hQ67?x?Do~#hQ~A@*Ty)%?!;b8D#CphT{2%w1yBi6q-~Fu3sWi>G(U5Qgo%Y(B z_Xf2LhBgfe_otUP!Axk{pVXDLl^f*S3GrA|OXv$ns22x_69gQ-{W*1pgZd0RLj^@O72Z z!PibfiTd!){uM6t0LX|_UNIa@X-GV3JfiD3XodR)fR1)GceIF0DGcID3KUHjXVJO@ zn=byDpy{MH)x{7&p;mmzwc@-gqr!jik{RYLZ)DVLx_)q{vin&2a4d{JE>|jsF_4vm@Bq zwV`XJCL{{iI(4?R=Ib?MNmps7oeM7&?)snIhlII_w{|=9gIDvA?gh(0hH)zxcma%Z zcRnpIorp@4zIhaTrDL4(r$~;19dkn`K=20BASm+kopF{1;k54=NDuJvoY2>&`6qqnP5UZ%+=1LiD8#^I{U1GZ&%_;oG#_m{ESu5aLUo zW!q^Ea6{>ha%$JTWD4H}Fi}04w))IO+f|@5$xzE?v!+_}jeF27B1}+7-Hu(QHjjrh z3^fAu>>-1-LF%>s6$Uib%hr2qO-l(-z0+^76Yq zcmiO5nc3iLZdmn=(i0PdG3Glkumvk(BDkGm)uq+8o-+<>#ntdDLDoF}^rx17C!IvQ7bxKgyC?Z+sD)@yzH{;(s@w;V>t(FS5BTQ->wC&v!C)+F zdDkRx(ZBvGfgAYIG-^qH!FAtN>^6Ea(#U)ZW%zds_)8=KW7__i5YGdYQHbDcp@x;@ z!yYK|Q^0=9(8NJXZ@ehwmUu7-bByTr1;?`zIO~(T35me;=FdCG--rEDUiYr?R95Rd z<#I{R9-n4d9JD_6_NbJuPsheIe_<);dQ`_?n8VCVU>N=(?QC0?9LpMrco58RV~9Zs z59D4eDb5D(bS;(woH8-LwipcvL5c{i`_3`HgY3;0165)`5pDd?rks7bB4A;{Fc}zb zt8+Gj;FAOvY7<`E`10)f;6V8L7jOSD(8E^nl%umykO_Fxg1?3Cbh`g(!CqChkqQ)Y z66iT)FVAQ3(%5`FYKz90U+|{qxra|r1U-r56hL4~83KYqx5F(CK}X6BSZ~I*q0PXt zrZ7(E&{rV(ho&Xym_L{PwKt_PVFmj<$;+&vdb~9$2-ah4@%HmqAhF%0zD*DxLNxyq zrB>z#Yj4V>GO`Vi6mB9x+E;|Y6h^%gGR+_18?_JS_3?;?lDA4Zsj#{9zQ!>0K0@og zsOI4xC9y*L);^KH>q2tWq)bG$+`*IwsIAK6*1xsiFzn^QVJHT z_Gb9+2uWvVZqAAPL;q8}Al+Ue3A_BXRWG37R&%TVqVcSq1iB@cg6YIkDEG#}kfz{s z=aR=$rg&*0{Qja%FOVsBP|>?Oz2%Tpx$(nG3xf?rXSyBd#Ltzd1%JbF@XUV1cb?6F z^;dhQw#ad7f66--_L{DL{Esqw$SjPg>|lu2>eJT4HPW9QLIbNBd7Bnpms;fzM_mz{~A=)ib`X-p5=LR~LA=C8l-+BROyU+r^ zy48Z6C=RAvar)`NZ)We?u(Ede*woS;&gH6;g*CHTTb8s?^oH;youi5kv5@|yULX&^4_SG7J%cnR1!_Kek*=k}V`hd9qRbEglW9%#z_WcftaPJfocMM9079fd?V6*Vz=~KXP=0g_>51-DRU|L zfC6Uetay?}y&^q)jfGcSZx?(UQ#Pw|xmx$R*A_4d%u`kFdPLu{nrV*qTHT@>gkF*N2naYWo{cx2xuCy(6oYoA`Q*ou2Fg@Y4U{DXYV)p z_nM|iOwR^$dE_gIPSh^Xfxt`OAPtwrpaC0Kr5>_3@52IY$+GgOhM0K6Z?j2%csO-9HVLJ z>Ns#Ub%`@B=20;KAO}QxhXcvYcd*9Xdh4=&`Sg|0M0e3m1K%BYpNad-M6(DXq-Ba$ zau>j>ne5_51IXVcL@wAN%47pd`RrdT!st*0b-X?9l~;dc7#r2^`yuJm$L~Q4(dNxe zpt${rieG7jfOLFki+QjkZu|A&r62z@-glKXp}S#yw88l(z^GQ@g@U=}3mf%=2Uf*g zCgNuzB77x#Ad1H$TVIe)1(2u{d&=ZA{Xq$YYL!Q`x8k9x)D&pFRQDRxU_A`L4okW~ zK>YzI0O=cSW7bojbsOg=)zP@(2Egs>o&I5Ltx<;j#!u2<+A;&Q0odS)dp0GS;THWB z*NuypLDK2UR6OdE#h+1t4kk@{qK?KG8K$f#;sr{ie%O%!QTDkN1&~!e#*)<8b+&jo|)$I zb}Bj?FXnbSu1N%e08Sdkew7>r`iBm&D#H8Nr(M5x=D|dQ)GupNX`L!FH=qiua$Ax^ zU=Qb&VBtuLod5fp6=&8^cPsKCgYibLdj@x_vCNCL;qp9t$aBW*o=)eGG`9 zv*l5>;x}C4XdQa`!_dGmPeH{jRG;X`$P1Z7f)|u1a{GC!C=*eJ4eV@E38{lkZJ+|( zu&6s-4=p@KE~yh9gj46Jt9df2_#YFE0>uacm^$O6k1i@qmen$?^y4cs71phTK# z$4*nCtMyVp+)%OMXgC01;*Co4#nT+?55Y?Gms82c2bG0D|RLEy^+V^Tw44k z?22tt##_4l!e;M`y&w3}fzcircaUi#8!Qd}yMVYGaY(ofL@-zMP`R|{Sg)qX-GY>hs?;lB+Ae5RZEez<+MA z`tG-r^8tkWGc8W4na7NCDYNRlKqO~YMUQvumPlm8GyH$4u(pp(1eX9nmv7!Nj!Q{gTOUZCQ>h4MQC6>4lc0`70c?>TbSSy%?JJzI(&h z9b89TfPeiv0y&n5Y^;pdh-!TrEgm1qh!m=;wKj;HgdEXTAJ$%v|1;fH;rIcmx0%^e ztwMqgkZ?Qk#0b~sb!UwYAxN<~>-|bjyVzK6de08#0bR2s$KdZV0NXd1ctb6zas!Oh z69*aQ01|4ih>`lBO9I_z;TNa9k}YL`(CRRTkn#;avPU&qsX-@P-gbE0^<0nUg`=lU z8@w-zm7zhG`q1s2BH1mn>5t5-m_qgFT|^is$A0&!xaUvg5lych1b1=j~8 zBT)g?8N#QdN+t-&9{bZMCC42l<%$Lw2tN$(Z>Af}cM8P4m+BMH>_s8;kd!C54>10a zTj+L~kfHu9Y#jhiA>^uoN_d4e!buF(R>t%S-<7v%YYYPQCFIVe`L0R)xY4RDpe`|| zi5dyrC5a4B+%whDbjGeEXm7e#%MwIvqmeRwUPilNCXsf_bDQU~*wt+x8v}A7Q8?as zQSQnN^X;Bl^qWS+u{PuYKgXU(GSnhwP1(WN`=i(o3hSDmZYe`R8`(Sq<+DYef?`6o9&{)mv;;0rc9c-iC*|>eQs9jQb8h>Tk93E$Z&<( zbF^vJ@76^vJFtGoeyfv5@|ec~lxe&e#Wvo(7n zIAH$FmSnmMIOWT!*e{`G`8*J>kf>~6G)JU{tN{F(w)Q9TDiTTPm0ZxzP=d|TOlt;0 z_ROf@Us<*tcVTu`eK86d&Bsrajkh$-hnk9nHNK{)$nGQ!#Pz3^uLwhnW5@1v${Y;Y zS|2EljBEs~2NRDQf3Uu8*(-5^GJ+RFHo+F#lDHE^qX3$j!bvP7f@`+1mXtF7$Zky~ zxunraAczk5q;F-$`9qA223I{>HsTBftUaenUq)xixx3?WbUH*sjz7ijp-SCwY$1>S zVr3+f;J(2N)VK?sr1#>s4(rz6t*iJh3hSo;AwTge$%<2oEi3yQTsUEOaa++ZXV||rAk`oZrj`dp zRQu+=a{4F;<%i)kUE}9e9zBTteJ3>2tVX(vPy8470<0<3wOZ~ua zfnfrr1YW11K8q?cmCH`KARz_J$2OMm%7FM;u{7DTFiYWB!KTM83|gT@iW$j8MgzJM zlQA-KUAWPUe=&-x=Et*w_xgs9f`W0Uw+d&O5^K>?D&X#Zu%qa2Ao z{rCW`giV(BqTgX=qTb3?Vv@m}=C ze;H780hKFjrEbk%NMcPOgci9p`yM3{yn zkA2iTzNSyCeBGkO_NVt9Zmp{ISPgtW#lo8}YEFlZbtf#-3I*2ye4fU;g@9tX2E9F& zLh1~sQnjy)4B?r;$C1memq_0hx`=U=X%ldIqmSFWRbV-VFVl3NN=d;8*^d_8@QyZE zCMl}P4G_;Z;g^b6X6-L5D66!221nlgINcVIhOB1E2%agbi#p#Vx^KoljJPz9NGQg< zRt_nDK?skeZ*Q-W7-(!;q{8x0z!`rhb^Nomogybvzlnqv949l0(U+2QxM_5ie9anp z@zuXUQ!Zrobx%<;ivcX1R#lM200f(DC+{gI*pjBOZ-5CJUD0eAwDf$hee%R;m_h3O zSFrHt-YLGKeAk;mF16^ggn-RuL9*mFqBNC1V8HiNuonV@>6xu) zf(6-s6L%WueAYId%{2zI) ze9sQ17}!FrCc*svwWtp4f#vbTe1@+f=ZPL3$}G&!YUpiC0}ue-j&Pvv_A+*+yjA>5 z4(6##QdGAJ>%~=QOH(R^`a6SPH{xFu=Uh9}Uv3O5X>e(GEXI%kyMW<87zRclPyk0> zRgAFogCASBlJD;J?hwDwWr0&4Sid{MBVouDI4iEy1uq@^tk;Z*ipeSLNK^6_TqpP4 z=4h4yVoOJfMtY14iue}|?wbGxqr;Aiib^8oEHQCcjrHh^fL@N3l>|lr+NKlhw~;M4 zn8-PMc;(Dy41qo-k3H^{*@`;+qN#MpXN2qjVB#H>;ihQ|_vu^fU)J3Y;CuQkz9CifU(ZchjdevPfKfX_0g?Re z`p2w{?t12PjkZeTzmXc@_rb^`A@VOJrUA%V=X5N=!{K`Y15^_5+|lZ~j@=cKQ>rvC zV~Vx(B>H^`cK2+XFlaULmv{hRTHw=_vohk@AyLK6Jp<%OUOVTgP=Dk)rKcO0ViNd53R@7hgbU5JHssPTwzXK(U4$NGlktXt=dPq1NT!u zaOi~JnDj7IKes^fp%K?p1f;mUBT_P&ZDXd9|49~;7pppKMe{LQeuJAxa{l3MuZ!i) zO-DO;$q2WZ!c1`@}K-7sBY_g}+p~$R+s{iai%mPVcUMhu z(JV7BO9sx`s+U%csW>#_dYiP(=sl`|$KK!RH)u#7SWKAshbXcSt{9$^!{!l3^mJPs z`$+Mx7q9yB-Mcp7!CVCV;nXL%AF7&HZWTdshU_aAs;Q<@Amg%}mZ=kED&=-rF*0^i zQj6N$Xq1jC&r^T-KWLwNqK|q8S?@6tt0L*saLa}Y%|T16Gy>kgdZUWT`DMBx&=TKh;d@$re;C;*??`d) zcksCAa$Ye)ob(YyN?d8#JteS3ue+RG3ooC?W4>4NfHG zcOI-HycX?7(~ACYcUp?sS}wp}kUpJ`ALw`q?^ZGP#WmX^pA zZ&dQO<(tMo1i&^~wRc3HoMmgw$(y=!l+_v_#uB2Qa=XP+Qy1kD0YHp45Z0?MSGXjH zey5eA;=8B|u-P5;{9sCnlk}UHasWz;4tiThSs$P)UWLh6>L=o7C*aS6%eOjQP9Ej) zu@PVoGRZ*cX}oZ2cDaV44lIV~&3kb>-mRu7-9TEZa(r3v^t}6|xVgR@le4L8mikky zPP0$y9{$`5GerZgY@Tx&*$e9eQCm*>zUqw|*gytDznV(m1kxZJz3OGN@RiXET}O1b zpN-45a?eS?-p~tXS{JggEMuh9~bK7D(uNg81~sq9i4=u~6@%voy|ET}FE zm}exA^lNG#@`E2$6bXgf)t_>32pe^8r)rFu9(gebGXo8kJfE?t^jGcYvXb=LB1oy0Ra8V#P|cpxv-F z-%|4!qNFM1aFS2%7UvS%U4`G-AFy_nFCue<)3;b5JyhQwOs_jmy^!Y~U6*Y66f2$3Dub2_p)1cL6O*=M zz**`0f#O9`FYgyH-;y`hOTWg|0Mxf{Aj2N{C%s_;bpJ-0|4p^0`Zf`FK(0o+z>L}H4M&yd3gEqYwQ^* zl7zb9$5roLoCfs2UHDu3N`T=F5oVsoQUK1Le>DntjA-`iuCbW?FRDDFu0G9;aSP`? zr$9n{coC2DXF&5#Zmfdfm3-LC}lufaB>JiS6e~Jgv>}S^I#e;h%6uN z+}pDcsodq@&B}Z5o(oAX*iX{6Aq~&5YE#AriVgN^fQ3rf5HeAbdoy`Yz%mYIw~*#) zS4JPkl;+u0@GjytFG(Cy&ZZe z+tCUj{U7VC0-$Y?PNLwkFh;1eP|nYi^{=fX%>MW_rH*M;xz<0cxrGOqe$*7h=#NPE zE}qc?ZOC*twaQ8<(Twe!^}tOJBNDC}_R_>+V7N$L}#yh-lsiS)niZuWmL zG0b>ICi&(t`*(z~T0wul_XL-{VY*z_z1@;5$QF}FiQ`x)wt@H4VYOZ-%(8vF7A|MO zTgR(%kW7dw++0N zTW^cqL|(KU2T8K`f_YUFy4n?A12MnAjn3*>FmYSis#Ls9H`RShJ^7t@eJOs>e%zdM zmP?jYI&p?Y1H$W$N*Oi|I!ga!j#|HwNO805?-ewCpXa{@{Y%qJp;Vjr>FIZpJVB;X z8QzAC$yaRJ2WPa^2hdxJXPH#%yQi!8FhI=lGmF(pftD2|-N0mYF^}1J)m=SqzCIQd z%SU1}R=MTjscpk`^{Y-N7@U{T8M<+ptvr|=Rl2=$R0YOM@N~LAk7eBLm4pf^*3o#m z6vGiJj~l_AE%;KdXG76QDmfRAc(%GC8rCaFmPIi5sGhz{fh>>kD?L1(j9L4?>g4v4LgN*rLC2_X%?>qCSH%8W9X%d0#2TE&7$ix-|>1up92;Y8+$t+SnKn(rMsrEXK1G zxw!ClX!$yBSDQazzlSV|Popi)pLnhtyB9D6VH^E~$e!_TXv5b7D2NDPlGW}H8|&G% z<4M~$|0{I~;Ecc*(M(5kY!g!tK4+TTR(Thegl2ViHQXh9C@FYZU-Yy3817s8+@vna zT(F1Rr%!XE#?gwPc$)XN3K+jrrreG-WRT%Xw9!%iqRXW8W8U8?1ZGoIV$o>fauwwL z5|`Q%{ubuwg%hO%yB|hC4-qk_cb*YK|6vZ-RbukWo^FI@JH|@~UVJ?#N|V=1r)t2a zg^iRJF*g0H9oQoYsq5U;TgQ~52!d`rulju@)Lz>-yGesx$DaZNNM}x*1DenWTqm3L zr@nN@$FmsCH2I)*^q*4b@99ToIq3s!E#)hmg){WYj!+3+A7z^gl$y~ptIzUOTjV|A z5t2(BWV>6dOADMy?Mz6GG*Q$g6%HR$GV@pd7L*`3x2I2B-c9GfH`*pzEj5Dxq%P!L zw-}Z{<{d~#yzLCsRcGUK#43SDc#5AiIVEc_GST4=-;Gp4GO^jV`M-ENu3U^wsXg9h zAdT+B^aSDZRuyy`oRe4W7?m;*w*XNXpj(pRk>O&jdNzcab=i}pZt7scUrlT*4fNf6 zQyp|el`hp0sa(O)?tQBx-r#~o1z=XdC8{xc(~xvIr-L(mVZ!>=7+TXAkQ4yo67?lU zKQ-9om1ztbI$8|~AIZQPvuP3@^nh>NV-{~J@v#hQK$9DNCQH-UKK!OC*Q1#OfxKM2(HY(bUN~|_dWHHcN=K(?E)Z!@cIoW zT}sy+BuDAuMRrM>bJ*kaJXE)>U0mw@e011&tB+NTj2pd@)s-NZ_-J2WAQY@t`_-5Z z#zY>4Zu`+QJ-_|KFb13lg*-Xf;T_FIVaSXe#{Dri8*u=AQ_MGg)tc&1zsjHEpop_`i4$r!`s8@aukm1e(Sq@<$2-RqOmbzbAQo5##rq(Bk%=V30aqovo5 zi`Uo+(Od57nEb5^7>n?+FodRuNKH$f`ar;(uVPhz3jBR}WOJq3AoCq*xE;p#p>SE%HGCzGr4)<3bn zFsc-bSAdGk)n5*e?`r<-Chf-An6E5KW)_IIAtbR`sG>F8FGBM8S02ce3$qS%wNCqT* z5o#QV&K6Ba3?LsfM>jk>!FCrR^;eYGt@T?=P)6c0aVHC0s~}fyk%s5b?sbn0x~{BC zuHw$}(_}jK121a#@VcUEj8HIqsR+&gCz{9eK{{O;u644X?W#c4s7w1q;Z(}wT1{zA z%xCS`1JJE*K_cqCre`g9;04H^-6IseZ*#KJrI%DFK?>&LiGH%z>O{k{uJOk0Kf4*ox6!s3>>1f{7+ip7W@es?E<0af|}j zCNvW|6L+M4=cY8uUY=%`!r}m+G|-Kl&4U|`8TMG$%)vOAt*fNeU5}l4GqWj<_b1JP zX$P!{1nsm@9L^&~i#;&Q(o!CB0ZsV?=sGLhF6@t0^68THef1t)S;N?9=sGvhZx;|hSP6rU8X%%XxtB7Mpjl8=l%=Sh+z(_PRVgc;! z){!V?pp38~`*KU68w8k!_svtAa)ZKL-3gzw~A)?2P*V ze(0BRpmLZ41il_9nEQ;X8=W2p$Lo&d__;{w(%2Jr+nZQvj*GO=X921;j($;XtnillW?ibCM*y5^(ImIw|b1u^|7wy-?(qXH|hlod1X=&7Zra@+6P~r`9hN=0D1L=I z|4kx!B{YOdG@N5#ObTEa0rqsAp2(%7ggA=Ge=e}rV^u`7VKUUz`Zwb_*P2p#K99kq z0Kd%a2QXS=_UrlFf);J{yImaykAU=@QEv4~;B7z|+l-5r@LH=TE>fdn5=OY~Rjo*_ z;IL@V^zqi3I2@4-WWche(mb92Xy$%LqCS{LcG>AL6glVWy&$YDuWbKxTUfF675y0m z`JT}7uM5QX*UPsu+UYdq zGEC1jhHK0qZeX_>gv)>?b6glfrs6q54hU{I2HvChhG~2_XrccA!>w>~NmyE<5uiu$ zOC3GmFi9ZMB;ICTAlLU=3nOV4g#3tak57V++BA(Bsqp!VV>dlc-E!Ef%Ebm+37dZ2 zJQJ+=#uIIIl%O|NKt?g;0~?m{I&T)fDTCoR9cF98X)9r#9T%>CL~`UU6K;NC_zgfK zPi*Vg-mm)Oa{#9rK)2ga)spZ71US*~wLU_%cRFzpEgdakkr;}CO137C%szq&)H;|W zwBKVaiF0tp&rLh}T@XeT_GXt0T)zi&mWbddX&cO53N=j}qo$^(#)Gi;%;Hf+#w4-iO@ppA%3Xz0M zQ|GD8uCk*C=Zj*Q&a0uBV6sEfAIuT7L>^h#CxaZDL**`>eXQWkp;GzkXQM0p;T=V5 z@*l+vd(=w7u7KX|BgH-@dreF5HGx2pt}=_B%|d~|znXmBnFD4jJ~f7W{oM}v+PG1j zGa^x6@~_Z7L1i1L89lM^HHS?}`Gay%HcK79W^JrIwp$LVzp}^|+S3M}owcLjBj3bF0cK z!dnYE-{ScY7Qq}xl$(XOEzZEb{r9;*ECnA4RbP&CoLnAUualXEB!tnm`;zN9D+-`! zd^bpuDjcZ^ME@+p%FEURv@e9Na274~+^FO2avgnE6&nY3ZhAd)jxpWH7I>gGMOaU9 zva+~cbMNDu#{R2NF(=3z+>jBPfn@SM0A7GsLtYG!8O{y57t4UX$K??I<+bSZ4=alB zmJ9)vhXCh56JfTZXz6?{qtSqNV?}9JZb4xl+fL3)Tf#Kz@S22=TNt5bSq^u3ZTFe1 zQa*#w7%Tv90@A%u5l0D)Uq_jA;QwE@t8~v3Hzx=QAmcPpBvH%n4JMufEMQDx3rw^5 zxQP0g=&y>vcg09JapEl4h-YGI5EWT0Buo5_W!4D;Wty^zlB~8zL_}p(9_8LRhkwfY z4(1<VnX zc|zv+J#oVCBYr^Y%qltyC=j&wJg0+v{vcVB#g&yMx zMUnb5b4(SAIy^KEViyiYdVW-noX(nFiLD}7fc-RMToN?XGuY8^oU$3GtoC1wF2t>E zLfb#vQJw}>JgG|vE*dr!Sa%jLrV)|6JH;nT5gxLjTXj>eXKX5K2)co+w-!x;bus@t zB|r5KwX+uxI&N<`BeridL*{`+rf2Q=;r9K=&SoPcq-S>> z1oI*JItnR{(VP>gjq1NM4Dz_HRzL}RGG`r9Un1_d!k6Rj%&DJK=|t&(lap_9OBCH{ zc^5!nGHUq8>)x4dug-4u%;-@r7fD!wwn>voOwz#!(Ekyg&AcM286`xwoc;}X=Itea z2n=Y+^WtIP&~uj5^GUI-ejXbMF(Ox^VNUh>))#{F)O3lKo|mvq#Yfc(V>M-}d&J3L zh4>}SSckI;wm@zmk$aS~ugp&}j6)BXNMiHRz^IiE=)VOM>TUG2j!u;%ZlJ3KX?M%f zL>0>A4>FgWFbNP^zy{cC26nW0GiFx1UtUqwwO%0*&+(4XyCC3J=0=#d1W;YCNfbJu zGVI@9E89;EbN_j31xO}9nIQGYCWQWnw7J5%b162VesMEG?!duFG>?{^XnJb}`uhNe zAc9FXaZf{-F54=ip|e(-p;|_F&UqyMBs>1(Ti?0K-Rfv>xA`_{nc4X>*>A+jcXH=P z8+2{C6YKezeX2NF)5xf4g%*Gl1Q~nxnL1g+!&PF#bhlPbHb9g0ZmUiJGhzXh`)(nC zeHEqSP<>)OWDo6}O-_DNaLJ0w2 zL4?n3kYw|KNRXLdbRvLo@$-@{a*xhC(FyO8FJw+?TL#NpBh@)}F;yMXq3WJLq zbL(8()XDZZ2hiytoJI4OXBCGBa3Go4sg>0X2#V|ko3iiNo=96T{yFVhq_z921Z~$b zi^^>mgxwQDT=u+f;W%J}t~|&-F;cXC=nH2HC0rUoARZWRqm73=M25J>$a-~MH;Zg* zRLRAr2TJcvhl^E1fJ{DkybaiN&VR0B-eKKU)_qrlKj&QKZ# z_1O%8n6Y;sL~uyy>Ufq_Q?P2C?LFzX8lm9U$EmX9xfVi^ z>nX8>{B5ARl?}`IN>tnkSXBF4{uhmO{>lI(kwG$+N|tWpBVGcZ5h4#f$H~&U%`KsZ z0(I&M{l(y}^FGE+#Fi2QOd>l44~P5GWA-cEw&mL8V%+q9?Y4t*`MHdUxN%$eRi*E} z?;m8nN#SnwH#@kj&FeMzm5r)tG24DgqHPGR&g7CQOAi<3AbW`uCwB?r^34xFs7*1r znix)fY1Gr=F*)nLRfLTb$q*|P8f03dl!C5vF1gkqF~<#7l5PN7fDo&U9Y-MQns4h8 z|3rNr^`%zwA@#O>#GJvqm%R`Q56N{^35^A}X4{HLn3sDx@bD0XfOF1va?{6A%_l{# zXreuGZiHegnfU5C8z_y5$gO@esNyF6%GNv1L;neo30hE1o@+|pp0SdzVco<_o!7dQ z+vK%LaTNTgeyk=BT_iXBnPGO+lGpXBwwx{gtUHaDM1KD{)#pj+_`kG^xPT07j|L%B zsw>Bh%ltg(ffYQ{dK_pi7d+KfYv((6wt;T^1?_DWu5*lKLN20W>bsfZLKaC z<|%z)06JqbkZu6aZuiB;m;fhv%L~WvJ56|E~AW&dSXBwd7CtMboqo>TEZ89WFr_SW$(Aq<|lHQAF85XvRl-skARaCqGi8uuE&f1{AmO#?oT}Zqq&h z!pr;3T11tE^3hZq}*rkhld??p)ytu~U9(ciA~qAFa-ZUoCK; zk0>bnw#0arOUeHtsC1+elghv)ozny+t~z%tiVVbVF~^B*q3?h=8Z=fu4z^k1!7jeJ zk{o5OT4{D(RqRZ*uL!`PbMNi6ppq8}$Bc~NWW$^V(R;tFUqPzZ=7md`B=G~E;4?7%ja6PqY$j`hf zTCj1vr}wV^UGv4$YO@J6O`l#C1z&wzHkJ~$Il_+t0(zJ1zOi3lIjvQiI~>IYzKC!V zWuuR94|4)|MY4QYi3e%kuZarZ5S9oMH@wD1;rbUJQ<2oD@=;)df&9dy1HRD)F1j=S zN+hw&hnhIaJxdArAzS{tbJ^}1wtvC%HGBe!g+`P^05>C@SR@|dK=&8e!%pT#LYC}a zCmNOBVMn=wl^Oo3=qQi^(iG;c3Y;;acfI7r5u^HJY=%Xg_`LOf`8-d0^88bsqA#;^`G)gUl zTi&fdCB&V}UoWU{wO3?#+?Q`a!oo1@2+5Wu%X_Z53%8`cP|+(A3CI0z4bsr>jY}vZ zNhuqpH&%NjatfNi1EUlL0}|!Qky3;4ZpZ^L>Z(8*<3Lb#_1$=|D4h(fv|wS8T~wwq z<_r^^{3GYqY%uO)jrp3{^g4+_YmM3BfR`1{hm(Q}$R4@_80`<%-xIW1#Q;Havzxx% zp0%*yQJ@KK-Ic_GfT95=Le|Q|tZ~f|c3OFoBOlZeZioK&7R?^dY^TZgrv)k;&P1910sTr=NU@Edc0exKAkTEnN9|ovySxk>s3# z2Nz$i3P9SpfWqLuil7S1ABc92ZieHzq<{m?xq;9Z{5sAe!&E=yIAi#jXvdt^O2LqJ zZvlg9NaclVJ;#TF=BNb3mu*OdC zQ+LO#3T3&SYVVcnGeh_#7Wm9Ba##jbbb5Y`4+%yC2t&wFq$N@;&69ZC$@31)PwRvj ze|3Ul+Tkvl&c4>$Wf!~gNL2QJDpvQ7*Ere;7<)hEdyy|xPUIw-jk9v`I+DI<2@whe z8BrO~opt&Vi4){er&34TRoHVH36}Rv?O2}$;iIoUifGPU1bP(EzQ)~j46Vy#>se;u z#qo8}PuXEL>wEm?@PJhutktGu#>5qqICo>5Vg+!8DvpjSp z4z;GnNr}U)K>Vbx(>b_ojOmYv+bcjqO_<>8$er^H~oe*`WG4RGq9jKnD ze&rVCQ&+n*O>5S3goDv3?eeI*=L3!CsO+kz8a06A@6GtuNP`kM)UY`z(RcJCQL`t3XDQ+GFGmist^jc!n>(buRw?}YCf*sBpj(4G(eg(NN z_-$o+&K|JFj7pZ&@@QFJ>?pfsvp*Qsdo}W7Va<&{kmLIgGjpfEIgv zp-#M4&}Zc%@M|M6L)RcNON>abjUvzvj+0|PZ?#~iHXGw|Sw zLyrZSFJXsQX$$=5gL(ja8v}AFOyvD9wl7^whA-U5y7@P4mK5oHMC3l?GUWZ zWWxvUKL%5Uw!6R;sH&|4FUyA}OhQRq3}Tp*YHE%{OIU+la%l86#dm?9�LuQBYbz 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πyEV8FQNTI5-VSPy&J;n7hC{R~t3ee-3xXF9T0 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%0A|Xui;Bv+yaQf~-_LLN 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 Date: Mon, 24 Mar 2014 18:36:17 -0400 Subject: [PATCH 171/326] Updated for upstream openjpeg changes. #206 --- glymur/test/test_opj_suite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 40bfa68..efbc794 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -3447,7 +3447,7 @@ class TestSuiteDump(unittest.TestCase): # Image header self.assertEqual(jp2.box[2].box[0].height, 400) self.assertEqual(jp2.box[2].box[0].width, 700) - self.assertEqual(jp2.box[2].box[0].num_components, 1) + self.assertEqual(jp2.box[2].box[0].num_components, 3) self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) self.assertEqual(jp2.box[2].box[0].signed, False) self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet From faf49b11c329dc56644ae38bc4450ed83fe99a7c Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 24 Mar 2014 20:54:49 -0400 Subject: [PATCH 172/326] Added ability to wrap more than one codestream. #206 --- glymur/jp2k.py | 43 +++++++++++++++++++++++----------- glymur/test/test_jp2box.py | 17 ++++++++++++++ glymur/test/test_jp2box_jpx.py | 12 ++++++++++ 3 files changed, 58 insertions(+), 14 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 1ae7839..fa2dfee 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -18,6 +18,7 @@ else: from collections import Counter import ctypes +import itertools import math import os import re @@ -606,7 +607,7 @@ class Jp2k(Jp2kBox): self.parse() def wrap(self, filename, boxes=None): - """Write the codestream back out to file, wrapped in new JP2 jacket. + """Create a new JP2/JPX file wrapped in a new jacket. Parameters ---------- @@ -640,10 +641,22 @@ class Jp2k(Jp2kBox): height = codestream.segment[1].ysiz width = codestream.segment[1].xsiz num_components = len(codestream.segment[1].xrsiz) + if num_components < 3: + colorspace = GREYSCALE + else: + if len(self.box) == 0: + # Best guess is SRGB + colorspace = SRGB + else: + # Take whatever the first jp2 header / color specification + # says. + jp2hs = [box for box in self.box if box.box_id == 'jp2h'] + colorspace = jp2hs[0].box[1].colorspace + boxes[2].box = [ImageHeaderBox(height=height, width=width, num_components=num_components), - ColourSpecificationBox(colorspace=SRGB)] + ColourSpecificationBox(colorspace=colorspace)] _validate_jp2_box_sequence(boxes) @@ -652,28 +665,30 @@ class Jp2k(Jp2kBox): if box.box_id != 'jp2c': box.write(ofile) else: - # The codestream gets written last. + # Codestreams require a bit more care. if len(self.box) == 0: # Am I a raw codestream? If so, then it is pretty # easy, just write the codestream box header plus all # of myself out to file. ofile.write(struct.pack('>I', self.length + 8)) - ofile.write('jp2c'.encode()) + ofile.write(b'jp2c') with open(self.filename, 'rb') as ifile: ofile.write(ifile.read()) else: # OK, I'm a jp2 file. Need to find out where the # raw codestream actually starts. - jp2c = [box for box in self.box - if box.box_id == 'jp2c'] - jp2c = jp2c[0] - ofile.write(struct.pack('>I', jp2c.length)) - ofile.write('jp2c'.encode()) + offset = box.offset + length = box.length + if offset == -1: + # Find the first codestream in the file. + jp2c = [box for box in self.box + if box.box_id == 'jp2c'] + offset = jp2c[0].offset + length = jp2c[0].length + with open(self.filename, 'rb') as ifile: - # Seek 8 bytes past the L, T fields to get to the - # raw codestream. - ifile.seek(jp2c.offset + 8) - ofile.write(ifile.read(jp2c.length - 8)) + ifile.seek(offset) + ofile.write(ifile.read(length)) ofile.flush() @@ -1301,7 +1316,7 @@ def _check_jp2h_child_boxes(boxes, parent_box_name): """Certain boxes can only reside in the JP2 header.""" box_ids = set([box.box_id for box in boxes]) intersection = box_ids.intersection(JP2H_CHILDREN) - if len(intersection) > 0 and parent_box_name != 'jp2h': + if len(intersection) > 0 and parent_box_name not in ['jp2h', 'jpch']: msg = "A '{0}' box can only be nested in a JP2 header box." raise IOError(msg.format(list(intersection)[0])) diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index d537316..6f3bac9 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -58,6 +58,23 @@ class TestDataEntryURL(unittest.TestCase): def setUp(self): self.jp2file = glymur.data.nemo() + def test_wrap_greyscale(self): + """A single component should be wrapped as GREYSCALE.""" + j = Jp2k(self.jp2file) + data = j.read() + red = data[:, :, 0] + + # Write it back out as a raw codestream. + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile1: + j2k = glymur.Jp2k(tfile1.name, 'wb') + j2k.write(data[:, :, 0]) + + # Ok, now rewrap it as JP2. The colorspace should be GREYSCALE. + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile2: + jp2 = j2k.wrap(tfile2.name) + self.assertEqual(jp2.box[2].box[1].colorspace, + glymur.core.GREYSCALE) + def test_basic_url(self): """Just your most basic URL box.""" # Wrap our j2k file in a JP2 box along with an interior url box. diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 98eac45..d9d697b 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -23,6 +23,7 @@ class TestJPXWrap(unittest.TestCase): """Test suite for wrapping JPX files.""" def setUp(self): + self.jpxfile = glymur.data.jpxfile() self.jp2file = glymur.data.nemo() self.j2kfile = glymur.data.goodstuff() @@ -44,6 +45,17 @@ class TestJPXWrap(unittest.TestCase): def tearDown(self): os.unlink(self.xmlfile) + def test_full_blown_jpx(self): + """Rewrap a jpx file.""" + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1: + jpx = Jp2k(self.jpxfile) + idx = list(range(5)) + list(range(6, 9)) + list(range(9, 12)) + [12] + boxes = [jpx.box[j] for j in idx] + jpx2 = jpx.wrap(tfile1.name, boxes=boxes) + exp_ids = [box.box_id for box in boxes] + act_ids = [box.box_id for box in jpx2.box] + self.assertEqual(exp_ids, act_ids) + def test_jpx_ftbl_no_codestream(self): """Can have a jpx with no codestream.""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1: From 9ecbc81e7aadb8444d1d72519b3a2dea9aafadfe Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 24 Mar 2014 21:26:04 -0400 Subject: [PATCH 173/326] Strengthened the test by reordering the boxes. #206 --- glymur/test/test_jp2box_jpx.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index d9d697b..f7cfec2 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -49,12 +49,16 @@ class TestJPXWrap(unittest.TestCase): """Rewrap a jpx file.""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1: jpx = Jp2k(self.jpxfile) - idx = list(range(5)) + list(range(6, 9)) + list(range(9, 12)) + [12] + idx = list(range(5)) + list(range(9, 12)) + list(range(6, 9)) + [12] boxes = [jpx.box[j] for j in idx] jpx2 = jpx.wrap(tfile1.name, boxes=boxes) exp_ids = [box.box_id for box in boxes] + lengths = [box.length for box in jpx.box] + exp_lengths = [lengths[j] for j in idx] act_ids = [box.box_id for box in jpx2.box] + act_lengths = [box.length for box in jpx2.box] self.assertEqual(exp_ids, act_ids) + self.assertEqual(exp_lengths, act_lengths) def test_jpx_ftbl_no_codestream(self): """Can have a jpx with no codestream.""" From 9b4e0a10fbef257399292ca469013bcc91d88540 Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 25 Mar 2014 21:17:29 -0400 Subject: [PATCH 174/326] More testing for jpx wrapping. Needs some refactoring, though. #206 --- glymur/jp2k.py | 39 ++++++++++++++++++++-- glymur/test/test_jp2box.py | 61 ++++++++++++++++++++++++++++++++++ glymur/test/test_jp2box_jpx.py | 15 --------- 3 files changed, 97 insertions(+), 18 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index fa2dfee..38e6973 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -607,7 +607,12 @@ class Jp2k(Jp2kBox): self.parse() def wrap(self, filename, boxes=None): - """Create a new JP2/JPX file wrapped in a new jacket. + """Create a new JP2/JPX file wrapped in a new set of JP2 boxes. + + This method is primarily aimed at wrapping a raw codestream in a set of + of JP2 boxes (turning it into a JP2 file instead of just a raw + codestream), or rewrapping a codestream in a JP2 file in a new "jacket" + of JP2 boxes. Parameters ---------- @@ -617,6 +622,8 @@ class Jp2k(Jp2kBox): JP2 box definitions to define the JP2 file format. If not provided, a default ""jacket" is assumed, consisting of JP2 signature, file type, JP2 header, and contiguous codestream boxes. + A JPX file rewrapped without the boxes argument results in a JP2 + file encompassing the first codestream. Returns ------- @@ -675,20 +682,46 @@ class Jp2k(Jp2kBox): with open(self.filename, 'rb') as ifile: ofile.write(ifile.read()) else: - # OK, I'm a jp2 file. Need to find out where the + # OK, I'm a jp2/jpx file. Need to find out where the # raw codestream actually starts. offset = box.offset length = box.length if offset == -1: + if self.box[1].brand == 'jpx ': + msg = "The codestream box must have its offset " + msg += "and length attributes fully specified " + msg += "if the file type brand is JPX." + raise IOError(msg) + # Find the first codestream in the file. jp2c = [box for box in self.box if box.box_id == 'jp2c'] offset = jp2c[0].offset length = jp2c[0].length + # Verify that the specified codestream is right. with open(self.filename, 'rb') as ifile: ifile.seek(offset) - ofile.write(ifile.read(length)) + read_buffer = ifile.read(8) + L, T = struct.unpack_from('>I4s', read_buffer, 0) + if T != b'jp2c': + msg = "Unable to locate the specified codestream." + raise IOError(msg) + if L == 0: + # The length of the box is presumed to last + # until the end of the file. Compute the + # effective length of the box. + L = os.path.getsize(ifile.name) - fptr.tell() + 8 + + elif L == 1: + # The length of the box is in the XL field, a + # 64-bit value. + read_buffer = ifile.read(8) + L, = struct.unpack('>Q', read_buffer) + + ifile.seek(offset) + read_buffer = ifile.read(L) + ofile.write(read_buffer) ofile.flush() diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 6f3bac9..f61bafc 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -858,6 +858,67 @@ class TestWrap(unittest.TestCase): with self.assertRaises(IOError): j2k.wrap(tfile.name, boxes=boxes) + def test_wrap_jpx_to_jp2_with_unadorned_jpch(self): + """A JPX file rewrapped with plain jpch is not allowed.""" + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1: + jpx = Jp2k(self.jpxfile) + boxes = [jpx.box[0], jpx.box[1], jpx.box[2], + glymur.jp2box.ContiguousCodestreamBox()] + with self.assertRaises(IOError): + jpx.wrap(tfile1.name, boxes=boxes) + + def test_wrap_jpx_to_jp2_with_incorrect_jp2c_offset(self): + """Reject A JPX file rewrapped with bad jp2c offset.""" + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1: + jpx = Jp2k(self.jpxfile) + jpch = jpx.box[5] + + # The offset should be 902. + jpch.offset = 901 + jpch.length = 313274 + boxes = [jpx.box[0], jpx.box[1], jpx.box[2], jpch] + with self.assertRaises(IOError): + jpx.wrap(tfile1.name, boxes=boxes) + + def test_wrap_jpx_to_jp2_with_correctly_specified_jp2c(self): + """Accept A JPX file rewrapped with good jp2c.""" + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1: + jpx = Jp2k(self.jpxfile) + jpch = jpx.box[5] + + # This time get it right. + jpch.offset = 903 + jpch.length = 313274 + boxes = [jpx.box[0], jpx.box[1], jpx.box[2], jpch] + jp2 = jpx.wrap(tfile1.name, boxes=boxes) + + act_ids = [box.box_id for box in jp2.box] + exp_ids = ['jP ', 'ftyp', 'jp2h', 'jp2c'] + self.assertEqual(act_ids, exp_ids) + + act_offsets = [box.offset for box in jp2.box] + exp_offsets = [0, 12, 40, 887] + self.assertEqual(act_offsets, exp_offsets) + + act_lengths = [box.length for box in jp2.box] + exp_lengths = [12, 28, 847, 313274] + self.assertEqual(act_lengths, exp_lengths) + + def test_full_blown_jpx(self): + """Rewrap a jpx file.""" + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1: + jpx = Jp2k(self.jpxfile) + idx = list(range(5)) + list(range(9, 12)) + list(range(6, 9)) + [12] + boxes = [jpx.box[j] for j in idx] + jpx2 = jpx.wrap(tfile1.name, boxes=boxes) + exp_ids = [box.box_id for box in boxes] + lengths = [box.length for box in jpx.box] + exp_lengths = [lengths[j] for j in idx] + act_ids = [box.box_id for box in jpx2.box] + act_lengths = [box.length for box in jpx2.box] + self.assertEqual(exp_ids, act_ids) + self.assertEqual(exp_lengths, act_lengths) + class TestJp2Boxes(unittest.TestCase): """Tests for canonical JP2 boxes.""" diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index f7cfec2..73e8f1b 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -45,21 +45,6 @@ class TestJPXWrap(unittest.TestCase): def tearDown(self): os.unlink(self.xmlfile) - def test_full_blown_jpx(self): - """Rewrap a jpx file.""" - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1: - jpx = Jp2k(self.jpxfile) - idx = list(range(5)) + list(range(9, 12)) + list(range(6, 9)) + [12] - boxes = [jpx.box[j] for j in idx] - jpx2 = jpx.wrap(tfile1.name, boxes=boxes) - exp_ids = [box.box_id for box in boxes] - lengths = [box.length for box in jpx.box] - exp_lengths = [lengths[j] for j in idx] - act_ids = [box.box_id for box in jpx2.box] - act_lengths = [box.length for box in jpx2.box] - self.assertEqual(exp_ids, act_ids) - self.assertEqual(exp_lengths, act_lengths) - def test_jpx_ftbl_no_codestream(self): """Can have a jpx with no codestream.""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1: From 6815a469022dd2cf9ff08bf671bafc528249666a Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 26 Mar 2014 21:04:28 -0400 Subject: [PATCH 175/326] Refactoring. #206 --- glymur/jp2k.py | 181 +++++++++++++++++++++++++------------------------ 1 file changed, 94 insertions(+), 87 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 38e6973..1f37584 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -18,7 +18,6 @@ else: from collections import Counter import ctypes -import itertools import math import os import re @@ -30,7 +29,7 @@ import numpy as np from .codestream import Codestream from .core import SRGB, GREYSCALE -from .core import PROGRESSION_ORDER, RSIZ, CINEMA_MODE +from .core import PROGRESSION_ORDER, CINEMA_MODE from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE from .jp2box import Jp2kBox from .jp2box import JPEG2000SignatureBox, FileTypeBox, JP2HeaderBox @@ -443,7 +442,7 @@ class Jp2k(Jp2kBox): If glymur is unable to load the openjp2 library. """ if opj2.OPENJP2 is not None: - self._write_openjp2(img_array, verbose=verbose, **kwargs) + self._write_openjp2(img_array, verbose=verbose, **kwargs) elif opj.OPENJPEG is not None: self._write_openjpeg(img_array, verbose=verbose, **kwargs) else: @@ -639,31 +638,7 @@ class Jp2k(Jp2kBox): >>> jp2 = j2k.wrap(tfile.name) """ if boxes is None: - # Try to create a reasonable default. - boxes = [JPEG2000SignatureBox(), - FileTypeBox(), - JP2HeaderBox(), - ContiguousCodestreamBox()] - codestream = self.get_codestream() - height = codestream.segment[1].ysiz - width = codestream.segment[1].xsiz - num_components = len(codestream.segment[1].xrsiz) - if num_components < 3: - colorspace = GREYSCALE - else: - if len(self.box) == 0: - # Best guess is SRGB - colorspace = SRGB - else: - # Take whatever the first jp2 header / color specification - # says. - jp2hs = [box for box in self.box if box.box_id == 'jp2h'] - colorspace = jp2hs[0].box[1].colorspace - - boxes[2].box = [ImageHeaderBox(height=height, - width=width, - num_components=num_components), - ColourSpecificationBox(colorspace=colorspace)] + boxes = self._get_default_jp2_boxes() _validate_jp2_box_sequence(boxes) @@ -672,62 +647,92 @@ class Jp2k(Jp2kBox): if box.box_id != 'jp2c': box.write(ofile) else: - # Codestreams require a bit more care. - if len(self.box) == 0: - # Am I a raw codestream? If so, then it is pretty - # easy, just write the codestream box header plus all - # of myself out to file. - ofile.write(struct.pack('>I', self.length + 8)) - ofile.write(b'jp2c') - with open(self.filename, 'rb') as ifile: - ofile.write(ifile.read()) - else: - # OK, I'm a jp2/jpx file. Need to find out where the - # raw codestream actually starts. - offset = box.offset - length = box.length - if offset == -1: - if self.box[1].brand == 'jpx ': - msg = "The codestream box must have its offset " - msg += "and length attributes fully specified " - msg += "if the file type brand is JPX." - raise IOError(msg) - - # Find the first codestream in the file. - jp2c = [box for box in self.box - if box.box_id == 'jp2c'] - offset = jp2c[0].offset - length = jp2c[0].length - - # Verify that the specified codestream is right. - with open(self.filename, 'rb') as ifile: - ifile.seek(offset) - read_buffer = ifile.read(8) - L, T = struct.unpack_from('>I4s', read_buffer, 0) - if T != b'jp2c': - msg = "Unable to locate the specified codestream." - raise IOError(msg) - if L == 0: - # The length of the box is presumed to last - # until the end of the file. Compute the - # effective length of the box. - L = os.path.getsize(ifile.name) - fptr.tell() + 8 - - elif L == 1: - # The length of the box is in the XL field, a - # 64-bit value. - read_buffer = ifile.read(8) - L, = struct.unpack('>Q', read_buffer) - - ifile.seek(offset) - read_buffer = ifile.read(L) - ofile.write(read_buffer) - + self._write_wrapped_codestream(ofile, box) ofile.flush() jp2 = Jp2k(filename) return jp2 + def _write_wrapped_codestream(self, ofile, box): + """Write wrapped codestream.""" + # Codestreams require a bit more care. + # Am I a raw codestream? + if len(self.box) == 0: + # Yes, just write the codestream box header plus all + # of myself out to file. + ofile.write(struct.pack('>I', self.length + 8)) + ofile.write(b'jp2c') + with open(self.filename, 'rb') as ifile: + ofile.write(ifile.read()) + return + + # OK, I'm a jp2/jpx file. Need to find out where the raw codestream + # actually starts. + offset = box.offset + if offset == -1: + if self.box[1].brand == 'jpx ': + msg = "The codestream box must have its offset and " + msg += "length attributes fully specified if the file " + msg += "type brand is JPX." + raise IOError(msg) + + # Find the first codestream in the file. + jp2c = [box for box in self.box if box.box_id == 'jp2c'] + offset = jp2c[0].offset + + # Ready to write the codestream. + with open(self.filename, 'rb') as ifile: + ifile.seek(offset) + + # Verify that the specified codestream is right. + read_buffer = ifile.read(8) + L, T = struct.unpack_from('>I4s', read_buffer, 0) + if T != b'jp2c': + msg = "Unable to locate the specified codestream." + raise IOError(msg) + if L == 0: + # The length of the box is presumed to last until the end of + # the file. Compute the effective length of the box. + L = os.path.getsize(ifile.name) - ifile.tell() + 8 + + elif L == 1: + # The length of the box is in the XL field, a 64-bit value. + read_buffer = ifile.read(8) + L, = struct.unpack('>Q', read_buffer) + + ifile.seek(offset) + read_buffer = ifile.read(L) + ofile.write(read_buffer) + + def _get_default_jp2_boxes(self): + """Create a default set of JP2 boxes.""" + # Try to create a reasonable default. + boxes = [JPEG2000SignatureBox(), + FileTypeBox(), + JP2HeaderBox(), + ContiguousCodestreamBox()] + codestream = self.get_codestream() + height = codestream.segment[1].ysiz + width = codestream.segment[1].xsiz + num_components = len(codestream.segment[1].xrsiz) + if num_components < 3: + colorspace = GREYSCALE + else: + if len(self.box) == 0: + # Best guess is SRGB + colorspace = SRGB + else: + # Take whatever the first jp2 header / color specification + # says. + jp2hs = [box for box in self.box if box.box_id == 'jp2h'] + colorspace = jp2hs[0].box[1].colorspace + + boxes[2].box = [ImageHeaderBox(height=height, width=width, + num_components=num_components), + ColourSpecificationBox(colorspace=colorspace)] + + return boxes + def read(self, **kwargs): """Read a JPEG 2000 image. @@ -801,7 +806,8 @@ class Jp2k(Jp2kBox): msg += "the read_bands method instead." raise RuntimeError(msg) - def _read_openjpeg(self, rlevel=0, ignore_pclr_cmap_cdef=False, verbose=False): + def _read_openjpeg(self, rlevel=0, ignore_pclr_cmap_cdef=False, + verbose=False): """Read a JPEG 2000 image using libopenjpeg. Parameters @@ -836,9 +842,9 @@ class Jp2k(Jp2kBox): # -1 is shorthand for the largest rlevel rlevel = max_rlevel elif rlevel < -1 or rlevel > max_rlevel: - msg = "rlevel must be in the range [-1, {0}] for this image." - msg = msg.format(max_rlevel) - raise IOError(msg) + msg = "rlevel must be in the range [-1, {0}] for this image." + msg = msg.format(max_rlevel) + raise IOError(msg) with ExitStack() as stack: try: @@ -970,7 +976,8 @@ class Jp2k(Jp2kBox): return img_array - def _populate_dparam(self, layer, rlevel, area, tile, ignore_pclr_cmap_cdef): + def _populate_dparam(self, layer, rlevel, area, tile, + ignore_pclr_cmap_cdef): """Populate decompression structure with appropriate input parameters. Parameters @@ -1244,11 +1251,11 @@ def _validate_jp2_box_sequence(boxes): _validate_jpx_box_sequence(boxes) else: count = _collect_box_count(boxes) - for id in count.keys(): - if id not in JP2_IDS: + for box_id in count.keys(): + if box_id not in JP2_IDS: msg = "The presence of a '{0}' box requires that the file type " msg += "brand be set to 'jpx '." - raise IOError(msg.format(id)) + raise IOError(msg.format(box_id)) def _validate_jpx_box_sequence(boxes): """Run through series of tests for JPX box legality.""" From 96cb70e5a5f4d5b74402d44516eb05ba08f51ead Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 31 Mar 2014 21:23:11 -0400 Subject: [PATCH 176/326] Added negative test for writing with bad approximation. #202 --- glymur/jp2k.py | 14 ++++++++++++++ glymur/test/test_jp2box.py | 12 ++++++++++++ 2 files changed, 26 insertions(+) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 1f37584..971c82d 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -1250,6 +1250,7 @@ def _validate_jp2_box_sequence(boxes): if boxes[1].brand == 'jpx ': _validate_jpx_box_sequence(boxes) else: + # Validate the JP2 box IDs. count = _collect_box_count(boxes) for box_id in count.keys(): if box_id not in JP2_IDS: @@ -1257,6 +1258,19 @@ def _validate_jp2_box_sequence(boxes): msg += "brand be set to 'jpx '." raise IOError(msg.format(box_id)) + _validate_jp2_colr(boxes) + +def _validate_jp2_colr(boxes): + """ + Validate JP2 requirements on colour specification boxes. + """ + lst = [box for box in boxes if box.box_id == 'jp2h'] + jp2h = lst[0] + for colr in [box for box in jp2h.box if box.box_id == 'colr']: + if colr.approximation != 0: + msg = "A JP2 colr box cannot have a non-zero approximation field." + raise IOError(msg) + def _validate_jpx_box_sequence(boxes): """Run through series of tests for JPX box legality.""" _validate_label(boxes) diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index f61bafc..1a00715 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -453,6 +453,18 @@ class TestColourSpecificationBox(unittest.TestCase): with self.assertRaises(IOError): j2k.wrap(tfile.name, boxes=boxes) + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") + def test_bad_approx_jp2_field(self): + """JP2 has requirements for approx field""" + j2k = Jp2k(self.j2kfile) + boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c] + colr = ColourSpecificationBox(colorspace=glymur.core.SRGB, + approximation=1) + boxes[2].box = [self.ihdr, colr] + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + with self.assertRaises(IOError): + j2k.wrap(tfile.name, boxes=boxes) + def test_default_colr(self): """basic colr instantiation""" colr = ColourSpecificationBox(colorspace=glymur.core.SRGB) From 6314458bc7206c10a71aa4eb61a222b506c3eb29 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 1 Apr 2014 20:53:59 -0400 Subject: [PATCH 177/326] Simplified pclr writing in most cases. #208 --- glymur/jp2box.py | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index dc1041b..cc09f31 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1912,17 +1912,34 @@ class PaletteBox(Jp2kBox): *bps_signed) fptr.write(write_buffer) - if self.bits_per_component[0] <= 8: - code = 'B' - elif self.bits_per_component[0] <= 16: - code = 'H' - elif self.bits_per_component[0] <= 32: - code = 'I' - - fmt = '>' + code * self.palette.shape[1] - for row in self.palette: - write_buffer = struct.pack(fmt, *row) + bps = self.bits_per_component + if any(b != bps[0] for b in bps): + # All components are the same. Writing is straightforward. + if self.bits_per_component[0] <= 8: + code = 'B' + dtype = np.uint8 + elif self.bits_per_component[0] <= 16: + code = 'H' + dtype = np.uint16 + elif self.bits_per_component[0] <= 32: + code = 'I' + dtype = np.uint32 + nelts = self.palette.shape[0] * self.palette.shape[1] + fmt = '>{0}{1}'.format(nelts, code) + write_buffer = struct.pack(fmt, + self.palette.astype(dtype).flatten()) fptr.write(write_buffer) + else: + # Not all the components are the same. More general, but much rarer + # case. Does this even happen. + code_dict = {8: 'B', 16: 'H', 32: 'I'} + codes = '' + for width in bps: + codes += code_dict[width] + fmt = '>' + codes + for row in self.palette: + write_buffer = struct.pack(fmt, *row) + fptr.write(write_buffer) @staticmethod def parse(fptr, offset, length): From 7418dd9cac3a9e38cdc55f83e9fc8a438daa127f Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 2 Apr 2014 07:06:13 -0400 Subject: [PATCH 178/326] Refactored try/except fragment of box parsing. --- glymur/jp2box.py | 31 +++++-------------------------- glymur/test/test_jp2box_jpx.py | 2 +- 2 files changed, 6 insertions(+), 27 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index dc1041b..7288b6a 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -176,40 +176,19 @@ class Jp2kBox(object): object corresponding to the current box """ try: - box = _BOX_WITH_ID[box_id].parse(fptr, start, num_bytes) - - except UnicodeDecodeError: - msg = 'Unrecognized box ({0}) encountered.'.format(box_id) - warnings.warn(msg) - box = UnknownBox(' ', offset=start, length=num_bytes, - longname='Unknown') + parser = _BOX_WITH_ID[box_id].parse except KeyError: + # We don't recognize the box ID, so create an UnknownBox and be + # done with it. msg = 'Unrecognized box ({0}) encountered.'.format(box_id) warnings.warn(msg) box = UnknownBox(box_id, offset=start, length=num_bytes, longname='Unknown') - cpos = fptr.tell() - if not ((cpos == start + 8) or (cpos == start + 16)): - # If the file pointer has advanced, then the KeyError - # ocurred during the parsing of the box. - pass - else: - # Could it be a superbox with recognizable child boxes? - # Peek ahead to see. - pos = fptr.tell() - read_buffer = fptr.read(8) - _, sub_id = struct.unpack('>I4s', read_buffer) - - # Regardless of whether or not we recognize the box, rewind back - # to properly advance to the next box. - fptr.seek(pos) - - # Now process any child boxes if we actually did recognize it. - if sub_id in _BOX_WITH_ID.keys(): - box.box = box.parse_superbox(fptr) + return box + box = parser(fptr, start, num_bytes) return box def parse_superbox(self, fptr): diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 73e8f1b..7fe407a 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -462,7 +462,7 @@ class TestJPX(unittest.TestCase): self.assertEqual(jpx.box[2].standard_flag, (5, 42, 45, 2, 18, 19, 1, 8, 12, 31, 20)) - @unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.") + @unittest.skip("Requires unnecessarily complicated code") def test_unknown_superbox(self): """Verify that we can handle an unknown superbox.""" with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile: From 9ef39ddecb963c7dd2e5a009f679ceaf5295c3d2 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 2 Apr 2014 09:00:23 -0400 Subject: [PATCH 179/326] Updated test for unknown superbox printing. #209 Can no longer detect known interior boxes. That's ok for now. --- glymur/test/test_printing.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index d770916..5286419 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -68,6 +68,9 @@ class TestPrinting(unittest.TestCase): # Add the header for an unknwon superbox. write_buffer = struct.pack('>I4s', 20, 'grp '.encode()) tfile.write(write_buffer) + + # Add a free box inside of it. We won't be able to identify it, + # but it's there. write_buffer = struct.pack('>I4sI', 12, 'free'.encode(), 0) tfile.write(write_buffer) tfile.flush() @@ -78,8 +81,7 @@ class TestPrinting(unittest.TestCase): with patch('sys.stdout', new=StringIO()) as fake_out: print(jpx.box[-1]) actual = fake_out.getvalue().strip() - lines = ["Unknown Box (b'grp ') @ (1399071, 20)", - ' Free Box (free) @ (1399079, 12)'] + lines = ["Unknown Box (b'grp ') @ (1399071, 20)"] expected = '\n'.join(lines) self.assertEqual(actual, expected) From 900a44f00b71ea14768d5fbfaf76dd32f027754f Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 2 Apr 2014 09:35:53 -0400 Subject: [PATCH 180/326] Changed static methods into class methods. #136 Since the static methods were being used as constructors, they really did deserve to be class methods. --- glymur/jp2box.py | 231 +++++++++++++++++++++-------------------------- 1 file changed, 105 insertions(+), 126 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index d051324..311e76f 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -390,8 +390,8 @@ class ColourSpecificationBox(Jp2kBox): self.colorspace) fptr.write(read_buffer) - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse JPEG 2000 color specification box. Parameters @@ -434,14 +434,13 @@ class ColourSpecificationBox(Jp2kBox): profile = _ICCProfile(fptr.read(numbytes)) icc_profile = profile.header - box = ColourSpecificationBox(method=method, - precedence=precedence, - approximation=approximation, - colorspace=colorspace, - icc_profile=icc_profile, - length=length, - offset=offset) - return box + return cls(method=method, + precedence=precedence, + approximation=approximation, + colorspace=colorspace, + icc_profile=icc_profile, + length=length, + offset=offset) class _ICCProfile(object): @@ -643,8 +642,8 @@ class ChannelDefinitionBox(Jp2kBox): self.channel_type[j], self.association[j])) - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse component definition box. Parameters @@ -670,11 +669,10 @@ class ChannelDefinitionBox(Jp2kBox): channel_type = data[1:num_components * 6:3] association = data[2:num_components * 6:3] - box = ChannelDefinitionBox(index=tuple(index), - channel_type=tuple(channel_type), - association=tuple(association), - length=length, offset=offset) - return box + return cls(index=tuple(index), + channel_type=tuple(channel_type), + association=tuple(association), + length=length, offset=offset) class CodestreamHeaderBox(Jp2kBox): @@ -712,8 +710,8 @@ class CodestreamHeaderBox(Jp2kBox): """ self._write_superbox(fptr) - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse codestream header box. Parameters @@ -729,7 +727,7 @@ class CodestreamHeaderBox(Jp2kBox): ------- CodestreamHeaderBox instance """ - box = CodestreamHeaderBox(length=length, offset=offset) + box = cls(length=length, offset=offset) # The codestream header box is a superbox, so go ahead and parse its # child boxes. @@ -781,8 +779,8 @@ class ColourGroupBox(Jp2kBox): self._validate(writing=True) self._write_superbox(fptr) - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse colour group box. Parameters @@ -798,7 +796,7 @@ class ColourGroupBox(Jp2kBox): ------- ColourGroupBox instance """ - box = ColourGroupBox(length=length, offset=offset) + box = cls(length=length, offset=offset) # The colour group box is a superbox, so go ahead and parse its # child boxes. @@ -844,8 +842,8 @@ class CompositingLayerHeaderBox(Jp2kBox): """ self._write_superbox(fptr) - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse compositing layer header box. Parameters @@ -861,7 +859,7 @@ class CompositingLayerHeaderBox(Jp2kBox): ------- CompositingLayerHeaderBox instance """ - box = CompositingLayerHeaderBox(length=length, offset=offset) + box = cls(length=length, offset=offset) # This box is a superbox, so go ahead and parse its # child boxes. box.box = box.parse_superbox(fptr) @@ -935,8 +933,8 @@ class ComponentMappingBox(Jp2kBox): self.palette_index[j]) fptr.write(write_buffer) - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse component mapping box. Parameters @@ -962,9 +960,8 @@ class ComponentMappingBox(Jp2kBox): mapping_type = data[1:num_bytes:3] palette_index = data[2:num_bytes:3] - box = ComponentMappingBox(component_index, mapping_type, palette_index, - length=length, offset=offset) - return box + return cls(component_index, mapping_type, palette_index, + length=length, offset=offset) class ContiguousCodestreamBox(Jp2kBox): @@ -1006,8 +1003,8 @@ class ContiguousCodestreamBox(Jp2kBox): return msg - @staticmethod - def parse(fptr, offset=0, length=0): + @classmethod + def parse(cls, fptr, offset=0, length=0): """Parse a codestream box. Parameters @@ -1024,9 +1021,7 @@ class ContiguousCodestreamBox(Jp2kBox): ContiguousCodestreamBox instance """ main_header = Codestream(fptr, length, header_only=True) - box = ContiguousCodestreamBox(main_header, length=length, - offset=offset) - return box + return cls(main_header, length=length, offset=offset) class DataReferenceBox(Jp2kBox): @@ -1106,8 +1101,8 @@ class DataReferenceBox(Jp2kBox): msg = 'glymur.jp2box.DataReferenceBox()' return msg - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse Label box. Parameters @@ -1139,8 +1134,7 @@ class DataReferenceBox(Jp2kBox): box = DataEntryURLBox.parse(fptr, start, box_length) data_entry_url_box_list.append(box) - return DataReferenceBox(data_entry_url_box_list, - length=length, offset=offset) + return cls(data_entry_url_box_list, length=length, offset=offset) class FileTypeBox(Jp2kBox): @@ -1223,8 +1217,8 @@ class FileTypeBox(Jp2kBox): for item in self.compatibility_list: fptr.write(item.encode()) - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse JPEG 2000 file type box. Parameters @@ -1259,10 +1253,9 @@ class FileTypeBox(Jp2kBox): compatibility_list = compatibility_list - box = FileTypeBox(brand=brand, minor_version=minor_version, - compatibility_list=compatibility_list, - length=length, offset=offset) - return box + return cls(brand=brand, minor_version=minor_version, + compatibility_list=compatibility_list, + length=length, offset=offset) class FragmentListBox(Jp2kBox): @@ -1338,8 +1331,8 @@ class FragmentListBox(Jp2kBox): self.data_reference[j]) fptr.write(write_buffer) - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse JPX free box. Parameters @@ -1363,8 +1356,8 @@ class FragmentListBox(Jp2kBox): frag_offset = lst[0::3] frag_len = lst[1::3] data_reference = lst[2::3] - return FragmentListBox(frag_offset, frag_len, data_reference, - length=length, offset=offset) + return cls(frag_offset, frag_len, data_reference, + length=length, offset=offset) class FragmentTableBox(Jp2kBox): @@ -1395,8 +1388,8 @@ class FragmentTableBox(Jp2kBox): msg = self._str_superbox() return msg - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse JPX fragment table superbox box. Parameters @@ -1412,7 +1405,7 @@ class FragmentTableBox(Jp2kBox): ------- FragmentTableBox instance """ - box = FragmentTableBox(length=length, offset=offset) + box = cls(length=length, offset=offset) # The FragmentTable box is a superbox, so go ahead and parse its child # boxes. @@ -1466,8 +1459,8 @@ class FreeBox(Jp2kBox): return msg - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse JPX free box. Parameters @@ -1483,7 +1476,7 @@ class FreeBox(Jp2kBox): ------- FreeBox instance """ - return FreeBox(length=length, offset=offset) + return cls(length=length, offset=offset) class ImageHeaderBox(Jp2kBox): @@ -1590,8 +1583,8 @@ class ImageHeaderBox(Jp2kBox): 1 if self.ip_provided else 0) fptr.write(read_buffer) - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse JPEG 2000 image header box. Parameters @@ -1619,14 +1612,13 @@ class ImageHeaderBox(Jp2kBox): colorspace_unknown = True if params[5] else False ip_provided = True if params[6] else False - box = ImageHeaderBox(height, width, num_components=num_components, - bits_per_component=bits_per_component, - signed=signed, - compression=compression, - colorspace_unknown=colorspace_unknown, - ip_provided=ip_provided, - length=length, offset=offset) - return box + return cls(height, width, num_components=num_components, + bits_per_component=bits_per_component, + signed=signed, + compression=compression, + colorspace_unknown=colorspace_unknown, + ip_provided=ip_provided, + length=length, offset=offset) class AssociationBox(Jp2kBox): @@ -1659,8 +1651,8 @@ class AssociationBox(Jp2kBox): msg = self._str_superbox() return msg - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse association box. Parameters @@ -1676,7 +1668,7 @@ class AssociationBox(Jp2kBox): ------- AssociationBox instance """ - box = AssociationBox(length=length, offset=offset) + box = cls(length=length, offset=offset) # The Association box is a superbox, so go ahead and parse its child # boxes. @@ -1725,8 +1717,8 @@ class JP2HeaderBox(Jp2kBox): """ self._write_superbox(fptr) - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse JPEG 2000 header box. Parameters @@ -1742,7 +1734,7 @@ class JP2HeaderBox(Jp2kBox): ------- JP2HeaderBox instance """ - box = JP2HeaderBox(length=length, offset=offset) + box = cls(length=length, offset=offset) # The JP2 header box is a superbox, so go ahead and parse its child # boxes. @@ -1793,8 +1785,8 @@ class JPEG2000SignatureBox(Jp2kBox): fptr.write(b'jP ') fptr.write(struct.pack('>BBBB', *self.signature)) - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse JPEG 2000 signature box. Parameters @@ -1813,9 +1805,7 @@ class JPEG2000SignatureBox(Jp2kBox): read_buffer = fptr.read(4) signature = struct.unpack('>BBBB', read_buffer) - box = JPEG2000SignatureBox(signature=signature, length=length, - offset=offset) - return box + return cls(signature=signature, length=length, offset=offset) class PaletteBox(Jp2kBox): @@ -1920,8 +1910,8 @@ class PaletteBox(Jp2kBox): write_buffer = struct.pack(fmt, *row) fptr.write(write_buffer) - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse palette box. Parameters @@ -1966,7 +1956,7 @@ class PaletteBox(Jp2kBox): palette[j] = struct.unpack_from(fmt, read_buffer, offset=j * row_nbytes) - return PaletteBox(palette, bps, signed, length=length, offset=offset) + return cls(palette, bps, signed, length=length, offset=offset) # Map rreq codes to display text. @@ -2132,8 +2122,8 @@ class ReaderRequirementsBox(Jp2kBox): return msg - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse reader requirements box. Parameters @@ -2179,10 +2169,9 @@ class ReaderRequirementsBox(Jp2kBox): msg += 'The box contents will not be interpreted.' warnings.warn(msg.format(mask_length), UserWarning) - box = ReaderRequirementsBox(fuam, dcm, standard_flag, standard_mask, - vendor_feature, vendor_mask, - length=length, offset=offset) - return box + return cls(fuam, dcm, standard_flag, standard_mask, + vendor_feature, vendor_mask, + length=length, offset=offset) def _parse_rreq3(fptr, length, offset): @@ -2340,8 +2329,8 @@ class ResolutionBox(Jp2kBox): msg = self._str_superbox() return msg - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse Resolution box. Parameters @@ -2357,7 +2346,7 @@ class ResolutionBox(Jp2kBox): ------- ResolutionBox instance """ - box = ResolutionBox(length=length, offset=offset) + box = cls(length=length, offset=offset) # The JP2 header box is a superbox, so go ahead and parse its child # boxes. @@ -2404,8 +2393,8 @@ class CaptureResolutionBox(Jp2kBox): msg += '\n HCR: {0}'.format(self.horizontal_resolution) return msg - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse CaptureResolutionBox. Parameters @@ -2426,9 +2415,7 @@ class CaptureResolutionBox(Jp2kBox): vres = rn1 / rd1 * math.pow(10, re1) hres = rn2 / rd2 * math.pow(10, re2) - box = CaptureResolutionBox(vres, hres, length=length, offset=offset) - - return box + return cls(vres, hres, length=length, offset=offset) class DisplayResolutionBox(Jp2kBox): @@ -2469,8 +2456,8 @@ class DisplayResolutionBox(Jp2kBox): msg += '\n HDR: {0}'.format(self.horizontal_resolution) return msg - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse display resolution box. Parameters @@ -2492,9 +2479,7 @@ class DisplayResolutionBox(Jp2kBox): vres = rn1 / rd1 * math.pow(10, re1) hres = rn2 / rd2 * math.pow(10, re2) - box = DisplayResolutionBox(vres, hres, length=length, offset=offset) - - return box + return cls(vres, hres, length=length, offset=offset) class LabelBox(Jp2kBox): @@ -2539,8 +2524,8 @@ class LabelBox(Jp2kBox): fptr.write(b'lbl ') fptr.write(self.label.encode()) - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse Label box. Parameters @@ -2559,8 +2544,7 @@ class LabelBox(Jp2kBox): num_bytes = offset + length - fptr.tell() read_buffer = fptr.read(num_bytes) label = read_buffer.decode('utf-8') - box = LabelBox(label, length=length, offset=offset) - return box + return cls(label, length=length, offset=offset) class NumberListBox(Jp2kBox): @@ -2611,8 +2595,8 @@ class NumberListBox(Jp2kBox): msg = 'glymur.jp2box.NumberListBox()' return msg - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse number list box. Parameters @@ -2632,8 +2616,7 @@ class NumberListBox(Jp2kBox): raw_data = fptr.read(num_bytes) num_associations = int(len(raw_data) / 4) lst = struct.unpack('>' + 'I' * num_associations, raw_data) - box = NumberListBox(lst, length=length, offset=offset) - return box + return cls(lst, length=length, offset=offset) def write(self, fptr): """Write a NumberList box to file. @@ -2716,8 +2699,8 @@ class XMLBox(Jp2kBox): fptr.write(b'xml ') fptr.write(read_buffer) - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse XML box. Parameters @@ -2778,8 +2761,7 @@ class XMLBox(Jp2kBox): warnings.warn(msg, UserWarning) xml = None - box = XMLBox(xml=xml, length=length, offset=offset) - return box + return cls(xml=xml, length=length, offset=offset) class UUIDListBox(Jp2kBox): @@ -2817,8 +2799,8 @@ class UUIDListBox(Jp2kBox): msg += '\n UUID[{0}]: {1}'.format(j, uuid_item) return msg - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse UUIDList box. Parameters @@ -2842,8 +2824,7 @@ class UUIDListBox(Jp2kBox): read_buffer = fptr.read(16) ulst.append(uuid.UUID(bytes=read_buffer)) - box = UUIDListBox(ulst, length=length, offset=offset) - return box + return cls(ulst, length=length, offset=offset) class UUIDInfoBox(Jp2kBox): @@ -2876,8 +2857,8 @@ class UUIDInfoBox(Jp2kBox): msg = self._str_superbox() return msg - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse UUIDInfo super box. Parameters @@ -2894,7 +2875,7 @@ class UUIDInfoBox(Jp2kBox): UUIDInfoBox instance """ - box = UUIDInfoBox(length=length, offset=offset) + box = cls(length=length, offset=offset) # The UUIDInfo box is a superbox, so go ahead and parse its child # boxes. @@ -2969,8 +2950,8 @@ class DataEntryURLBox(Jp2kBox): self.url) return msg - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse data entry URL box. Parameters @@ -2994,8 +2975,7 @@ class DataEntryURLBox(Jp2kBox): numbytes = offset + length - fptr.tell() read_buffer = fptr.read(numbytes) url = read_buffer.decode('utf-8').rstrip(chr(0)) - box = DataEntryURLBox(version, flag, url, length=length, offset=offset) - return box + return cls(version, flag, url, length=length, offset=offset) class UnknownBox(Jp2kBox): @@ -3144,8 +3124,8 @@ class UUIDBox(Jp2kBox): fptr.write(self.uuid.bytes) fptr.write(self.raw_data) - @staticmethod - def parse(fptr, offset, length): + @classmethod + def parse(cls, fptr, offset, length): """Parse UUID box. Parameters @@ -3167,8 +3147,7 @@ class UUIDBox(Jp2kBox): numbytes = offset + length - fptr.tell() read_buffer = fptr.read(numbytes) - box = UUIDBox(the_uuid, read_buffer, length=length, offset=offset) - return box + return cls(the_uuid, read_buffer, length=length, offset=offset) # Map each box ID to the corresponding class. From 8e6dc486da4ded181e594a263601081585fe5b72 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 2 Apr 2014 10:35:24 -0400 Subject: [PATCH 181/326] If palette columns are the same width, read it in one step. #210 Should be more efficient, hopefully. --- glymur/jp2box.py | 52 +++++++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 311e76f..46b00bc 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1933,27 +1933,43 @@ class PaletteBox(Jp2kBox): # Need to determine bps and signed or not read_buffer = fptr.read(num_columns) - data = struct.unpack('>' + 'B' * num_columns, read_buffer) - bps = [((x & 0x7f) + 1) for x in data] - signed = [((x & 0x80) > 1) for x in data] + bps_signed = struct.unpack('>' + 'B' * num_columns, read_buffer) + bps = [((x & 0x7f) + 1) for x in bps_signed] + signed = [((x & 0x80) > 1) for x in bps_signed] - fmt = '>' - for bits in bps: - if bits <= 8: - fmt += 'B' - elif bits <= 16: - fmt += 'H' - elif bits <= 32: - fmt += 'I' + if any(b != bps_signed[0] for b in bps_signed): + # Ok the palette has the same datatype for all columns. We should + # be able to efficiently read it. + if bps <= 8: + dtype = np.uint8 + elif bps <= 16: + dtype = np.uint16 + elif bps <= 32: + dtype = np.uint32 + + read_buffer = fptr.read(num_entries * np.sum(bps) / 8) + palette = np.frombuffer(read_buffer, dtype) + palette.reshape((num_entries, num_columns)) - # 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]) + else: + # General case where the columns may not be the same width. + fmt = '>' + for bits in bps: + if bits <= 8: + fmt += 'B' + elif bits <= 16: + fmt += 'H' + elif bits <= 32: + fmt += 'I' - read_buffer = fptr.read(num_entries * row_nbytes) - palette = np.zeros((num_entries, num_columns), dtype=np.int32) - for j in range(num_entries): - palette[j] = struct.unpack_from(fmt, read_buffer, + # 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]) + + read_buffer = fptr.read(num_entries * row_nbytes) + palette = np.zeros((num_entries, num_columns), dtype=np.int32) + for j in range(num_entries): + palette[j] = struct.unpack_from(fmt, read_buffer, offset=j * row_nbytes) return cls(palette, bps, signed, length=length, offset=offset) From eb1df90fb9524a28ca2582054911e241633ceb2d Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 2 Apr 2014 11:28:00 -0400 Subject: [PATCH 182/326] Speed up of about 33%, maybe? #210 Added a real fix for #209, seeing about a 22% speed up there. --- glymur/jp2box.py | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 46b00bc..f62567d 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1882,21 +1882,14 @@ class PaletteBox(Jp2kBox): fptr.write(write_buffer) bps = self.bits_per_component - if any(b != bps[0] for b in bps): + if all(b == bps[0] for b in bps): # All components are the same. Writing is straightforward. if self.bits_per_component[0] <= 8: - code = 'B' - dtype = np.uint8 + write_buffer = np.getbuffer(self.palette.astype(np.uint8)) elif self.bits_per_component[0] <= 16: - code = 'H' - dtype = np.uint16 + write_buffer = np.getbuffer(self.palette.astype(np.uint16)) elif self.bits_per_component[0] <= 32: - code = 'I' - dtype = np.uint32 - nelts = self.palette.shape[0] * self.palette.shape[1] - fmt = '>{0}{1}'.format(nelts, code) - write_buffer = struct.pack(fmt, - self.palette.astype(dtype).flatten()) + write_buffer = np.getbuffer(self.palette.astype(np.uint32)) fptr.write(write_buffer) else: # Not all the components are the same. More general, but much rarer @@ -1937,19 +1930,22 @@ class PaletteBox(Jp2kBox): bps = [((x & 0x7f) + 1) for x in bps_signed] signed = [((x & 0x80) > 1) for x in bps_signed] - if any(b != bps_signed[0] for b in bps_signed): + if all(b == bps_signed[0] for b in bps_signed): # Ok the palette has the same datatype for all columns. We should # be able to efficiently read it. - if bps <= 8: + if bps[0] <= 8: + nbytes_per_row = num_columns dtype = np.uint8 - elif bps <= 16: + elif bps[0] <= 16: + nbytes_per_row = 2 * num_columns dtype = np.uint16 - elif bps <= 32: + elif bps[0] <= 32: + nbytes_per_row = 3 * num_columns dtype = np.uint32 - read_buffer = fptr.read(num_entries * np.sum(bps) / 8) - palette = np.frombuffer(read_buffer, dtype) - palette.reshape((num_entries, num_columns)) + read_buffer = fptr.read(num_entries * nbytes_per_row) + palette = np.frombuffer(read_buffer, dtype=dtype) + palette = np.reshape(palette, (num_entries, num_columns)) else: # General case where the columns may not be the same width. From 63d203796b8d1cd981a5e2585450f4cc12ffd97b Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 2 Apr 2014 12:21:56 -0400 Subject: [PATCH 183/326] Use memoryview instead of np.getbuffer. #210 np.getbuffer is not available in python3. --- glymur/jp2box.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index f62567d..92c94ba 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1885,11 +1885,11 @@ class PaletteBox(Jp2kBox): if all(b == bps[0] for b in bps): # All components are the same. Writing is straightforward. if self.bits_per_component[0] <= 8: - write_buffer = np.getbuffer(self.palette.astype(np.uint8)) + write_buffer = memoryview(self.palette.astype(np.uint8)) elif self.bits_per_component[0] <= 16: - write_buffer = np.getbuffer(self.palette.astype(np.uint16)) + write_buffer = memoryview(self.palette.astype(np.uint16)) elif self.bits_per_component[0] <= 32: - write_buffer = np.getbuffer(self.palette.astype(np.uint32)) + write_buffer = memoryview(self.palette.astype(np.uint32)) fptr.write(write_buffer) else: # Not all the components are the same. More general, but much rarer From 0e370f5882286271e4160070e4aba5b612f7eca8 Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 2 Apr 2014 20:49:17 -0400 Subject: [PATCH 184/326] Refactoring, lint cleanup. --- glymur/jp2box.py | 64 ++++++++++++++++++++++++++---------------------- glymur/jp2k.py | 64 ++++++++++++++++++++---------------------------- 2 files changed, 62 insertions(+), 66 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 92c94ba..2dd30d0 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -43,7 +43,7 @@ _METHOD_DISPLAY = { ANY_ICC_PROFILE: 'any ICC profile', VENDOR_COLOR_METHOD: 'vendor color method'} -_factory = lambda x: '{0} (invalid)'.format(x) +_factory = lambda x: '{0} (invalid)'.format(x) _APPROX_DISPLAY = _Keydefaultdict(_factory, {1: 'accurately represents correct colorspace definition', 2: 'approximates correct colorspace definition, exceptional quality', @@ -125,7 +125,7 @@ class Jp2kBox(object): String to be indented. indent_level : str Number of spaces of indentation to add. - + Returns ------- indented_string : str @@ -407,15 +407,16 @@ class ColourSpecificationBox(Jp2kBox): ------- ColourSpecificationBox instance """ + num_bytes = offset + length - fptr.tell() + read_buffer = fptr.read(num_bytes) # Read the brand, minor version. - read_buffer = fptr.read(3) - (method, precedence, approximation) = struct.unpack('>BBB', - read_buffer) + (method, precedence, approximation) = struct.unpack_from('>BBB', + read_buffer, + offset=0) if method == 1: # enumerated colour space - read_buffer = fptr.read(4) - colorspace, = struct.unpack('>I', read_buffer) + colorspace, = struct.unpack_from('>I', read_buffer, offset=3) if colorspace not in _COLORSPACE_MAP_DISPLAY.keys(): msg = "Unrecognized colorspace: {0}".format(colorspace) warnings.warn(msg) @@ -424,14 +425,13 @@ class ColourSpecificationBox(Jp2kBox): else: # ICC profile colorspace = None - numbytes = offset + length - fptr.tell() - if numbytes < 128: + if (num_bytes - 3) < 128: msg = "ICC profile header is corrupt, length is " msg += "only {0} instead of 128." - warnings.warn(msg.format(numbytes), UserWarning) + warnings.warn(msg.format(num_bytes - 3), UserWarning) icc_profile = None else: - profile = _ICCProfile(fptr.read(numbytes)) + profile = _ICCProfile(read_buffer[3:]) icc_profile = profile.header return cls(method=method, @@ -659,12 +659,14 @@ class ChannelDefinitionBox(Jp2kBox): ------- ComponentDefinitionBox instance """ - # Read the number of components. - read_buffer = fptr.read(2) - num_components, = struct.unpack('>H', read_buffer) + num_bytes = offset + length - fptr.tell() + read_buffer = fptr.read(num_bytes) - read_buffer = fptr.read(num_components * 6) - data = struct.unpack('>' + 'HHH' * num_components, read_buffer) + # Read the number of components. + num_components, = struct.unpack_from('>H', read_buffer) + + data = struct.unpack_from('>' + 'HHH' * num_components, read_buffer, + offset=2) index = data[0:num_components * 6:3] channel_type = data[1:num_components * 6:3] association = data[2:num_components * 6:3] @@ -1234,19 +1236,21 @@ class FileTypeBox(Jp2kBox): ------- FileTypeBox instance """ + current_pos = fptr.tell() + num_bytes = (offset + length - current_pos) + read_buffer = fptr.read(num_bytes) + # Read the brand, minor version. - read_buffer = fptr.read(8) - (brand, minor_version) = struct.unpack('>4sI', read_buffer) + (brand, minor_version) = struct.unpack_from('>4sI', read_buffer, + offset=0) if sys.hexversion >= 0x030000: brand = brand.decode('utf-8') # Read the compatibility list. Each entry has 4 bytes. - current_pos = fptr.tell() - num_bytes = (offset + length - current_pos) / 4 - read_buffer = fptr.read(int(num_bytes) * 4) compatibility_list = [] - for j in range(int(num_bytes)): - entry, = struct.unpack('>4s', read_buffer[4*j:4*(j+1)]) + num_entries = int((offset + length - current_pos - 8) / 4) + for j in range(num_entries): + entry, = struct.unpack_from('>4s', read_buffer, offset=8 + (4 * j)) if sys.hexversion >= 0x03000000: entry = entry.decode('utf-8') compatibility_list.append(entry) @@ -1942,7 +1946,7 @@ class PaletteBox(Jp2kBox): elif bps[0] <= 32: nbytes_per_row = 3 * num_columns dtype = np.uint32 - + read_buffer = fptr.read(num_entries * nbytes_per_row) palette = np.frombuffer(read_buffer, dtype=dtype) palette = np.reshape(palette, (num_entries, num_columns)) @@ -2828,13 +2832,15 @@ class UUIDListBox(Jp2kBox): ------- UUIDListBox instance """ - read_buffer = fptr.read(2) - num_uuids, = struct.unpack('>H', read_buffer) + num_bytes = offset + length - fptr.tell() + read_buffer = fptr.read(num_bytes) + + num_uuids, = struct.unpack_from('>H', read_buffer) ulst = [] - for _ in range(num_uuids): - read_buffer = fptr.read(16) - ulst.append(uuid.UUID(bytes=read_buffer)) + for j in range(num_uuids): + uuid_buffer = read_buffer[2 + j * 16 : 2 + (j + 1) * 16] + ulst.append(uuid.UUID(bytes=uuid_buffer)) return cls(ulst, length=length, offset=offset) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 971c82d..dee6700 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -691,7 +691,7 @@ class Jp2k(Jp2kBox): msg = "Unable to locate the specified codestream." raise IOError(msg) if L == 0: - # The length of the box is presumed to last until the end of + # The length of the box is presumed to last until the end of # the file. Compute the effective length of the box. L = os.path.getsize(ifile.name) - ifile.tell() + 8 @@ -833,38 +833,13 @@ class Jp2k(Jp2kBox): """ self._subsampling_sanity_check() - # Must check the specified rlevel against the maximum. - if rlevel != 0: - # Must check the specified rlevel against the maximum. - codestream = self.get_codestream() - max_rlevel = codestream.segment[2].spcod[4] - if rlevel == -1: - # -1 is shorthand for the largest rlevel - rlevel = max_rlevel - elif rlevel < -1 or rlevel > max_rlevel: - msg = "rlevel must be in the range [-1, {0}] for this image." - msg = msg.format(max_rlevel) - raise IOError(msg) + dparameters = self._populate_dparam(rlevel, ignore_pclr_cmap_cdef) with ExitStack() as stack: try: - # Set decoding parameters. - # TODO: look to refactor, use _populate_dparam - dparameters = opj.DecompressionParametersType() - opj.set_default_decoder_parameters(ctypes.byref(dparameters)) - - if ignore_pclr_cmap_cdef is True: - # Return raw codestream components. - dparameters.flags |= 1 - dparameters.cp_reduce = rlevel dparameters.decod_format = self._codec_format - infile = self.filename.encode() - nelts = opj.PATH_LEN - len(infile) - infile += b'0' * nelts - dparameters.infile = infile - dinfo = opj.create_decompress(dparameters.decod_format) event_mgr = opj.EventMgrType() @@ -931,8 +906,8 @@ class Jp2k(Jp2kBox): """ self._subsampling_sanity_check() - dparam = self._populate_dparam(layer, rlevel, area, tile, - ignore_pclr_cmap_cdef) + dparam = self._populate_dparam(rlevel, ignore_pclr_cmap_cdef, + layer=layer, tile=tile, area=area) with ExitStack() as stack: if hasattr(opj2.OPENJP2, @@ -976,8 +951,8 @@ class Jp2k(Jp2kBox): return img_array - def _populate_dparam(self, layer, rlevel, area, tile, - ignore_pclr_cmap_cdef): + def _populate_dparam(self, rlevel, ignore_pclr_cmap_cdef, tile=None, + layer=None, area=None): """Populate decompression structure with appropriate input parameters. Parameters @@ -1000,7 +975,11 @@ class Jp2k(Jp2kBox): dparam : DecompressionParametersType (ctypes) Corresponds to openjp2 decompression parameters structure. """ - dparam = opj2.set_default_decoder_parameters() + if opj2.OPENJP2 is not None: + dparam = opj2.set_default_decoder_parameters() + else: + dparam = opj.DecompressionParametersType() + opj.set_default_decoder_parameters(ctypes.byref(dparam)) infile = self.filename.encode() nelts = opj2.PATH_LEN - len(infile) @@ -1009,12 +988,22 @@ class Jp2k(Jp2kBox): dparam.decod_format = self._codec_format - dparam.cp_layer = layer + if layer is not None: + dparam.cp_layer = layer - if rlevel == -1: - # Get the lowest resolution thumbnail. + # Must check the specified rlevel against the maximum. + if rlevel != 0: + # Must check the specified rlevel against the maximum. codestream = self.get_codestream() - rlevel = codestream.segment[2].spcod[4] + max_rlevel = codestream.segment[2].spcod[4] + if rlevel == -1: + # -1 is shorthand for the largest rlevel + rlevel = max_rlevel + elif rlevel < -1 or rlevel > max_rlevel: + msg = "rlevel must be in the range [-1, {0}] for this image." + msg = msg.format(max_rlevel) + raise IOError(msg) + dparam.cp_reduce = rlevel if area is not None: @@ -1088,7 +1077,8 @@ class Jp2k(Jp2kBox): "of OpenJP2 installed before using " "this functionality.") - dparam = self._populate_dparam(layer, rlevel, area, tile, ignore_pclr_cmap_cdef) + dparam = self._populate_dparam(rlevel, ignore_pclr_cmap_cdef, + layer=layer, tile=tile, area=area) with ExitStack() as stack: if hasattr(opj2.OPENJP2, From 7ebe4ff59eccd2cde91cd9f89c997bf9ffe54ffe Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 2 Apr 2014 21:30:01 -0400 Subject: [PATCH 185/326] Fixed rlevel assignment issue causing segfault in 1.5. Segfault introduced in 0e370f5882286271e4160070e4aba5b612f7eca8 --- glymur/jp2k.py | 1 - 1 file changed, 1 deletion(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index dee6700..8110c50 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -837,7 +837,6 @@ class Jp2k(Jp2kBox): with ExitStack() as stack: try: - dparameters.cp_reduce = rlevel dparameters.decod_format = self._codec_format dinfo = opj.create_decompress(dparameters.decod_format) From 576571fa8511ecb1e5e94d738467faa738405fe7 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 3 Apr 2014 07:09:34 -0400 Subject: [PATCH 186/326] Streamlined SIZ parsing. #130 --- glymur/codestream.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/glymur/codestream.py b/glymur/codestream.py index 2f9d37a..544b8a2 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -670,8 +670,8 @@ class Codestream(object): read_buffer = fptr.read(2) length, = struct.unpack('>H', read_buffer) - xy_buffer = fptr.read(36) - data = struct.unpack('>HIIIIIIIIH', xy_buffer) + read_buffer = fptr.read(length - 2) + data = struct.unpack_from('>HIIIIIIIIH', read_buffer) rsiz = data[0] if rsiz not in _KNOWN_PROFILES: @@ -685,9 +685,8 @@ class Codestream(object): # Csiz is the number of components Csiz = data[9] - component_buffer = fptr.read(Csiz * 3) - data = struct.unpack('>' + 'B' * len(component_buffer), - component_buffer) + data = struct.unpack_from('>' + 'B' * (length - 36 - 2), + read_buffer, offset=36) bitdepth = tuple(((x & 0x7f) + 1) for x in data[0::3]) signed = tuple(((x & 0x80) > 0) for x in data[0::3]) From 4e19e688a0704f05c6916f6559a5eaa4c6d8d19a Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 3 Apr 2014 22:52:11 -0400 Subject: [PATCH 187/326] Variable renaming. #130 --- glymur/jp2box.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 701483f..22ef99d 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1922,13 +1922,11 @@ class PaletteBox(Jp2kBox): ------- PaletteBox instance """ - num_bytes = offset + length - fptr.tell() - read_buffer = fptr.read(num_bytes) - (nrows, ncols) = struct.unpack_from('>HB', read_buffer, offset=0) + read_buffer = fptr.read(3) + (nrows, ncols) = struct.unpack('>HB', read_buffer) - # Need to determine bps and signed or not - read_buffer = fptr.read(num_columns) - bps_signed = struct.unpack('>' + 'B' * num_columns, read_buffer) + read_buffer = fptr.read(ncols) + bps_signed = struct.unpack('>' + 'B' * ncols, read_buffer) bps = [((x & 0x7f) + 1) for x in bps_signed] signed = [((x & 0x80) > 1) for x in bps_signed] @@ -1936,18 +1934,18 @@ class PaletteBox(Jp2kBox): # Ok the palette has the same datatype for all columns. We should # be able to efficiently read it. if bps[0] <= 8: - nbytes_per_row = num_columns + nbytes_per_row = ncols dtype = np.uint8 elif bps[0] <= 16: - nbytes_per_row = 2 * num_columns + nbytes_per_row = 2 * ncols dtype = np.uint16 elif bps[0] <= 32: - nbytes_per_row = 3 * num_columns + nbytes_per_row = 3 * ncols dtype = np.uint32 - read_buffer = fptr.read(num_entries * nbytes_per_row) + read_buffer = fptr.read(nrows * nbytes_per_row) palette = np.frombuffer(read_buffer, dtype=dtype) - palette = np.reshape(palette, (num_entries, num_columns)) + palette = np.reshape(palette, (nrows, ncols)) else: # General case where the columns may not be the same width. @@ -1964,9 +1962,9 @@ class PaletteBox(Jp2kBox): # That means a list comprehension does this in one shot. row_nbytes = sum([int(math.ceil(x/8.0)) for x in bps]) - read_buffer = fptr.read(num_entries * row_nbytes) - palette = np.zeros((num_entries, num_columns), dtype=np.int32) - for j in range(num_entries): + read_buffer = fptr.read(nrows * row_nbytes) + palette = np.zeros((nrows, ncols), dtype=np.int32) + for j in range(nrows): palette[j] = struct.unpack_from(fmt, read_buffer, offset=j * row_nbytes) From 828abe942ac4f2ebbac27c9e8339fa42e36870e6 Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 3 Apr 2014 22:53:52 -0400 Subject: [PATCH 188/326] Removed one read statement, using struct.unpack_from. #130 --- glymur/codestream.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/glymur/codestream.py b/glymur/codestream.py index 544b8a2..2a257ed 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -380,13 +380,13 @@ class Codestream(object): COD segment instance. """ offset = fptr.tell() - 2 - offset = fptr.tell() - 2 - read_buffer = fptr.read(3) - length, scod = struct.unpack('>HB', read_buffer) + read_buffer = fptr.read(2) + length, = struct.unpack('>H', read_buffer) - numbytes = offset + 2 + length - fptr.tell() - spcod = fptr.read(numbytes) + read_buffer = fptr.read(length - 2) + scod, = struct.unpack_from('>B', read_buffer, offset=0) + spcod = read_buffer[1:] spcod = np.frombuffer(spcod, dtype=np.uint8) if spcod[0] not in [LRCP, RLCP, RPCL, PCRL, CPRL]: msg = "Invalid progression order in COD segment: {0}." From 3dbaf9f458579ef10facac922aedc77566a67916 Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 4 Apr 2014 07:11:43 -0400 Subject: [PATCH 189/326] Refactored QCC segment parsing. #130 --- glymur/codestream.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/glymur/codestream.py b/glymur/codestream.py index 2a257ed..fb9917c 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -583,22 +583,21 @@ class Codestream(object): read_buffer = fptr.read(2) length, = struct.unpack('>H', read_buffer) + read_buffer = fptr.read(length - 2) if self._csiz > 256: - read_buffer = fptr.read(3) fmt = '>HB' - mantissa_exponent_buffer_length = length - 5 + mantissa_exponent_offset = 3 else: - read_buffer = fptr.read(2) fmt = '>BB' - mantissa_exponent_buffer_length = length - 4 - cqcc, sqcc = struct.unpack(fmt, read_buffer) + mantissa_exponent_offset = 2 + cqcc, sqcc = struct.unpack_from(fmt, read_buffer) if cqcc >= self._csiz: msg = "Invalid component number ({0}), " msg += "number of components is only {1}." msg = msg.format(cqcc, self._csiz) warnings.warn(msg) - spqcc = fptr.read(mantissa_exponent_buffer_length) + spqcc = read_buffer[mantissa_exponent_offset:] return QCCsegment(cqcc, sqcc, spqcc, length, offset) From 2dc4e8400e81479c0b54ad18b7d40b55bbac2198 Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 4 Apr 2014 12:17:58 -0400 Subject: [PATCH 190/326] Refactored the TLM segment. #130 --- glymur/codestream.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/glymur/codestream.py b/glymur/codestream.py index fb9917c..6830fab 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -802,8 +802,8 @@ class Codestream(object): read_buffer = fptr.read(2) length, = struct.unpack('>H', read_buffer) - read_buffer = fptr.read(2) - ztlm, stlm = struct.unpack('>BB', read_buffer) + read_buffer = fptr.read(length - 2) + ztlm, stlm = struct.unpack_from('>BB', read_buffer) ttlm_st = (stlm >> 4) & 0x3 ptlm_sp = (stlm >> 6) & 0x1 @@ -813,7 +813,6 @@ class Codestream(object): else: ntiles = nbytes / (ttlm_st + (ptlm_sp + 1) * 2) - read_buffer = fptr.read(nbytes) if ttlm_st == 0: ttlm = None fmt = '' @@ -827,7 +826,8 @@ class Codestream(object): else: fmt += 'I' - data = struct.unpack('>' + fmt * int(ntiles), read_buffer) + data = struct.unpack_from('>' + fmt * int(ntiles), read_buffer, + offset=2) if ttlm_st == 0: ttlm = None ptlm = data From 196255d780a86b15ef38bb5922b269e9a13413f4 Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 4 Apr 2014 12:27:11 -0400 Subject: [PATCH 191/326] No superbox needs to encode its box ID anymore. #204 --- glymur/jp2box.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 22ef99d..5ea275b 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -138,18 +138,19 @@ class Jp2kBox(object): return '\n'.join(lst) - def _write_superbox(self, fptr): + def _write_superbox(self, fptr, box_id): """Write a superbox. Parameters ---------- fptr : file or file object Superbox (box of boxes) to be written to this file. + box_id : bytes + 4-byte sequence that identifies the superbox. """ # Write the contained boxes, then come back and write the length. orig_pos = fptr.tell() - fptr.write(struct.pack('>I', 0)) - fptr.write(self.box_id.encode()) + fptr.write(struct.pack('>I4s', 0, box_id)) for box in self.box: box.write(fptr) @@ -710,7 +711,7 @@ class CodestreamHeaderBox(Jp2kBox): def write(self, fptr): """Write a codestream header box to file. """ - self._write_superbox(fptr) + self._write_superbox(fptr, b'jpch') @classmethod def parse(cls, fptr, offset, length): @@ -779,7 +780,7 @@ class ColourGroupBox(Jp2kBox): """Write a colour group box to file. """ self._validate(writing=True) - self._write_superbox(fptr) + self._write_superbox(fptr, b'cgrp') @classmethod def parse(cls, fptr, offset, length): @@ -842,7 +843,7 @@ class CompositingLayerHeaderBox(Jp2kBox): def write(self, fptr): """Write a compositing layer header box to file. """ - self._write_superbox(fptr) + self._write_superbox(fptr, b'jplh') @classmethod def parse(cls, fptr, offset, length): @@ -1427,7 +1428,7 @@ class FragmentTableBox(Jp2kBox): """Write a fragment table box to file. """ self._validate(writing=True) - self._write_superbox(fptr) + self._write_superbox(fptr, b'ftbl') @@ -1681,7 +1682,7 @@ class AssociationBox(Jp2kBox): def write(self, fptr): """Write an association box to file. """ - self._write_superbox(fptr) + self._write_superbox(fptr, b'asoc') class JP2HeaderBox(Jp2kBox): @@ -1717,7 +1718,7 @@ class JP2HeaderBox(Jp2kBox): def write(self, fptr): """Write a JP2 Header box to file. """ - self._write_superbox(fptr) + self._write_superbox(fptr, b'jp2h') @classmethod def parse(cls, fptr, offset, length): From 1485bda7ff55eaea493c63e9ed709bd9673719e4 Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 4 Apr 2014 13:24:16 -0400 Subject: [PATCH 192/326] Passing down XL flag into each box parser. #212 This should prevent having to call the tell method on the file pointer. --- glymur/jp2box.py | 226 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 171 insertions(+), 55 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 5ea275b..df65818 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -159,18 +159,22 @@ class Jp2kBox(object): fptr.write(struct.pack('>I', end_pos - orig_pos)) fptr.seek(end_pos) - def _parse_this_box(self, fptr, box_id, start, num_bytes): + def _parse_this_box(self, fptr, box_id, start, num_bytes, box_is_XL): """Parse the current box. Parameters ---------- fptr : file - Open file object. + Open file object, currently points to start of box payload, not the + start of the box. box_id : str 4-letter identifier for the current box. - start, num_bytes: int + start, num_bytes : int Byte offset and length of the current box. - + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- box : Jp2kBox @@ -189,7 +193,7 @@ class Jp2kBox(object): return box - box = parser(fptr, start, num_bytes) + box = parser(fptr, start, num_bytes, box_is_XL=box_is_XL) return box def parse_superbox(self, fptr): @@ -221,6 +225,7 @@ class Jp2kBox(object): warnings.warn(msg) return superbox + box_is_XL = False (box_length, box_id) = struct.unpack('>I4s', read_buffer) if box_length == 0: # The length of the box is presumed to last until the end of @@ -231,12 +236,14 @@ class Jp2kBox(object): # The length of the box is in the XL field, a 64-bit value. read_buffer = fptr.read(8) num_bytes, = struct.unpack('>Q', read_buffer) + box_is_XL = True else: # The box_length value really is the length of the box! num_bytes = box_length - box = self._parse_this_box(fptr, box_id, start, num_bytes) + box = self._parse_this_box(fptr, box_id, start, num_bytes, + box_is_XL) superbox.append(box) @@ -392,7 +399,7 @@ class ColourSpecificationBox(Jp2kBox): fptr.write(read_buffer) @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse JPEG 2000 color specification box. Parameters @@ -403,12 +410,16 @@ class ColourSpecificationBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- ColourSpecificationBox instance """ - num_bytes = offset + length - fptr.tell() + num_bytes = length - 16 if box_is_XL else length - 8 read_buffer = fptr.read(num_bytes) # Read the brand, minor version. (method, precedence, approximation) = struct.unpack_from('>BBB', @@ -644,7 +655,7 @@ class ChannelDefinitionBox(Jp2kBox): self.association[j])) @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse component definition box. Parameters @@ -655,12 +666,16 @@ class ChannelDefinitionBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- ComponentDefinitionBox instance """ - num_bytes = offset + length - fptr.tell() + num_bytes = length - 16 if box_is_XL else length - 8 read_buffer = fptr.read(num_bytes) # Read the number of components. @@ -714,7 +729,7 @@ class CodestreamHeaderBox(Jp2kBox): self._write_superbox(fptr, b'jpch') @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse codestream header box. Parameters @@ -725,6 +740,10 @@ class CodestreamHeaderBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- @@ -783,7 +802,7 @@ class ColourGroupBox(Jp2kBox): self._write_superbox(fptr, b'cgrp') @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse colour group box. Parameters @@ -794,6 +813,10 @@ class ColourGroupBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- @@ -846,7 +869,7 @@ class CompositingLayerHeaderBox(Jp2kBox): self._write_superbox(fptr, b'jplh') @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse compositing layer header box. Parameters @@ -857,6 +880,10 @@ class CompositingLayerHeaderBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- @@ -937,7 +964,7 @@ class ComponentMappingBox(Jp2kBox): fptr.write(write_buffer) @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse component mapping box. Parameters @@ -948,12 +975,16 @@ class ComponentMappingBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- ComponentMappingBox instance """ - num_bytes = offset + length - fptr.tell() + num_bytes = length - 16 if box_is_XL else length - 8 num_components = int(num_bytes/4) read_buffer = fptr.read(num_bytes) @@ -1007,7 +1038,7 @@ class ContiguousCodestreamBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset=0, length=0): + def parse(cls, fptr, offset=0, length=0, box_is_XL=False): """Parse a codestream box. Parameters @@ -1018,6 +1049,10 @@ class ContiguousCodestreamBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- @@ -1105,7 +1140,7 @@ class DataReferenceBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse data reference box. Parameters @@ -1116,6 +1151,10 @@ class DataReferenceBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- @@ -1221,7 +1260,7 @@ class FileTypeBox(Jp2kBox): fptr.write(item.encode()) @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse JPEG 2000 file type box. Parameters @@ -1232,12 +1271,17 @@ class FileTypeBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- FileTypeBox instance """ - read_buffer = fptr.read(length - 8) + num_bytes = length - 16 if box_is_XL else length - 8 + read_buffer = fptr.read(num_bytes) # Extract the brand, minor version. (brand, minor_version) = struct.unpack_from('>4sI', read_buffer, 0) if sys.hexversion >= 0x030000: @@ -1333,7 +1377,7 @@ class FragmentListBox(Jp2kBox): fptr.write(write_buffer) @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse JPX free box. Parameters @@ -1344,12 +1388,16 @@ class FragmentListBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- FragmentListBox instance """ - num_bytes = offset + length - fptr.tell() + num_bytes = length - 16 if box_is_XL else length - 8 read_buffer = fptr.read(num_bytes) num_fragments, = struct.unpack_from('>H', read_buffer, offset=0) @@ -1392,7 +1440,7 @@ class FragmentTableBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse JPX fragment table superbox box. Parameters @@ -1403,6 +1451,10 @@ class FragmentTableBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- @@ -1463,7 +1515,7 @@ class FreeBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse JPX free box. Parameters @@ -1474,6 +1526,10 @@ class FreeBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- @@ -1587,7 +1643,7 @@ class ImageHeaderBox(Jp2kBox): fptr.write(read_buffer) @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse JPEG 2000 image header box. Parameters @@ -1598,6 +1654,10 @@ class ImageHeaderBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- @@ -1655,7 +1715,7 @@ class AssociationBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse association box. Parameters @@ -1666,6 +1726,10 @@ class AssociationBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- @@ -1721,7 +1785,7 @@ class JP2HeaderBox(Jp2kBox): self._write_superbox(fptr, b'jp2h') @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse JPEG 2000 header box. Parameters @@ -1732,6 +1796,10 @@ class JP2HeaderBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- @@ -1789,7 +1857,7 @@ class JPEG2000SignatureBox(Jp2kBox): fptr.write(struct.pack('>BBBB', *self.signature)) @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse JPEG 2000 signature box. Parameters @@ -1800,6 +1868,10 @@ class JPEG2000SignatureBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- @@ -1907,7 +1979,7 @@ class PaletteBox(Jp2kBox): fptr.write(write_buffer) @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse palette box. Parameters @@ -1918,6 +1990,10 @@ class PaletteBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- @@ -2136,7 +2212,7 @@ class ReaderRequirementsBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse reader requirements box. Parameters @@ -2147,6 +2223,10 @@ class ReaderRequirementsBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- @@ -2343,7 +2423,7 @@ class ResolutionBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse Resolution box. Parameters @@ -2354,6 +2434,10 @@ class ResolutionBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- @@ -2407,7 +2491,7 @@ class CaptureResolutionBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse CaptureResolutionBox. Parameters @@ -2418,6 +2502,10 @@ class CaptureResolutionBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- @@ -2470,7 +2558,7 @@ class DisplayResolutionBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse display resolution box. Parameters @@ -2481,6 +2569,10 @@ class DisplayResolutionBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- @@ -2538,7 +2630,7 @@ class LabelBox(Jp2kBox): fptr.write(self.label.encode()) @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse Label box. Parameters @@ -2549,12 +2641,16 @@ class LabelBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- LabelBox instance """ - num_bytes = offset + length - fptr.tell() + num_bytes = length - 16 if box_is_XL else length - 8 read_buffer = fptr.read(num_bytes) label = read_buffer.decode('utf-8') return cls(label, length=length, offset=offset) @@ -2609,7 +2705,7 @@ class NumberListBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse number list box. Parameters @@ -2620,12 +2716,16 @@ class NumberListBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- LabelBox instance """ - num_bytes = offset + length - fptr.tell() + num_bytes = length - 16 if box_is_XL else length - 8 raw_data = fptr.read(num_bytes) num_associations = int(len(raw_data) / 4) lst = struct.unpack('>' + 'I' * num_associations, raw_data) @@ -2713,7 +2813,7 @@ class XMLBox(Jp2kBox): fptr.write(read_buffer) @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse XML box. Parameters @@ -2724,12 +2824,16 @@ class XMLBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- XMLBox instance """ - num_bytes = offset + length - fptr.tell() + num_bytes = length - 16 if box_is_XL else length - 8 read_buffer = fptr.read(num_bytes) try: text = read_buffer.decode('utf-8') @@ -2813,7 +2917,7 @@ class UUIDListBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse UUIDList box. Parameters @@ -2824,12 +2928,16 @@ class UUIDListBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- UUIDListBox instance """ - num_bytes = offset + length - fptr.tell() + num_bytes = length - 16 if box_is_XL else length - 8 read_buffer = fptr.read(num_bytes) num_uuids, = struct.unpack_from('>H', read_buffer) @@ -2873,7 +2981,7 @@ class UUIDInfoBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse UUIDInfo super box. Parameters @@ -2884,6 +2992,10 @@ class UUIDInfoBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- @@ -2966,7 +3078,7 @@ class DataEntryURLBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse data entry URL box. Parameters @@ -2977,19 +3089,22 @@ class DataEntryURLBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- DataEntryURLbox instance """ - read_buffer = fptr.read(4) - data = struct.unpack('>BBBB', read_buffer) + num_bytes = length - 16 if box_is_XL else length - 8 + read_buffer = fptr.read(num_bytes) + data = struct.unpack_from('>BBBB', read_buffer) version = data[0] flag = data[1:4] - numbytes = offset + length - fptr.tell() - read_buffer = fptr.read(numbytes) - url = read_buffer.decode('utf-8').rstrip(chr(0)) + url = read_buffer[4:].decode('utf-8').rstrip(chr(0)) return cls(version, flag, url, length=length, offset=offset) @@ -3140,7 +3255,7 @@ class UUIDBox(Jp2kBox): fptr.write(self.raw_data) @classmethod - def parse(cls, fptr, offset, length): + def parse(cls, fptr, offset, length, box_is_XL=False): """Parse UUID box. Parameters @@ -3151,18 +3266,19 @@ class UUIDBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. + box_is_XL : bool + True if the box has an XL field. In this case, the start of the + box is 16 bytes behind the start of the box payload instead of being + 8 bytes behind. Returns ------- UUIDBox instance """ - - read_buffer = fptr.read(16) - the_uuid = uuid.UUID(bytes=read_buffer) - - numbytes = offset + length - fptr.tell() - read_buffer = fptr.read(numbytes) - return cls(the_uuid, read_buffer, length=length, offset=offset) + num_bytes = length - 16 if box_is_XL else length - 8 + read_buffer = fptr.read(num_bytes) + the_uuid = uuid.UUID(bytes=read_buffer[0:16]) + return cls(the_uuid, read_buffer[16:], length=length, offset=offset) # Map each box ID to the corresponding class. From 2e1af15cb157dbcd6b252eac2f0b77112b6564b6 Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 4 Apr 2014 13:54:58 -0400 Subject: [PATCH 193/326] Adjusted some comments in XML box parsing. #213 --- glymur/jp2box.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 5ea275b..b983cbe 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2738,6 +2738,7 @@ class XMLBox(Jp2kBox): # Try to search for '): text = text[38:] From bb4e5cfe166d9be97627d9affe2cc0f210b7cc6d Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 5 Apr 2014 15:41:49 -0400 Subject: [PATCH 194/326] Refactored dtbl box to use just a single file read. #212 --- glymur/jp2box.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index df65818..571f540 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -15,6 +15,7 @@ References from collections import OrderedDict import datetime +import io import math import os import pprint @@ -1160,22 +1161,32 @@ class DataReferenceBox(Jp2kBox): ------- DataReferenceBox instance """ + num_bytes = length - 16 if box_is_XL else length - 8 + read_buffer = fptr.read(num_bytes) + # Read the number of data references - read_buffer = fptr.read(2) - ndr, = struct.unpack('>H', read_buffer) + ndr, = struct.unpack_from('>H', read_buffer, offset=0) + + # Need to keep track of where the next url box starts. + box_offset = 2 - # Read each data entry url box. data_entry_url_box_list = [] - for _ in range(ndr): - start = fptr.tell() - read_buffer = fptr.read(8) - (box_length, box_id) = struct.unpack('>I4s', read_buffer) - if sys.hexversion >= 0x03000000: - box_id = box_id.decode('utf-8') + for j in range(ndr): - box = DataEntryURLBox.parse(fptr, start, box_length) + # Create an in-memory binary stream for each URL box. + box_fptr = io.BytesIO(read_buffer[box_offset:]) + box_buffer = box_fptr.read(8) + (box_length, box_id) = struct.unpack_from('>I4s', box_buffer, + offset=0) + box = DataEntryURLBox.parse(box_fptr, 0, box_length) + + # Need to adjust the box start to that of the "real" file. + box.start = offset + box_offset data_entry_url_box_list.append(box) + # Point to the next embedded URL box. + box_offset += box_length + return cls(data_entry_url_box_list, length=length, offset=offset) From 578470e149e4aa9675726eb30780da64950ac82e Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 5 Apr 2014 17:16:49 -0400 Subject: [PATCH 195/326] Refactored PCLR box parsing to use a single file read operation. #212 --- glymur/jp2box.py | 29 +++++++++++++++++++---------- glymur/test/test_opj_suite.py | 1 + 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 571f540..52c491b 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -194,7 +194,17 @@ class Jp2kBox(object): return box - box = parser(fptr, start, num_bytes, box_is_XL=box_is_XL) + try: + box = parser(fptr, start, num_bytes, box_is_XL=box_is_XL) + except ValueError as err: + msg = "Encountered an unrecoverable ValueError while parsing a {0} " + msg += "box at byte offset {1}. The original error message was " + msg += "\"{2}\"" + msg = msg.format(box_id.decode('utf-8'), start, str(err)) + warnings.warn(msg, UserWarning) + box = UnknownBox(box_id.decode('utf-8'), + length=num_bytes, offset=start, longname='Unknown') + return box def parse_superbox(self, fptr): @@ -2010,11 +2020,12 @@ class PaletteBox(Jp2kBox): ------- PaletteBox instance """ - read_buffer = fptr.read(3) - (nrows, ncols) = struct.unpack('>HB', read_buffer) + num_bytes = length - 16 if box_is_XL else length - 8 + read_buffer = fptr.read(num_bytes) + nrows, ncols = struct.unpack_from('>HB', read_buffer, offset=0) - read_buffer = fptr.read(ncols) - bps_signed = struct.unpack('>' + 'B' * ncols, read_buffer) + bps_signed = struct.unpack_from('>' + 'B' * ncols, read_buffer, + offset=3) bps = [((x & 0x7f) + 1) for x in bps_signed] signed = [((x & 0x80) > 1) for x in bps_signed] @@ -2031,8 +2042,7 @@ class PaletteBox(Jp2kBox): nbytes_per_row = 3 * ncols dtype = np.uint32 - read_buffer = fptr.read(nrows * nbytes_per_row) - palette = np.frombuffer(read_buffer, dtype=dtype) + palette = np.frombuffer(read_buffer[3 + ncols:], dtype=dtype) palette = np.reshape(palette, (nrows, ncols)) else: @@ -2050,11 +2060,10 @@ class PaletteBox(Jp2kBox): # That means a list comprehension does this in one shot. row_nbytes = sum([int(math.ceil(x/8.0)) for x in bps]) - read_buffer = fptr.read(nrows * row_nbytes) palette = np.zeros((nrows, ncols), dtype=np.int32) for j in range(nrows): - palette[j] = struct.unpack_from(fmt, read_buffer, - offset=j * row_nbytes) + poff = 3 + ncols + j * row_nbytes + palette[j] = struct.unpack_from(fmt, read_buffer, offset=poff) return cls(palette, bps, signed, length=length, offset=offset) diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index efbc794..3c247fa 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -5784,6 +5784,7 @@ class TestSuiteDump(unittest.TestCase): self.assertEqual(c.segment[4].ccme.decode('latin-1'), "Kakadu-v5.2.1") + @unittest.skip("Bad PCLR box") def test_NR_mem_b2ace68c_1381_dump(self): jfile = opj_data_file('input/nonregression/mem-b2ace68c-1381.jp2') with warnings.catch_warnings(): From f15913cee62210281a64c6d410347b896f7bcbe9 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 5 Apr 2014 22:24:11 -0400 Subject: [PATCH 196/326] Refactored reader requireents box to use just a single file read. #212 --- glymur/jp2box.py | 73 +++++++++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 52c491b..462fbe8 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2252,29 +2252,34 @@ class ReaderRequirementsBox(Jp2kBox): ------- ReaderRequirementsBox instance """ - read_buffer = fptr.read(1) - mask_length, = struct.unpack('>B', read_buffer) + num_bytes = length - 16 if box_is_XL else length - 8 + read_buffer = fptr.read(num_bytes) + mask_length, = struct.unpack_from('>B', read_buffer, offset=0) if mask_length == 3: - return _parse_rreq3(fptr, length, offset) + return _parse_rreq3(read_buffer, length, offset) # Fully Understands Aspect Mask # Decodes Completely Mask - read_buffer = fptr.read(2 * mask_length) - fuam = dcm = standard_flag = standard_mask = [] vendor_feature = vendor_mask = [] # The mask length tells us the format string to use when unpacking # from the buffer read from file. - try: mask_format = {1: 'B', 2: 'H', 4: 'I', 8: 'Q'}[mask_length] - fuam, dcm = struct.unpack('>' + mask_format * 2, read_buffer) - standard_flag, standard_mask = _parse_standard_flag(fptr, - mask_length) - vendor_feature, vendor_mask = _parse_vendor_features(fptr, - mask_length) + fuam, dcm = struct.unpack_from('>' + mask_format * 2, read_buffer, + offset=1) + std_flg_offset = 1 + 2 * mask_length + data = _parse_standard_flag(read_buffer[std_flg_offset:], + mask_length) + standard_flag, standard_mask = data + + nflags = len(standard_flag) + vendor_offset = 1 + 2 * mask_length + 2 + (2 + mask_length) * nflags + data = _parse_vendor_features(read_buffer[vendor_offset:], + mask_length) + vendor_feature, vendor_mask = data except KeyError: msg = 'The ReaderRequirements box (rreq) has a mask length of {0} ' @@ -2287,27 +2292,23 @@ class ReaderRequirementsBox(Jp2kBox): length=length, offset=offset) -def _parse_rreq3(fptr, length, offset): +def _parse_rreq3(read_buffer, length, offset): """Parse a reader requirements box. Special case when mask length is 3.""" # Fully Understands Aspect Mask # Decodes Completely Mask - read_buffer = fptr.read(2 * 3) - fuam = dcm = standard_flag = standard_mask = [] vendor_feature = vendor_mask = [] # The mask length tells us the format string to use when unpacking # from the buffer read from file. - lst = struct.unpack('>BBBBBB', read_buffer) + lst = struct.unpack_from('>BBBBBB', read_buffer, offset=1) fuam = lst[0] << 16 | lst[1] << 8 | lst[2] dcm = lst[3] << 16 | lst[4] << 8 | lst[5] - read_buffer = fptr.read(2) - num_standard_features, = struct.unpack('>H', read_buffer) + num_standard_features, = struct.unpack_from('>H', read_buffer, offset=7) fmt = '>' + 'HBBB' * num_standard_features - read_buffer = fptr.read(num_standard_features * 5) - lst = struct.unpack(fmt, read_buffer) + lst = struct.unpack(fmt, read_buffer, offset=9) standard_flag = lst[0::4] standard_mask = [] @@ -2316,21 +2317,23 @@ def _parse_rreq3(fptr, length, offset): mask = items[0] << 16 | items[1] << 8 | items[2] standard_mask.append(mask) - read_buffer = fptr.read(2) - num_vendor_features, = struct.unpack('>H', read_buffer) + boffset = 9 + num_standard_features * 5 + num_vendor_features, = struct.unpack_from('>H', read_buffer, + offset=boffset) fmt = '>' + 'HBBB' * num_vendor_features - read_buffer = fptr.read(num_vendor_features * 5) - lst = struct.unpack(fmt, read_buffer) + buffer_offset = 11 + num_standard_features * 5 + lst = struct.unpack_from(fmt, read_buffer, offset=buffer_offset) # 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 + 3 - read_buffer = fptr.read(num_vendor_features * entry_length) vendor_feature = [] vendor_mask = [] + read_buffer = read_buffer[9 + num_standard_features * 10:] for j in range(num_vendor_features): - ubuffer = read_buffer[j * entry_length:(j + 1) * entry_length] + uslice = slice(j * entry_length, (j + 1) * entry_length) + ubuffer = read_buffer[slice] vendor_feature.append(uuid.UUID(bytes=ubuffer[0:16])) lst = struct.unpack('>BBB', ubuffer[16:]) @@ -2343,7 +2346,7 @@ def _parse_rreq3(fptr, length, offset): return box -def _parse_standard_flag(fptr, mask_length): +def _parse_standard_flag(read_buffer, mask_length): """Construct standard flag, standard mask data from the file. Specifically working on Reader Requirements box. @@ -2359,16 +2362,16 @@ def _parse_standard_flag(fptr, mask_length): # 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_buffer = fptr.read(2) + num_standard_flags, = struct.unpack_from('>H', read_buffer, offset=0) # 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)) + #read_buffer = fptr.read(num_standard_flags * (2 + mask_length)) fmt = '>' + ('H' + mask_format) * num_standard_flags - data = struct.unpack(fmt, read_buffer) + data = struct.unpack_from(fmt, read_buffer, offset=2) standard_flag = data[0:num_standard_flags * 2:2] standard_mask = data[1:num_standard_flags * 2:2] @@ -2376,7 +2379,7 @@ def _parse_standard_flag(fptr, mask_length): return standard_flag, standard_mask -def _parse_vendor_features(fptr, mask_length): +def _parse_vendor_features(read_buffer, mask_length): """Construct vendor features, vendor mask data from the file. Specifically working on Reader Requirements box. @@ -2392,17 +2395,17 @@ def _parse_vendor_features(fptr, mask_length): # 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) + num_vendor_features, = struct.unpack_from('>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) + #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] + uslice = slice(2 + j * entry_length, 2 + (j + 1) * entry_length) + ubuffer = read_buffer[uslice] vendor_feature.append(uuid.UUID(bytes=ubuffer[0:16])) vmask = struct.unpack('>' + mask_format, ubuffer[16:]) From 96db5e1a6a941a0dcf97ec761fb2de4be0f31023 Mon Sep 17 00:00:00 2001 From: jevans Date: Sun, 6 Apr 2014 10:51:50 -0400 Subject: [PATCH 197/326] Removed box_is_XL parameter. #212 We only get around a 1% performance increase, which is not worth the increased complexitiy. --- glymur/jp2box.py | 210 +++++++++++------------------------------------ 1 file changed, 46 insertions(+), 164 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 462fbe8..ee3e973 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -160,7 +160,7 @@ class Jp2kBox(object): fptr.write(struct.pack('>I', end_pos - orig_pos)) fptr.seek(end_pos) - def _parse_this_box(self, fptr, box_id, start, num_bytes, box_is_XL): + def _parse_this_box(self, fptr, box_id, start, num_bytes): """Parse the current box. Parameters @@ -172,10 +172,7 @@ class Jp2kBox(object): 4-letter identifier for the current box. start, num_bytes : int Byte offset and length of the current box. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. + Returns ------- box : Jp2kBox @@ -195,7 +192,7 @@ class Jp2kBox(object): return box try: - box = parser(fptr, start, num_bytes, box_is_XL=box_is_XL) + box = parser(fptr, start, num_bytes) except ValueError as err: msg = "Encountered an unrecoverable ValueError while parsing a {0} " msg += "box at byte offset {1}. The original error message was " @@ -236,7 +233,6 @@ class Jp2kBox(object): warnings.warn(msg) return superbox - box_is_XL = False (box_length, box_id) = struct.unpack('>I4s', read_buffer) if box_length == 0: # The length of the box is presumed to last until the end of @@ -247,14 +243,12 @@ class Jp2kBox(object): # The length of the box is in the XL field, a 64-bit value. read_buffer = fptr.read(8) num_bytes, = struct.unpack('>Q', read_buffer) - box_is_XL = True else: # The box_length value really is the length of the box! num_bytes = box_length - box = self._parse_this_box(fptr, box_id, start, num_bytes, - box_is_XL) + box = self._parse_this_box(fptr, box_id, start, num_bytes) superbox.append(box) @@ -410,7 +404,7 @@ class ColourSpecificationBox(Jp2kBox): fptr.write(read_buffer) @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse JPEG 2000 color specification box. Parameters @@ -421,16 +415,12 @@ class ColourSpecificationBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- ColourSpecificationBox instance """ - num_bytes = length - 16 if box_is_XL else length - 8 + num_bytes = offset + length - fptr.tell() read_buffer = fptr.read(num_bytes) # Read the brand, minor version. (method, precedence, approximation) = struct.unpack_from('>BBB', @@ -666,7 +656,7 @@ class ChannelDefinitionBox(Jp2kBox): self.association[j])) @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse component definition box. Parameters @@ -677,16 +667,12 @@ class ChannelDefinitionBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- ComponentDefinitionBox instance """ - num_bytes = length - 16 if box_is_XL else length - 8 + num_bytes = offset + length - fptr.tell() read_buffer = fptr.read(num_bytes) # Read the number of components. @@ -740,7 +726,7 @@ class CodestreamHeaderBox(Jp2kBox): self._write_superbox(fptr, b'jpch') @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse codestream header box. Parameters @@ -751,10 +737,6 @@ class CodestreamHeaderBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- @@ -813,7 +795,7 @@ class ColourGroupBox(Jp2kBox): self._write_superbox(fptr, b'cgrp') @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse colour group box. Parameters @@ -824,10 +806,6 @@ class ColourGroupBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- @@ -880,7 +858,7 @@ class CompositingLayerHeaderBox(Jp2kBox): self._write_superbox(fptr, b'jplh') @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse compositing layer header box. Parameters @@ -891,10 +869,6 @@ class CompositingLayerHeaderBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- @@ -975,7 +949,7 @@ class ComponentMappingBox(Jp2kBox): fptr.write(write_buffer) @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse component mapping box. Parameters @@ -986,16 +960,12 @@ class ComponentMappingBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- ComponentMappingBox instance """ - num_bytes = length - 16 if box_is_XL else length - 8 + num_bytes = offset + length - fptr.tell() num_components = int(num_bytes/4) read_buffer = fptr.read(num_bytes) @@ -1049,7 +1019,7 @@ class ContiguousCodestreamBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset=0, length=0, box_is_XL=False): + def parse(cls, fptr, offset=0, length=0): """Parse a codestream box. Parameters @@ -1060,10 +1030,6 @@ class ContiguousCodestreamBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- @@ -1151,7 +1117,7 @@ class DataReferenceBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse data reference box. Parameters @@ -1162,16 +1128,12 @@ class DataReferenceBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- DataReferenceBox instance """ - num_bytes = length - 16 if box_is_XL else length - 8 + num_bytes = offset + length - fptr.tell() read_buffer = fptr.read(num_bytes) # Read the number of data references @@ -1281,7 +1243,7 @@ class FileTypeBox(Jp2kBox): fptr.write(item.encode()) @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse JPEG 2000 file type box. Parameters @@ -1292,16 +1254,12 @@ class FileTypeBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- FileTypeBox instance """ - num_bytes = length - 16 if box_is_XL else length - 8 + num_bytes = offset + length - fptr.tell() read_buffer = fptr.read(num_bytes) # Extract the brand, minor version. (brand, minor_version) = struct.unpack_from('>4sI', read_buffer, 0) @@ -1398,7 +1356,7 @@ class FragmentListBox(Jp2kBox): fptr.write(write_buffer) @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse JPX free box. Parameters @@ -1409,16 +1367,12 @@ class FragmentListBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- FragmentListBox instance """ - num_bytes = length - 16 if box_is_XL else length - 8 + num_bytes = offset + length - fptr.tell() read_buffer = fptr.read(num_bytes) num_fragments, = struct.unpack_from('>H', read_buffer, offset=0) @@ -1461,7 +1415,7 @@ class FragmentTableBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse JPX fragment table superbox box. Parameters @@ -1472,10 +1426,6 @@ class FragmentTableBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- @@ -1536,7 +1486,7 @@ class FreeBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse JPX free box. Parameters @@ -1547,10 +1497,6 @@ class FreeBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- @@ -1664,7 +1610,7 @@ class ImageHeaderBox(Jp2kBox): fptr.write(read_buffer) @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse JPEG 2000 image header box. Parameters @@ -1675,10 +1621,6 @@ class ImageHeaderBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- @@ -1736,7 +1678,7 @@ class AssociationBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse association box. Parameters @@ -1747,10 +1689,6 @@ class AssociationBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- @@ -1806,7 +1744,7 @@ class JP2HeaderBox(Jp2kBox): self._write_superbox(fptr, b'jp2h') @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse JPEG 2000 header box. Parameters @@ -1817,10 +1755,6 @@ class JP2HeaderBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- @@ -1878,7 +1812,7 @@ class JPEG2000SignatureBox(Jp2kBox): fptr.write(struct.pack('>BBBB', *self.signature)) @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse JPEG 2000 signature box. Parameters @@ -1889,10 +1823,6 @@ class JPEG2000SignatureBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- @@ -2000,7 +1930,7 @@ class PaletteBox(Jp2kBox): fptr.write(write_buffer) @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse palette box. Parameters @@ -2011,16 +1941,12 @@ class PaletteBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- PaletteBox instance """ - num_bytes = length - 16 if box_is_XL else length - 8 + num_bytes = offset + length - fptr.tell() read_buffer = fptr.read(num_bytes) nrows, ncols = struct.unpack_from('>HB', read_buffer, offset=0) @@ -2232,7 +2158,7 @@ class ReaderRequirementsBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse reader requirements box. Parameters @@ -2243,16 +2169,12 @@ class ReaderRequirementsBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- ReaderRequirementsBox instance """ - num_bytes = length - 16 if box_is_XL else length - 8 + num_bytes = offset + length - fptr.tell() read_buffer = fptr.read(num_bytes) mask_length, = struct.unpack_from('>B', read_buffer, offset=0) @@ -2446,7 +2368,7 @@ class ResolutionBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse Resolution box. Parameters @@ -2457,10 +2379,6 @@ class ResolutionBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- @@ -2514,7 +2432,7 @@ class CaptureResolutionBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse CaptureResolutionBox. Parameters @@ -2525,10 +2443,6 @@ class CaptureResolutionBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- @@ -2581,7 +2495,7 @@ class DisplayResolutionBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse display resolution box. Parameters @@ -2592,10 +2506,6 @@ class DisplayResolutionBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- @@ -2653,7 +2563,7 @@ class LabelBox(Jp2kBox): fptr.write(self.label.encode()) @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse Label box. Parameters @@ -2664,16 +2574,12 @@ class LabelBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- LabelBox instance """ - num_bytes = length - 16 if box_is_XL else length - 8 + num_bytes = offset + length - fptr.tell() read_buffer = fptr.read(num_bytes) label = read_buffer.decode('utf-8') return cls(label, length=length, offset=offset) @@ -2728,7 +2634,7 @@ class NumberListBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse number list box. Parameters @@ -2739,16 +2645,12 @@ class NumberListBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- LabelBox instance """ - num_bytes = length - 16 if box_is_XL else length - 8 + num_bytes = offset + length - fptr.tell() raw_data = fptr.read(num_bytes) num_associations = int(len(raw_data) / 4) lst = struct.unpack('>' + 'I' * num_associations, raw_data) @@ -2836,7 +2738,7 @@ class XMLBox(Jp2kBox): fptr.write(read_buffer) @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse XML box. Parameters @@ -2847,16 +2749,12 @@ class XMLBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- XMLBox instance """ - num_bytes = length - 16 if box_is_XL else length - 8 + num_bytes = offset + length - fptr.tell() read_buffer = fptr.read(num_bytes) try: text = read_buffer.decode('utf-8') @@ -2940,7 +2838,7 @@ class UUIDListBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse UUIDList box. Parameters @@ -2951,16 +2849,12 @@ class UUIDListBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- UUIDListBox instance """ - num_bytes = length - 16 if box_is_XL else length - 8 + num_bytes = offset + length - fptr.tell() read_buffer = fptr.read(num_bytes) num_uuids, = struct.unpack_from('>H', read_buffer) @@ -3004,7 +2898,7 @@ class UUIDInfoBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse UUIDInfo super box. Parameters @@ -3015,10 +2909,6 @@ class UUIDInfoBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- @@ -3101,7 +2991,7 @@ class DataEntryURLBox(Jp2kBox): return msg @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse data entry URL box. Parameters @@ -3112,16 +3002,12 @@ class DataEntryURLBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- DataEntryURLbox instance """ - num_bytes = length - 16 if box_is_XL else length - 8 + num_bytes = offset + length - fptr.tell() read_buffer = fptr.read(num_bytes) data = struct.unpack_from('>BBBB', read_buffer) version = data[0] @@ -3278,7 +3164,7 @@ class UUIDBox(Jp2kBox): fptr.write(self.raw_data) @classmethod - def parse(cls, fptr, offset, length, box_is_XL=False): + def parse(cls, fptr, offset, length): """Parse UUID box. Parameters @@ -3289,16 +3175,12 @@ class UUIDBox(Jp2kBox): Start position of box in bytes. length : int Length of the box in bytes. - box_is_XL : bool - True if the box has an XL field. In this case, the start of the - box is 16 bytes behind the start of the box payload instead of being - 8 bytes behind. Returns ------- UUIDBox instance """ - num_bytes = length - 16 if box_is_XL else length - 8 + num_bytes = offset + length - fptr.tell() read_buffer = fptr.read(num_bytes) the_uuid = uuid.UUID(bytes=read_buffer[0:16]) return cls(the_uuid, read_buffer[16:], length=length, offset=offset) From b71931355475f6c197194c735bea1b7a13f7fa16 Mon Sep 17 00:00:00 2001 From: jevans Date: Sun, 6 Apr 2014 18:31:23 -0400 Subject: [PATCH 198/326] main_header of Codestream object is now a property. #212 This improves loading time of glymur.data.jpxfile() by 33%. --- glymur/jp2box.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index ee3e973..f541930 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -997,10 +997,23 @@ class ContiguousCodestreamBox(Jp2kBox): """ def __init__(self, main_header=None, length=0, offset=-1): Jp2kBox.__init__(self, box_id='jp2c', longname='Contiguous Codestream') - self.main_header = main_header + self._main_header = main_header self.length = length self.offset = offset + # The filename can be set if lazy loading is desired. + self._filename = None + + @property + def main_header(self): + if self._main_header is None: + if self._filename is not None: + with open(self._filename, 'rb') as fptr: + fptr.seek(self._offset + 8) + main_header = Codestream(fptr, self._length, header_only=True) + self._main_header = main_header + return self._main_header + def __repr__(self): msg = "glymur.jp2box.ContiguousCodeStreamBox(main_header={0})" return msg.format(repr(self.main_header)) @@ -1035,8 +1048,11 @@ class ContiguousCodestreamBox(Jp2kBox): ------- ContiguousCodestreamBox instance """ - main_header = Codestream(fptr, length, header_only=True) - return cls(main_header, length=length, offset=offset) + box = cls(None, length=length, offset=offset) + box._filename = fptr.name + box._length = length + box._offset = offset + return box class DataReferenceBox(Jp2kBox): From d89be23af26c67e8c0b4b6b90b29f35e164f00f7 Mon Sep 17 00:00:00 2001 From: bogdanni Date: Tue, 8 Apr 2014 23:27:59 +0200 Subject: [PATCH 199/326] Small cleanups in jp2box.py --- glymur/jp2box.py | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index b983cbe..cf849a5 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -381,8 +381,7 @@ class ColourSpecificationBox(Jp2kBox): """ self._write_validate() length = 15 if self.icc_profile is None else 11 + len(self.icc_profile) - fptr.write(struct.pack('>I', length)) - fptr.write(b'colr') + fptr.write(struct.pack('>I4s', length, b'colr')) read_buffer = struct.pack('>BBBI', self.method, @@ -634,8 +633,7 @@ class ChannelDefinitionBox(Jp2kBox): """ self._validate(writing=True) num_components = len(self.association) - fptr.write(struct.pack('>I', 8 + 2 + num_components * 6)) - fptr.write(b'cdef') + fptr.write(struct.pack('>I4s', 8 + 2 + num_components * 6, b'cdef')) fptr.write(struct.pack('>H', num_components)) for j in range(num_components): fptr.write(struct.pack('>' + 'H' * 3, @@ -1076,8 +1074,7 @@ class DataReferenceBox(Jp2kBox): # Very similar to the say a superbox is written. orig_pos = fptr.tell() - fptr.write(struct.pack('>I', 0)) - fptr.write(b'dtbl') + fptr.write(struct.pack('>I4s', 0, b'dtbl')) # Write the number of data entry url boxes. write_buffer = struct.pack('>H', len(self.DR)) @@ -1212,8 +1209,7 @@ class FileTypeBox(Jp2kBox): """ self._validate(writing=True) length = 16 + 4*len(self.compatibility_list) - fptr.write(struct.pack('>I', length)) - fptr.write(b'ftyp') + fptr.write(struct.pack('>I4s', length, b'ftyp')) fptr.write(self.brand.encode()) fptr.write(struct.pack('>I', self.minor_version)) @@ -1252,8 +1248,6 @@ class FileTypeBox(Jp2kBox): entry = entry.decode('utf-8') compatibility_list.append(entry) - compatibility_list = compatibility_list - return cls(brand=brand, minor_version=minor_version, compatibility_list=compatibility_list, length=length, offset=offset) @@ -1322,8 +1316,7 @@ class FragmentListBox(Jp2kBox): self._validate(writing=True) num_items = len(self.fragment_offset) length = 8 + 2 + num_items * 14 - fptr.write(struct.pack('>I', length)) - fptr.write(b'flst') + fptr.write(struct.pack('>I4s', length, b'flst')) fptr.write(struct.pack('>H', num_items)) for j in range(num_items): write_buffer = struct.pack('>QIH', @@ -1570,8 +1563,7 @@ class ImageHeaderBox(Jp2kBox): def write(self, fptr): """Write an Image Header box to file. """ - fptr.write(struct.pack('>I', 22)) - fptr.write(b'ihdr') + fptr.write(struct.pack('>I4s', 22, b'ihdr')) # signedness and bps are stored together in a single byte bit_depth_signedness = 0x80 if self.signed else 0x00 @@ -1784,8 +1776,7 @@ class JPEG2000SignatureBox(Jp2kBox): def write(self, fptr): """Write a JPEG 2000 Signature box to file. """ - fptr.write(struct.pack('>I', 12)) - fptr.write(b'jP ') + fptr.write(struct.pack('>I4s', 12, b'jP ')) fptr.write(struct.pack('>BBBB', *self.signature)) @classmethod @@ -2533,8 +2524,7 @@ class LabelBox(Jp2kBox): """Write a Label box to file. """ length = 8 + len(self.label.encode()) - fptr.write(struct.pack('>I', length)) - fptr.write(b'lbl ') + fptr.write(struct.pack('>I4s', length, b'lbl ')) fptr.write(self.label.encode()) @classmethod @@ -2634,8 +2624,7 @@ class NumberListBox(Jp2kBox): def write(self, fptr): """Write a NumberList box to file. """ - fptr.write(struct.pack('>I', len(self.associations) * 4 + 8)) - fptr.write(b'nlst') + fptr.write(struct.pack('>I4s', len(self.associations) * 4 + 8, b'nlst')) fmt = '>' + 'I' * len(self.associations) write_buffer = struct.pack(fmt, *self.associations) @@ -2708,8 +2697,7 @@ class XMLBox(Jp2kBox): # AssertionError on 2.6 read_buffer = ET.tostring(self.xml.getroot(), encoding='utf-8') - fptr.write(struct.pack('>I', len(read_buffer) + 8)) - fptr.write(b'xml ') + fptr.write(struct.pack('>I4s', len(read_buffer) + 8, b'xml ')) fptr.write(read_buffer) @classmethod From 487cc9c967c0c5226e6fdc6a9427660dfa6196e8 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 8 Apr 2014 20:54:16 -0400 Subject: [PATCH 200/326] Added set and get_parseoptions to control codestream parsing. #212 --- glymur/__init__.py | 1 + glymur/jp2box.py | 50 +++++++++++++++++++++- glymur/test/test_icc.py | 2 +- glymur/test/test_jp2k.py | 29 +++++++++++++ glymur/test/test_opj_suite.py | 80 +++++++++++++++++++---------------- 5 files changed, 124 insertions(+), 38 deletions(-) diff --git a/glymur/__init__.py b/glymur/__init__.py index 5826f8c..f39d6ef 100644 --- a/glymur/__init__.py +++ b/glymur/__init__.py @@ -9,6 +9,7 @@ __version__ = version.version from .jp2k import Jp2k from .jp2dump import jp2dump from .jp2box import get_printoptions, set_printoptions +from .jp2box import get_parseoptions, set_parseoptions from . import data diff --git a/glymur/jp2box.py b/glymur/jp2box.py index f541930..bf90f00 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1048,7 +1048,11 @@ class ContiguousCodestreamBox(Jp2kBox): ------- ContiguousCodestreamBox instance """ - box = cls(None, length=length, offset=offset) + if _parseoptions['codestream'] is True: + main_header = Codestream(fptr, length, header_only=True) + else: + main_header = None + box = cls(main_header, length=length, offset=offset) box._filename = fptr.name box._length = length box._offset = offset @@ -3233,6 +3237,50 @@ _BOX_WITH_ID = { b'uuid': UUIDBox, b'xml ': XMLBox} +_parseoptions = {'codestream': True} + +def set_parseoptions(codestream=True): + """Set parsing options. + + These options determine the way JPEG 2000 boxes are parsed. + + Parameters + ---------- + codestream : bool, defaults to True + When False, the codestream header is only parsed when accessed. This + can results in faster JP2/JPX parsing. + + See also + -------- + get_parseoptions + + Examples + -------- + To put back the default options, you can use: + + >>> import glymur + >>> glymur.set_parseoptions(codestream=True) + """ + _parseoptions['codestream'] = codestream + +def get_parseoptions(): + """Return the current parsing options. + + Returns + ------- + print_opts : dict + Dictionary of current print options with keys + + - codestream : bool + + For a full description of these options, see `set_parseoptions`. + + See also + -------- + set_parseoptions + """ + return _parseoptions + _printoptions = {'short': False, 'xml': True, 'codestream': True} def set_printoptions(**kwargs): diff --git a/glymur/test/test_icc.py b/glymur/test/test_icc.py index c49055e..d2dd959 100644 --- a/glymur/test/test_icc.py +++ b/glymur/test/test_icc.py @@ -35,7 +35,7 @@ class TestICC(unittest.TestCase): # The file has a bad compatibility list entry. Not important here. warnings.simplefilter("ignore") j = Jp2k(filename) - profile = j.box[2].box[1].icc_profile + profile = j.box[3].box[1].icc_profile self.assertEqual(profile['Size'], 546) self.assertEqual(profile['Preferred CMM Type'], 0) self.assertEqual(profile['Version'], '2.2.0') diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 6cd1ffd..a69f426 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -754,6 +754,35 @@ class TestJp2k_2_1(unittest.TestCase): with self.assertRaisesRegex((IOError, OSError), regexp): j.read(rlevel=1) +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class TestParsing(unittest.TestCase): + """Tests for verifying how paring may be altered.""" + def setUp(self): + # Reset parseoptions for every test. + glymur.set_parseoptions(codestream=True) + + def tearDown(self): + pass + + def test_bad_rsiz(self): + """Should not warn if RSIZ when parsing is turned off.""" + # Actually there are three warning triggered by this codestream. + filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') + glymur.set_parseoptions(codestream=False) + with warnings.catch_warnings(record=True) as w: + j = Jp2k(filename) + self.assertEqual(len(w), 0) + + glymur.set_parseoptions(codestream=True) + with warnings.catch_warnings(record=True) as w: + j = Jp2k(filename) + self.assertEqual(len(w), 3) + + if sys.hexversion >= 0x03000000: + with self.assertWarns(UserWarning): + jp2 = Jp2k(filename) + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") class TestJp2kOpjDataRoot(unittest.TestCase): diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 3c247fa..899a5b5 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -358,7 +358,11 @@ class TestSuite(unittest.TestCase): def test_ETS_JP2_file5(self): jfile = opj_data_file('input/conformance/file5.jp2') - jp2k = Jp2k(jfile) + with warnings.catch_warnings(): + # There's a warning for an unknown compatibility entry. + # Ignore it here. + warnings.simplefilter("ignore") + jp2k = Jp2k(jfile) jpdata = jp2k.read() self.assertEqual(jpdata.shape, (512, 768, 3)) @@ -3306,40 +3310,44 @@ class TestSuiteDump(unittest.TestCase): # profile and using the JPX-defined enumerated code for the ROMM-RGB # colourspace. jfile = opj_data_file('input/conformance/file5.jp2') - jp2 = Jp2k(jfile) + with warnings.catch_warnings(): + # There's a warning for an unknown compatibility entry. + # Ignore it here. + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) + ids = [box.box_id for box in jp2.box[3].box] + self.assertEqual(ids, ['ihdr', 'colr', 'colr']) # Signature box. Check for corruption. self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].brand, 'jpx ') self.assertEqual(jp2.box[1].minor_version, 0) self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') # Jp2 Header # Image header - self.assertEqual(jp2.box[2].box[0].height, 512) - self.assertEqual(jp2.box[2].box[0].width, 768) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) + self.assertEqual(jp2.box[3].box[0].height, 512) + self.assertEqual(jp2.box[3].box[0].width, 768) + self.assertEqual(jp2.box[3].box[0].num_components, 3) + self.assertEqual(jp2.box[3].box[0].signed, False) + self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[3].box[0].ip_provided, False) # Jp2 Header # Colour specification - self.assertEqual(jp2.box[2].box[1].method, + self.assertEqual(jp2.box[3].box[1].method, glymur.core.RESTRICTED_ICC_PROFILE) # enumerated - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[2].box[1].icc_profile['Size'], 546) - self.assertIsNone(jp2.box[2].box[1].colorspace) + self.assertEqual(jp2.box[3].box[1].precedence, 0) + self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact + self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 546) + self.assertIsNone(jp2.box[3].box[1].colorspace) def test_NR_file6_dump(self): jfile = opj_data_file('input/conformance/file6.jp2') @@ -3390,37 +3398,37 @@ class TestSuiteDump(unittest.TestCase): jp2 = Jp2k(jfile) ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) + ids = [box.box_id for box in jp2.box[3].box] + self.assertEqual(ids, ['ihdr', 'colr', 'colr']) # Signature box. Check for corruption. self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].brand, 'jpx ') self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') # Jp2 Header # Image header - self.assertEqual(jp2.box[2].box[0].height, 640) - self.assertEqual(jp2.box[2].box[0].width, 480) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 16) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) + self.assertEqual(jp2.box[3].box[0].height, 640) + self.assertEqual(jp2.box[3].box[0].width, 480) + self.assertEqual(jp2.box[3].box[0].num_components, 3) + self.assertEqual(jp2.box[3].box[0].bits_per_component, 16) + self.assertEqual(jp2.box[3].box[0].signed, False) + self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[3].box[0].ip_provided, False) # Jp2 Header # Colour specification - self.assertEqual(jp2.box[2].box[1].method, + self.assertEqual(jp2.box[3].box[1].method, glymur.core.RESTRICTED_ICC_PROFILE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 1) - self.assertEqual(jp2.box[2].box[1].icc_profile['Size'], 13332) - self.assertIsNone(jp2.box[2].box[1].colorspace) + self.assertEqual(jp2.box[3].box[1].precedence, 0) + self.assertEqual(jp2.box[3].box[1].approximation, 1) + self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 13332) + self.assertIsNone(jp2.box[3].box[1].colorspace) def test_NR_file8_dump(self): # One 8-bit component in a gamma 1.8 space. The colourspace is @@ -3447,7 +3455,7 @@ class TestSuiteDump(unittest.TestCase): # Image header self.assertEqual(jp2.box[2].box[0].height, 400) self.assertEqual(jp2.box[2].box[0].width, 700) - self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].num_components, 1) self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) self.assertEqual(jp2.box[2].box[0].signed, False) self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet From f71aeaee73285d66ecf1133d7e9d3b9f7b240eb4 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 8 Apr 2014 21:03:34 -0400 Subject: [PATCH 201/326] Fixed test on python2.7 --- glymur/test/test_jp2k.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index a69f426..3901cc5 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -775,10 +775,6 @@ class TestParsing(unittest.TestCase): self.assertEqual(len(w), 0) glymur.set_parseoptions(codestream=True) - with warnings.catch_warnings(record=True) as w: - j = Jp2k(filename) - self.assertEqual(len(w), 3) - if sys.hexversion >= 0x03000000: with self.assertWarns(UserWarning): jp2 = Jp2k(filename) From e5dd2229051cccb614409bdb67bb9df68816c618 Mon Sep 17 00:00:00 2001 From: bogdanni Date: Wed, 9 Apr 2014 14:41:23 +0200 Subject: [PATCH 202/326] jp2box.py: _parse_rreq3 - small fixup --- glymur/jp2box.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 80c8718..9291fe4 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2241,7 +2241,7 @@ def _parse_rreq3(read_buffer, length, offset): num_standard_features, = struct.unpack_from('>H', read_buffer, offset=7) fmt = '>' + 'HBBB' * num_standard_features - lst = struct.unpack(fmt, read_buffer, offset=9) + lst = struct.unpack_from(fmt, read_buffer, offset=9) standard_flag = lst[0::4] standard_mask = [] From 0c19deb182f1df65201a83b0c86ec34ccc92c292 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 9 Apr 2014 17:02:36 -0400 Subject: [PATCH 203/326] Added main_header_offset to ContiguousCodestreamBox. #218 --- glymur/jp2box.py | 14 ++++++++++---- glymur/test/test_jp2box.py | 9 +++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 9291fe4..5887864 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -990,14 +990,18 @@ class ContiguousCodestreamBox(Jp2kBox): offset of the box from the start of the file. longname : str more verbose description of the box. - main_header : list - List of segments in the codestream header. + main_header : Codestream object + contains list of main header marker/segments + main_header_offset : int + offset of main header from start of file """ - def __init__(self, main_header=None, length=0, offset=-1): + def __init__(self, main_header=None, main_header_offset=None, length=0, + offset=-1): Jp2kBox.__init__(self, box_id='jp2c', longname='Contiguous Codestream') self._main_header = main_header self.length = length self.offset = offset + self.main_header_offset = main_header_offset # The filename can be set if lazy loading is desired. self._filename = None @@ -1046,11 +1050,13 @@ class ContiguousCodestreamBox(Jp2kBox): ------- ContiguousCodestreamBox instance """ + main_header_offset = fptr.tell() if _parseoptions['codestream'] is True: main_header = Codestream(fptr, length, header_only=True) else: main_header = None - box = cls(main_header, length=length, offset=offset) + box = cls(main_header, main_header_offset=main_header_offset, + length=length, offset=offset) box._filename = fptr.name box._length = length box._offset = offset diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 1a00715..90f205d 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -935,6 +935,9 @@ class TestWrap(unittest.TestCase): class TestJp2Boxes(unittest.TestCase): """Tests for canonical JP2 boxes.""" + def setUp(self): + self.jpxfile = glymur.data.jpxfile() + def test_default_jp2k(self): """Should be able to instantiate a JPEG2000SignatureBox""" jp2k = glymur.jp2box.JPEG2000SignatureBox() @@ -971,6 +974,12 @@ class TestJp2Boxes(unittest.TestCase): self.assertEqual(box.box_id, 'jp2c') self.assertIsNone(box.main_header) + def test_codestream_main_header_offset(self): + """main_header_offset is an attribute of the CCS box""" + j = Jp2k(self.jpxfile); + self.assertEqual(j.box[5].main_header_offset, + j.box[5].offset + 8) + class TestRepr(unittest.TestCase): """Tests for __repr__ methods.""" From a7e160133ff049355a6b931b3e14acf20ae613a7 Mon Sep 17 00:00:00 2001 From: Bogdan Nicula Date: Sun, 20 Apr 2014 02:22:59 +0300 Subject: [PATCH 204/326] DataEntryURLBox.write(): one too many url.encode() --- glymur/jp2box.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 5887864..e1b066b 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2976,14 +2976,15 @@ class DataEntryURLBox(Jp2kBox): url = self.url if self.url[-1] != chr(0): url = url + chr(0) + url = url.encode() - length = 8 + 1 + 3 + len(url.encode()) + length = 8 + 1 + 3 + len(url) write_buffer = struct.pack('>I4sBBBB', length, b'url ', self.version, self.flag[0], self.flag[1], self.flag[2]) fptr.write(write_buffer) - fptr.write(url.encode()) + fptr.write(url) def __repr__(self): From 506e4fff9220b93aa471417a25ad89f5925dd255 Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 10 Apr 2014 21:17:26 -0400 Subject: [PATCH 205/326] Improved code coverage to over 95% --- glymur/_uuid_io.py | 7 +- glymur/codestream.py | 10 +-- glymur/jp2box.py | 56 ++++++++-------- glymur/jp2k.py | 45 ++++--------- glymur/test/fixtures.py | 54 +++++++++++++++ glymur/test/test_jp2box.py | 106 ++++++++++++++++++++++++++++++ glymur/test/test_jp2box_jpx.py | 90 +++++++++++++++++++++++-- glymur/test/test_jp2k.py | 11 ++++ glymur/test/test_opj_suite_neg.py | 22 +++++++ glymur/test/test_printing.py | 81 ++++++++++++++++++++++- 10 files changed, 399 insertions(+), 83 deletions(-) diff --git a/glymur/_uuid_io.py b/glymur/_uuid_io.py index 8cd5bcf..daa1994 100644 --- a/glymur/_uuid_io.py +++ b/glymur/_uuid_io.py @@ -2,6 +2,7 @@ """ Part of glymur. """ +from collections import OrderedDict import pprint import re import struct @@ -10,12 +11,6 @@ import warnings import lxml.etree as ET -if sys.hexversion < 0x02070000: - # pylint: disable=F0401,E0611 - from ordereddict import OrderedDict -else: - from collections import OrderedDict - def xml(raw_data): """ XMP data to be parsed as XML. diff --git a/glymur/codestream.py b/glymur/codestream.py index 6830fab..acc7791 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -188,15 +188,7 @@ class Codestream(object): while True: read_buffer = fptr.read(2) - try: - self._marker_id, = struct.unpack('>H', read_buffer) - except struct.error: - # Treat this as a warning. - msg = "Marker had length {0} instead of expected length of 2 " - msg += "bytes. Codestream parsing terminated." - warnings.warn(msg.format(len(read_buffer))) - break - + self._marker_id, = struct.unpack('>H', read_buffer) self._offset = fptr.tell() - 2 if self._marker_id == 0xff90 and header_only: diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 5887864..b20895b 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1011,7 +1011,7 @@ class ContiguousCodestreamBox(Jp2kBox): if self._main_header is None: if self._filename is not None: with open(self._filename, 'rb') as fptr: - fptr.seek(self._offset + 8) + fptr.seek(self.main_header_offset) main_header = Codestream(fptr, self._length, header_only=True) self._main_header = main_header return self._main_header @@ -1059,7 +1059,6 @@ class ContiguousCodestreamBox(Jp2kBox): length=length, offset=offset) box._filename = fptr.name box._length = length - box._offset = offset return box @@ -1110,7 +1109,7 @@ class DataReferenceBox(Jp2kBox): """ self._write_validate() - # Very similar to the say a superbox is written. + # Very similar to the way a superbox is written. orig_pos = fptr.tell() fptr.write(struct.pack('>I4s', 0, b'dtbl')) @@ -1176,7 +1175,7 @@ class DataReferenceBox(Jp2kBox): box = DataEntryURLBox.parse(box_fptr, 0, box_length) # Need to adjust the box start to that of the "real" file. - box.start = offset + box_offset + box.offset = offset + 8 + box_offset data_entry_url_box_list.append(box) # Point to the next embedded URL box. @@ -1341,7 +1340,10 @@ class FragmentListBox(Jp2kBox): self._dispatch_validation_error(msg, writing=writing) def __repr__(self): - msg = "glymur.jp2box.FragmentListBox()" + msg = "glymur.jp2box.FragmentListBox({0}, {1}, {2})" + msg = msg.format(str(self.fragment_offset), + str(self.fragment_length), + str(self.data_reference)) return msg def __str__(self): @@ -1426,7 +1428,8 @@ class FragmentTableBox(Jp2kBox): self.box = box if box is not None else [] def __repr__(self): - msg = "glymur.jp2box.FragmentTableBox()" + msg = "glymur.jp2box.FragmentTableBox(box={0})" + msg = msg.format(None) if (len(self.box) == 0) else msg.format(self.box) return msg def __str__(self): @@ -1884,6 +1887,12 @@ class PaletteBox(Jp2kBox): msg = "The length of the 'bits_per_component' and the 'signed' " msg += "members must equal the number of columns of the palette." self._dispatch_validation_error(msg, writing=writing) + bps = self.bits_per_component + if writing and not all(b == bps[0] for b in bps): + # We don't support writing palettes with bit depths that are + # different. + msg = "Writing palettes with varying bit depths is not supported." + self._dispatch_validation_error(msg, writing=writing) def __repr__(self): msg = "glymur.jp2box.PaletteBox({0}, bits_per_component={1}, " @@ -1925,26 +1934,14 @@ class PaletteBox(Jp2kBox): fptr.write(write_buffer) bps = self.bits_per_component - if all(b == bps[0] for b in bps): - # All components are the same. Writing is straightforward. - if self.bits_per_component[0] <= 8: - write_buffer = memoryview(self.palette.astype(np.uint8)) - elif self.bits_per_component[0] <= 16: - write_buffer = memoryview(self.palette.astype(np.uint16)) - elif self.bits_per_component[0] <= 32: - write_buffer = memoryview(self.palette.astype(np.uint32)) - fptr.write(write_buffer) - else: - # Not all the components are the same. More general, but much rarer - # case. Does this even happen. - code_dict = {8: 'B', 16: 'H', 32: 'I'} - codes = '' - for width in bps: - codes += code_dict[width] - fmt = '>' + codes - for row in self.palette: - write_buffer = struct.pack(fmt, *row) - fptr.write(write_buffer) + # All components are the same. Writing is straightforward. + if self.bits_per_component[0] <= 8: + write_buffer = memoryview(self.palette.astype(np.uint8)) + elif self.bits_per_component[0] <= 16: + write_buffer = memoryview(self.palette.astype(np.uint16)) + elif self.bits_per_component[0] <= 32: + write_buffer = memoryview(self.palette.astype(np.uint32)) + fptr.write(write_buffer) @classmethod def parse(cls, fptr, offset, length): @@ -2635,18 +2632,19 @@ class NumberListBox(Jp2kBox): msg += 'the rendered result' elif (association >> 24) == 1: idx = association & 0x00FFFFFF - msg += 'Codestream {0}' + msg += 'codestream {0}' msg = msg.format(idx) elif (association >> 24) == 2: idx = association & 0x00FFFFFF - msg += 'Compositing Layer {0}' + msg += 'compositing layer {0}' msg = msg.format(idx) else: msg += 'unrecognized' return msg def __repr__(self): - msg = 'glymur.jp2box.NumberListBox()' + msg = 'glymur.jp2box.NumberListBox(associations={0})' + msg = msg.format(self.associations) return msg @classmethod diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 8110c50..3fffea1 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -547,13 +547,13 @@ class Jp2k(Jp2kBox): opj2.setup_encoder(codec, cparams, image) - if _OPENJP2_IS_OFFICIAL_V2: + if re.match("2.0", version.openjpeg_version) is not None: fptr = libc.fopen(self.filename, 'wb') strm = opj2.stream_create_default_file_stream(fptr, False) stack.callback(opj2.stream_destroy, strm) stack.callback(libc.fclose, fptr) else: - # This routine introduced in 2.0 devel series. + # Introduced in 2.1 devel series. strm = opj2.stream_create_default_file_stream_v3(self.filename, False) stack.callback(opj2.stream_destroy_v3, strm) @@ -1080,17 +1080,17 @@ class Jp2k(Jp2kBox): layer=layer, tile=tile, area=area) with ExitStack() as stack: - if hasattr(opj2.OPENJP2, - 'opj_stream_create_default_file_stream_v3'): - filename = self.filename - stream = opj2.stream_create_default_file_stream_v3(filename, - True) - stack.callback(opj2.stream_destroy_v3, stream) - else: + if re.match("2.0", version.openjpeg_version): fptr = libc.fopen(self.filename, 'rb') stack.callback(libc.fclose, fptr) stream = opj2.stream_create_default_file_stream(fptr, True) stack.callback(opj2.stream_destroy, stream) + else: + # API change in 2.1+ + filename = self.filename + stream = opj2.stream_create_default_file_stream_v3(filename, + True) + stack.callback(opj2.stream_destroy_v3, stream) codec = opj2.create_decompress(self._codec_format) stack.callback(opj2.destroy_codec, codec) @@ -1147,12 +1147,6 @@ class Jp2k(Jp2kBox): Bitdepth: (8, 8, 8) Signed: (False, False, False) Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1)) - - Raises - ------ - IOError - If the file is JPX with more than one codestream and no JP2 - compatibility is advertised. """ with open(self.filename, 'rb') as fptr: if self._codec_format == opj2.CODEC_J2K: @@ -1161,11 +1155,6 @@ class Jp2k(Jp2kBox): else: ftyp = self.box[1] box = [x for x in self.box if x.box_id == 'jp2c'] - if len(box) > 1 and 'jp2 ' not in ftyp.compatibility_list: - msg = "If more than one codestream exists, JP2 " - msg += "compatibiltity must be advertised by the FileType " - msg += "box." - raise RuntimeError(msg) fptr.seek(box[0].offset) read_buffer = fptr.read(8) (box_length, _) = struct.unpack('>I4s', read_buffer) @@ -1183,7 +1172,7 @@ class Jp2k(Jp2kBox): return codestream -def component2dtype(component): +def _component2dtype(component): """Take an OpenJPEG component structure and determine the numpy datatype. Parameters @@ -1474,7 +1463,7 @@ def extract_image_cube(image): """ ncomps = image.contents.numcomps component = image.contents.comps[0] - dtype = component2dtype(component) + dtype = _component2dtype(component) nrows = component.h ncols = component.w @@ -1508,7 +1497,7 @@ def extract_image_bands(image): for k in range(image.contents.numcomps): component = image.contents.comps[k] - dtype = component2dtype(component) + dtype = _component2dtype(component) nrows = component.h ncols = component.w @@ -1698,7 +1687,7 @@ def _validate_compression_params(img_array, cparams): msg = "{0}D imagery is not allowed.".format(img_array.ndim) raise IOError(msg) - if _OPENJP2_IS_OFFICIAL_V2: + if re.match("2.0", version.openjpeg_version) is not None: if (((img_array.ndim != 2) and (img_array.shape[2] != 1 and img_array.shape[2] != 3))): msg = "Writing images is restricted to single-channel " @@ -1711,14 +1700,6 @@ def _validate_compression_params(img_array, cparams): msg = "Only uint8 and uint16 images are currently supported." raise RuntimeError(msg) -# Need to known if openjp2 library is the officially release v2.0.0 or not. -_OPENJP2_IS_OFFICIAL_V2 = False -if opj2.OPENJP2 is not None: - if opj2.version() == '2.0.0': - if not hasattr(opj2.OPENJP2, - 'opj_stream_create_default_file_stream_v3'): - _OPENJP2_IS_OFFICIAL_V2 = True - _COLORSPACE_MAP = {'rgb': opj2.CLRSPC_SRGB, 'gray': opj2.CLRSPC_GRAY, 'grey': opj2.CLRSPC_GRAY, diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 1ee7aa1..683ecca 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -622,3 +622,57 @@ cinema2k_profile = """SIZ marker segment @ (2, 47) Bitdepth: (12, 12, 12) Signed: (False, False, False) Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1))""" + +jplh_color_group_box = r"""Compositing Layer Header Box (jplh) @ (314227, 31) + Colour Group Box (cgrp) @ (314235, 23) + Colour Specification Box (colr) @ (314243, 15) + Method: enumerated colorspace + Precedence: 0 + Colorspace: sRGB""" + +fragment_list_box = r"""Fragment List Box (flst) @ (-1, 0) + Offset 0: 89 + Fragment Length 0: 1132288 + Data Reference 0: 0""" + +number_list_box = r"""Number List Box (nlst) @ (-1, 0) + Association[0]: the rendered result + Association[1]: codestream 0 + Association[2]: compositing layer 0""" + + +goodstuff = r"""Codestream: + SOC marker segment @ (0, 0) + SIZ marker segment @ (2, 47) + Profile: no profile + Reference Grid Height, Width: (800 x 480) + Vertical, Horizontal Reference Grid Offset: (0 x 0) + Reference Tile Height, Width: (800 x 480) + Vertical, Horizontal Reference Tile Offset: (0 x 0) + Bitdepth: (8, 8, 8) + Signed: (False, False, False) + Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1)) + COD marker segment @ (51, 12) + Coding style: + Entropy coder, without partitions + SOP marker segments: False + EPH marker segments: False + Coding style parameters: + Progression order: LRCP + Number of layers: 1 + Multiple component transformation usage: reversible + Number of resolutions: 6 + Code block height, width: (64 x 64) + Wavelet transform: 5-3 reversible + Precinct size: default, 2^15 x 2^15 + Code block context: + Selective arithmetic coding bypass: False + Reset context probabilities on coding pass boundaries: False + Termination on each coding pass: False + Vertically stripe causal context: False + Predictable termination: False + Segmentation symbols: False + QCD marker segment @ (65, 19) + Quantization style: no quantization, 2 guard bits + Step size: [(0, 8), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10)]""" + diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 90f205d..e19a097 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -543,6 +543,16 @@ class TestPaletteBox(unittest.TestCase): pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps, signed=signed) + def test_writing_with_different_bitdepths(self): + """Bitdepths must be the same when writing.""" + palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint16) + bps = (8, 16, 8) + signed = (False, False, False) + pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps, + signed=signed) + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + with self.assertRaises(IOError): + pclr.write(tfile) class TestAppend(unittest.TestCase): """Tests for append method.""" @@ -733,6 +743,49 @@ class TestWrap(unittest.TestCase): boxes = [box.box_id for box in jp2.box] self.assertEqual(boxes, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + def test_wrap_jp2_Lzero(self): + """Wrap jp2 with jp2c box length is zero""" + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + with open(self.jp2file, 'rb') as ifile: + tfile.write(ifile.read()) + # Rewrite with codestream length as zero. + tfile.seek(3223) + tfile.write(struct.pack('>I', 0)) + tfile.flush() + jp2 = Jp2k(tfile.name) + + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile2: + jp2 = jp2.wrap(tfile2.name) + boxes = [box for box in jp2.box] + self.assertEqual(boxes[3].length, 1132296) + + def test_wrap_jp2_Lone(self): + """Wrap jp2 with jp2c box length is 1, implies Q field""" + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + with open(self.jp2file, 'rb') as ifile: + tfile.write(ifile.read(3223)) + # Write new L, T, Q fields + tfile.write(struct.pack('>I4sQ', 1, b'jp2c', 1132296 + 8)) + # skip over the old L, T fields + ifile.seek(3231) + tfile.write(ifile.read()) + tfile.flush() + jp2 = Jp2k(tfile.name) + + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile2: + jp2 = jp2.wrap(tfile2.name) + boxes = [box for box in jp2.box] + self.assertEqual(boxes[3].length, 1132296 + 8) + + def test_wrap_compatibility_not_jp2(self): + """File type compatibility must contain jp2""" + jp2 = Jp2k(self.jp2file) + boxes = [box for box in jp2.box] + boxes[1].compatibility_list = ['jpx '] + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + with self.assertRaises(IOError): + jp2.wrap(tfile.name, boxes=boxes) + def test_empty_jp2h(self): """JP2H box list cannot be empty.""" jp2 = Jp2k(self.jp2file) @@ -992,6 +1045,59 @@ class TestRepr(unittest.TestCase): self.assertTrue(isinstance(newbox, glymur.jp2box.JPEG2000SignatureBox)) self.assertEqual(newbox.signature, (13, 10, 135, 10)) + def test_free(self): + """Should be able to instantiate a free box""" + free = glymur.jp2box.FreeBox() + + # Test the representation instantiation. + newbox = eval(repr(free)) + self.assertTrue(isinstance(newbox, glymur.jp2box.FreeBox)) + + def test_nlst(self): + """Should be able to instantiate a number list box""" + assn = (0, 1, 2) + nlst = glymur.jp2box.NumberListBox(assn) + + # Test the representation instantiation. + newbox = eval(repr(nlst)) + self.assertTrue(isinstance(newbox, glymur.jp2box.NumberListBox)) + self.assertEqual(newbox.associations, (0, 1, 2)) + + def test_ftbl(self): + """Should be able to instantiate a fragment table box""" + ftbl = glymur.jp2box.FragmentTableBox() + + # Test the representation instantiation. + newbox = eval(repr(ftbl)) + self.assertTrue(isinstance(newbox, glymur.jp2box.FragmentTableBox)) + + def test_dref(self): + """Should be able to instantiate a data reference box""" + dref = glymur.jp2box.DataReferenceBox() + + # Test the representation instantiation. + newbox = eval(repr(dref)) + self.assertTrue(isinstance(newbox, glymur.jp2box.DataReferenceBox)) + + def test_flst(self): + """Should be able to instantiate a fragment list box""" + flst = glymur.jp2box.FragmentListBox([89], [1132288], [0]) + + # Test the representation instantiation. + newbox = eval(repr(flst)) + self.assertTrue(isinstance(newbox, glymur.jp2box.FragmentListBox)) + self.assertEqual(newbox.fragment_offset, [89]) + self.assertEqual(newbox.fragment_length, [1132288]) + self.assertEqual(newbox.data_reference, [0]) + + def test_default_cgrp(self): + """Should be able to instantiate a color group box""" + cgrp = glymur.jp2box.ColourGroupBox() + + # Test the representation instantiation. + newbox = eval(repr(cgrp)) + self.assertTrue(isinstance(newbox, glymur.jp2box.ColourGroupBox)) + def test_default_ftyp(self): """Should be able to instantiate a FileTypeBox""" ftyp = glymur.jp2box.FileTypeBox() diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 7fe407a..3e09c83 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -3,6 +3,7 @@ Test suite specifically targeting JPX box layout. """ +import ctypes import os import struct import sys @@ -52,6 +53,7 @@ class TestJPXWrap(unittest.TestCase): tfile1.write(fptr.read()) tfile1.flush() jp2_1 = Jp2k(tfile1.name) + jp2h = jp2_1.box[2] jp2c = [box for box in jp2_1.box if box.box_id == 'jp2c'][0] @@ -61,12 +63,13 @@ class TestJPXWrap(unittest.TestCase): clen = [] dr_idx = [] - coff.append(jp2c.offset + 8) + coff.append(jp2c.main_header_offset) clen.append(jp2c.length - (coff[0] - jp2c.offset)) dr_idx.append(1) # Make the url box for this codestream. url1 = DataEntryURLBox(0, [0, 0, 0], 'file://' + tfile1.name) + url1_name_len = len(url1.url) + 1 with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2: @@ -74,7 +77,7 @@ class TestJPXWrap(unittest.TestCase): jp2_2 = j2k.wrap(tfile2.name) jp2c = [box for box in jp2_2.box if box.box_id == 'jp2c'][0] - coff.append(jp2c.offset + 8) + coff.append(jp2c.main_header_offset) clen.append(jp2c.length - (coff[0] - jp2c.offset)) dr_idx.append(2) @@ -85,7 +88,7 @@ class TestJPXWrap(unittest.TestCase): FileTypeBox(brand='jpx ', compatibility_list=['jpx ', 'jp2 ', 'jpxb']), - jp2_1.box[2]] + jp2h] with tempfile.NamedTemporaryFile(suffix='.jpx') as tjpx: for box in boxes: box.write(tjpx) @@ -99,6 +102,15 @@ class TestJPXWrap(unittest.TestCase): dtbl.write(tjpx) tjpx.flush() + jpx_no_jp2c = Jp2k(tjpx.name) + jpx_boxes = [box.box_id for box in jpx_no_jp2c.box] + self.assertEqual(jpx_boxes, ['jP ', 'ftyp', 'jp2h', + 'ftbl', 'dtbl']) + self.assertEqual(jpx_no_jp2c.box[4].DR[0].offset, 141) + + offset = 141 + 8 + 4 + url1_name_len + self.assertEqual(jpx_no_jp2c.box[4].DR[1].offset, offset) + def test_jp2_with_jpx_box(self): """If the brand is jp2, then no jpx boxes are allowed.""" jp2 = Jp2k(self.jp2file) @@ -154,8 +166,8 @@ class TestJPXWrap(unittest.TestCase): self.assertEqual(jpx.box[-1].box[0].box_id, 'colr') self.assertEqual(jpx.box[-1].box[1].box_id, 'colr') - def test_cgrp_neg(self): - """Can't write a cgrp with anything but colr sub boxes""" + def test_label_neg(self): + """Can't write a label box embedded in any old box.""" jp2 = Jp2k(self.jp2file) boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] @@ -173,6 +185,26 @@ class TestJPXWrap(unittest.TestCase): with self.assertRaises(IOError): jpx = jp2.wrap(tfile.name, boxes=boxes) + def test_cgrp_neg(self): + """Can't write a cgrp with anything but colr sub boxes""" + jp2 = Jp2k(self.jp2file) + boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] + + # The ftyp box must be modified to jpx. + boxes[1].brand = 'jpx ' + boxes[1].compatibility_list = ['jp2 ', 'jpxb'] + + the_xml = ET.fromstring('0') + xmlb = glymur.jp2box.XMLBox(xml=the_xml) + box = [xmlb] + + cgrp = glymur.jp2box.ColourGroupBox(box=box) + boxes.append(cgrp) + + with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: + with self.assertRaises(IOError): + jpx = jp2.wrap(tfile.name, boxes=boxes) + def test_ftbl(self): """Write a fragment table box.""" # Add a negative test where offset < 0 @@ -459,7 +491,7 @@ class TestJPX(unittest.TestCase): self.assertEqual(jpx.box[2].box_id, 'rreq') self.assertEqual(type(jpx.box[2]), glymur.jp2box.ReaderRequirementsBox) - self.assertEqual(jpx.box[2].standard_flag, + self.asserwrite_buffertEqual(jpx.box[2].standard_flag, (5, 42, 45, 2, 18, 19, 1, 8, 12, 31, 20)) @unittest.skip("Requires unnecessarily complicated code") @@ -552,6 +584,52 @@ class TestJPX(unittest.TestCase): self.assertEqual(jpx.box[-1].box[0].fragment_length, (170246,)) self.assertEqual(jpx.box[-1].box[0].data_reference, (3,)) + def test_rreq3(self): + """Verify that we can read a rreq box with mask length 3 bytes""" + rreq_buffer = ctypes.create_string_buffer(74) + struct.pack_into('>I4s', rreq_buffer, 0, 74, b'rreq') + + # mask length + struct.pack_into('>B', rreq_buffer, 8, 3) + + # fuam, dcm. 6 bytes, two sets of 3. + lst = (255, 224, 0, 0, 31, 252) + struct.pack_into('>BBBBBB', rreq_buffer, 9, *lst) + + # number of standard features: 11 + struct.pack_into('>H', rreq_buffer, 15, 11) + + standard_flags = [5, 42, 45, 2, 18, 19, 1, 8, 12, 31, 20] + standard_masks = [8388608, 4194304, 2097152, 1048576, 524288, 262144, + 131072, 65536, 32768, 16384, 8192] + for j in range(len(standard_flags)): + mask = (standard_masks[j] >> 16, + standard_masks[j] & 0x0000ffff>> 8, + standard_masks[j] & 0x000000ff) + struct.pack_into('>HBBB', rreq_buffer, 17 + j * 5, + standard_flags[j], *mask) + + # num vendor features: 0 + struct.pack_into('>H', rreq_buffer, 72, 0) + + # Ok, done with the box, we can now insert it into the jpx file after + # the ftyp box. + with tempfile.NamedTemporaryFile(suffix=".jpx") as ofile: + with open(self.jpxfile, 'rb') as ifile: + ofile.write(ifile.read(40)) + ofile.write(rreq_buffer) + ofile.write(ifile.read()) + ofile.flush() + + jpx = Jp2k(ofile.name) + + self.assertEqual(jpx.box[2].box_id, 'rreq') + self.assertEqual(type(jpx.box[2]), + glymur.jp2box.ReaderRequirementsBox) + self.assertEqual(jpx.box[2].standard_flag, + (5, 42, 45, 2, 18, 19, 1, 8, 12, 31, 20)) + + def test_nlst(self): """Verify that we can handle a number list box.""" j = Jp2k(self.jpxfile) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 3901cc5..7d2c911 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -779,6 +779,17 @@ class TestParsing(unittest.TestCase): with self.assertWarns(UserWarning): jp2 = Jp2k(filename) + @unittest.skip("blah") + def test_main_header(self): + """Verify that the main header is not loaded when parsing turned off.""" + # The hidden _main_header attribute should show up after accessing it. + glymur.set_parseoptions(codestream=False) + jp2 = Jp2k(self.jp2file) + jp2c = jp2.box[4] + self.assertIsNone(jp2c._main_header) + main_header = jp2c.main_header + self.assertIsNotNone(jp2c._main_header) + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") class TestJp2kOpjDataRoot(unittest.TestCase): diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index c9a7e8c..f6ef098 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -16,6 +16,13 @@ import unittest import numpy as np +try: + import skimage.io + skimage.io.use_plugin('freeimage', 'imread') + _HAS_SKIMAGE_FREEIMAGE_SUPPORT = True +except ((ImportError, RuntimeError)): + _HAS_SKIMAGE_FREEIMAGE_SUPPORT = False + from .fixtures import OPJ_DATA_ROOT, opj_data_file, read_image from .fixtures import NO_READ_BACKEND, NO_READ_BACKEND_MSG @@ -35,6 +42,21 @@ class TestSuiteNegative(unittest.TestCase): def tearDown(self): pass + + @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, + "Cannot read input image without scikit-image/freeimage") + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") + def test_cinema2K_bad_frame_rate(self): + """Cinema2k frame rate must be either 24 or 48.""" + relfile = 'input/nonregression/X_5_2K_24_235_CBR_STEM24_000.tif' + infile = opj_data_file(relfile) + data = skimage.io.imread(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError): + j.write(data, cinema2k=36) + + @unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG) @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_psnr_with_cratios(self): diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 5286419..db40530 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -50,6 +50,18 @@ class TestPrinting(unittest.TestCase): def tearDown(self): pass + def test_codestream(self): + """Should be able to print a raw codestream.""" + j = glymur.Jp2k(self.j2kfile) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(j) + actual = fake_out.getvalue().strip() + # Remove the file line, as that is filesystem-dependent. + lines = actual.split('\n') + actual = '\n'.join(lines[1:]) + + self.assertEqual(actual, fixtures.codestream) + def test_version_info(self): """Should be able to print(glymur.version.info)""" with patch('sys.stdout', new=StringIO()) as fake_out: @@ -598,6 +610,63 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) + def test_flst(self): + """Verify printing of fragment list box.""" + flst = glymur.jp2box.FragmentListBox([89], [1132288], [0]) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(flst) + actual = fake_out.getvalue().strip() + self.assertEqual(actual, fixtures.fragment_list_box) + + def test_dref(self): + """Verify printing of data reference box.""" + dref = glymur.jp2box.DataReferenceBox() + with patch('sys.stdout', new=StringIO()) as fake_out: + print(dref) + actual = fake_out.getvalue().strip() + self.assertEqual(actual, 'Data Reference Box (dtbl) @ (-1, 0)') + + def test_jplh_cgrp(self): + """Verify printing of compositing layer header box, color group box.""" + jpx = glymur.Jp2k(self.jpxfile) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(jpx.box[7]) + actual = fake_out.getvalue().strip() + self.assertEqual(actual, fixtures.jplh_color_group_box) + + def test_free(self): + """Verify printing of Free box.""" + free = glymur.jp2box.FreeBox() + with patch('sys.stdout', new=StringIO()) as fake_out: + print(free) + actual = fake_out.getvalue().strip() + self.assertEqual(actual, 'Free Box (free) @ (-1, 0)') + + def test_nlst(self): + """Verify printing of number list box.""" + assn = (0, 16777216, 33554432) + nlst = glymur.jp2box.NumberListBox(assn) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(nlst) + actual = fake_out.getvalue().strip() + self.assertEqual(actual, fixtures.number_list_box) + + def test_ftbl(self): + """Verify printing of fragment table box.""" + ftbl = glymur.jp2box.FragmentTableBox() + with patch('sys.stdout', new=StringIO()) as fake_out: + print(ftbl) + actual = fake_out.getvalue().strip() + self.assertEqual(actual, 'Fragment Table Box (ftbl) @ (-1, 0)') + + def test_jpch(self): + """Verify printing of JPCH box.""" + jpx = glymur.Jp2k(self.jpxfile) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(jpx.box[3]) + actual = fake_out.getvalue().strip() + self.assertEqual(actual, 'Codestream Header Box (jpch) @ (887, 8)') + @unittest.skipIf(sys.hexversion < 0x03000000, "Ordered dicts not printing well in 2.7") def test_exif_uuid(self): @@ -893,6 +962,17 @@ class TestPrintingOpjDataRoot(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) + def test_componentmapping_box_alpha(self): + """Verify __repr__ method on cmap box.""" + cmap = glymur.jp2box.ComponentMappingBox(component_index=(0, 0, 0), + mapping_type=(1, 1, 1), + palette_index=(0, 1, 2)) + newbox = eval(repr(cmap)) + self.assertEqual(newbox.box_id, 'cmap') + self.assertEqual(newbox.component_index, (0, 0, 0)) + self.assertEqual(newbox.mapping_type, (1, 1, 1)) + self.assertEqual(newbox.palette_index, (0, 1, 2)) + def test_palette7(self): """verify printing of pclr box""" filename = opj_data_file('input/conformance/file9.jp2') @@ -905,7 +985,6 @@ class TestPrintingOpjDataRoot(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) - @unittest.skip("file7 no longer has a rreq") def test_rreq(self): """verify printing of reader requirements box""" filename = opj_data_file('input/nonregression/text_GBR.jp2') From e4578db53518cc6ae72bb985a33ace7100cd201a Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 24 Apr 2014 12:01:53 -0400 Subject: [PATCH 206/326] Removed OPENJP2_IS_V2_OFFICIAL token. #224 This can be done by a regular expression against the library version. It had not been done because much of the original development was against the svn 2.0+ version of openjpeg before the library version got bumped to 2.1 --- glymur/jp2k.py | 4 ++-- glymur/lib/test/test_openjp2.py | 11 ++++------- glymur/test/fixtures.py | 8 -------- glymur/test/test_jp2box.py | 8 ++++---- glymur/test/test_jp2k.py | 18 +++++++++--------- glymur/test/test_opj_suite.py | 9 ++++----- glymur/test/test_opj_suite_write.py | 11 +++++------ 7 files changed, 28 insertions(+), 41 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 3fffea1..1e6a790 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -167,7 +167,7 @@ class Jp2k(Jp2kBox): fps : int Frames per second, should be either 24 or 48. """ - if re.match("(1.5|2.0)", version.openjpeg_version) is not None: + if re.match("(1.5|2.0.0)", version.openjpeg_version) is not None: msg = "Writing Cinema2K or Cinema4K files is not supported with " msg += 'openjpeg library versions less than 2.0.1.' raise IOError(msg) @@ -1687,7 +1687,7 @@ def _validate_compression_params(img_array, cparams): msg = "{0}D imagery is not allowed.".format(img_array.ndim) raise IOError(msg) - if re.match("2.0", version.openjpeg_version) is not None: + if re.match("2.0.0", version.openjpeg_version) is not None: if (((img_array.ndim != 2) and (img_array.shape[2] != 1 and img_array.shape[2] != 3))): msg = "Writing images is restricted to single-channel " diff --git a/glymur/lib/test/test_openjp2.py b/glymur/lib/test/test_openjp2.py index 0abac84..67d6750 100644 --- a/glymur/lib/test/test_openjp2.py +++ b/glymur/lib/test/test_openjp2.py @@ -6,6 +6,7 @@ Tests for libopenjp2 wrapping functions. # pylint: disable=R0904,W0142 import os +import re import sys import tempfile import unittest @@ -15,17 +16,13 @@ import numpy as np import glymur from glymur.lib import openjp2 -OPENJP2_IS_V2_OFFICIAL = False -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(openjp2.OPENJP2 is None, "Missing openjp2 library.") -@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL, "API followed here specific to V2.0+") +@unittest.skipIf(re.match(r'''(1|2.0)''', + glymur.version.openjpeg_version) is not None, + "Not to be run until 2.1.0") class TestOpenJP2(unittest.TestCase): """Test openjp2 library functionality. diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 683ecca..0fc5be1 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -25,14 +25,6 @@ try: except: HAS_PYTHON_XMP_TOOLKIT = False -# Need to know of the libopenjp2 version is the official 2.0.0 release and NOT -# the 2.0+ development version. -OPENJP2_IS_V2_OFFICIAL = False -if glymur.lib.openjp2.OPENJP2 is not None: - if not hasattr(glymur.lib.openjp2.OPENJP2, - 'opj_stream_create_default_file_stream_v3'): - OPENJP2_IS_V2_OFFICIAL = True - NO_READ_BACKEND_MSG = "Matplotlib with the PIL backend must be available in " NO_READ_BACKEND_MSG += "order to run the tests in this suite." diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index e19a097..d8866cf 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -36,7 +36,7 @@ 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, opj_data_file +from .fixtures import opj_data_file try: FORMAT_CORPUS_DATA_ROOT = os.environ['FORMAT_CORPUS_DATA_ROOT'] @@ -117,9 +117,9 @@ class TestDataEntryURL(unittest.TestCase): self.assertEqual(url + chr(0), read_url) -@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2 or - OPENJP2_IS_V2_OFFICIAL, - "Not supported until 2.0+.") +@unittest.skipIf(re.match(r'''(1|2.0.0)''', + glymur.version.openjpeg_version) is not None, + "Not supported until 2.1") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") class TestChannelDefinition(unittest.TestCase): """Test suite for channel definition boxes.""" diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 7d2c911..f012c54 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -29,7 +29,7 @@ import pkg_resources import glymur from glymur import Jp2k -from .fixtures import HAS_PYTHON_XMP_TOOLKIT, OPENJP2_IS_V2_OFFICIAL +from .fixtures import HAS_PYTHON_XMP_TOOLKIT if HAS_PYTHON_XMP_TOOLKIT: import libxmp from libxmp import XMPMeta @@ -379,10 +379,9 @@ class TestJp2k(unittest.TestCase): creator_tool = xmp.get_property(libxmp.consts.XMP_NS_XMP, 'CreatorTool') self.assertEqual(creator_tool, 'Google') - @unittest.skipIf(fixtures.OPENJP2_IS_V2_OFFICIAL, - "Feature not supported in 2.0.0 official") - @unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1, - "Feature not supported in 1.5") + @unittest.skipIf(re.match(r'''(1|2.0.0)''', + glymur.version.openjpeg_version) is not None, + "Not supported until 2.0.1") def test_jpx_mult_codestreams_jp2_brand(self): """Read JPX codestream when jp2-compatible.""" # The file in question has multiple codestreams. @@ -581,7 +580,8 @@ class TestJp2k_1_x(unittest.TestCase): j2k.read(layer=1) -@unittest.skipIf(not OPENJP2_IS_V2_OFFICIAL, +@unittest.skipIf(re.match(r'''2.0.0''', + glymur.version.openjpeg_version) is None, "Tests only to be run on 2.0 official.") class TestJp2k_2_0_official(unittest.TestCase): """Test suite to only be run on v2.0 official.""" @@ -679,9 +679,9 @@ class TestJp2k_2_0(unittest.TestCase): self.assertEqual(jasoc.box[3].box[1].box_id, 'xml ') -@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2 or - OPENJP2_IS_V2_OFFICIAL, - "Missing openjp2 library version 2.0+.") +@unittest.skipIf(re.match(r'''(1|2.0.0)''', + glymur.version.openjpeg_version) is not None, + "Not to be run until unless 2.0.1 or higher is present") class TestJp2k_2_1(unittest.TestCase): """Only to be run in 2.0+.""" diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 899a5b5..f4534ef 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -38,7 +38,7 @@ import numpy as np from glymur import Jp2k import glymur -from .fixtures import OPENJP2_IS_V2_OFFICIAL, OPJ_DATA_ROOT +from .fixtures import OPJ_DATA_ROOT from .fixtures import mse, peak_tolerance, read_pgx, opj_data_file @@ -6599,10 +6599,9 @@ class TestSuite2point0(unittest.TestCase): @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") -@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL, - "Test not in done in v2.0.0 official") -@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1, - "Tests not introduced until 2.1") +@unittest.skipIf(re.match(r'''(1|2.0.0)''', + glymur.version.openjpeg_version) is not None, + "Only supported in 2.0.1 or higher") class TestSuite2point1(unittest.TestCase): """Runs tests introduced in version 2.0+ or that pass only in 2.0+""" diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index c92c500..076f02b 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -29,10 +29,9 @@ import glymur @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, "Cannot read input image without scikit-image/freeimage") @unittest.skipIf(os.name == "nt", "no write support on windows, period") -@unittest.skipIf(fixtures.OPENJP2_IS_V2_OFFICIAL, - "Feature not supported in 2.0.0 official") -@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1, - "Feature not supported in 1.5") +@unittest.skipIf(re.match(r'''(1|2.0.0)''', + glymur.version.openjpeg_version) is not None, + "Uses features not supported until 2.0.1") @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") class TestSuiteWriteCinema(unittest.TestCase): @@ -225,8 +224,8 @@ class TestSuiteWriteCinema(unittest.TestCase): @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, "Cannot read input image without scikit-image/freeimage") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") -@unittest.skipIf(not re.match("(1.5|2.0)", glymur.version.openjpeg_version), - "Functionality implemented for 2.1") +@unittest.skipIf(not re.match("(1.5|2.0.0)", glymur.version.openjpeg_version), + "Functionality implemented for 2.0.1") @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_OPJ_DATA_ROOT environment variable not set") class TestSuiteNegative2pointzero(unittest.TestCase): From b24ae781a6738d44c2b11888cd71a982aaf7c03e Mon Sep 17 00:00:00 2001 From: Bogdan Nicula Date: Thu, 24 Apr 2014 21:04:08 +0200 Subject: [PATCH 207/326] Lower case for 'glymur' package name in setup.py Is there a reason for different capitalization? --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5b91590..d86d9e2 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import os import re import sys -kwargs = {'name': 'Glymur', +kwargs = {'name': 'glymur', 'description': 'Tools for accessing JPEG2000 files', 'long_description': open('README.md').read(), 'author': 'John Evans', From e6723b18dd81e08886051a0a394117be74058225 Mon Sep 17 00:00:00 2001 From: Bogdan Nicula Date: Thu, 24 Apr 2014 21:06:44 +0200 Subject: [PATCH 208/326] Python 2.6 is not supported anymore --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index d8ea732..22338f1 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ glymur: a Python interface for JPEG 2000 **glymur** contains a Python interface to the OpenJPEG library which allows one to read and write JPEG 2000 files. **glymur** works on -Python 2.6, 2.7 and 3.3. Python 3.3 is strongly -recommended. +Python 2.7 and 3.3. Python 3.3 is strongly recommended. Please read the docs, https://glymur.readthedocs.org/en/latest/ From 1e754f0e8568adbed200e729ba7b388483015a42 Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 24 Apr 2014 20:06:15 -0400 Subject: [PATCH 209/326] Updated openjp2 API for upstream openjpeg r2837 and r2847. #221 Removed "_v3" suffixes from glymur.lib.openjp2 functions. Changed JP2k object to use both 2.0 and 2.1 means of setting cinema compression parameter values. --- glymur/core.py | 57 ++++++++++++++++++++++++++ glymur/jp2k.py | 62 ++++++++++++++++++---------- glymur/lib/openjp2.py | 71 +++++++++++++++++---------------- glymur/lib/test/test_openjp2.py | 12 +++--- 4 files changed, 139 insertions(+), 63 deletions(-) diff --git a/glymur/core.py b/glymur/core.py index 07949f9..4de6283 100644 --- a/glymur/core.py +++ b/glymur/core.py @@ -44,6 +44,63 @@ CINEMA_MODE = { 'cinema2k_48': CINEMA2K_48, 'cinema4k_24': CINEMA4K_24, } +PROFILE = { + # no profile, conform to 15444-1 + 'none': 0x0000, + # profile 0 as described in 15444-1,Table A.45 + '0': 0x0001, + 'zero': 0x0001, + 0: 0x0001, + # profile 1 as described in 15444-1,Table A.45 + '1': 0x0002, + 'one': 0x0002, + 1: 0x0002, + # at least 1 extension defined in 15444-2 (Part-2) + '2': 0x8000, + 2: 0x8000, + 'two': 0x8000, + # 2K cinema profile defined in 15444-1 AMD1 + 'cinema_2k': 0x0003, + # 4K cinema profile defined in 15444-1 AMD1 + 'cinema_4k': 0x0004, + # scalable 2K cinema profile defined in 15444-1 AMD2 + 'cinema_s2k': 0x0005, + # scalable 4K cinema profile defined in 15444-1 AMD2 + 'cinema_s4k': 0x0006, + # long term storage cinema profile defined in 15444-1 AMD2 + 'cinema_lts': 0x0007, + # Single Multi Tile Broadcast profile defined in 15444-1 AMD3 + 'bc_single': 0x0100, + # Multi Tile Broadcast profile defined in 15444-1 AMD3 + 'bc_multi': 0x0200, + # Multi Tile Reversible Broadcast profile defined in 15444-1 AMD3 + 'bc_multi_r': 0x0300, + # 2K Single Tile Lossy IMF profile defined in 15444-1 AMD 8 + 'imf_2k': 0x0400, + # 4K Single Tile Lossy IMF profile defined in 15444-1 AMD 8 + 'imf_4k': 0x0401, + # 8K Single Tile Lossy IMF profile defined in 15444-1 AMD 8 + 'imf_8k': 0x0402, + # 2K Single/Multi Tile Reversible IMF profile defined in 15444-1 AMD 8 + 'imf_2k_r': 0x0403, + # 4K Single/Multi Tile Reversible IMF profile defined in 15444-1 AMD 8 + 'imf_4k_r': 0x0800, + # 8K Single/Multi Tile Reversible IMF profile defined in 15444-1 AMD 8 + 'imf_8k_r': 0x0801, + } + +# JPEG 2000 codestream and component size limits in cinema profiles +# +# Maximum codestream length for 24fps +OPJ_CINEMA_24_CS = 1302083 +# Maximum codestream length for 48fps +OPJ_CINEMA_48_CS = 651041 +# Maximum size per color component for 2K & 4K @ 24fps +OPJ_CINEMA_24_COMP = 1041666 +# Maximum size per color component for 2K @ 48fps +OPJ_CINEMA_48_COMP = 520833 + + PROGRESSION_ORDER = { 'LRCP': LRCP, 'RLCP': RLCP, diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 1e6a790..5885c1d 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -29,8 +29,9 @@ import numpy as np from .codestream import Codestream from .core import SRGB, GREYSCALE -from .core import PROGRESSION_ORDER, CINEMA_MODE +from .core import PROGRESSION_ORDER from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE +from . import core from .jp2box import Jp2kBox from .jp2box import JPEG2000SignatureBox, FileTypeBox, JP2HeaderBox from .jp2box import ColourSpecificationBox, ContiguousCodestreamBox @@ -173,14 +174,34 @@ class Jp2k(Jp2kBox): raise IOError(msg) if cinema_mode == 'cinema2k': - if fps == 24: - cparams.cp_cinema = CINEMA_MODE['cinema2k_24'] - elif fps == 48: - cparams.cp_cinema = CINEMA_MODE['cinema2k_48'] - else: + if fps not in [24, 48]: raise IOError('Cinema2K frame rate must be either 24 or 48.') + + if re.match("2.0", version.openjpeg_version) is not None: + # 2.0 API + if fps == 24: + cparams.cp_cinema = core.CINEMA_MODE['cinema2k_24'] + else: + cparams.cp_cinema = core.CINEMA_MODE['cinema2k_48'] + else: + # 2.1 API + if fps == 24: + cparams.rsiz = core.PROFILE['cinema_2k'] + cparams.max_comp_size = core.CINEMA_24_COMP + cparams.max_cs_size = core.CINEMA_24_CS + else: + cparams.rsiz = core.PROFILE['cinema_2k'] + cparams.max_comp_size = core.CINEMA_48_COMP + cparams.max_cs_size = core.CINEMA_48_CS + else: - cparams.cp_cinema = CINEMA_MODE['cinema4k_24'] + # cinema4k + if re.match("2.0", version.openjpeg_version) is not None: + # 2.0 API + cparams.cp_cinema = core.CINEMA_MODE['cinema4k_24'] + else: + # 2.1 API + cparams.rsiz = core.PROFILE['cinema_4k'] return @@ -554,9 +575,9 @@ class Jp2k(Jp2kBox): stack.callback(libc.fclose, fptr) else: # Introduced in 2.1 devel series. - strm = opj2.stream_create_default_file_stream_v3(self.filename, - False) - stack.callback(opj2.stream_destroy_v3, strm) + strm = opj2.stream_create_default_file_stream(self.filename, + False) + stack.callback(opj2.stream_destroy, strm) opj2.start_compress(codec, image, strm) opj2.encode(codec, strm) @@ -909,12 +930,10 @@ class Jp2k(Jp2kBox): layer=layer, tile=tile, area=area) with ExitStack() as stack: - if hasattr(opj2.OPENJP2, - 'opj_stream_create_default_file_stream_v3'): + if re.match("2.1", version.openjpeg_version): filename = self.filename - stream = opj2.stream_create_default_file_stream_v3(filename, - True) - stack.callback(opj2.stream_destroy_v3, stream) + stream = opj2.stream_create_default_file_stream(filename, True) + stack.callback(opj2.stream_destroy, stream) else: fptr = libc.fopen(self.filename, 'rb') stack.callback(libc.fclose, fptr) @@ -1080,17 +1099,16 @@ class Jp2k(Jp2kBox): layer=layer, tile=tile, area=area) with ExitStack() as stack: - if re.match("2.0", version.openjpeg_version): + if re.match("2.1", version.openjpeg_version): + # API change in 2.1 + filename = self.filename + stream = opj2.stream_create_default_file_stream(filename, True) + stack.callback(opj2.stream_destroy, 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) - else: - # API change in 2.1+ - filename = self.filename - stream = opj2.stream_create_default_file_stream_v3(filename, - True) - stack.callback(opj2.stream_destroy_v3, stream) codec = opj2.create_decompress(self._codec_format) stack.callback(opj2.destroy_codec, codec) diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py index 2da8d75..5b5f3c4 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -5,6 +5,7 @@ Wraps individual functions in openjp2 library. # pylint: disable=C0302,R0903,W0201 import ctypes +import re import sys from .config import glymur_config @@ -12,7 +13,11 @@ OPENJP2, OPENJPEG = glymur_config() def version(): """Wrapper for opj_version library routine.""" - OPENJP2.opj_version.restype = ctypes.c_char_p + try: + OPENJP2.opj_version.restype = ctypes.c_char_p + except: + return "0.0.0" + library_version = OPENJP2.opj_version() if sys.hexversion >= 0x03000000: return library_version.decode('utf-8') @@ -43,6 +48,13 @@ JPWL_MAX_NO_TILESPECS = 16 TRUE = 1 FALSE = 0 +#PROFILE = {'none': 0, # No profile +# 0: 1, # Profile 0 +# 1: 2, # Profile 1 +# 'part2': 0x8000, # At least one extension +# 'Cinema2K': 0x0003, # 2K cinema profile +# 'Cinema4K': 0x0004, # 4K cinema profile + # supported color spaces CLRSPC_UNKNOWN = -1 CLRSPC_UNSPECIFIED = 0 @@ -368,6 +380,16 @@ class CompressionParametersType(ctypes.Structure): # based encoding without offset concerning all the components. ("mct_data", ctypes.c_void_p)] + if _MINOR == '1': + # Maximum size (in bytes) for the whole codestream. + # If == 0, codestream size limitation is not considered. + # If it does not comply with tcp_rates, max_cs_size prevails and a + # warning is issued. + _fields_.append(("max_cs_size", ctypes.c_int32)) + + # To be used to combine OPJ_PROFILE_*, OPJ_EXTENSION_* and (sub)levels + # values. + _fields_.append(("rsiz", ctypes.c_uint16)) class ImageCompType(ctypes.Structure): """Defines a single image component. @@ -1278,7 +1300,7 @@ def start_compress(codec, image, stream): OPENJP2.opj_start_compress(codec, image, stream) -def stream_create_default_file_stream(fptr, isa_read_stream): +def _stream_create_default_file_stream_2p0(fptr, isa_read_stream): """Wraps openjp2 library function opj_stream_create_default_vile_stream. Sets the stream to be a file stream. This is valid only for version 2.0.0 @@ -1303,12 +1325,11 @@ def stream_create_default_file_stream(fptr, isa_read_stream): stream = OPENJP2.opj_stream_create_default_file_stream(fptr, read_stream) return stream - -def stream_create_default_file_stream_v3(fname, isa_read_stream): - """Wraps openjp2 library function opj_stream_create_default_vile_stream_v3. +def _stream_create_default_file_stream_2p1(fname, isa_read_stream): + """Wraps openjp2 library function opj_stream_create_default_vile_stream. Sets the stream to be a file stream. This function is only valid for the - trunk/development 2.0+ version of the openjp2 library. + 2.1 version of the openjp2 library. Parameters ---------- @@ -1323,13 +1344,18 @@ def stream_create_default_file_stream_v3(fname, isa_read_stream): An OpenJPEG file stream. """ ARGTYPES = [ctypes.c_char_p, ctypes.c_int32] - OPENJP2.opj_stream_create_default_file_stream_v3.argtypes = ARGTYPES - OPENJP2.opj_stream_create_default_file_stream_v3.restype = STREAM_TYPE_P + OPENJP2.opj_stream_create_default_file_stream.argtypes = ARGTYPES + OPENJP2.opj_stream_create_default_file_stream.restype = STREAM_TYPE_P read_stream = 1 if isa_read_stream else 0 file_argument = ctypes.c_char_p(fname.encode()) - stream = OPENJP2.opj_stream_create_default_file_stream_v3(file_argument, - read_stream) + stream = OPENJP2.opj_stream_create_default_file_stream(file_argument, + read_stream) return stream + +if re.match(r'''2.0''', version()): + stream_create_default_file_stream = _stream_create_default_file_stream_2p0 +else: + stream_create_default_file_stream = _stream_create_default_file_stream_2p1 def stream_destroy(stream): @@ -1347,21 +1373,6 @@ def stream_destroy(stream): OPENJP2.opj_stream_destroy(stream) -def stream_destroy_v3(stream): - """Wraps openjp2 library function opj_stream_destroy_v3. - - Destroys the stream created by create_stream_v3. - - Parameters - ---------- - stream : STREAM_TYPE_P - The file stream. - """ - OPENJP2.opj_stream_destroy_v3.argtypes = [STREAM_TYPE_P] - OPENJP2.opj_stream_destroy_v3.restype = ctypes.c_void_p - OPENJP2.opj_stream_destroy_v3(stream) - - def write_tile(codec, tile_index, data, data_size, stream): """Wraps openjp2 library function opj_write_tile. @@ -1403,13 +1414,3 @@ def write_tile(codec, tile_index, data, data_size, stream): def set_error_message(msg): """The openjpeg error handler has recorded an error message.""" ERROR_MSG_LST.append(msg) - - -def version(): - """Wrapper for opj_version library routine.""" - OPENJP2.opj_version.restype = ctypes.c_char_p - library_version = OPENJP2.opj_version() - if sys.hexversion >= 0x03000000: - return library_version.decode('utf-8') - else: - return library_version diff --git a/glymur/lib/test/test_openjp2.py b/glymur/lib/test/test_openjp2.py index 67d6750..70ef9b6 100644 --- a/glymur/lib/test/test_openjp2.py +++ b/glymur/lib/test/test_openjp2.py @@ -79,7 +79,7 @@ class TestOpenJP2(unittest.TestCase): openjp2.set_warning_handler(codec, None) openjp2.set_error_handler(codec, None) - stream = openjp2.stream_create_default_file_stream_v3(filename, True) + stream = openjp2.stream_create_default_file_stream(filename, True) openjp2.setup_decoder(codec, dparam) image = openjp2.read_header(stream, codec) @@ -100,7 +100,7 @@ class TestOpenJP2(unittest.TestCase): openjp2.end_decompress(codec, stream) openjp2.destroy_codec(codec) - openjp2.stream_destroy_v3(stream) + openjp2.stream_destroy(stream) openjp2.image_destroy(image) def test_tte0(self): @@ -308,7 +308,7 @@ def tile_encoder(**kwargs): openjp2.setup_encoder(codec, l_param, l_image) - stream = openjp2.stream_create_default_file_stream_v3(kwargs['filename'], + stream = openjp2.stream_create_default_file_stream(kwargs['filename'], False) openjp2.start_compress(codec, l_image, stream) @@ -316,7 +316,7 @@ def tile_encoder(**kwargs): openjp2.write_tile(codec, j, data, tile_size, stream) openjp2.end_compress(codec, stream) - openjp2.stream_destroy_v3(stream) + openjp2.stream_destroy(stream) openjp2.destroy_codec(codec) openjp2.image_destroy(l_image) @@ -325,7 +325,7 @@ def tile_decoder(**kwargs): Reads a tile. That's all it does. """ - stream = openjp2.stream_create_default_file_stream_v3(kwargs['filename'], + stream = openjp2.stream_create_default_file_stream(kwargs['filename'], True) dparam = openjp2.set_default_decoder_parameters() @@ -361,7 +361,7 @@ def tile_decoder(**kwargs): openjp2.end_decompress(codec, stream) openjp2.destroy_codec(codec) - openjp2.stream_destroy_v3(stream) + openjp2.stream_destroy(stream) openjp2.image_destroy(image) def ttx0_setup(filename): From 4d15054b0cb6477670ae326dab78b1ec98f28f50 Mon Sep 17 00:00:00 2001 From: John Evans Date: Sun, 27 Apr 2014 14:15:06 -0400 Subject: [PATCH 210/326] Fixed cinema profile writing on 2.1. #227 Was not forcing CINEMA_2K, 4K images to be written out as 12bps in 2.1.0. Was ok in 2.0.1. Removed "OPJ_" prefix from CINEMA_{24,48}_COMP and CINEMA_{24,48}_CS fields in core. --- glymur/core.py | 89 +++++++++++++---------------- glymur/jp2k.py | 34 ++++++----- glymur/test/test_opj_suite_write.py | 11 +++- 3 files changed, 70 insertions(+), 64 deletions(-) diff --git a/glymur/core.py b/glymur/core.py index 4de6283..4d9a3af 100644 --- a/glymur/core.py +++ b/glymur/core.py @@ -38,56 +38,47 @@ CINEMA2K_24 = 1 CINEMA2K_48 = 2 CINEMA4K_24 = 3 -CINEMA_MODE = { - 'off': OFF, - 'cinema2k_24': CINEMA2K_24, - 'cinema2k_48': CINEMA2K_48, - 'cinema4k_24': CINEMA4K_24, } +OPJ_OFF = 0 # Not Digital Cinema +OPJ_CINEMA2K_24 = 1 # 2K Digital Cinema at 24 fps +OPJ_CINEMA2K_48 = 2 # 2K Digital Cinema at 48 fps +OPJ_CINEMA4K_24 = 3 # 4K Digital Cinema at 24 fps -PROFILE = { - # no profile, conform to 15444-1 - 'none': 0x0000, - # profile 0 as described in 15444-1,Table A.45 - '0': 0x0001, - 'zero': 0x0001, - 0: 0x0001, - # profile 1 as described in 15444-1,Table A.45 - '1': 0x0002, - 'one': 0x0002, - 1: 0x0002, - # at least 1 extension defined in 15444-2 (Part-2) - '2': 0x8000, - 2: 0x8000, - 'two': 0x8000, - # 2K cinema profile defined in 15444-1 AMD1 - 'cinema_2k': 0x0003, - # 4K cinema profile defined in 15444-1 AMD1 - 'cinema_4k': 0x0004, - # scalable 2K cinema profile defined in 15444-1 AMD2 - 'cinema_s2k': 0x0005, - # scalable 4K cinema profile defined in 15444-1 AMD2 - 'cinema_s4k': 0x0006, - # long term storage cinema profile defined in 15444-1 AMD2 - 'cinema_lts': 0x0007, - # Single Multi Tile Broadcast profile defined in 15444-1 AMD3 - 'bc_single': 0x0100, - # Multi Tile Broadcast profile defined in 15444-1 AMD3 - 'bc_multi': 0x0200, - # Multi Tile Reversible Broadcast profile defined in 15444-1 AMD3 - 'bc_multi_r': 0x0300, - # 2K Single Tile Lossy IMF profile defined in 15444-1 AMD 8 - 'imf_2k': 0x0400, - # 4K Single Tile Lossy IMF profile defined in 15444-1 AMD 8 - 'imf_4k': 0x0401, - # 8K Single Tile Lossy IMF profile defined in 15444-1 AMD 8 - 'imf_8k': 0x0402, - # 2K Single/Multi Tile Reversible IMF profile defined in 15444-1 AMD 8 - 'imf_2k_r': 0x0403, - # 4K Single/Multi Tile Reversible IMF profile defined in 15444-1 AMD 8 - 'imf_4k_r': 0x0800, - # 8K Single/Multi Tile Reversible IMF profile defined in 15444-1 AMD 8 - 'imf_8k_r': 0x0801, - } +# no profile, conform to 15444-1 +OPJ_PROFILE_NONE = 0x0000 +# Profile 0 as described in 15444-1,Table A.45 +OPJ_PROFILE_0 = 0x0001 +# Profile 1 as described in 15444-1,Table A.45 +OPJ_PROFILE_1 = 0x0002 +# At least 1 extension defined in 15444-2 (Part-2) +OPJ_PROFILE_PART2 = 0x8000 +# 2K cinema profile defined in 15444-1 AMD1 +OPJ_PROFILE_CINEMA_2K = 0x0003 +# 4K cinema profile defined in 15444-1 AMD1 +OPJ_PROFILE_CINEMA_4K = 0x0004 +# Scalable 2K cinema profile defined in 15444-1 AMD2 +OPJ_PROFILE_CINEMA_S2K = 0x0005 +# Scalable 4K cinema profile defined in 15444-1 AMD2 +OPJ_PROFILE_CINEMA_S4K = 0x0006 +# Long term storage cinema profile defined in 15444-1 AMD2 +OPJ_PROFILE_CINEMA_LTS = 0x0007 +# Single Tile Broadcast profile defined in 15444-1 AMD3 +OPJ_PROFILE_BC_SINGLE = 0x0100 +# Multi Tile Broadcast profile defined in 15444-1 AMD3 +OPJ_PROFILE_BC_MULTI = 0x0200 +# Multi Tile Reversible Broadcast profile defined in 15444-1 AMD3 +OPJ_PROFILE_BC_MULTI_R = 0x0300 +# 2K Single Tile Lossy IMF profile defined in 15444-1 AMD 8 +OPJ_PROFILE_IMF_2K = 0x0400 +# 4K Single Tile Lossy IMF profile defined in 15444-1 AMD 8 +OPJ_PROFILE_IMF_4K = 0x0401 +# 8K Single Tile Lossy IMF profile defined in 15444-1 AMD 8 +OPJ_PROFILE_IMF_8K = 0x0402 +# 2K Single/Multi Tile Reversible IMF profile defined in 15444-1 AMD 8 +OPJ_PROFILE_IMF_2K_R = 0x0403 +# 4K Single/Multi Tile Reversible IMF profile defined in 15444-1 AMD 8 +OPJ_PROFILE_IMF_4K_R = 0x0800 +# 8K Single/Multi Tile Reversible IMF profile defined in 15444-1 AMD 8 +OPJ_PROFILE_IMF_8K_R = 0x0801 # JPEG 2000 codestream and component size limits in cinema profiles # diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 5885c1d..a9e6ab2 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -180,28 +180,28 @@ class Jp2k(Jp2kBox): if re.match("2.0", version.openjpeg_version) is not None: # 2.0 API if fps == 24: - cparams.cp_cinema = core.CINEMA_MODE['cinema2k_24'] + cparams.cp_cinema = core.OPJ_CINEMA_2K_24 else: - cparams.cp_cinema = core.CINEMA_MODE['cinema2k_48'] + cparams.cp_cinema = core.OPJ_CINEMA_2K_48 else: # 2.1 API if fps == 24: - cparams.rsiz = core.PROFILE['cinema_2k'] - cparams.max_comp_size = core.CINEMA_24_COMP - cparams.max_cs_size = core.CINEMA_24_CS + cparams.rsiz = core.OPJ_PROFILE_CINEMA_2K + cparams.max_comp_size = core.OPJ_CINEMA_24_COMP + cparams.max_cs_size = core.OPJ_CINEMA_24_CS else: - cparams.rsiz = core.PROFILE['cinema_2k'] - cparams.max_comp_size = core.CINEMA_48_COMP - cparams.max_cs_size = core.CINEMA_48_CS + cparams.rsiz = core.OPJ_PROFILE_CINEMA_2K + cparams.max_comp_size = core.OPJ_CINEMA_48_COMP + cparams.max_cs_size = core.OPJ_CINEMA_48_CS else: # cinema4k if re.match("2.0", version.openjpeg_version) is not None: # 2.0 API - cparams.cp_cinema = core.CINEMA_MODE['cinema4k_24'] + cparams.cp_cinema = core.OPJ_CINEMA_4K_24 else: # 2.1 API - cparams.rsiz = core.PROFILE['cinema_4k'] + cparams.rsiz = core.OPJ_PROFILE_CINEMA_4K return @@ -1642,9 +1642,17 @@ def _populate_image_struct(cparams, image, imgdata): # Stage the image data to the openjpeg data structure. for k in range(0, num_comps): - if cparams.cp_cinema: - image.contents.comps[k].prec = 12 - image.contents.comps[k].bpp = 12 + if re.match("2.0", version.openjpeg_version) is not None: + # 2.0 API + if cparams.cp_cinema: + image.contents.comps[k].prec = 12 + image.contents.comps[k].bpp = 12 + else: + # 2.1 API + if cparams.rsiz in (core.OPJ_PROFILE_CINEMA_2K, + core.OPJ_PROFILE_CINEMA_4K): + image.contents.comps[k].prec = 12 + image.contents.comps[k].bpp = 12 layer = np.ascontiguousarray(imgdata[:, :, k], dtype=np.int32) dest = image.contents.comps[k].data diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 076f02b..99e6f0b 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -11,6 +11,7 @@ import re import sys import tempfile import unittest +import warnings try: import skimage.io @@ -156,7 +157,10 @@ class TestSuiteWriteCinema(unittest.TestCase): data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, 'wb') - j.write(data, cinema4k=True) + with warnings.catch_warnings(): + # Just turn off warnings. + warnings.simplefilter("ignore") + j.write(data, cinema4k=True) codestream = j.get_codestream() self.check_cinema4k_codestream(codestream, (4096, 2160)) @@ -216,7 +220,10 @@ class TestSuiteWriteCinema(unittest.TestCase): data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, 'wb') - j.write(data, cinema2k=48) + with warnings.catch_warnings(): + # Just turn off warnings. + warnings.simplefilter("ignore") + j.write(data, cinema2k=48) codestream = j.get_codestream() self.check_cinema2k_codestream(codestream, (1998, 1080)) From 564a9d2410b130e29fb42d2b560bf1a738b87bfd Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 8 May 2014 20:16:02 -0400 Subject: [PATCH 211/326] Removed tests that still cannot be run as of 2.1.0 --- glymur/test/test_jp2box_jpx.py | 10 - glymur/test/test_jp2k.py | 3 +- glymur/test/test_opj_suite.py | 388 ---------------------------- glymur/test/test_opj_suite_write.py | 2 +- 4 files changed, 3 insertions(+), 400 deletions(-) diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 3e09c83..a39cc48 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -484,16 +484,6 @@ class TestJPX(unittest.TestCase): with tempfile.TemporaryFile() as tfile: ftbl.write(tfile) - @unittest.skip("No such jpx file anymore.") - def test_jpx_rreq_mask_length_3(self): - """There are some JPX files with rreq mask length of 3.""" - jpx = Jp2k(self.jpxfile) - self.assertEqual(jpx.box[2].box_id, 'rreq') - self.assertEqual(type(jpx.box[2]), - glymur.jp2box.ReaderRequirementsBox) - self.asserwrite_buffertEqual(jpx.box[2].standard_flag, - (5, 42, 45, 2, 18, 19, 1, 8, 12, 31, 20)) - @unittest.skip("Requires unnecessarily complicated code") def test_unknown_superbox(self): """Verify that we can handle an unknown superbox.""" diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index f012c54..5aa7ec8 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -759,6 +759,7 @@ class TestJp2k_2_1(unittest.TestCase): class TestParsing(unittest.TestCase): """Tests for verifying how paring may be altered.""" def setUp(self): + self.jp2file = glymur.data.nemo() # Reset parseoptions for every test. glymur.set_parseoptions(codestream=True) @@ -779,7 +780,7 @@ class TestParsing(unittest.TestCase): with self.assertWarns(UserWarning): jp2 = Jp2k(filename) - @unittest.skip("blah") + #@unittest.skip("blah") def test_main_header(self): """Verify that the main header is not loaded when parsing turned off.""" # The hidden _main_header attribute should show up after accessing it. diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index f4534ef..cd15f81 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -161,38 +161,6 @@ class TestSuite(unittest.TestCase): pgxdata = read_pgx(pgxfile) np.testing.assert_array_equal(jpdata, pgxdata) - @unittest.skip("fprintf stderr output in r2343.") - def test_ETS_C1P0_p0_12_j2k(self): - jfile = opj_data_file('input/conformance/p0_12.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c1p0_12_0.pgx') - pgxdata = read_pgx(pgxfile) - np.testing.assert_array_equal(jpdata, pgxdata) - - @unittest.skip("fprintf stderr output in r2343.") - def test_ETS_C1P0_p0_13_j2k(self): - jfile = opj_data_file('input/conformance/p0_13.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c1p0_13_0.pgx') - pgxdata = read_pgx(pgxfile) - np.testing.assert_array_equal(jpdata[:, :, 0], pgxdata) - - pgxfile = opj_data_file('baseline/conformance/c1p0_13_1.pgx') - pgxdata = read_pgx(pgxfile) - np.testing.assert_array_equal(jpdata[:, :, 1], pgxdata) - - pgxfile = opj_data_file('baseline/conformance/c1p0_13_2.pgx') - pgxdata = read_pgx(pgxfile) - np.testing.assert_array_equal(jpdata[:, :, 2], pgxdata) - - pgxfile = opj_data_file('baseline/conformance/c1p0_13_3.pgx') - pgxdata = read_pgx(pgxfile) - np.testing.assert_array_equal(jpdata[:, :, 3], pgxdata) - def test_ETS_C1P0_p0_14_j2k(self): jfile = opj_data_file('input/conformance/p0_14.j2k') jp2k = Jp2k(jfile) @@ -267,64 +235,6 @@ class TestSuite(unittest.TestCase): self.assertTrue(peak_tolerance(jpdata, pgxdata) < 624) self.assertTrue(mse(jpdata, pgxdata) < 3080) - @unittest.skip("fprintf stderr output in r2343.") - def test_ETS_C1P1_p1_05_j2k(self): - jfile = opj_data_file('input/conformance/p1_05.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read() - - pgxfile = opj_data_file('baseline/conformance/c1p1_05_0.pgx') - pgxdata = read_pgx(pgxfile) - self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 40) - self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 8.458) - - pgxfile = opj_data_file('baseline/conformance/c1p1_05_1.pgx') - pgxdata = read_pgx(pgxfile) - self.assertTrue(peak_tolerance(jpdata[:, :, 1], pgxdata) < 40) - self.assertTrue(mse(jpdata[:, :, 1], pgxdata) < 9.816) - - pgxfile = opj_data_file('baseline/conformance/c1p1_05_2.pgx') - pgxdata = read_pgx(pgxfile) - self.assertTrue(peak_tolerance(jpdata[:, :, 2], pgxdata) < 40) - self.assertTrue(mse(jpdata[:, :, 2], pgxdata) < 10.154) - - @unittest.skip("fprintf stderr output in r2343.") - def test_ETS_C1P1_p1_06_j2k(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read() - - pgxfile = opj_data_file('baseline/conformance/c1p1_06_0.pgx') - pgxdata = read_pgx(pgxfile) - self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 2) - self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 0.6) - - pgxfile = opj_data_file('baseline/conformance/c1p1_06_1.pgx') - pgxdata = read_pgx(pgxfile) - self.assertTrue(peak_tolerance(jpdata[:, :, 1], pgxdata) < 2) - self.assertTrue(mse(jpdata[:, :, 1], pgxdata) < 0.6) - - pgxfile = opj_data_file('baseline/conformance/c1p1_06_2.pgx') - pgxdata = read_pgx(pgxfile) - self.assertTrue(peak_tolerance(jpdata[:, :, 2], pgxdata) < 2) - self.assertTrue(mse(jpdata[:, :, 2], pgxdata) < 0.6) - - @unittest.skip("fprintf stderr output in r2343.") - def test_ETS_C1P1_p1_07_j2k(self): - jfile = opj_data_file('input/conformance/p1_07.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read_bands() - - pgxfile = opj_data_file('baseline/conformance/c1p1_07_0.pgx') - pgxdata = read_pgx(pgxfile) - self.assertTrue(peak_tolerance(jpdata[0], pgxdata) <= 0) - self.assertTrue(mse(jpdata[0], pgxdata) <= 0) - - pgxfile = opj_data_file('baseline/conformance/c1p1_07_1.pgx') - pgxdata = read_pgx(pgxfile) - self.assertTrue(peak_tolerance(jpdata[1], pgxdata) <= 0) - self.assertTrue(mse(jpdata[1], pgxdata) <= 0) - def test_ETS_JP2_file1(self): jfile = opj_data_file('input/conformance/file1.jp2') with warnings.catch_warnings(): @@ -402,13 +312,6 @@ class TestSuite(unittest.TestCase): jp2.read() self.assertTrue(True) - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_123_j2c_3_decode(self): - jfile = opj_data_file('input/nonregression/123.j2c') - jp2 = Jp2k(jfile) - jp2.read() - self.assertTrue(True) - @unittest.skipIf(sys.hexversion < 0x03020000, "Uses features introduced in 3.2.") def test_NR_DEC_broken_jp2_4_decode(self): @@ -431,12 +334,6 @@ class TestSuite(unittest.TestCase): with self.assertRaises(IOError): j.read() - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_bug_j2c_8_decode(self): - jfile = opj_data_file('input/nonregression/bug.j2c') - Jp2k(jfile).read() - self.assertTrue(True) - def test_NR_DEC_buxI_j2k_9_decode(self): jfile = opj_data_file('input/nonregression/buxI.j2k') Jp2k(jfile).read() @@ -464,15 +361,6 @@ class TestSuite(unittest.TestCase): Jp2k(jfile).read() self.assertTrue(True) - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_illegalcolortransform_j2k_14_decode(self): - # Stream too short, expected SOT. - jfile = opj_data_file('input/nonregression/illegalcolortransform.j2k') - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - Jp2k(jfile).read() - self.assertTrue(True) - def test_NR_DEC_j2k32_j2k_15_decode(self): jfile = opj_data_file('input/nonregression/j2k32.j2k') Jp2k(jfile).read() @@ -541,86 +429,6 @@ class TestSuite(unittest.TestCase): Jp2k(jfile).read() self.assertTrue(True) - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_76_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - fulldata = jp2k.read() - tiledata = jp2k.read(tile=0) - np.testing.assert_array_equal(tiledata, fulldata[0:3, 0:3]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_77_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - fulldata = jp2k.read() - tiledata = jp2k.read(tile=5) - np.testing.assert_array_equal(tiledata, fulldata[3:6, 3:6]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_78_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - fulldata = jp2k.read() - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - tiledata = jp2k.read(tile=9) - np.testing.assert_array_equal(tiledata, fulldata[6:9, 3:6]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_79_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - fulldata = jp2k.read() - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - tiledata = jp2k.read(tile=15) - np.testing.assert_array_equal(tiledata, fulldata[9:12, 9:12]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_80_decode(self): - # Just read the data, don't bother verifying. - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - jp2k.read(tile=0, rlevel=2) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_81_decode(self): - # Just read the data, don't bother verifying. - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - jp2k.read(tile=5, rlevel=2) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_82_decode(self): - # Just read the data, don't bother verifying. - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - jp2k.read(tile=9, rlevel=2) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_83_decode(self): - # tile size is 3x3. Reducing two levels results in no data. - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - with self.assertRaises((IOError, OSError)): - jp2k.read(tile=15, rlevel=2) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_84_decode(self): - # Just read the data, don't bother verifying. - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - jp2k.read(rlevel=4) - @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @@ -4068,7 +3876,6 @@ class TestSuiteDump(unittest.TestCase): # Comment value self.assertEqual(c.segment[4].ccme.decode('latin-1'), "Kakadu-v6.3.1") - @unittest.skip("fprintf stderr output in r2343.") def test_NR_illegalcolortransform_dump(self): jfile = opj_data_file('input/nonregression/illegalcolortransform.j2k') jp2k = Jp2k(jfile) @@ -5792,120 +5599,6 @@ class TestSuiteDump(unittest.TestCase): self.assertEqual(c.segment[4].ccme.decode('latin-1'), "Kakadu-v5.2.1") - @unittest.skip("Bad PCLR box") - def test_NR_mem_b2ace68c_1381_dump(self): - jfile = opj_data_file('input/nonregression/mem-b2ace68c-1381.jp2') - with warnings.catch_warnings(): - # This file has a bad pclr box, we test for this elsewhere. - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'pclr', 'cmap']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jpxb') - self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') - - # Reader requirements talk. - # cmyk colourspace - self.assertTrue(55 in jp2.box[2].standard_flag) - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 865) - self.assertEqual(jp2.box[3].box[0].width, 649) - self.assertEqual(jp2.box[3].box[0].num_components, 1) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 1) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[1].precedence, 2) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[3].box[1].colorspace, glymur.core.CMYK) - - # Jp2 Header - # Palette box. - self.assertEqual(jp2.box[3].box[2].palette.shape, (1, 4)) - - # Jp2 Header - # Component mapping box - self.assertEqual(jp2.box[3].box[3].component_index, (0, 0, 0, 0)) - self.assertEqual(jp2.box[3].box[3].mapping_type, (1, 1, 1, 1)) - self.assertEqual(jp2.box[3].box[3].palette_index, (0, 1, 2, 3)) - - c = jp2.box[4].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 649) - self.assertEqual(c.segment[1].ysiz, 865) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (256, 256)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (1,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, [1] + [2, 2, 3] * 5) - def test_NR_mem_b2b86b74_2753_dump(self): jfile = opj_data_file('input/nonregression/mem-b2b86b74-2753.jp2') jp2 = Jp2k(jfile) @@ -6635,16 +6328,6 @@ class TestSuite2point1(unittest.TestCase): Jp2k(jfile).read() self.assertTrue(True) - @unittest.skip("Failing as of r2436") - def test_NR_DEC_mem_b2ace68c_1381_jp2_34_decode(self): - jfile = opj_data_file('input/nonregression/mem-b2ace68c-1381.jp2') - with warnings.catch_warnings(): - # This file has a bad pclr box, we test for this elsewhere. - warnings.simplefilter("ignore") - j = Jp2k(jfile) - j.read() - self.assertTrue(True) - def test_NR_DEC_mem_b2b86b74_2753_jp2_35_decode(self): jfile = opj_data_file('input/nonregression/mem-b2b86b74-2753.jp2') Jp2k(jfile).read() @@ -6838,77 +6521,6 @@ class TestSuite2point1(unittest.TestCase): with self.assertRaises(IOError): j.read() - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_61_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 0, 12, 12)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[0:12, 0:12]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_62_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(1, 8, 8, 11)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[1:8, 8:11]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_63_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(9, 9, 12, 12)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[9:12, 9:12]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_64_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(10, 4, 12, 10)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[10:12, 4:10]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_65_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(3, 3, 9, 9)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[3:9, 3:9]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_66_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(4, 4, 7, 7)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[4:7, 4:7]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_67_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(4, 4, 5, 5)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[4:5, 4: 5]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_68_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 0, 12, 12), rlevel=1) - odata = jp2k.read(rlevel=1) - np.testing.assert_array_equal(ssdata, odata[0:6, 0:6]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_69_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(1, 8, 8, 11), rlevel=1) - self.assertEqual(ssdata.shape, (3, 2, 3)) - def test_NR_DEC_p1_06_j2k_70_decode(self): jfile = opj_data_file('input/conformance/p1_06.j2k') jp2k = Jp2k(jfile) diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 99e6f0b..861b31b 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -1020,7 +1020,7 @@ class TestSuiteWrite(unittest.TestCase): glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) - @unittest.skip("Known failure in openjpeg test suite.") + #@unittest.skip("Known failure in openjpeg test suite.") def test_NR_ENC_random_issue_0005_tif_12_encode(self): """NR-ENC-random-issue-0005.tif-12-encode""" # opj_decompress has trouble reading it, but that is not an issue here. From 937d8c971cbcd2c5f2dd93f4613a203f6fd4a3ea Mon Sep 17 00:00:00 2001 From: John Evans Date: Sat, 10 May 2014 15:29:36 -0400 Subject: [PATCH 212/326] Warnings tested with warnings.catch_warnings infrastructure. Until there's some explanation as to why assertWarns method is failing, this seems to be the only way forward. --- glymur/test/test_codestream.py | 56 +- glymur/test/test_config.py | 6 +- glymur/test/test_icc.py | 7 +- glymur/test/test_jp2box.py | 46 +- glymur/test/test_jp2box_jpx.py | 35 +- glymur/test/test_jp2box_uuid.py | 17 +- glymur/test/test_jp2box_xml.py | 38 +- glymur/test/test_jp2k.py | 68 +- glymur/test/test_opj_suite.py | 1140 +++++++++++++++-------------- glymur/test/test_opj_suite_neg.py | 11 +- glymur/test/test_printing.py | 14 +- 11 files changed, 723 insertions(+), 715 deletions(-) diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py index 8bac19c..91c1adb 100644 --- a/glymur/test/test_codestream.py +++ b/glymur/test/test_codestream.py @@ -54,46 +54,39 @@ class TestCodestreamOpjData(unittest.TestCase): def test_bad_rsiz(self): """Should warn if RSIZ is bad. Issue196""" filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') - if sys.hexversion < 0x03000000: - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - j = Jp2k(filename) - else: - with self.assertWarns(UserWarning): - j = Jp2k(filename) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + j = Jp2k(filename) + self.assertEqual(len(w), 3) + self.assertTrue(issubclass(w[0].category, UserWarning)) + self.assertTrue('Invalid profile' in str(w[0].message)) def test_bad_wavelet_transform(self): """Should warn if wavelet transform is bad. Issue195""" filename = opj_data_file('input/nonregression/edf_c2_10025.jp2') - if sys.hexversion < 0x03000000: - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - j = Jp2k(filename) - else: - with self.assertWarns(UserWarning): - j = Jp2k(filename) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + j = Jp2k(filename) + self.assertTrue(issubclass(w[0].category, UserWarning)) + self.assertTrue('Invalid wavelet transform' in str(w[0].message)) def test_invalid_progression_order(self): """Should still be able to parse even if prog order is invalid.""" jfile = opj_data_file('input/nonregression/2977.pdf.asan.67.2198.jp2') - if sys.hexversion < 0x03000000: - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - Jp2k(jfile) - else: - with self.assertWarns(UserWarning): - Jp2k(jfile) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + Jp2k(jfile) + self.assertTrue(issubclass(w[0].category, UserWarning)) + self.assertTrue('Invalid progression order' in str(w[0].message)) def test_tile_height_is_zero(self): """Zero tile height should not cause an exception.""" filename = opj_data_file('input/nonregression/2539.pdf.SIGFPE.706.1712.jp2') - if sys.hexversion < 0x03000000: - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - Jp2k(filename) - else: - with self.assertWarns(UserWarning): - Jp2k(filename) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + Jp2k(filename) + self.assertTrue(issubclass(w[0].category, UserWarning)) + self.assertTrue('Invalid tile dimensions' in str(w[0].message)) @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @@ -127,8 +120,6 @@ class TestCodestreamOpjData(unittest.TestCase): 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.""" @@ -151,8 +142,11 @@ class TestCodestreamOpjData(unittest.TestCase): tfile.write(read_buffer) tfile.flush() - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') codestream = Jp2k(tfile.name).get_codestream() + self.assertTrue(issubclass(w[0].category, UserWarning)) + self.assertTrue('Unrecognized marker' in str(w[0].message)) self.assertEqual(codestream.segment[2].marker_id, '0xff79') self.assertEqual(codestream.segment[2].length, 3) diff --git a/glymur/test/test_config.py b/glymur/test/test_config.py index 6884d2c..b8c97ae 100644 --- a/glymur/test/test_config.py +++ b/glymur/test/test_config.py @@ -16,6 +16,7 @@ import os import sys import tempfile import unittest +import warnings if sys.hexversion <= 0x03030000: from mock import patch @@ -83,8 +84,11 @@ class TestSuite(unittest.TestCase): with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}): # Misconfigured new configuration file should # be rejected. - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') imp.reload(glymur.lib.openjp2) + self.assertTrue(issubclass(w[0].category,UserWarning)) + self.assertTrue('could not be loaded' in str(w[0].message)) @unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None and diff --git a/glymur/test/test_icc.py b/glymur/test/test_icc.py index d2dd959..3ddc249 100644 --- a/glymur/test/test_icc.py +++ b/glymur/test/test_icc.py @@ -62,16 +62,17 @@ class TestICC(unittest.TestCase): self.assertEqual(profile['Creator'], 'JPEG') - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") def test_invalid_profile_header(self): """invalid ICC header data should cause UserWarning""" jfile = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2') # assertWarns in Python 3.3 (python2.7/pylint issue) # pylint: disable=E1101 - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') Jp2k(jfile) + self.assertTrue(issubclass(w[0].category,UserWarning)) + self.assertTrue('ICC profile header is corrupt' in str(w[0].message)) if __name__ == "__main__": unittest.main() diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index d8866cf..cef0d41 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -357,7 +357,6 @@ class TestChannelDefinition(unittest.TestCase): with self.assertRaises((IOError, OSError)): j2k.wrap(tfile.name, boxes=boxes) - @unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.") def test_bad_type(self): """Channel types are limited to 0, 1, 2, 65535 Should reject if not all of index, channel_type, association the @@ -365,20 +364,26 @@ class TestChannelDefinition(unittest.TestCase): """ channel_type = (COLOR, COLOR, 3) association = (RED, GREEN, BLUE) - with self.assertWarns(UserWarning): + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type, association=association) + self.assertEqual(len(w), 1) + self.assertTrue(issubclass(w[0].category, UserWarning)) - @unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.") def test_wrong_lengths(self): """Should reject if not all of index, channel_type, association the same length. """ channel_type = (COLOR, COLOR) association = (RED, GREEN, BLUE) - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type, association=association) + self.assertEqual(len(w), 1) + self.assertTrue(issubclass(w[0].category, UserWarning)) class TestFileTypeBox(unittest.TestCase): @@ -474,32 +479,41 @@ class TestColourSpecificationBox(unittest.TestCase): self.assertEqual(colr.colorspace, glymur.core.SRGB) self.assertIsNone(colr.icc_profile) - @unittest.skipIf(sys.hexversion < 0x03030000, "Requires 3.3+") def test_colr_with_cspace_and_icc(self): """Colour specification boxes can't have both.""" - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') colorspace = glymur.core.SRGB rawb = b'\x01\x02\x03\x04' glymur.jp2box.ColourSpecificationBox(colorspace=colorspace, icc_profile=rawb) + self.assertTrue(issubclass(w[0].category,UserWarning)) + msg = 'Colorspace and icc_profile cannot both be set' + self.assertTrue(msg in str(w[0].message)) - @unittest.skipIf(sys.hexversion < 0x03030000, "Requires 3.3+") def test_colr_with_bad_method(self): """colr must have a valid method field""" colorspace = glymur.core.SRGB method = -1 - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') glymur.jp2box.ColourSpecificationBox(colorspace=colorspace, method=method) + self.assertTrue(issubclass(w[0].category,UserWarning)) + msg = 'Invalid method' + self.assertTrue(msg in str(w[0].message)) - @unittest.skipIf(sys.hexversion < 0x03030000, "Requires 3.3+") def test_colr_with_bad_approx(self): """colr should have a valid approximation field""" colorspace = glymur.core.SRGB approx = -1 - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') glymur.jp2box.ColourSpecificationBox(colorspace=colorspace, approximation=approx) + self.assertTrue(issubclass(w[0].category,UserWarning)) + msg = 'Invalid approximation' + self.assertTrue(msg in str(w[0].message)) def test_colr_with_bad_color(self): """colr must have a valid color, strange as though that may sound.""" @@ -523,25 +537,29 @@ class TestPaletteBox(unittest.TestCase): def tearDown(self): pass - @unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.") def test_mismatched_bitdepth_signed(self): """bitdepth and signed arguments must have equal length""" palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint8) bps = (8, 8, 8) signed = (False, False) - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps, signed=signed) + self.assertEqual(len(w), 1) + self.assertTrue(issubclass(w[0].category, UserWarning)) - @unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.") def test_mismatched_signed_palette(self): """bitdepth and signed arguments must have equal length""" palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint8) bps = (8, 8, 8, 8) signed = (False, False, False, False) - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps, signed=signed) + self.assertEqual(len(w), 1) + self.assertTrue(issubclass(w[0].category, UserWarning)) def test_writing_with_different_bitdepths(self): """Bitdepths must be the same when writing.""" diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 3e09c83..3a5d42e 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -305,15 +305,17 @@ class TestJPXWrap(unittest.TestCase): with self.assertRaises(IOError): jp2.wrap(tfile.name, boxes=boxes) - @unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.") def test_deurl_child_of_dtbl(self): """Data reference boxes can only contain data entry url boxes.""" jp2 = Jp2k(self.jp2file) boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] ftyp = glymur.jp2box.FileTypeBox() - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') dref = glymur.jp2box.DataReferenceBox([ftyp]) + self.assertEqual(len(w), 1) + self.assertTrue(issubclass(w[0].category, UserWarning)) # Try to get around it by appending the ftyp box after creation. dref = glymur.jp2box.DataReferenceBox() @@ -484,35 +486,6 @@ class TestJPX(unittest.TestCase): with tempfile.TemporaryFile() as tfile: ftbl.write(tfile) - @unittest.skip("No such jpx file anymore.") - def test_jpx_rreq_mask_length_3(self): - """There are some JPX files with rreq mask length of 3.""" - jpx = Jp2k(self.jpxfile) - self.assertEqual(jpx.box[2].box_id, 'rreq') - self.assertEqual(type(jpx.box[2]), - glymur.jp2box.ReaderRequirementsBox) - self.asserwrite_buffertEqual(jpx.box[2].standard_flag, - (5, 42, 45, 2, 18, 19, 1, 8, 12, 31, 20)) - - @unittest.skip("Requires unnecessarily complicated code") - def test_unknown_superbox(self): - """Verify that we can handle an unknown superbox.""" - with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile: - with open(self.jpxfile, 'rb') as ifile: - tfile.write(ifile.read()) - - # Add the header for an unknwon superbox. - write_buffer = struct.pack('>I4s', 20, 'grp '.encode()) - tfile.write(write_buffer) - write_buffer = struct.pack('>I4sI', 12, 'free'.encode(), 0) - tfile.write(write_buffer) - tfile.flush() - - with self.assertWarns(UserWarning): - jpx = Jp2k(tfile.name) - self.assertEqual(jpx.box[-1].box_id, b'grp ') - self.assertEqual(jpx.box[-1].box[0].box_id, 'free') - def test_data_reference_requires_dtbl(self): """The existance of a data reference box requires a ftbl box as well.""" flag = 0 diff --git a/glymur/test/test_jp2box_uuid.py b/glymur/test/test_jp2box_uuid.py index a904174..b6e9762 100644 --- a/glymur/test/test_jp2box_uuid.py +++ b/glymur/test/test_jp2box_uuid.py @@ -83,7 +83,6 @@ class TestUUIDExif(unittest.TestCase): def tearDown(self): pass - @unittest.skipIf(sys.hexversion < 0x03000000, "Requires assertWarns, 3.2+") def test_unrecognized_exif_tag(self): """Verify warning in case of unrecognized tag.""" with tempfile.NamedTemporaryFile(suffix='.jp2', mode='wb') as tfile: @@ -106,10 +105,13 @@ class TestUUIDExif(unittest.TestCase): tfile.write(struct.pack('= 0x03000000: - with self.assertWarns(UserWarning): - jp2 = Jp2k(filename) + with warnings.catch_warnings(record=True) as w: + jp2 = Jp2k(filename) + self.assertTrue(issubclass(w[0].category, UserWarning)) + self.assertTrue('Invalid profile' in str(w[0].message)) - @unittest.skip("blah") def test_main_header(self): """Verify that the main header is not loaded when parsing turned off.""" # The hidden _main_header attribute should show up after accessing it. @@ -792,44 +793,62 @@ class TestParsing(unittest.TestCase): @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") -class TestJp2kOpjDataRoot(unittest.TestCase): +class TestJp2kOpjDataRootWarnings(unittest.TestCase): """These tests should be run by just about all configuration.""" def test_undecodeable_box_id(self): """Should warn in case of undecodeable box ID but not error out.""" filename = opj_data_file('input/nonregression/edf_c2_1013627.jp2') - if sys.hexversion < 0x03000000: - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - jp2 = Jp2k(filename) - else: - with self.assertWarns(UserWarning): - jp2 = Jp2k(filename) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + jp2 = Jp2k(filename) + self.assertTrue(issubclass(w[0].category, UserWarning)) + self.assertTrue('Unrecognized box' in str(w[0].message)) # Now make sure we got all of the boxes. Ignore the last, which was # bad. box_ids = [box.box_id for box in jp2.box[:-1]] self.assertEqual(box_ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - def test_invalid_approximation(self): + def test_bad_ftyp_brand(self): """Should warn in case of bad ftyp brand.""" filename = opj_data_file('input/nonregression/edf_c2_1000290.jp2') - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') jp2 = Jp2k(filename) + self.assertTrue(issubclass(w[0].category, UserWarning)) - @unittest.skipIf(sys.hexversion < 0x03000000, "Test requires Python 3.3+") def test_invalid_approximation(self): """Should warn in case of invalid approximation.""" filename = opj_data_file('input/nonregression/edf_c2_1015644.jp2') - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') jp2 = Jp2k(filename) + self.assertTrue(issubclass(w[0].category, UserWarning)) + self.assertTrue('Invalid approximation' in str(w[0].message)) - @unittest.skipIf(sys.hexversion < 0x03000000, "Test requires Python 3.3+") def test_invalid_colorspace(self): """Should warn in case of invalid colorspace.""" filename = opj_data_file('input/nonregression/edf_c2_1103421.jp2') - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') jp2 = Jp2k(filename) + self.assertTrue(issubclass(w[1].category, UserWarning)) + self.assertTrue('Unrecognized colorspace' in str(w[1].message)) + + def test_stupid_windows_eol_at_end(self): + """Garbage characters at the end of the file.""" + filename = opj_data_file('input/nonregression/issue211.jp2') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + jp2 = Jp2k(filename) + self.assertTrue(issubclass(w[1].category, UserWarning)) + + +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class TestJp2kOpjDataRoot(unittest.TestCase): + """These tests should be run by just about all configuration.""" def test_no_cxform_pclr_jp2(self): """Indices for pclr jpxfile if no color transform""" @@ -849,17 +868,6 @@ class TestJp2kOpjDataRoot(unittest.TestCase): rgb_from_idx[r, c] = palette[idx[r, c]] np.testing.assert_array_equal(rgb, rgb_from_idx) - def test_stupid_windows_eol_at_end(self): - """Garbage characters at the end of the file.""" - filename = opj_data_file('input/nonregression/issue211.jp2') - if sys.hexversion < 0x03000000: - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - jp2 = Jp2k(filename) - else: - with self.assertWarns(UserWarning): - jp2 = Jp2k(filename) - 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 diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index f4534ef..ca9d532 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -65,19 +65,6 @@ class TestSuite(unittest.TestCase): np.testing.assert_array_equal(jpdata, pgxdata) - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") - def test_ETS_C1P0_p0_02_j2k(self): - jfile = opj_data_file('input/conformance/p0_02.j2k') - jp2k = Jp2k(jfile) - with self.assertWarns(UserWarning): - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c1p0_02_0.pgx') - pgxdata = read_pgx(pgxfile) - - np.testing.assert_array_equal(jpdata, pgxdata) - def test_ETS_C1P0_p0_03_j2k(self): jfile = opj_data_file('input/conformance/p0_03.j2k') jp2k = Jp2k(jfile) @@ -409,28 +396,6 @@ class TestSuite(unittest.TestCase): jp2.read() self.assertTrue(True) - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") - def test_NR_DEC_broken_jp2_4_decode(self): - jfile = opj_data_file('input/nonregression/broken.jp2') - with self.assertWarns(UserWarning): - # colr box has bad length. - jp2 = Jp2k(jfile) - with self.assertRaises(IOError): - jp2.read() - self.assertTrue(True) - - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") - def test_NR_DEC_broken3_jp2_6_decode(self): - jfile = opj_data_file('input/nonregression/broken3.jp2') - with self.assertWarns(UserWarning): - # colr box has bad length. - j = Jp2k(jfile) - - with self.assertRaises(IOError): - j.read() - @unittest.skip("fprintf stderr output in r2343.") def test_NR_DEC_bug_j2c_8_decode(self): jfile = opj_data_file('input/nonregression/bug.j2c') @@ -622,6 +587,482 @@ class TestSuite(unittest.TestCase): jp2k.read(rlevel=4) +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class TestSuiteWarn(unittest.TestCase): + """ + All these tests issue warnings. + """ + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_NR_text_GBR_dump(self): + # brand is 'jp2 ', but has any icc profile. + # Verify the warning on python3, but ignore it otherwise. + jfile = opj_data_file('input/nonregression/text_GBR.jp2') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + lst = ['jP ', 'ftyp', 'rreq', 'jp2h', + 'uuid', 'uuid', 'uuid', 'uuid', 'jp2c'] + self.assertEqual(ids, lst) + + ids = [box.box_id for box in jp2.box[3].box] + self.assertEqual(ids, ['ihdr', 'colr', 'res ']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Reader requirements. + # Compositing layer uses any icc profile + self.assertTrue(44 in jp2.box[2].standard_flag) + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[3].box[0].height, 400) + self.assertEqual(jp2.box[3].box[0].width, 400) + self.assertEqual(jp2.box[3].box[0].num_components, 3) + self.assertEqual(jp2.box[3].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[3].box[0].signed, False) + self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True) + self.assertEqual(jp2.box[3].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[3].box[1].method, + glymur.core.ANY_ICC_PROFILE) + self.assertEqual(jp2.box[3].box[1].precedence, 2) + self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact + self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 1328) + self.assertIsNone(jp2.box[3].box[1].colorspace) + + # UUID boxes. All mentioned in the RREQ box. + self.assertEqual(jp2.box[2].vendor_feature[0], jp2.box[4].uuid) + self.assertEqual(jp2.box[2].vendor_feature[1], jp2.box[5].uuid) + self.assertEqual(jp2.box[2].vendor_feature[2], jp2.box[6].uuid) + self.assertEqual(jp2.box[2].vendor_feature[3], jp2.box[7].uuid) + + c = jp2.box[8].main_header + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 400) + self.assertEqual(c.segment[1].ysiz, 400) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) + self.assertEqual(c.segment[2].layers, 6) # layers = 6 + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), + (32, 32)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) + self.assertEqual(c.segment[3].guard_bits, 2) + self.assertEqual(c.segment[3].mantissa, [0] * 16) + self.assertEqual(c.segment[3].exponent, + [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) + + def test_ETS_C1P0_p0_02_j2k(self): + jfile = opj_data_file('input/conformance/p0_02.j2k') + jp2k = Jp2k(jfile) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') + jpdata = jp2k.read(rlevel=0) + + pgxfile = opj_data_file('baseline/conformance/c1p0_02_0.pgx') + pgxdata = read_pgx(pgxfile) + + np.testing.assert_array_equal(jpdata, pgxdata) + + def test_NR_DEC_broken_jp2_4_decode(self): + jfile = opj_data_file('input/nonregression/broken.jp2') + with warnings.catch_warnings(record=True) as w: + # colr box has bad length. + warnings.simplefilter('ignore') + jp2 = Jp2k(jfile) + with self.assertRaises(IOError): + jp2.read() + self.assertTrue(True) + + def test_NR_DEC_broken3_jp2_6_decode(self): + jfile = opj_data_file('input/nonregression/broken3.jp2') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') + # colr box has bad length. + j = Jp2k(jfile) + + with self.assertRaises(IOError): + j.read() + +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class TestSuiteDumpWarnings(unittest.TestCase): + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_NR_broken_jp2_dump(self): + jfile = opj_data_file('input/nonregression/broken.jp2') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') + # colr box has bad length. + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 152) + self.assertEqual(jp2.box[2].box[0].width, 203) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, + glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) # not allowed? + self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) + + c = jp2.box[3].main_header + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 203) + self.assertEqual(c.segment[1].ysiz, 152) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (203, 152)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COM: comment + # Registration + self.assertEqual(c.segment[2].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[2].ccme.decode('latin-1'), + "Creator: JasPer Version 1.701.0") + + # COD: Coding style default + self.assertFalse(c.segment[3].scod & 2) # no sop + self.assertFalse(c.segment[3].scod & 4) # no eph + self.assertEqual(c.segment[3].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[3].layers, 1) # layers = 1 + self.assertEqual(c.segment[3].spcod[3], 1) # mct + self.assertEqual(c.segment[3].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[3].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[3].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[3].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[3].spcod[7] & 0x0020) + self.assertEqual(c.segment[3].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[3].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[4].sqcd & 0x1f, 0) + self.assertEqual(c.segment[4].guard_bits, 2) + self.assertEqual(c.segment[4].mantissa, [0] * 16) + self.assertEqual(c.segment[4].exponent, + [8] + [9, 9, 10] * 5) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[5].cqcc, 1) + self.assertEqual(c.segment[5].guard_bits, 2) + # quantization type + self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[5].mantissa, [0] * 16) + self.assertEqual(c.segment[5].exponent, + [8] + [9, 9, 10] * 5) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[6].cqcc, 2) + self.assertEqual(c.segment[6].guard_bits, 2) + # quantization type + self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[6].mantissa, [0] * 16) + self.assertEqual(c.segment[6].exponent, + [8] + [9, 9, 10] * 5) + + def test_NR_broken2_jp2_dump(self): + # Invalid marker ID on codestream. + jfile = opj_data_file('input/nonregression/broken2.jp2') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') + jp2 = Jp2k(jfile) + + self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') + + def test_NR_broken3_jp2_dump(self): + jfile = opj_data_file('input/nonregression/broken3.jp2') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 152) + self.assertEqual(jp2.box[2].box[0].width, 203) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, + glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 + self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) + + c = jp2.box[3].main_header + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 203) + self.assertEqual(c.segment[1].ysiz, 152) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (203, 152)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COM: comment + # Registration + self.assertEqual(c.segment[2].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[2].ccme.decode('latin-1'), + "Creator: JasPer Vers)on 1.701.0") + + # COD: Coding style default + self.assertFalse(c.segment[3].scod & 2) # no sop + self.assertFalse(c.segment[3].scod & 4) # no eph + self.assertEqual(c.segment[3].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[3].layers, 1) # layers = 1 + self.assertEqual(c.segment[3].spcod[3], 1) # mct + self.assertEqual(c.segment[3].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[3].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[3].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[3].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[3].spcod[7] & 0x0020) + self.assertEqual(c.segment[3].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[3].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[4].sqcd & 0x1f, 0) + self.assertEqual(c.segment[4].guard_bits, 2) + self.assertEqual(c.segment[4].mantissa, [0] * 16) + self.assertEqual(c.segment[4].exponent, + [8] + [9, 9, 10] * 5) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[5].cqcc, 1) + self.assertEqual(c.segment[5].guard_bits, 2) + # quantization type + self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[5].mantissa, [0] * 16) + self.assertEqual(c.segment[5].exponent, + [8] + [9, 9, 10] * 5) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[6].cqcc, 2) + self.assertEqual(c.segment[6].guard_bits, 2) + # quantization type + self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[6].mantissa, [0] * 16) + self.assertEqual(c.segment[6].exponent, + [8] + [9, 9, 10] * 5) + + def test_NR_broken4_jp2_dump(self): + # Has an invalid marker in the main header + jfile = opj_data_file('input/nonregression/broken4.jp2') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') + jp2 = Jp2k(jfile) + + self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') + + def test_NR_gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc_patch_jp2(self): + lst = ['input', 'nonregression', + 'gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc.patch.jp2'] + jfile = opj_data_file('/'.join(lst)) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') + Jp2k(jfile) + + def test_NR_gdal_fuzzer_check_comp_dx_dy_jp2_dump(self): + lst = ['input', 'nonregression', 'gdal_fuzzer_check_comp_dx_dy.jp2'] + jfile = opj_data_file('/'.join(lst)) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') + Jp2k(jfile) + + def test_NR_gdal_fuzzer_check_number_of_tiles(self): + # Has an impossible tiling setup. + lst = ['input', 'nonregression', + 'gdal_fuzzer_check_number_of_tiles.jp2'] + jfile = opj_data_file('/'.join(lst)) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') + Jp2k(jfile) + + def test_NR_gdal_fuzzer_unchecked_numresolutions_dump(self): + # Has an invalid number of resolutions. + lst = ['input', 'nonregression', + 'gdal_fuzzer_unchecked_numresolutions.jp2'] + jfile = opj_data_file('/'.join(lst)) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') + Jp2k(jfile) + + def test_NR_DEC_issue188_beach_64bitsbox_jp2_41_decode(self): + # Has an 'XML ' box instead of 'xml '. Yes that is pedantic, but it + # really does deserve a warning. + relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2' + jfile = opj_data_file(relpath) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') + Jp2k(jfile).read() + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") class TestSuiteDump(unittest.TestCase): @@ -632,6 +1073,103 @@ class TestSuiteDump(unittest.TestCase): def tearDown(self): pass + def test_NR_file409752(self): + jfile = opj_data_file('input/nonregression/file409752.jp2') + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 243) + self.assertEqual(jp2.box[2].box[0].width, 720) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, + glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 + self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.YCC) + + c = jp2.box[3].main_header + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 720) + self.assertEqual(c.segment[1].ysiz, 243) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (720, 243)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1), (2, 1), (2, 1)]) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), + (32, 128)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 2) + self.assertEqual(c.segment[3].guard_bits, 1) + self.assertEqual(c.segment[3].mantissa, + [1816, 1792, 1792, 1724, 1770, 1770, 1724, 1868, + 1868, 1892, 3, 3, 69, 2002, 2002, 1889]) + self.assertEqual(c.segment[3].exponent, + [13] * 4 + [12] * 3 + [11] * 3 + [9] * 6) + def test_NR_p0_01_dump(self): jfile = opj_data_file('input/conformance/p0_01.j2k') c = Jp2k(jfile).get_codestream(header_only=False) @@ -4964,412 +5502,6 @@ class TestSuiteDump(unittest.TestCase): self.assertEqual(c.segment[4].ccme.decode('latin-1'), "DCP-Werkstatt") - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") - def test_NR_broken_jp2_dump(self): - jfile = opj_data_file('input/nonregression/broken.jp2') - with self.assertWarns(UserWarning): - # colr box has bad length. - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 152) - self.assertEqual(jp2.box[2].box[0].width, 203) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # not allowed? - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 203) - self.assertEqual(c.segment[1].ysiz, 152) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (203, 152)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COM: comment - # Registration - self.assertEqual(c.segment[2].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[2].ccme.decode('latin-1'), - "Creator: JasPer Version 1.701.0") - - # COD: Coding style default - self.assertFalse(c.segment[3].scod & 2) # no sop - self.assertFalse(c.segment[3].scod & 4) # no eph - self.assertEqual(c.segment[3].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[3].layers, 1) # layers = 1 - self.assertEqual(c.segment[3].spcod[3], 1) # mct - self.assertEqual(c.segment[3].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[3].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcod[7] & 0x0020) - self.assertEqual(c.segment[3].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[3].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) - self.assertEqual(c.segment[4].guard_bits, 2) - self.assertEqual(c.segment[4].mantissa, [0] * 16) - self.assertEqual(c.segment[4].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 1) - self.assertEqual(c.segment[5].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[5].mantissa, [0] * 16) - self.assertEqual(c.segment[5].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 2) - self.assertEqual(c.segment[6].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[6].mantissa, [0] * 16) - self.assertEqual(c.segment[6].exponent, - [8] + [9, 9, 10] * 5) - - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2, 'assertWarns'.") - def test_NR_broken2_jp2_dump(self): - # Invalid marker ID on codestream. - jfile = opj_data_file('input/nonregression/broken2.jp2') - with self.assertWarns(UserWarning): - jp2 = Jp2k(jfile) - - self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') - - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") - def test_NR_broken3_jp2_dump(self): - jfile = opj_data_file('input/nonregression/broken3.jp2') - with self.assertWarns(UserWarning): - # colr box has bad length. - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 152) - self.assertEqual(jp2.box[2].box[0].width, 203) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 203) - self.assertEqual(c.segment[1].ysiz, 152) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (203, 152)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COM: comment - # Registration - self.assertEqual(c.segment[2].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[2].ccme.decode('latin-1'), - "Creator: JasPer Vers)on 1.701.0") - - # COD: Coding style default - self.assertFalse(c.segment[3].scod & 2) # no sop - self.assertFalse(c.segment[3].scod & 4) # no eph - self.assertEqual(c.segment[3].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[3].layers, 1) # layers = 1 - self.assertEqual(c.segment[3].spcod[3], 1) # mct - self.assertEqual(c.segment[3].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[3].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcod[7] & 0x0020) - self.assertEqual(c.segment[3].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[3].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) - self.assertEqual(c.segment[4].guard_bits, 2) - self.assertEqual(c.segment[4].mantissa, [0] * 16) - self.assertEqual(c.segment[4].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 1) - self.assertEqual(c.segment[5].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[5].mantissa, [0] * 16) - self.assertEqual(c.segment[5].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 2) - self.assertEqual(c.segment[6].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[6].mantissa, [0] * 16) - self.assertEqual(c.segment[6].exponent, - [8] + [9, 9, 10] * 5) - - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2, 'assertWarns'") - def test_NR_broken4_jp2_dump(self): - # Has an invalid marker in the main header - jfile = opj_data_file('input/nonregression/broken4.jp2') - with self.assertWarns(UserWarning): - jp2 = Jp2k(jfile) - - self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') - - def test_NR_file409752(self): - jfile = opj_data_file('input/nonregression/file409752.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 243) - self.assertEqual(jp2.box[2].box[0].width, 720) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.YCC) - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 720) - self.assertEqual(c.segment[1].ysiz, 243) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (720, 243)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (2, 1), (2, 1)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 128)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, - [1816, 1792, 1792, 1724, 1770, 1770, 1724, 1868, - 1868, 1892, 3, 3, 69, 2002, 2002, 1889]) - self.assertEqual(c.segment[3].exponent, - [13] * 4 + [12] * 3 + [11] * 3 + [9] * 6) - - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") - def test_NR_gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc_patch_jp2(self): - lst = ['input', 'nonregression', - 'gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc.patch.jp2'] - jfile = opj_data_file('/'.join(lst)) - with self.assertWarns(UserWarning): - Jp2k(jfile) - - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") - def test_NR_gdal_fuzzer_check_comp_dx_dy_jp2_dump(self): - lst = ['input', 'nonregression', 'gdal_fuzzer_check_comp_dx_dy.jp2'] - jfile = opj_data_file('/'.join(lst)) - with self.assertWarns(UserWarning): - Jp2k(jfile) - - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") - def test_NR_gdal_fuzzer_check_number_of_tiles(self): - # Has an impossible tiling setup. - lst = ['input', 'nonregression', - 'gdal_fuzzer_check_number_of_tiles.jp2'] - jfile = opj_data_file('/'.join(lst)) - with self.assertWarns(UserWarning): - Jp2k(jfile) - - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") - def test_NR_gdal_fuzzer_unchecked_numresolutions_dump(self): - # Has an invalid number of resolutions. - lst = ['input', 'nonregression', - 'gdal_fuzzer_unchecked_numresolutions.jp2'] - jfile = opj_data_file('/'.join(lst)) - with self.assertWarns(UserWarning): - Jp2k(jfile) - def test_NR_issue104_jpxstream_dump(self): jfile = opj_data_file('input/nonregression/issue104_jpxstream.jp2') jp2 = Jp2k(jfile) @@ -6319,122 +6451,6 @@ class TestSuiteDump(unittest.TestCase): self.assertEqual(c.segment[3].exponent, [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - def test_NR_text_GBR_dump(self): - # brand is 'jp2 ', but has any icc profile. - # Verify the warning on python3, but ignore it otherwise. - jfile = opj_data_file('input/nonregression/text_GBR.jp2') - if sys.hexversion > 0x03030000: - with self.assertWarns(UserWarning): - jp2 = Jp2k(jfile) - else: - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - lst = ['jP ', 'ftyp', 'rreq', 'jp2h', - 'uuid', 'uuid', 'uuid', 'uuid', 'jp2c'] - self.assertEqual(ids, lst) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'res ']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Reader requirements. - # Compositing layer uses any icc profile - self.assertTrue(44 in jp2.box[2].standard_flag) - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 400) - self.assertEqual(jp2.box[3].box[0].width, 400) - self.assertEqual(jp2.box[3].box[0].num_components, 3) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.ANY_ICC_PROFILE) - self.assertEqual(jp2.box[3].box[1].precedence, 2) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 1328) - self.assertIsNone(jp2.box[3].box[1].colorspace) - - # UUID boxes. All mentioned in the RREQ box. - self.assertEqual(jp2.box[2].vendor_feature[0], jp2.box[4].uuid) - self.assertEqual(jp2.box[2].vendor_feature[1], jp2.box[5].uuid) - self.assertEqual(jp2.box[2].vendor_feature[2], jp2.box[6].uuid) - self.assertEqual(jp2.box[2].vendor_feature[3], jp2.box[7].uuid) - - c = jp2.box[8].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 400) - self.assertEqual(c.segment[1].ysiz, 400) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 6) # layers = 6 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @@ -6684,16 +6700,6 @@ class TestSuite2point1(unittest.TestCase): with self.assertRaises(RuntimeError): Jp2k(jfile).read() - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") - def test_NR_DEC_issue188_beach_64bitsbox_jp2_41_decode(self): - # Has an 'XML ' box instead of 'xml '. Yes that is pedantic, but it - # really does deserve a warning. - relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2' - jfile = opj_data_file(relpath) - with self.assertWarns(UserWarning): - Jp2k(jfile).read() - def test_NR_DEC_issue206_image_000_jp2_42_decode(self): jfile = opj_data_file('input/nonregression/issue206_image-000.jp2') Jp2k(jfile).read() diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index f6ef098..5a475a2 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -13,6 +13,7 @@ import re import sys import tempfile import unittest +import warnings import numpy as np @@ -77,14 +78,13 @@ class TestSuiteNegative(unittest.TestCase): jp2k.get_codestream(header_only=False) self.assertTrue(True) - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") def test_nr_illegalclrtransform(self): """EOC marker is bad""" relpath = 'input/nonregression/illegalcolortransform.j2k' jfile = opj_data_file(relpath) jp2k = Jp2k(jfile) - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') codestream = jp2k.get_codestream(header_only=False) # Verify that the last segment returned in the codestream is SOD, @@ -119,8 +119,6 @@ class TestSuiteNegative(unittest.TestCase): with self.assertRaises(IOError): j.write(data, cbsize=(2, 2048)) - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") def test_exceeded_box(self): """should warn if reading past end of a box""" # Verify that a warning is issued if we read past the end of a box @@ -128,7 +126,8 @@ class TestSuiteNegative(unittest.TestCase): # short. infile = os.path.join(OPJ_DATA_ROOT, 'input/nonregression/mem-b2ace68c-1381.jp2') - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') Jp2k(infile) @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index db40530..6734d01 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -70,7 +70,6 @@ class TestPrinting(unittest.TestCase): self.assertTrue(True) - @unittest.skipIf(sys.hexversion < 0x03000000, "Needs unittest in 3.x.") def test_unknown_superbox(self): """Verify that we can handle an unknown superbox.""" with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile: @@ -87,14 +86,19 @@ class TestPrinting(unittest.TestCase): tfile.write(write_buffer) tfile.flush() - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') jpx = Jp2k(tfile.name) + self.assertTrue(len(w), 1) + glymur.set_printoptions(short=True) with patch('sys.stdout', new=StringIO()) as fake_out: print(jpx.box[-1]) actual = fake_out.getvalue().strip() - lines = ["Unknown Box (b'grp ') @ (1399071, 20)"] - expected = '\n'.join(lines) + if sys.hexversion < 0x03000000: + expected = "Unknown Box (grp ) @ (1399071, 20)" + else: + expected = "Unknown Box (b'grp ') @ (1399071, 20)" self.assertEqual(actual, expected) def test_printoptions_bad_argument(self): @@ -767,7 +771,7 @@ class TestPrintingOpjDataRoot(unittest.TestCase): with warnings.catch_warnings(): warnings.simplefilter("ignore") jp2 = Jp2k(jfile) - codestream = jp2.get_codestream() + codestream = jp2.get_codestream() with patch('sys.stdout', new=StringIO()) as fake_out: print(codestream.segment[2]) actual = fake_out.getvalue().strip() From a79e234f8a0be109ea5e2b28cd171b1ae62e1ef7 Mon Sep 17 00:00:00 2001 From: John Evans Date: Sat, 10 May 2014 15:54:28 -0400 Subject: [PATCH 213/326] Removed apparently unreachable XML code for 2.6. --- glymur/jp2box.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 316f059..851ae3b 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2740,12 +2740,7 @@ class XMLBox(Jp2kBox): def write(self, fptr): """Write an XML box to file. """ - try: - read_buffer = ET.tostring(self.xml, encoding='utf-8') - except (AttributeError, AssertionError): - # AssertionError on 2.6 - read_buffer = ET.tostring(self.xml.getroot(), encoding='utf-8') - + read_buffer = ET.tostring(self.xml, encoding='utf-8') fptr.write(struct.pack('>I4s', len(read_buffer) + 8, b'xml ')) fptr.write(read_buffer) From bf65a00f28b3acec2f7706c4dda5605b9974003b Mon Sep 17 00:00:00 2001 From: John Evans Date: Sat, 10 May 2014 15:58:57 -0400 Subject: [PATCH 214/326] Improved warning message when brand is not proper. #232 --- glymur/jp2box.py | 6 ++++-- glymur/test/test_jp2k.py | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 851ae3b..b2986ae 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1241,8 +1241,10 @@ class FileTypeBox(Jp2kBox): def _validate(self, writing=False): """Validate the box before writing to file.""" if self.brand not in ['jp2 ', 'jpx ']: - msg = "The file type brand must be either 'jp2 ' or 'jpx '." - self._dispatch_validation_error(msg, writing=writing) + msg = "The file type brand was '{0}'. " + msg += "It should be either 'jp2 ' or 'jpx '." + self._dispatch_validation_error(msg.format(self.brand), + writing=writing) valid_cls = ['jp2 ', 'jpx ', 'jpxb'] for item in self.compatibility_list: if item not in valid_cls: diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 05d4ccc..aba1463 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -814,9 +814,9 @@ class TestJp2kOpjDataRootWarnings(unittest.TestCase): """Should warn in case of bad ftyp brand.""" filename = opj_data_file('input/nonregression/edf_c2_1000290.jp2') with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - jp2 = Jp2k(filename) - self.assertTrue(issubclass(w[0].category, UserWarning)) + warnings.simplefilter('always') + jp2 = Jp2k(filename) + self.assertTrue(issubclass(w[0].category, UserWarning)) def test_invalid_approximation(self): """Should warn in case of invalid approximation.""" From 0a4bb217e33bc781d505a33f4e354e0a1097f093 Mon Sep 17 00:00:00 2001 From: John Evans Date: Sat, 10 May 2014 16:02:35 -0400 Subject: [PATCH 215/326] Removed two tests that we chose not to run because of stderr issues. They produce too much output on stderr, it would make interpreting test results difficult. --- glymur/test/test_opj_suite.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index c8dbad4..88669ce 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -299,19 +299,6 @@ class TestSuite(unittest.TestCase): jp2.read() self.assertTrue(True) - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_123_j2c_3_decode(self): - jfile = opj_data_file('input/nonregression/123.j2c') - jp2 = Jp2k(jfile) - jp2.read() - self.assertTrue(True) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_bug_j2c_8_decode(self): - jfile = opj_data_file('input/nonregression/bug.j2c') - Jp2k(jfile).read() - self.assertTrue(True) - def test_NR_DEC_buxI_j2k_9_decode(self): jfile = opj_data_file('input/nonregression/buxI.j2k') Jp2k(jfile).read() From 2750aced14c51a6fddb707513e4ff6dc1cd24c4a Mon Sep 17 00:00:00 2001 From: John Evans Date: Sat, 10 May 2014 16:10:58 -0400 Subject: [PATCH 216/326] Updated versioning for pending 0.6.0 release. Removed documentation cruft. --- docs/source/changelog.rst | 6 +-- docs/source/conf.py | 4 +- docs/source/detailed_installation.rst | 70 +-------------------------- glymur/version.py | 2 +- 4 files changed, 8 insertions(+), 74 deletions(-) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index c3eae6c..a24d1e8 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -2,14 +2,14 @@ ChangeLog --------- -0.6.0 (pending) -=============== +0.6.0 +===== + * Added support for OpenJPEG 2.1.0, dropped support for 1.3 and 1.4. * Added Cinema2K, Cinema4K write support. * Added lxml requirement. * added set_printoptions, get_printoptions function * dropped support for Python 2.6, added support for Python 3.4 - * dropped support for OpenJPEG versions 1.3 and 1.4 * dropped windows support (it might work, it might not, I don't much care) * added write support for JP2 UUID, dataEntryURL, palette, and component mapping boxes * added read/write support for JPX free, number list, and data reference boxes diff --git a/docs/source/conf.py b/docs/source/conf.py index 4b0eb14..3e6b26e 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -76,9 +76,9 @@ copyright = u'2013, John Evans' # built documents. # # The short X.Y version. -version = '0.5' +version = '0.6' # The full version, including alpha/beta/rc tags. -release = '0.5.10' +release = '0.6.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index 67c230a..e919681 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -1,7 +1,6 @@ ---------------------------------- Advanced Installation Instructions ---------------------------------- -Most users won't need to read this! You've been warned... '''''''''''''''''''''' Glymur Configuration @@ -9,11 +8,8 @@ Glymur Configuration The default glymur installation process relies upon OpenJPEG being properly installed on your system. If you have version 1.5 you can -both read and write JPEG 2000 files, but you may wish to install version 2.0 -or the 2.0+ version from OpenJPEG's development trunk for better performance. -If you do that, you should compile it as a shared library (named *openjp2* -instead of *openjpeg*) from the developmental source that you can retrieve -via subversion. As of this time of writing, svn revision r2691 works. +both read and write JPEG 2000 files, but version 2.1 is recommended. +If you compile OpenJPEG yourself, please compile it as a shared library. You should also download the test data for the purpose of configuring and running OpenJPEG's test suite, check their instructions for all this. You should set the **OPJ_DATA_ROOT** environment variable for the purpose @@ -51,60 +47,6 @@ installed in a non-standard place, i.e. :: [library] openjpeg: /not/the/usual/location/lib/libopenjpeg.so -'''''''''''''''''''''''''''''' -Package Management Suggestions -'''''''''''''''''''''''''''''' - -You only need to read this section if you want detailed -platform-specific instructions on running as many tests as possible or wish to -use your system's package manager to install as many required -packages/RPMs/ports/whatever without going through pip. - - -Mac OS X --------- -All the necessary packages are available to use glymur with MacPorts. -For python 3.3, you should install the following set of ports: - - * python33 - * py33-numpy - * py33-lxml - * py33-distribute - * py33-Pillow (optional, for running certain tests) - -MacPorts supplies both OpenJPEG 1.5.0 and OpenJPEG 2.0.0. - -Linux ------ -For the most part, you only need python and numpy to run glymur, so on -just about all distributions you are already set to go (and you don't -need to mess around with a configuration file, as the openjpeg shared -libraries are found in the usual places thanks to your package manager). -In order to run as many tests as possible, however, the following Python -packages may also need to be installed. Consult your package manager -documentation or use pip. - - * setuptools - * python-lxml - * matplotlib - * pillow - * contextlib2 (2.7 only) - * mock (2.7 only) - -Glymur 0.6 been tested on the following linux platforms without any unexpected -difficulties: - - * OpenSUSE 13.1 - * Fedora 19 - * Raspian - * Travis CI (currently Ubuntu 12.04?) - -Windows -------- -The 0.6.x series of Glymur is untested on windows and I make no promises here. -I suggest that windows users check the 0.5.x series. - - ''''''' Testing ''''''' @@ -128,11 +70,3 @@ or from the command line. :: $ cd /to/where/you/unpacked/glymur $ python -m unittest discover - -Quite a few tests are currently skipped. These include tests whose -OpenJPEG counterparts are already failing, and others which do pass but -still produce heaps of output on stderr. Rather than let this swamp -the signal (that most of those tests are actually passing), they've been -filtered out for now. There are also more skipped tests on Python 2.7 -than on Python3. The important part is whether or not any test -errors are reported at the end. diff --git a/glymur/version.py b/glymur/version.py index 409db9a..8ffd80f 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -19,7 +19,7 @@ from .lib import openjp2 as opj2 # Do not change the format of this next line! Doing so risks breaking # setup.py -version = "0.5.10" +version = "0.6.0" _sv = LooseVersion(version) version_tuple = _sv.version From c31751685be546c3adc1e88b23177dd5a66c6da2 Mon Sep 17 00:00:00 2001 From: John Evans Date: Sat, 10 May 2014 16:19:28 -0400 Subject: [PATCH 217/326] Added Python 3.4 for travis-ci runs. #233 --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6a57264..3a9cb61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ language: python python: - "2.7" - "3.3" + - "3.4" before_install: - sudo apt-get update -qq @@ -13,6 +14,7 @@ before_install: install: - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install lxml contextlib2 mock; fi - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install lxml numpy; fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.4' ]]; then pip install lxml numpy; fi # command to run tests script: From c01774c036b135bd886df116ebbefb6162fee4c4 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 10 May 2014 17:39:40 -0400 Subject: [PATCH 218/326] Improved warning message when the tiff exif byte order is bad. #230 --- glymur/_uuid_io.py | 4 +++- glymur/test/test_jp2box_uuid.py | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/glymur/_uuid_io.py b/glymur/_uuid_io.py index daa1994..f64baa6 100644 --- a/glymur/_uuid_io.py +++ b/glymur/_uuid_io.py @@ -37,7 +37,9 @@ def tiff_header(read_buffer): # big endian endian = '>' else: - msg = "Bad byte order indication: {0}".format(read_buffer[6:8]) + msg = "The byte order indication in the TIFF header ({0}) is invalid. " + msg += "It should be either {1} or {2}." + msg = msg.format(read_buffer[6:8], bytes([73, 73]), bytes([77, 77])) raise IOError(msg) _, offset = struct.unpack(endian + 'HI', read_buffer[8:14]) diff --git a/glymur/test/test_jp2box_uuid.py b/glymur/test/test_jp2box_uuid.py index b6e9762..f24aafe 100644 --- a/glymur/test/test_jp2box_uuid.py +++ b/glymur/test/test_jp2box_uuid.py @@ -167,6 +167,10 @@ class TestUUIDExif(unittest.TestCase): warnings.simplefilter('always') j = glymur.Jp2k(tfile.name) self.assertTrue(issubclass(w[0].category, UserWarning)) + msg = 'The byte order indication in the TIFF header ' + msg += "(b'JI') is invalid. " + msg += "It should be either b'II' or b'MM'." + self.assertTrue(msg in str(w[0].message)) self.assertEqual(j.box[-1].box_id, 'uuid') From 61192b73d3e265a135eca6903ddbff5e0e779ca1 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 10 May 2014 18:35:19 -0400 Subject: [PATCH 219/326] Fixed warnings. #229 --- glymur/_uuid_io.py | 8 ++++++-- glymur/test/test_jp2box.py | 1 - glymur/test/test_jp2box_uuid.py | 2 ++ glymur/test/test_jp2box_xml.py | 11 +++++------ glymur/test/test_opj_suite_write.py | 1 - glymur/test/test_printing.py | 2 ++ 6 files changed, 15 insertions(+), 10 deletions(-) diff --git a/glymur/_uuid_io.py b/glymur/_uuid_io.py index f64baa6..bf9960a 100644 --- a/glymur/_uuid_io.py +++ b/glymur/_uuid_io.py @@ -101,8 +101,12 @@ class _Ifd(object): def parse_tag(self, dtype, count, offset_buf): """Interpret an Exif image tag data payload. """ - fmt = self.datatype2fmt[dtype][0] * count - payload_size = self.datatype2fmt[dtype][1] * count + try: + fmt = self.datatype2fmt[dtype][0] * count + payload_size = self.datatype2fmt[dtype][1] * count + except KeyError: + msg = 'Invalid TIFF tag datatype ({0}).'.format(dtype) + raise IOError(msg) if payload_size <= 4: # Interpret the payload from the 4 bytes in the tag entry. diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index cef0d41..da5fea1 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -1332,7 +1332,6 @@ class TestRepr(unittest.TestCase): else: self.assertRegex(repr(box), regexp) - @unittest.skipIf(sys.hexversion < 0x02070000, "Requires 2.7+") def test_uuid_box_xmp(self): """Verify uuid repr method for XMP UUID box.""" jp2file = glymur.data.nemo() diff --git a/glymur/test/test_jp2box_uuid.py b/glymur/test/test_jp2box_uuid.py index f24aafe..e346ade 100644 --- a/glymur/test/test_jp2box_uuid.py +++ b/glymur/test/test_jp2box_uuid.py @@ -138,6 +138,8 @@ class TestUUIDExif(unittest.TestCase): warnings.simplefilter('always') j = glymur.Jp2k(tfile.name) self.assertTrue(issubclass(w[0].category, UserWarning)) + msg = 'Invalid TIFF tag' + self.assertTrue(msg in str(w[0].message)) self.assertEqual(j.box[-1].box_id, 'uuid') diff --git a/glymur/test/test_jp2box_xml.py b/glymur/test/test_jp2box_xml.py index 7841154..f8e2878 100644 --- a/glymur/test/test_jp2box_xml.py +++ b/glymur/test/test_jp2box_xml.py @@ -207,15 +207,14 @@ class TestJp2kBadXmlFile(unittest.TestCase): def tearDown(self): pass - def test_invalid_xml_box_warning(self): - """Should warn in case of bad XML""" - Jp2k(self._bad_xml_file) - def test_invalid_xml_box(self): """Should be able to recover info from xml box with bad xml.""" - with warnings.catch_warnings(): - warnings.simplefilter("ignore") + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") jp2k = Jp2k(self._bad_xml_file) + self.assertTrue(issubclass(w[0].category, UserWarning)) + msg = 'No XML was retrieved' + self.assertTrue(msg in str(w[0].message)) self.assertEqual(jp2k.box[3].box_id, 'xml ') self.assertEqual(jp2k.box[3].offset, 77) diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 861b31b..77ef262 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -1020,7 +1020,6 @@ class TestSuiteWrite(unittest.TestCase): glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) - #@unittest.skip("Known failure in openjpeg test suite.") def test_NR_ENC_random_issue_0005_tif_12_encode(self): """NR-ENC-random-issue-0005.tif-12-encode""" # opj_decompress has trouble reading it, but that is not an issue here. diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 6734d01..efd1ccf 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -725,6 +725,8 @@ class TestPrintingOpjDataRoot(unittest.TestCase): # Reset printoptions for every test. glymur.set_printoptions(short=False, xml=True, codestream=True) + warnings.resetwarnings() + def tearDown(self): pass From c230a9b0b2e41175c4779b1582cf3227067b1a3e Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 10 May 2014 20:02:12 -0400 Subject: [PATCH 220/326] Fixed expected warning message on 2.7. #229 --- glymur/test/test_jp2box_uuid.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/glymur/test/test_jp2box_uuid.py b/glymur/test/test_jp2box_uuid.py index e346ade..3c322a5 100644 --- a/glymur/test/test_jp2box_uuid.py +++ b/glymur/test/test_jp2box_uuid.py @@ -170,8 +170,12 @@ class TestUUIDExif(unittest.TestCase): j = glymur.Jp2k(tfile.name) self.assertTrue(issubclass(w[0].category, UserWarning)) msg = 'The byte order indication in the TIFF header ' - msg += "(b'JI') is invalid. " - msg += "It should be either b'II' or b'MM'." + if sys.hexversion < 0x03000000: + msg += "(JI) is invalid. " + msg += "It should be either [73, 73] or [77, 77]." + else: + msg += "(b'JI') is invalid. " + msg += "It should be either b'II' or b'MM'." self.assertTrue(msg in str(w[0].message)) self.assertEqual(j.box[-1].box_id, 'uuid') From 6a7be5363b61ae88d1553ee8b8a15a54a6e9f2b2 Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 12 May 2014 06:34:32 -0400 Subject: [PATCH 221/326] Cinema parameters were incorrectly specified for 2.0.1 #234 --- glymur/jp2k.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index a9e6ab2..6bab50b 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -180,9 +180,9 @@ class Jp2k(Jp2kBox): if re.match("2.0", version.openjpeg_version) is not None: # 2.0 API if fps == 24: - cparams.cp_cinema = core.OPJ_CINEMA_2K_24 + cparams.cp_cinema = core.OPJ_CINEMA2K_24 else: - cparams.cp_cinema = core.OPJ_CINEMA_2K_48 + cparams.cp_cinema = core.OPJ_CINEMA2K_48 else: # 2.1 API if fps == 24: @@ -198,7 +198,7 @@ class Jp2k(Jp2kBox): # cinema4k if re.match("2.0", version.openjpeg_version) is not None: # 2.0 API - cparams.cp_cinema = core.OPJ_CINEMA_4K_24 + cparams.cp_cinema = core.OPJ_CINEMA4K_24 else: # 2.1 API cparams.rsiz = core.OPJ_PROFILE_CINEMA_4K From 5936706f0eb19b74097a8063401fb9ebc0f2a442 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 12 Apr 2014 18:36:44 -0400 Subject: [PATCH 222/326] Releasing 0.5.11. Updated to support OpenJPEG versions 1.5.2, 2.0.1, and 2.1. Updated to support Python 3.4. Cleaned up and refactored many tests. Dropped some tests that were always skipped. --- .travis.yml | 3 + CHANGES.txt | 3 + README.md | 3 +- docs/source/conf.py | 2 +- docs/source/detailed_installation.rst | 116 +- docs/source/how_do_i.rst | 16 +- docs/source/introduction.rst | 9 +- glymur/jp2k.py | 44 +- glymur/lib/openjp2.py | 78 +- glymur/lib/test/test_openjp2.py | 29 +- glymur/test/fixtures.py | 249 + glymur/test/test_codestream.py | 5 +- glymur/test/test_config.py | 5 +- glymur/test/test_conformance.py | 45 +- glymur/test/test_icc.py | 7 +- glymur/test/test_jp2box.py | 3 +- glymur/test/test_jp2box_jpx.py | 4 +- glymur/test/test_jp2box_xml.py | 17 +- glymur/test/test_jp2k.py | 17 +- glymur/test/test_opj_suite.py | 6691 +------------------------ glymur/test/test_opj_suite_2p1.py | 407 ++ glymur/test/test_opj_suite_neg.py | 15 +- glymur/test/test_opj_suite_write.py | 1 - glymur/test/test_printing.py | 126 +- glymur/version.py | 2 +- 25 files changed, 860 insertions(+), 7037 deletions(-) create mode 100644 glymur/test/test_opj_suite_2p1.py diff --git a/.travis.yml b/.travis.yml index 9434d04..5223dcf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ python: - "2.6" - "2.7" - "3.3" + - "3.4" before_install: - sudo apt-get update -qq @@ -15,12 +16,14 @@ install: - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install --use-mirrors contextlib2 mock ordereddict unittest2; fi - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors contextlib2 mock; fi - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install --use-mirrors numpy; fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.4' ]]; then pip install --use-mirrors numpy; fi # command to run tests script: - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then unit2 discover; fi - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then python -m unittest discover; fi - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then python -m unittest discover; fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.4' ]]; then python -m unittest discover; fi notifications: email: "john.g.evans.ne@gmail.com" diff --git a/CHANGES.txt b/CHANGES.txt index d50310c..4799831 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +May 09, 2014 - v0.5.11 Added support for Python 3.4, OpenJPEG 2.0.1, and + OpenJPEG 2.1.0. + Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask length is unsupported. diff --git a/README.md b/README.md index d8ea732..457e456 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ glymur: a Python interface for JPEG 2000 **glymur** contains a Python interface to the OpenJPEG library which allows one to read and write JPEG 2000 files. **glymur** works on -Python 2.6, 2.7 and 3.3. Python 3.3 is strongly -recommended. +Python 2.6, 2.7, 3.3, and 3.4. Please read the docs, https://glymur.readthedocs.org/en/latest/ diff --git a/docs/source/conf.py b/docs/source/conf.py index 4b0eb14..35fe2c0 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -78,7 +78,7 @@ copyright = u'2013, John Evans' # The short X.Y version. version = '0.5' # The full version, including alpha/beta/rc tags. -release = '0.5.10' +release = '0.5.11' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index 3e4d9dc..15c5ed4 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -7,27 +7,18 @@ Most users won't need to read this! You've been warned... Glymur Configuration '''''''''''''''''''''' -The default glymur installation process relies upon OpenJPEG -being properly installed on your system. If you have version 1.5 you can -both read and write JPEG 2000 files, but you may wish to install version 2.0 -or the 2.0+ version from OpenJPEG's development trunk for better performance. -If you do that, you should compile it as a shared library (named *openjp2* -instead of *openjpeg*) from the developmental source that you can retrieve -via subversion. As of this time of writing, svn revision 2347 works. -You should also download the test data for the purpose of configuring -and running OpenJPEG's test suite, check their instructions for all this. -You should set the **OPJ_DATA_ROOT** environment variable for the purpose -of running Glymur's test suite. :: +The default glymur installation process relies upon OpenJPEG being +properly installed on your system as a shared library. You need +at least version 1.5 in order to read and write JPEG 2000 files. - $ svn co http://openjpeg.googlecode.com/svn/data - $ export OPJ_DATA_ROOT=`pwd`/data - -Glymur uses ctypes to access the openjp2/openjpeg libraries, -and because ctypes accesses libraries in a platform-dependent manner, it is -recommended that you create a configuration file to help Glymur properly find -the openjpeg or openjp2 libraries (linux users don't need to bother with this -if you are using OpenJPEG as provided by your package manager). The -configuration format is the same as used by Python's configparser module, +Glymur uses ctypes to access the openjp2/openjpeg libraries, and +because ctypes accesses libraries in a platform-dependent manner, +it is recommended that if you compile and install OpenJPEG into a +non-standard location, you should create a configuration file to +help Glymur properly find the openjpeg or openjp2 libraries (linux +users or macports users don't need to bother with this if you are +using OpenJPEG as provided by your package manager). The configuration +format is the same as used by Python's configparser module, i.e. :: [library] @@ -35,7 +26,7 @@ i.e. :: This assumes, of course, that you've installed OpenJPEG into /opt/openjp2-svn on a linux system. The location of the configuration file -can vary as well (of course). If you use either linux or mac, the path +can vary as well. If you use either linux or mac, the path to the configuration file would normally be :: $HOME/.config/glymur/glymurrc @@ -58,78 +49,17 @@ installed in a non-standard place, i.e. :: [library] openjpeg: /not/the/usual/location/lib/libopenjpeg.so -'''''''''''''''''''''''''''''' -Package Management Suggestions -'''''''''''''''''''''''''''''' - -You only need to read this section if you want detailed -platform-specific instructions on running as many tests as possible or wish to -use your system's package manager to install as many required -packages/RPMs/ports/whatever without going through pip. - - -Mac OS X --------- -All the necessary packages are available to use glymur with Python 2.6, 2.7, -and 3.3 via MacPorts. For python 3.3, you should install the following set of -ports: - - * python33 - * py33-numpy - * py33-distribute - * py33-matplotlib (optional, for running certain tests) - * py33-Pillow (optional, for running certain tests) - -MacPorts supplies both OpenJPEG 1.5.0 and OpenJPEG 2.0.0. - -Linux ------ -For the most part, you only need python and numpy to run glymur, so on -just about all distributions you are already set to go (and you don't -need to mess around with a configuration file, as the openjpeg shared -libraries are found in the usual places thanks to your package manager). -In order to run as many tests as possible, however, the following Python -packages may also need to be installed. Consult your package manager -documentation or use pip. - - * setuptools - * matplotlib - * pillow - * contextlib2 (python 2.6, 2.7 only) - * mock (python 2.6, 2.7 only) - * ordereddict (python 2.6 only) - -Glymur's been tested on the following linux platforms without any unexpected -difficulties: - - * OpenSUSE 12.3 - * Fedora 17, 18, 19 - * Raspian - * Travis CI (currently Ubuntu 12.04?) - * CentOS 6.4 - -Windows -------- -32-bit WinPython 2.7.5 seemed to work with OpenJPEG 1.X, 2.0, and the -development version, but still required contextlib2 and mock to be -installed via pip. WinPython 3.3.2, however, seems to have trouble -with OpenJPEG 2.0, so I would suggest using the development version with -that configuration. I no longer have any access to a windows machine, -so I cannot currently offer much guidance here. - - ''''''' Testing ''''''' +It is not necessary, but you may wish to download OpenJPEG's test +data for the purpose of configuring and running OpenJPEG's test +suite. Check their instructions on how to do that. You can then +set the **OPJ_DATA_ROOT** environment variable for the purpose of +pointing Glymur to OpenJPEG's test suite. :: -There are two environment variables you may wish to set before running the -tests. - - * **OPJ_DATA_ROOT** - points to directory for OpenJPEG test data (see above) - * **FORMAT_CORPUS_DATA_ROOT** - points to directory for format-corpus repository (see https://github.com/openplanets/format-corpus if you wish, but you really don't need to bother with this) - -Setting these two environment variables is not required, as any tests using -either of them will be skipped. + $ svn co http://openjpeg.googlecode.com/svn/data + $ export OPJ_DATA_ROOT=`pwd`/data In order to run the tests, you can either run them from within python as follows ... :: @@ -141,11 +71,3 @@ or from the command line. :: $ cd /to/where/you/unpacked/glymur $ python -m unittest discover - -Quite a few tests are currently skipped. These include tests whose -OpenJPEG counterparts are already failing, and others which do pass but -still produce heaps of output on stderr. Rather than let this swamp -the signal (that most of those tests are actually passing), they've been -filtered out for now. There are also more skipped tests on Python 2.7 -than on Python 3.3. The important part is whether or not any test -errors are reported at the end. diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index 212862d..5cf2c8f 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -4,7 +4,7 @@ How do I...? ... read the lowest resolution thumbnail? -===================================== +========================================= Printing the Jp2k object should reveal the number of resolutions (look in the COD segment section), but you can take a shortcut by supplying -1 as the resolution level. :: @@ -15,7 +15,7 @@ resolution level. :: >>> thumbnail = j.read(rlevel=-1) ... display metadata? -================= +===================== There are two ways. From the unix command line, the script *jp2dump* is available. :: @@ -35,7 +35,7 @@ codestream box, only the main header is printed. It is possible to print >>> print(j.get_codestream()) ... add XML metadata? -================= +===================== You can append any number of XML boxes to a JP2 file (not to a raw codestream). Consider the following XML file `data.xml` : :: @@ -67,7 +67,7 @@ The **append** method can add an XML box as shown below:: >>> print(jp2) ... add metadata in a more general fashion? -======================================= +=========================================== An existing raw codestream (or JP2 file) can be wrapped (re-wrapped) in a user-defined set of JP2 boxes. To get just a minimal JP2 jacket on the codestream provided by `goodstuff.j2k` (a file consisting of a raw codestream), @@ -158,11 +158,11 @@ while **append** modifies an existing file and is currently limited to XML boxes. ... create an image with an alpha layer? -==================================== +======================================== -OpenJPEG can create JP2 files with more than 3 components (requires -the development version of OpenJPEG), but by default, any extra components are -not described as such. In order to do so, we need to rewrap such +OpenJPEG can create JP2 files with more than 3 components (requires version +2.1), but by default any extra components are not described as such by the JP2 +boxes created by OpenJPEG. In order to do so, we need to rewrap such an image in a set of boxes that includes a channel definition box. This example is based on SciPy example code found at diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index e1a3b3b..bce4598 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -13,14 +13,13 @@ some very limited support for reading JPX metadata. For instance, **asoc** and **labl** boxes are recognized, so GMLJP2 metadata can be retrieved from such JPX files. -Glymur works on Python 2.6, 2.7, and 3.3. +Glymur works on Python 2.6, 2.7, 3.3, and 3.4. OpenJPEG Installation ===================== -Glymur will read JPEG 2000 images with versions 1.3, 1.4, 1.5, 2.0, -and the trunk/development version of OpenJPEG. Writing images is -only supported with the 1.5 or better, however, and the trunk/development -version is strongly recommended. For more information about OpenJPEG, +Glymur will read JPEG 2000 images with versions 1.3, 1.4, 1.5, 2.0, and 2.1 of +OpenJPEG. Writing images is only supported with the 1.5 or better, however, +and version 2.1 is strongly recommended. For more information about OpenJPEG, please consult http://www.openjpeg.org. If you use MacPorts or if you have a sufficiently recent version of diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 1bd36a4..da59ac1 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -19,6 +19,7 @@ else: import ctypes import math import os +import re import struct import warnings @@ -490,16 +491,16 @@ class Jp2k(Jp2kBox): opj2.setup_encoder(codec, cparams, image) - if _OPENJP2_IS_OFFICIAL_V2: + if re.match("2.0", version.openjpeg_version): fptr = libc.fopen(self.filename, 'wb') strm = opj2.stream_create_default_file_stream(fptr, False) stack.callback(opj2.stream_destroy, strm) stack.callback(libc.fclose, fptr) else: - # This routine introduced in 2.0 devel series. - strm = opj2.stream_create_default_file_stream_v3(self.filename, + # This routine introduced in 2.1. + strm = opj2.stream_create_default_file_stream(self.filename, False) - stack.callback(opj2.stream_destroy_v3, strm) + stack.callback(opj2.stream_destroy, strm) opj2.start_compress(codec, image, strm) opj2.encode(codec, strm) @@ -807,17 +808,16 @@ class Jp2k(Jp2kBox): 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: + if re.match("2.0", version.openjpeg_version): fptr = libc.fopen(self.filename, 'rb') stack.callback(libc.fclose, fptr) stream = opj2.stream_create_default_file_stream(fptr, True) stack.callback(opj2.stream_destroy, stream) + else: + filename = self.filename + stream = opj2.stream_create_default_file_stream(filename, True) + stack.callback(opj2.stream_destroy, stream) + codec = opj2.create_decompress(self._codec_format) stack.callback(opj2.destroy_codec, codec) @@ -952,17 +952,15 @@ class Jp2k(Jp2kBox): 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: + if re.match("2.0", version.openjpeg_version): fptr = libc.fopen(self.filename, 'rb') stack.callback(libc.fclose, fptr) stream = opj2.stream_create_default_file_stream(fptr, True) stack.callback(opj2.stream_destroy, stream) + else: + filename = self.filename + stream = opj2.stream_create_default_file_stream(filename, True) + stack.callback(opj2.stream_destroy, stream) codec = opj2.create_decompress(self._codec_format) stack.callback(opj2.destroy_codec, codec) @@ -1389,7 +1387,7 @@ def _validate_compression_params(img_array, cparams): msg = "{0}D imagery is not allowed.".format(img_array.ndim) raise IOError(msg) - if _OPENJP2_IS_OFFICIAL_V2: + if re.match("2.0.0", version.openjpeg_version): 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 " @@ -1402,14 +1400,6 @@ def _validate_compression_params(img_array, cparams): msg = "Only uint8 and uint16 images are currently supported." raise RuntimeError(msg) -# Need to known if openjp2 library is the officially release v2.0.0 or not. -_OPENJP2_IS_OFFICIAL_V2 = False -if opj2.OPENJP2 is not None: - if opj2.version() == '2.0.0': - if not hasattr(opj2.OPENJP2, - 'opj_stream_create_default_file_stream_v3'): - _OPENJP2_IS_OFFICIAL_V2 = True - _COLORSPACE_MAP = {'rgb': opj2.CLRSPC_SRGB, 'gray': opj2.CLRSPC_GRAY, 'grey': opj2.CLRSPC_GRAY, diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py index f056db0..a28ddfd 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -5,11 +5,29 @@ Wraps individual functions in openjp2 library. # pylint: disable=C0302,R0903,W0201 import ctypes +import re import sys from .config import glymur_config OPENJP2, OPENJPEG = glymur_config() +def version(): + """Wrapper for opj_version library routine.""" + try: + OPENJP2.opj_version.restype = ctypes.c_char_p + except AttributeError: + # No library, so the version is zero. + return "0.0.0" + library_version = OPENJP2.opj_version() + if sys.hexversion >= 0x03000000: + return library_version.decode('utf-8') + else: + return library_version + +if OPENJP2 is not None: + _MAJOR, _MINOR, _PATH = version().split('.') +else: + _MINOR = 0 ERROR_MSG_LST = [] # Map certain atomic OpenJPEG datatypes to the ctypes equivalents. @@ -353,6 +371,16 @@ class CompressionParametersType(ctypes.Structure): # based encoding without offset concerning all the components. ("mct_data", ctypes.c_void_p)] + if _MINOR == '1': + # Maximum size (in bytes) for the whole codestream. + # If == 0, codestream size limitation is not considered. + # If it does not comply with tcp_rates, max_cs_size prevails and a + # warning is issued. + _fields_.append(("max_cs_size", ctypes.c_int32)) + + # To be used to combine OPJ_PROFILE_*, OPJ_EXTENSION_* and (sub)levels + # values. + _fields_.append(("rsiz", ctypes.c_uint16)) class ImageCompType(ctypes.Structure): """Defines a single image component. @@ -392,6 +420,9 @@ class ImageCompType(ctypes.Structure): # image component data ("data", ctypes.POINTER(ctypes.c_int32))] + if _MINOR == '1': + _fields_.append(("alpha", ctypes.c_uint16)) + class ImageType(ctypes.Structure): """Defines image data and characteristics. @@ -1261,11 +1292,10 @@ def start_compress(codec, image, stream): OPENJP2.opj_start_compress(codec, image, stream) -def stream_create_default_file_stream(fptr, isa_read_stream): +def _stream_create_default_file_stream_2p0(fptr, isa_read_stream): """Wraps openjp2 library function opj_stream_create_default_vile_stream. - Sets the stream to be a file stream. This is valid only for version 2.0.0 - of OpenJPEG. + Sets the stream to be a file stream. Parameters ---------- @@ -1287,11 +1317,10 @@ def stream_create_default_file_stream(fptr, isa_read_stream): return stream -def stream_create_default_file_stream_v3(fname, isa_read_stream): - """Wraps openjp2 library function opj_stream_create_default_vile_stream_v3. +def _stream_create_default_file_stream_2p1(fname, isa_read_stream): + """Wraps openjp2 library function opj_stream_create_default_vile_stream. - Sets the stream to be a file stream. This function is only valid for the - trunk/development 2.0+ version of the openjp2 library. + Sets the stream to be a file stream. Parameters ---------- @@ -1306,14 +1335,18 @@ def stream_create_default_file_stream_v3(fname, isa_read_stream): An OpenJPEG file stream. """ ARGTYPES = [ctypes.c_char_p, ctypes.c_int32] - OPENJP2.opj_stream_create_default_file_stream_v3.argtypes = ARGTYPES - OPENJP2.opj_stream_create_default_file_stream_v3.restype = STREAM_TYPE_P + OPENJP2.opj_stream_create_default_file_stream.argtypes = ARGTYPES + OPENJP2.opj_stream_create_default_file_stream.restype = STREAM_TYPE_P read_stream = 1 if isa_read_stream else 0 file_argument = ctypes.c_char_p(fname.encode()) - stream = OPENJP2.opj_stream_create_default_file_stream_v3(file_argument, - read_stream) + stream = OPENJP2.opj_stream_create_default_file_stream(file_argument, + read_stream) return stream +if re.match(r'''2.0''', version()): + stream_create_default_file_stream = _stream_create_default_file_stream_2p0 +else: + stream_create_default_file_stream = _stream_create_default_file_stream_2p1 def stream_destroy(stream): """Wraps openjp2 library function opj_stream_destroy. @@ -1330,21 +1363,6 @@ def stream_destroy(stream): OPENJP2.opj_stream_destroy(stream) -def stream_destroy_v3(stream): - """Wraps openjp2 library function opj_stream_destroy_v3. - - Destroys the stream created by create_stream_v3. - - Parameters - ---------- - stream : STREAM_TYPE_P - The file stream. - """ - OPENJP2.opj_stream_destroy_v3.argtypes = [STREAM_TYPE_P] - OPENJP2.opj_stream_destroy_v3.restype = ctypes.c_void_p - OPENJP2.opj_stream_destroy_v3(stream) - - def write_tile(codec, tile_index, data, data_size, stream): """Wraps openjp2 library function opj_write_tile. @@ -1388,11 +1406,3 @@ def set_error_message(msg): ERROR_MSG_LST.append(msg) -def version(): - """Wrapper for opj_version library routine.""" - OPENJP2.opj_version.restype = ctypes.c_char_p - library_version = OPENJP2.opj_version() - if sys.hexversion >= 0x03000000: - return library_version.decode('utf-8') - else: - return library_version diff --git a/glymur/lib/test/test_openjp2.py b/glymur/lib/test/test_openjp2.py index 54d8254..442a261 100644 --- a/glymur/lib/test/test_openjp2.py +++ b/glymur/lib/test/test_openjp2.py @@ -9,6 +9,7 @@ Tests for libopenjp2 wrapping functions. # pylint: disable=F0401 import os +import re import sys import tempfile @@ -22,17 +23,15 @@ import numpy as np import glymur from glymur.lib import openjp2 -OPENJP2_IS_V2_OFFICIAL = False -if openjp2.OPENJP2 is not None: - if not hasattr(openjp2.OPENJP2, - 'opj_stream_create_default_file_stream_v3'): - OPENJP2_IS_V2_OFFICIAL = True - +if re.match("2.0", glymur.version.openjpeg_version): + OPENJP2_IS_V2_OFFICIAL = True +else: + OPENJP2_IS_V2_OFFICIAL = False @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @unittest.skipIf(openjp2.OPENJP2 is None, "Missing openjp2 library.") -@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL, "API followed here specific to V2.0+") +@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL, "API followed here specific to V2.1") class TestOpenJP2(unittest.TestCase): """Test openjp2 library functionality. @@ -89,7 +88,7 @@ class TestOpenJP2(unittest.TestCase): openjp2.set_warning_handler(codec, None) openjp2.set_error_handler(codec, None) - stream = openjp2.stream_create_default_file_stream_v3(filename, True) + stream = openjp2.stream_create_default_file_stream(filename, True) openjp2.setup_decoder(codec, dparam) image = openjp2.read_header(stream, codec) @@ -110,7 +109,7 @@ class TestOpenJP2(unittest.TestCase): openjp2.end_decompress(codec, stream) openjp2.destroy_codec(codec) - openjp2.stream_destroy_v3(stream) + openjp2.stream_destroy(stream) openjp2.image_destroy(image) def test_tte0(self): @@ -318,15 +317,15 @@ def tile_encoder(**kwargs): openjp2.setup_encoder(codec, l_param, l_image) - stream = openjp2.stream_create_default_file_stream_v3(kwargs['filename'], - False) + stream = openjp2.stream_create_default_file_stream(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.stream_destroy(stream) openjp2.destroy_codec(codec) openjp2.image_destroy(l_image) @@ -335,8 +334,8 @@ def tile_decoder(**kwargs): Reads a tile. That's all it does. """ - stream = openjp2.stream_create_default_file_stream_v3(kwargs['filename'], - True) + stream = openjp2.stream_create_default_file_stream(kwargs['filename'], + True) dparam = openjp2.set_default_decoder_parameters() dparam.decod_format = kwargs['codec_format'] @@ -371,7 +370,7 @@ def tile_decoder(**kwargs): openjp2.end_decompress(codec, stream) openjp2.destroy_codec(codec) - openjp2.stream_destroy_v3(stream) + openjp2.stream_destroy(stream) openjp2.image_destroy(image) def ttx0_setup(filename): diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index b872f1d..e1df82b 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -167,3 +167,252 @@ def read_pgx_header(pgx_file): header = header.rstrip() return header, pos + +text_gbr_27 = """Colour Specification Box (colr) @ (179, 1339) + Method: any ICC profile + Precedence: 2 + Approximation: accurately represents correct colorspace definition + ICC Profile: + {'Color Space': 'RGB', + 'Connection Space': 'XYZ', + 'Creator': u'appl', + 'Datetime': datetime.datetime(2009, 2, 25, 11, 26, 11), + 'Device Attributes': 'reflective, glossy, positive media polarity, color media', + 'Device Class': 'display device profile', + 'Device Manufacturer': u'appl', + 'Device Model': '', + 'File Signature': u'acsp', + 'Flags': 'not embedded, can be used independently', + 'Illuminant': array([ 0.96420288, 1. , 0.8249054 ]), + 'Platform': u'APPL', + 'Preferred CMM Type': 1634758764, + 'Rendering Intent': 'perceptual', + 'Size': 1328, + 'Version': '2.2.0'}""" + +text_gbr_33 = """Colour Specification Box (colr) @ (179, 1339) + Method: any ICC profile + Precedence: 2 + Approximation: accurately represents correct colorspace definition + ICC Profile: + {'Size': 1328, + 'Preferred CMM Type': 1634758764, + 'Version': '2.2.0', + 'Device Class': 'display device profile', + 'Color Space': 'RGB', + 'Connection Space': 'XYZ', + 'Datetime': datetime.datetime(2009, 2, 25, 11, 26, 11), + 'File Signature': 'acsp', + 'Platform': 'APPL', + 'Flags': 'not embedded, can be used independently', + 'Device Manufacturer': 'appl', + 'Device Model': '', + 'Device Attributes': 'reflective, glossy, positive media polarity, color media', + 'Rendering Intent': 'perceptual', + 'Illuminant': array([ 0.96420288, 1. , 0.8249054 ]), + 'Creator': 'appl'}""" + +text_gbr_34 = """Colour Specification Box (colr) @ (179, 1339) + Method: any ICC profile + Precedence: 2 + Approximation: accurately represents correct colorspace definition + ICC Profile: + {'Size': 1328, + 'Preferred CMM Type': 1634758764, + 'Version': '2.2.0', + 'Device Class': 'display device profile', + 'Color Space': 'RGB', + 'Connection Space': 'XYZ', + 'Datetime': datetime.datetime(2009, 2, 25, 11, 26, 11), + 'File Signature': 'acsp', + 'Platform': 'APPL', + 'Flags': 'not embedded, can be used independently', + 'Device Manufacturer': 'appl', + 'Device Model': '', + 'Device Attributes': 'reflective, glossy, positive media polarity, color ' + 'media', + 'Rendering Intent': 'perceptual', + 'Illuminant': array([ 0.96420288, 1. , 0.8249054 ]), + 'Creator': 'appl'}""" + + +# Metadata dump of nemo. +nemo_dump_full_opj2 = r'''JPEG 2000 Signature Box (jP ) @ (0, 12) + Signature: 0d0a870a +File Type Box (ftyp) @ (12, 20) + Brand: jp2 + Compatibility: ['jp2 '] +JP2 Header Box (jp2h) @ (32, 45) + Image Header Box (ihdr) @ (40, 22) + Size: [1456 2592 3] + Bitdepth: 8 + Signed: False + Compression: wavelet + Colorspace Unknown: False + Colour Specification Box (colr) @ (62, 15) + Method: enumerated colorspace + Precedence: 0 + Colorspace: sRGB +UUID Box (uuid) @ (77, 638) + UUID: 4a706754-6966-6645-7869-662d3e4a5032 (Exif) + UUID Data: +{'Image': {'Make': 'HTC', + 'Model': 'HTC Glacier', + 'XResolution': 72.0, + 'YResolution': 72.0, + 'ResolutionUnit': 2, + 'YCbCrPositioning': 1, + 'ExifTag': 138, + 'GPSTag': 354}, + 'Photo': {'ISOSpeedRatings': 76, + 'ExifVersion': (48, 50, 50, 48), + 'DateTimeOriginal': '2013:02:09 14:47:53', + 'DateTimeDigitized': '2013:02:09 14:47:53', + 'ComponentsConfiguration': (1, 2, 3, 0), + 'FocalLength': 3.53, + 'FlashpixVersion': (48, 49, 48, 48), + 'ColorSpace': 1, + 'PixelXDimension': 2528, + 'PixelYDimension': 1424, + 'InteroperabilityTag': 324}, + 'GPSInfo': {'GPSVersionID': (2, 2, 0), + 'GPSLatitudeRef': 'N', + 'GPSLatitude': [42.0, 20.0, 33.61], + 'GPSLongitudeRef': 'W', + 'GPSLongitude': [71.0, 5.0, 17.32], + 'GPSAltitudeRef': 0, + 'GPSAltitude': 0.0, + 'GPSTimeStamp': [19.0, 47.0, 53.0], + 'GPSMapDatum': 'WGS-84', + 'GPSProcessingMethod': (65, + 83, + 67, + 73, + 73, + 0, + 0, + 0, + 78, + 69, + 84, + 87, + 79, + 82, + 75), + 'GPSDateStamp': '2013:02:09'}, + 'Iop': None} +UUID Box (uuid) @ (715, 2412) + UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) + UUID Data: + + + + + + +Contiguous Codestream Box (jp2c) @ (3127, 1132296) + Main header: + SOC marker segment @ (3135, 0) + SIZ marker segment @ (3137, 47) + Profile: 2 + Reference Grid Height, Width: (1456 x 2592) + Vertical, Horizontal Reference Grid Offset: (0 x 0) + Reference Tile Height, Width: (1456 x 2592) + Vertical, Horizontal Reference Tile Offset: (0 x 0) + Bitdepth: (8, 8, 8) + Signed: (False, False, False) + Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1)) + COD marker segment @ (3186, 12) + Coding style: + Entropy coder, without partitions + SOP marker segments: False + EPH marker segments: False + Coding style parameters: + Progression order: LRCP + Number of layers: 2 + Multiple component transformation usage: reversible + Number of resolutions: 2 + Code block height, width: (64 x 64) + Wavelet transform: 5-3 reversible + Precinct size: default, 2^15 x 2^15 + Code block context: + Selective arithmetic coding bypass: False + Reset context probabilities on coding pass boundaries: False + Termination on each coding pass: False + Vertically stripe causal context: False + Predictable termination: False + Segmentation symbols: False + QCD marker segment @ (3200, 7) + Quantization style: no quantization, 2 guard bits + Step size: [(0, 8), (0, 9), (0, 9), (0, 10)] + CME marker segment @ (3209, 37) + "Created by OpenJPEG version 2.0.0"''' +nemo_dump_full_p27 = r'''JPEG 2000 Signature Box (jP ) @ (0, 12) + Signature: 0d0a870a +File Type Box (ftyp) @ (12, 20) + Brand: jp2 + Compatibility: ['jp2 '] +JP2 Header Box (jp2h) @ (32, 45) + Image Header Box (ihdr) @ (40, 22) + Size: [1456 2592 3] + Bitdepth: 8 + Signed: False + Compression: wavelet + Colorspace Unknown: False + Colour Specification Box (colr) @ (62, 15) + Method: enumerated colorspace + Precedence: 0 + Colorspace: sRGB +UUID Box (uuid) @ (77, 638) + UUID: 4a706754-6966-6645-7869-662d3e4a5032 (Exif) + UUID Data: +{'GPSInfo': OrderedDict([('GPSVersionID', (2, 2, 0)), ('GPSLatitudeRef', 'N'), ('GPSLatitude', [42.0, 20.0, 33.61]), ('GPSLongitudeRef', 'W'), ('GPSLongitude', [71.0, 5.0, 17.32]), ('GPSAltitudeRef', 0), ('GPSAltitude', 0.0), ('GPSTimeStamp', [19.0, 47.0, 53.0]), ('GPSMapDatum', 'WGS-84'), ('GPSProcessingMethod', (65, 83, 67, 73, 73, 0, 0, 0, 78, 69, 84, 87, 79, 82, 75)), ('GPSDateStamp', '2013:02:09')]), + 'Image': OrderedDict([('Make', 'HTC'), ('Model', 'HTC Glacier'), ('XResolution', 72.0), ('YResolution', 72.0), ('ResolutionUnit', 2), ('YCbCrPositioning', 1), ('ExifTag', 138), ('GPSTag', 354)]), + 'Iop': None, + 'Photo': OrderedDict([('ISOSpeedRatings', 76), ('ExifVersion', (48, 50, 50, 48)), ('DateTimeOriginal', '2013:02:09 14:47:53'), ('DateTimeDigitized', '2013:02:09 14:47:53'), ('ComponentsConfiguration', (1, 2, 3, 0)), ('FocalLength', 3.53), ('FlashpixVersion', (48, 49, 48, 48)), ('ColorSpace', 1), ('PixelXDimension', 2528), ('PixelYDimension', 1424), ('InteroperabilityTag', 324)])} +UUID Box (uuid) @ (715, 2412) + UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) + UUID Data: + + + + + + +Contiguous Codestream Box (jp2c) @ (3127, 1132296) + Main header: + SOC marker segment @ (3135, 0) + SIZ marker segment @ (3137, 47) + Profile: 2 + Reference Grid Height, Width: (1456 x 2592) + Vertical, Horizontal Reference Grid Offset: (0 x 0) + Reference Tile Height, Width: (1456 x 2592) + Vertical, Horizontal Reference Tile Offset: (0 x 0) + Bitdepth: (8, 8, 8) + Signed: (False, False, False) + Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1)) + COD marker segment @ (3186, 12) + Coding style: + Entropy coder, without partitions + SOP marker segments: False + EPH marker segments: False + Coding style parameters: + Progression order: LRCP + Number of layers: 2 + Multiple component transformation usage: reversible + Number of resolutions: 2 + Code block height, width: (64 x 64) + Wavelet transform: 5-3 reversible + Precinct size: default, 2^15 x 2^15 + Code block context: + Selective arithmetic coding bypass: False + Reset context probabilities on coding pass boundaries: False + Termination on each coding pass: False + Vertically stripe causal context: False + Predictable termination: False + Segmentation symbols: False + QCD marker segment @ (3200, 7) + Quantization style: no quantization, 2 guard bits + Step size: [(0, 8), (0, 9), (0, 9), (0, 10)] + CME marker segment @ (3209, 37) + "Created by OpenJPEG version 2.0.0"''' diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py index 8db5039..1141460 100644 --- a/glymur/test/test_codestream.py +++ b/glymur/test/test_codestream.py @@ -15,6 +15,7 @@ import os import struct import sys import tempfile +import warnings if sys.hexversion < 0x02070000: import unittest2 as unittest @@ -98,8 +99,10 @@ class TestCodestream(unittest.TestCase): tfile.write(read_buffer) tfile.flush() - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") codestream = Jp2k(tfile.name).get_codestream() + self.assertEqual(len(w), 1) self.assertEqual(codestream.segment[2].marker_id, '0xff79') self.assertEqual(codestream.segment[2].length, 3) diff --git a/glymur/test/test_config.py b/glymur/test/test_config.py index 32f067a..2efd7b1 100644 --- a/glymur/test/test_config.py +++ b/glymur/test/test_config.py @@ -15,6 +15,7 @@ import imp import os import sys import tempfile +import warnings if sys.hexversion < 0x02070000: import unittest2 as unittest @@ -87,8 +88,10 @@ class TestSuite(unittest.TestCase): with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}): # Misconfigured new configuration file should # be rejected. - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") imp.reload(glymur.lib.openjp2) + self.assertEqual(len(w), 1) @unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None and diff --git a/glymur/test/test_conformance.py b/glymur/test/test_conformance.py index 5f53b5f..ccdaffa 100644 --- a/glymur/test/test_conformance.py +++ b/glymur/test/test_conformance.py @@ -14,6 +14,7 @@ import os from os.path import join import re import sys +import warnings if sys.hexversion < 0x02070000: import unittest2 as unittest @@ -36,8 +37,6 @@ except KeyError: @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): """Test suite for files in format corpus repository.""" @@ -49,32 +48,16 @@ class TestSuiteFormatCorpus(unittest.TestCase): jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT, 'jp2k-test/byteCorruption/balloon_trunc1.jp2') j2k = Jp2k(jfile) - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") codestream = j2k.get_codestream(header_only=False) + self.assertEqual(len(w), 1) # The last segment is truncated, so there should not be an EOC marker. self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC') # The codestream is not as long as claimed. - with self.assertRaises(OSError): - j2k.read(rlevel=-1) - - @unittest.skipIf(re.match(r"""1\.[01234]""", - glymur.version.openjpeg_version) is not None, - "Needs 1.4+ to catch this.") - def test_balloon_trunc2(self): - """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): - codestream = j2k.get_codestream(header_only=False) - - # The last segment is truncated, so there should not be an EOC marker. - self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC') - - # The codestream is not as long as claimed. - with self.assertRaises(OSError): + with self.assertRaises((OSError, IOError)): j2k.read(rlevel=-1) def test_balloon_trunc3(self): @@ -82,8 +65,10 @@ class TestSuiteFormatCorpus(unittest.TestCase): jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT, 'jp2k-test/byteCorruption/balloon_trunc3.jp2') j2k = Jp2k(jfile) - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") codestream = j2k.get_codestream(header_only=False) + self.assertEqual(len(w), 1) # The last segment is truncated, so there should not be an EOC marker. self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC') @@ -97,8 +82,10 @@ class TestSuiteFormatCorpus(unittest.TestCase): jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT, 'jp2k-test', 'icc', 'balloon_eciRGBv2_ps_adobeplugin.jpf') - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") Jp2k(jfile) + self.assertEqual(len(w), 1) def test_jp2_brand_iccpr_mult_colr(self): """Has colr box, one that conforms, one that does not.""" @@ -109,14 +96,14 @@ class TestSuiteFormatCorpus(unittest.TestCase): # ("Input Device") changed relative to original profile. jfile = join(FORMAT_CORPUS_DATA_ROOT, 'jp2k-test', 'icc', 'balloon_eciRGBv2_ps_adobeplugin_jp2compatible.jpf') - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") Jp2k(jfile) + self.assertEqual(len(w), 1) @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.""" @@ -130,8 +117,10 @@ class TestSuiteOpj(unittest.TestCase): """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): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") Jp2k(filename) + self.assertEqual(len(w), 1) if __name__ == "__main__": unittest.main() diff --git a/glymur/test/test_icc.py b/glymur/test/test_icc.py index 8668999..b998d7c 100644 --- a/glymur/test/test_icc.py +++ b/glymur/test/test_icc.py @@ -11,6 +11,7 @@ ICC profile tests. import datetime import os import sys +import warnings if sys.hexversion < 0x02070000: import unittest2 as unittest @@ -65,16 +66,16 @@ class TestICC(unittest.TestCase): self.assertEqual(profile['Creator'], 'JPEG') - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") def test_invalid_profile_header(self): """invalid ICC header data should cause UserWarning""" jfile = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2') # assertWarns in Python 3.3 (python2.7/pylint issue) # pylint: disable=E1101 - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") Jp2k(jfile) + self.assertEqual(len(w), 1) if __name__ == "__main__": unittest.main() diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index ef62460..7d63233 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -18,6 +18,7 @@ Test suite specifically targeting JP2 box layout. import doctest import os +import re import shutil import struct import sys @@ -57,7 +58,7 @@ def load_tests(loader, tests, ignore): return tests @unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2 or - OPENJP2_IS_V2_OFFICIAL, + re.match(r'''2.0.0''', glymur.version.openjpeg_version), "Not supported until 2.0+.") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") class TestChannelDefinition(unittest.TestCase): diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 344a3cd..0a403f9 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -53,8 +53,10 @@ class TestReaderRequirements(unittest.TestCase): tfile.write(nemof.read()) tfile.flush() - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") j = Jp2k(tfile.name) + self.assertEqual(len(w), 1) self.assertEqual(j.box[2].box_id, 'rreq') self.assertEqual(type(j.box[2]), glymur.jp2box.ReaderRequirementsBox) diff --git a/glymur/test/test_jp2box_xml.py b/glymur/test/test_jp2box_xml.py index b875188..d1b7903 100644 --- a/glymur/test/test_jp2box_xml.py +++ b/glymur/test/test_jp2box_xml.py @@ -23,6 +23,7 @@ import sys import tempfile import warnings import xml.etree.cElementTree as ET +import warnings if sys.hexversion < 0x03000000: from StringIO import StringIO @@ -212,18 +213,12 @@ class TestJp2kBadXmlFile(unittest.TestCase): def tearDown(self): pass - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") - def test_invalid_xml_box_warning(self): - """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 info from xml box with bad xml.""" - with warnings.catch_warnings(): - warnings.simplefilter("ignore") + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") jp2k = Jp2k(self._bad_xml_file) + self.assertEqual(len(w), 1) self.assertEqual(jp2k.box[3].box_id, 'xml ') self.assertEqual(jp2k.box[3].offset, 77) @@ -276,8 +271,10 @@ class TestBadButRecoverableXmlFile(unittest.TestCase): "Uses features introduced in 3.2.") def test_bad_xml_box_warning(self): """Should warn in case of bad XML""" - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") Jp2k(self._bad_xml_file) + self.assertEqual(len(w), 1) def test_recover_from_bad_xml(self): """Should be able to recover info from xml box with bad xml.""" diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 636ea9a..3130d0a 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -1,9 +1,6 @@ """ 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 @@ -384,13 +381,9 @@ class TestJp2k(unittest.TestCase): # Verify that a warning is issued, but only on python3. # On python2, just suppress the warning. - if sys.hexversion < 0x03030000: - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - j = Jp2k(tfile.name) - else: - with self.assertWarns(UserWarning): - j = Jp2k(tfile.name) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + j = Jp2k(tfile.name) exif = j.box[3].data # Were the tag == 271, 'Make' would be in the keys instead. @@ -590,8 +583,8 @@ class TestJp2k_1_x(unittest.TestCase): j2k.read(layer=1) -@unittest.skipIf(not OPENJP2_IS_V2_OFFICIAL, - "Tests only to be run on 2.0 official.") +@unittest.skipIf(re.match("2.0.0", glymur.version.openjpeg_version) is None, + "Tests only to be run on 2.0.0.") class TestJp2k_2_0_official(unittest.TestCase): """Test suite to only be run on v2.0 official.""" diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 456ff9b..c956354 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -83,16 +83,6 @@ class TestSuite(unittest.TestCase): pgxdata = read_pgx(pgxfile) np.testing.assert_array_equal(jpdata, pgxdata) - @unittest.skip("Known failure in OPENJPEG test suite.") - def test_ETS_C0P0_p0_03_j2k(self): - jfile = opj_data_file('input/conformance/p0_03.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c0p0_03r0.pgx') - pgxdata = read_pgx(pgxfile) - np.testing.assert_array_equal(jpdata, pgxdata) - def test_ETS_C0P0_p0_03_j2k_r1(self): jfile = opj_data_file('input/conformance/p0_03.j2k') jp2k = Jp2k(jfile) @@ -102,42 +92,6 @@ class TestSuite(unittest.TestCase): pgxdata = read_pgx(pgxfile) np.testing.assert_array_equal(jpdata, pgxdata) - @unittest.skip("Known failure in OPENJPEG test suite.") - def test_ETS_C0P0_p0_04_j2k(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=3) - - pgxfile = opj_data_file('baseline/conformance/c0p0_04.pgx') - pgxdata = read_pgx(pgxfile) - - self.assertTrue(peak_tolerance(jpdata[:, :, 2], pgxdata) < 33) - self.assertTrue(mse(jpdata[:, :, 2], pgxdata) < 55.8) - - @unittest.skip("Known failure in OPENJPEG test suite.") - def test_ETS_C0P0_p0_07_j2k(self): - jfile = opj_data_file('input/conformance/p0_07.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read() - - pgxfile = opj_data_file('baseline/conformance/c0p0_07.pgx') - pgxdata = read_pgx(pgxfile) - - self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 10) - self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 0.34) - - @unittest.skip("8-bit pgx data vs 12-bit j2k data") - def test_ETS_C0P0_p0_08_j2k(self): - jfile = opj_data_file('input/conformance/p0_08.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=5) - - pgxfile = opj_data_file('baseline/conformance/c0p0_08.pgx') - pgxdata = read_pgx(pgxfile) - - self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 7) - self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 6.72) - def test_ETS_C0P0_p0_09_j2k(self): jfile = opj_data_file('input/conformance/p0_09.j2k') jp2k = Jp2k(jfile) @@ -149,18 +103,6 @@ class TestSuite(unittest.TestCase): self.assertTrue(peak_tolerance(jpdata, pgxdata) < 4) self.assertTrue(mse(jpdata, pgxdata) < 1.47) - @unittest.skip("Known failure in OPENJPEG test suite.") - def test_ETS_C0P0_p0_10_j2k(self): - jfile = opj_data_file('input/conformance/p0_10.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c0p0_10.pgx') - pgxdata = read_pgx(pgxfile) - - self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 10) - self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 2.84) - def test_ETS_C0P0_p0_11_j2k(self): jfile = opj_data_file('input/conformance/p0_11.j2k') jp2k = Jp2k(jfile) @@ -171,50 +113,6 @@ class TestSuite(unittest.TestCase): np.testing.assert_array_equal(jpdata, pgxdata) - @unittest.skip("fprintf stderr output in r2343.") - def test_ETS_C0P0_p0_12_j2k(self): - jfile = opj_data_file('input/conformance/p0_12.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c0p0_12.pgx') - pgxdata = read_pgx(pgxfile) - - np.testing.assert_array_equal(jpdata, pgxdata) - - @unittest.skip("Known failure in OPENJPEG test suite.") - def test_ETS_C0P0_p0_13_j2k(self): - jfile = opj_data_file('input/conformance/p0_13.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c0p0_13.pgx') - pgxdata = read_pgx(pgxfile) - - np.testing.assert_array_equal(jpdata[:, :, 0], pgxdata) - - @unittest.skip("Known failure in OPENJPEG test suite.") - def test_ETS_C0P0_p0_14_j2k(self): - jfile = opj_data_file('input/conformance/p0_14.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=2) - - pgxfile = opj_data_file('baseline/conformance/c0p0_14.pgx') - pgxdata = read_pgx(pgxfile) - - np.testing.assert_array_equal(jpdata[:, :, 0], pgxdata) - - @unittest.skip("Known failure in OPENJPEG test suite.") - def test_ETS_C0P0_p0_15_j2k(self): - jfile = opj_data_file('input/conformance/p0_15.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c0p0_15r0.pgx') - pgxdata = read_pgx(pgxfile) - - np.testing.assert_array_equal(jpdata, pgxdata) - def test_ETS_C0P0_p0_15_j2k_r1(self): jfile = opj_data_file('input/conformance/p0_15.j2k') jp2k = Jp2k(jfile) @@ -245,83 +143,6 @@ class TestSuite(unittest.TestCase): np.testing.assert_array_equal(jpdata, pgxdata) - @unittest.skip("Known failure in OPENJPEG test suite operation.") - def test_ETS_C0P1_p1_02_j2k(self): - jfile = opj_data_file('input/conformance/p1_02.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=3) - - pgxfile = opj_data_file('baseline/conformance/c0p1_02.pgx') - pgxdata = read_pgx(pgxfile) - - self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 35) - self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 74) - - @unittest.skip("Known failure in OPENJPEG test suite operation.") - def test_ETS_C0P1_p1_04_j2k(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c0p1_04r0.pgx') - pgxdata = read_pgx(pgxfile) - - print(peak_tolerance(jpdata, pgxdata)) - self.assertTrue(peak_tolerance(jpdata, pgxdata) < 2) - self.assertTrue(mse(jpdata, pgxdata) < 0.55) - - @unittest.skip("Known failure in OPENJPEG test suite, precision issue.") - def test_ETS_C0P1_p1_04_j2k_r3(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=3) - - pgxfile = opj_data_file('baseline/conformance/c0p1_04r3.pgx') - pgxdata = read_pgx(pgxfile) - - print(peak_tolerance(jpdata, pgxdata)) - self.assertTrue(peak_tolerance(jpdata, pgxdata) < 2) - self.assertTrue(mse(jpdata, pgxdata) < 0.55) - - @unittest.skip("Known failure in OPENJPEG test suite operation.") - def test_ETS_C0P1_p1_05_j2k(self): - jfile = opj_data_file('input/conformance/p1_05.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=4) - - pgxfile = opj_data_file('baseline/conformance/c0p1_05.pgx') - pgxdata = read_pgx(pgxfile) - - print(peak_tolerance(jpdata[:, :, 0], pgxdata)) - print(peak_tolerance(jpdata[:, :, 1], pgxdata)) - self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 128) - self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 16384) - - @unittest.skip("Known failure in OPENJPEG test suite operation.") - def test_ETS_C0P1_p1_06_j2k(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=1) - - pgxfile = opj_data_file('baseline/conformance/c0p1_06.pgx') - pgxdata = read_pgx(pgxfile) - - print(peak_tolerance(jpdata[:, :, 0], pgxdata)) - print(peak_tolerance(jpdata[:, :, 1], pgxdata)) - self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 128) - self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 16384) - - @unittest.skip("fprintf stderr output in r2345.") - def test_ETS_C0P1_p1_07_j2k(self): - jfile = opj_data_file('input/conformance/p1_07.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read_bands(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c0p1_07.pgx') - pgxdata = read_pgx(pgxfile) - - np.testing.assert_array_equal(jpdata[0], pgxdata) - def test_ETS_C1P0_p0_01_j2k(self): jfile = opj_data_file('input/conformance/p0_01.j2k') jp2k = Jp2k(jfile) @@ -372,24 +193,6 @@ class TestSuite(unittest.TestCase): self.assertTrue(peak_tolerance(jpdata[:, :, 2], pgxdata) < 6) self.assertTrue(mse(jpdata[:, :, 2], pgxdata) < 1.07) - @unittest.skip("Known failure in OPENJPEG test suite operation.") - def test_ETS_C1P0_p0_07_j2k(self): - jfile = opj_data_file('input/conformance/p0_07.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read() - - pgxfile = opj_data_file('baseline/conformance/c1p0_07_0.pgx') - pgxdata = read_pgx(pgxfile) - np.testing.assert_array_equal(jpdata[:, :, 0], pgxdata) - - pgxfile = opj_data_file('baseline/conformance/c1p0_07_1.pgx') - pgxdata = read_pgx(pgxfile) - np.testing.assert_array_equal(jpdata[:, : 1], pgxdata) - - pgxfile = opj_data_file('baseline/conformance/c1p0_07_2.pgx') - pgxdata = read_pgx(pgxfile) - np.testing.assert_array_equal(jpdata[:, : 2], pgxdata) - def test_ETS_C1P0_p0_08_j2k(self): jfile = opj_data_file('input/conformance/p0_08.j2k') jp2k = Jp2k(jfile) @@ -425,38 +228,6 @@ class TestSuite(unittest.TestCase): pgxdata = read_pgx(pgxfile) np.testing.assert_array_equal(jpdata, pgxdata) - @unittest.skip("fprintf stderr output in r2343.") - def test_ETS_C1P0_p0_12_j2k(self): - jfile = opj_data_file('input/conformance/p0_12.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c1p0_12_0.pgx') - pgxdata = read_pgx(pgxfile) - np.testing.assert_array_equal(jpdata, pgxdata) - - @unittest.skip("fprintf stderr output in r2343.") - def test_ETS_C1P0_p0_13_j2k(self): - jfile = opj_data_file('input/conformance/p0_13.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c1p0_13_0.pgx') - pgxdata = read_pgx(pgxfile) - np.testing.assert_array_equal(jpdata[:, :, 0], pgxdata) - - pgxfile = opj_data_file('baseline/conformance/c1p0_13_1.pgx') - pgxdata = read_pgx(pgxfile) - np.testing.assert_array_equal(jpdata[:, :, 1], pgxdata) - - pgxfile = opj_data_file('baseline/conformance/c1p0_13_2.pgx') - pgxdata = read_pgx(pgxfile) - np.testing.assert_array_equal(jpdata[:, :, 2], pgxdata) - - pgxfile = opj_data_file('baseline/conformance/c1p0_13_3.pgx') - pgxdata = read_pgx(pgxfile) - np.testing.assert_array_equal(jpdata[:, :, 3], pgxdata) - def test_ETS_C1P0_p0_14_j2k(self): jfile = opj_data_file('input/conformance/p0_14.j2k') jp2k = Jp2k(jfile) @@ -531,64 +302,6 @@ class TestSuite(unittest.TestCase): self.assertTrue(peak_tolerance(jpdata, pgxdata) < 624) self.assertTrue(mse(jpdata, pgxdata) < 3080) - @unittest.skip("fprintf stderr output in r2343.") - def test_ETS_C1P1_p1_05_j2k(self): - jfile = opj_data_file('input/conformance/p1_05.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read() - - pgxfile = opj_data_file('baseline/conformance/c1p1_05_0.pgx') - pgxdata = read_pgx(pgxfile) - self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 40) - self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 8.458) - - pgxfile = opj_data_file('baseline/conformance/c1p1_05_1.pgx') - pgxdata = read_pgx(pgxfile) - self.assertTrue(peak_tolerance(jpdata[:, :, 1], pgxdata) < 40) - self.assertTrue(mse(jpdata[:, :, 1], pgxdata) < 9.816) - - pgxfile = opj_data_file('baseline/conformance/c1p1_05_2.pgx') - pgxdata = read_pgx(pgxfile) - self.assertTrue(peak_tolerance(jpdata[:, :, 2], pgxdata) < 40) - self.assertTrue(mse(jpdata[:, :, 2], pgxdata) < 10.154) - - @unittest.skip("fprintf stderr output in r2343.") - def test_ETS_C1P1_p1_06_j2k(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read() - - pgxfile = opj_data_file('baseline/conformance/c1p1_06_0.pgx') - pgxdata = read_pgx(pgxfile) - self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 2) - self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 0.6) - - pgxfile = opj_data_file('baseline/conformance/c1p1_06_1.pgx') - pgxdata = read_pgx(pgxfile) - self.assertTrue(peak_tolerance(jpdata[:, :, 1], pgxdata) < 2) - self.assertTrue(mse(jpdata[:, :, 1], pgxdata) < 0.6) - - pgxfile = opj_data_file('baseline/conformance/c1p1_06_2.pgx') - pgxdata = read_pgx(pgxfile) - self.assertTrue(peak_tolerance(jpdata[:, :, 2], pgxdata) < 2) - self.assertTrue(mse(jpdata[:, :, 2], pgxdata) < 0.6) - - @unittest.skip("fprintf stderr output in r2343.") - def test_ETS_C1P1_p1_07_j2k(self): - jfile = opj_data_file('input/conformance/p1_07.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read_bands() - - pgxfile = opj_data_file('baseline/conformance/c1p1_07_0.pgx') - pgxdata = read_pgx(pgxfile) - self.assertTrue(peak_tolerance(jpdata[0], pgxdata) <= 0) - self.assertTrue(mse(jpdata[0], pgxdata) <= 0) - - pgxfile = opj_data_file('baseline/conformance/c1p1_07_1.pgx') - pgxdata = read_pgx(pgxfile) - self.assertTrue(peak_tolerance(jpdata[1], pgxdata) <= 0) - self.assertTrue(mse(jpdata[1], pgxdata) <= 0) - def test_ETS_JP2_file1(self): jfile = opj_data_file('input/conformance/file1.jp2') jp2k = Jp2k(jfile) @@ -663,41 +376,34 @@ class TestSuite(unittest.TestCase): jp2.read() self.assertTrue(True) - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_123_j2c_3_decode(self): - jfile = opj_data_file('input/nonregression/123.j2c') - jp2 = Jp2k(jfile) - jp2.read() - self.assertTrue(True) - + @unittest.skipIf(re.match(r"""1\.5\.2""", glymur.version.openjpeg_version), + "Test fails in 1.5.2") @unittest.skipIf(sys.hexversion < 0x03020000, "Uses features introduced in 3.2.") def test_NR_DEC_broken_jp2_4_decode(self): jfile = opj_data_file('input/nonregression/broken.jp2') - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") # colr box has bad length. jp2 = Jp2k(jfile) + self.assertEqual(len(w), 2) with self.assertRaises(IOError): jp2.read() self.assertTrue(True) - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") + @unittest.skipIf(re.match(r"""1\.5\.2""", glymur.version.openjpeg_version), + "Test fails in 1.5.2") def test_NR_DEC_broken3_jp2_6_decode(self): jfile = opj_data_file('input/nonregression/broken3.jp2') - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") # colr box has bad length. j = Jp2k(jfile) + self.assertEqual(len(w), 2) with self.assertRaises(IOError): j.read() - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_bug_j2c_8_decode(self): - jfile = opj_data_file('input/nonregression/bug.j2c') - Jp2k(jfile).read() - self.assertTrue(True) - def test_NR_DEC_buxI_j2k_9_decode(self): jfile = opj_data_file('input/nonregression/buxI.j2k') Jp2k(jfile).read() @@ -725,13 +431,6 @@ class TestSuite(unittest.TestCase): Jp2k(jfile).read() self.assertTrue(True) - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_illegalcolortransform_j2k_14_decode(self): - # Stream too short, expected SOT. - jfile = opj_data_file('input/nonregression/illegalcolortransform.j2k') - Jp2k(jfile).read() - self.assertTrue(True) - def test_NR_DEC_j2k32_j2k_15_decode(self): jfile = opj_data_file('input/nonregression/j2k32.j2k') Jp2k(jfile).read() @@ -800,5929 +499,6 @@ class TestSuite(unittest.TestCase): Jp2k(jfile).read() self.assertTrue(True) - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_76_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - fulldata = jp2k.read() - tiledata = jp2k.read(tile=0) - np.testing.assert_array_equal(tiledata, fulldata[0:3, 0:3]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_77_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - fulldata = jp2k.read() - tiledata = jp2k.read(tile=5) - np.testing.assert_array_equal(tiledata, fulldata[3:6, 3:6]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_78_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - fulldata = jp2k.read() - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - tiledata = jp2k.read(tile=9) - np.testing.assert_array_equal(tiledata, fulldata[6:9, 3:6]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_79_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - fulldata = jp2k.read() - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - tiledata = jp2k.read(tile=15) - np.testing.assert_array_equal(tiledata, fulldata[9:12, 9:12]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_80_decode(self): - # Just read the data, don't bother verifying. - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - jp2k.read(tile=0, rlevel=2) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_81_decode(self): - # Just read the data, don't bother verifying. - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - jp2k.read(tile=5, rlevel=2) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_82_decode(self): - # Just read the data, don't bother verifying. - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - jp2k.read(tile=9, rlevel=2) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_83_decode(self): - # tile size is 3x3. Reducing two levels results in no data. - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - with self.assertRaises((IOError, OSError)): - jp2k.read(tile=15, rlevel=2) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_84_decode(self): - # Just read the data, don't bother verifying. - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - jp2k.read(rlevel=4) - - -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -class TestSuiteDump(unittest.TestCase): - - def setUp(self): - pass - - def tearDown(self): - pass - - def test_NR_p0_01_dump(self): - jfile = opj_data_file('input/conformance/p0_01.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # Segment IDs. - actual = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'QCD', 'COD', 'SOT', 'SOD', 'EOC'] - self.assertEqual(actual, expected) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 128) - self.assertEqual(c.segment[1].ysiz, 128) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # QCD: Quantization default - self.assertEqual(c.segment[2].sqcd & 0x1f, 0) - self.assertEqual(c.segment[2].guard_bits, 2) - self.assertEqual(c.segment[2].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - self.assertEqual(c.segment[2].mantissa, - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - - # COD: Coding style default - self.assertFalse(c.segment[3].scod & 2) # no sop - self.assertFalse(c.segment[3].scod & 4) # no eph - self.assertEqual(c.segment[3].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[3].layers, 1) # layers = 1 - self.assertEqual(c.segment[3].spcod[3], 0) # mct - self.assertEqual(c.segment[3].spcod[4], 3) # layers - self.assertEqual(tuple(c.segment[3].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcod[7] & 0x0020) - self.assertEqual(c.segment[3].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # SOT: start of tile part - self.assertEqual(c.segment[4].isot, 0) - self.assertEqual(c.segment[4].psot, 7314) - self.assertEqual(c.segment[4].tpsot, 0) - self.assertEqual(c.segment[4].tnsot, 1) - - def test_NR_p0_02_dump(self): - jfile = opj_data_file('input/conformance/p0_02.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 127) - self.assertEqual(c.segment[1].ysiz, 126) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (127, 126)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(2, 1)]) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) # sop - self.assertTrue(c.segment[2].scod & 4) # eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 6) # layers = 6 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 3) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertTrue(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertTrue(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - - # COC: Coding style component - self.assertEqual(c.segment[3].ccoc, 0) - self.assertEqual(c.segment[3].spcoc[0], 3) # levels - self.assertEqual(tuple(c.segment[3].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[3].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcoc[3] & 0x08) - # Predictable termination - self.assertTrue(c.segment[3].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertTrue(c.segment[3].spcoc[3] & 0x0020) - self.assertEqual(c.segment[3].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[4].guard_bits, 3) - self.assertEqual(c.segment[4].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - self.assertEqual(c.segment[4].mantissa, - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - - # COM: comment - # Registration - self.assertEqual(c.segment[5].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[5].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # One unknown marker - self.assertEqual(c.segment[6].marker_id, '0xff30') - - # SOT: start of tile part - self.assertEqual(c.segment[7].isot, 0) - self.assertEqual(c.segment[7].psot, 6047) - self.assertEqual(c.segment[7].tpsot, 0) - self.assertEqual(c.segment[7].tnsot, 1) - - # SOD: start of data - # Just one. - self.assertEqual(c.segment[8].marker_id, 'SOD') - - # SOP, EPH - sop = [x.marker_id for x in c.segment if x.marker_id == 'SOP'] - eph = [x.marker_id for x in c.segment if x.marker_id == 'EPH'] - self.assertEqual(len(sop), 24) - self.assertEqual(len(eph), 24) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_p0_03_dump(self): - jfile = opj_data_file('input/conformance/p0_03.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 256) - self.assertEqual(c.segment[1].ysiz, 256) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (4,)) - # signed - self.assertEqual(c.segment[1].signed, (True,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) - self.assertEqual(c.segment[2].layers, 8) # 8 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 1) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 1) # scalar implicit - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].exponent, [0]) - self.assertEqual(c.segment[3].mantissa, [0]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[4].cqcc, 0) - self.assertEqual(c.segment[4].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[4].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[4].exponent, [4, 5, 5, 6]) - self.assertEqual(c.segment[4].mantissa, [0, 0, 0, 0]) - - # POD: progression order change - self.assertEqual(c.segment[5].rspod, (0,)) - self.assertEqual(c.segment[5].cspod, (0,)) - self.assertEqual(c.segment[5].lyepod, (8,)) - self.assertEqual(c.segment[5].repod, (33,)) - self.assertEqual(c.segment[5].cdpod, (255,)) - self.assertEqual(c.segment[5].ppod, (glymur.core.LRCP,)) - - # CRG: component registration - self.assertEqual(c.segment[6].xcrg, (65424,)) - self.assertEqual(c.segment[6].ycrg, (32558,)) - - # COM: comment - # Registration - self.assertEqual(c.segment[7].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[7].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # COM: comment - # Registration - self.assertEqual(c.segment[8].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[8].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000," - + "2001 Algo Vision Technology") - - # COM: comment - # Registration - self.assertEqual(c.segment[9].rcme, glymur.core.RCME_BINARY) - # Comment value - self.assertEqual(len(c.segment[9].ccme), 62) - - # TLM (tile-part length) - self.assertEqual(c.segment[10].ztlm, 0) - self.assertEqual(c.segment[10].ttlm, (0, 1, 2, 3)) - self.assertEqual(c.segment[10].ptlm, (4267, 2117, 4080, 2081)) - - # SOT: start of tile part - self.assertEqual(c.segment[11].isot, 0) - self.assertEqual(c.segment[11].psot, 4267) - self.assertEqual(c.segment[11].tpsot, 0) - self.assertEqual(c.segment[11].tnsot, 1) - - # RGN: region of interest - self.assertEqual(c.segment[12].crgn, 0) - self.assertEqual(c.segment[12].srgn, 0) - self.assertEqual(c.segment[12].sprgn, 7) - - # SOD: start of data - # Just one. - self.assertEqual(c.segment[13].marker_id, 'SOD') - - def test_NR_p0_04_dump(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 640) - self.assertEqual(c.segment[1].ysiz, 480) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (640, 480)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (1, 1), (1, 1)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 20) # 20 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 6) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(c.segment[2].precinct_size, - [(128, 128), (128, 128), (128, 128), (128, 128), - (128, 128), (128, 128), (128, 128)]) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # scalar expounded - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].exponent, - [16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, - 11, 11, 11, 11, 11, 11]) - self.assertEqual(c.segment[3].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, 1845, - 1868, 1925, 1925, 2007, 32, 32, 131, 2002, 2002, - 1888]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[4].cqcc, 1) - # quantization type - self.assertEqual(c.segment[4].sqcc & 0x1f, 2) # none - self.assertEqual(c.segment[4].guard_bits, 3) - self.assertEqual(c.segment[4].exponent, - [14, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, - 9, 9, 9, 9, 9, 9]) - self.assertEqual(c.segment[4].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, - 1845, 1868, 1925, 1925, 2007, 32, 32, 131, 2002, - 2002, 1888]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 2) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 2) # none - self.assertEqual(c.segment[5].guard_bits, 3) - self.assertEqual(c.segment[5].exponent, - [14, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, - 9, 9, 9, 9, 9, 9]) - self.assertEqual(c.segment[5].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, - 1845, 1868, 1925, 1925, 2007, 32, 32, 131, 2002, - 2002, 1888]) - - # COM: comment - # Registration - self.assertEqual(c.segment[6].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[6].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # SOT: start of tile part - self.assertEqual(c.segment[7].isot, 0) - self.assertEqual(c.segment[7].psot, 264383) - self.assertEqual(c.segment[7].tpsot, 0) - self.assertEqual(c.segment[7].tnsot, 1) - - # SOD: start of data - # Just one. - self.assertEqual(c.segment[8].marker_id, 'SOD') - - def test_NR_p0_05_dump(self): - jfile = opj_data_file('input/conformance/p0_05.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1024) - self.assertEqual(c.segment[1].ysiz, 1024) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1024, 1024)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (1, 1), (2, 2), (2, 2)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) - self.assertEqual(c.segment[2].layers, 7) # 7 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 6) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # COC: Coding style component - self.assertEqual(c.segment[3].ccoc, 1) - self.assertEqual(c.segment[3].spcoc[0], 3) # levels - self.assertEqual(tuple(c.segment[3].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcoc[3] & 0x0020) - self.assertEqual(c.segment[3].spcoc[4], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - - # COC: Coding style component - self.assertEqual(c.segment[4].ccoc, 3) - self.assertEqual(c.segment[4].spcoc[0], 6) # levels - self.assertEqual(tuple(c.segment[4].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[4].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[4].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[4].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[4].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[4].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[4].spcoc[3] & 0x0020) - self.assertEqual(c.segment[4].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[5].sqcd & 0x1f, 2) # scalar expounded - self.assertEqual(c.segment[5].guard_bits, 3) - self.assertEqual(c.segment[5].exponent, - [16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, - 11, 11, 11, 11, 11, 11]) - self.assertEqual(c.segment[5].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, - 1845, 1868, 1925, 1925, 2007, 32, 32, 131, 2002, - 2002, 1888]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 0) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 1) # scalar derived - self.assertEqual(c.segment[6].guard_bits, 3) - self.assertEqual(c.segment[6].exponent, [14]) - self.assertEqual(c.segment[6].mantissa, [0]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[7].cqcc, 3) - # quantization type - self.assertEqual(c.segment[7].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[7].guard_bits, 3) - self.assertEqual(c.segment[7].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, - 9, 9, 10]) - self.assertEqual(c.segment[7].mantissa, [0] * 19) - - # COM: comment - # Registration - self.assertEqual(c.segment[8].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[8].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # TLM (tile-part length) - self.assertEqual(c.segment[9].ztlm, 0) - self.assertEqual(c.segment[9].ttlm, (0,)) - self.assertEqual(c.segment[9].ptlm, (1310540,)) - - # SOT: start of tile part - self.assertEqual(c.segment[10].isot, 0) - self.assertEqual(c.segment[10].psot, 1310540) - self.assertEqual(c.segment[10].tpsot, 0) - self.assertEqual(c.segment[10].tnsot, 1) - - # SOD: start of data - # Just one. - self.assertEqual(c.segment[11].marker_id, 'SOD') - - def test_NR_p0_06_dump(self): - jfile = opj_data_file('input/conformance/p0_06.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 513) - self.assertEqual(c.segment[1].ysiz, 129) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (513, 129)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12, 12, 12, 12)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (2, 1), (1, 2), (2, 2)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.RPCL) - self.assertEqual(c.segment[2].layers, 4) # 4 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 6) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # scalar expounded - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].mantissa, - [512, 518, 522, 524, 516, 524, 522, 527, 523, 549, - 557, 561, 853, 852, 700, 163, 78, 1508, 1831]) - self.assertEqual(c.segment[3].exponent, - [7, 7, 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 2, 1, 2, - 1]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[4].cqcc, 1) - # quantization type - self.assertEqual(c.segment[4].sqcc & 0x1f, 2) # scalar derived - self.assertEqual(c.segment[4].guard_bits, 4) - self.assertEqual(c.segment[4].mantissa, - [1527, 489, 665, 506, 487, 502, 493, 493, 500, 485, - 505, 491, 490, 491, 499, 509, 503, 496, 558]) - self.assertEqual(c.segment[4].exponent, - [10, 10, 10, 10, 9, 9, 9, 8, 8, 8, 7, 7, 7, 6, 6, 6, - 5, 5, 5]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 2) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 2) # scalar derived - self.assertEqual(c.segment[5].guard_bits, 5) - self.assertEqual(c.segment[5].mantissa, - [1337, 728, 890, 719, 716, 726, 700, 718, 704, 704, - 712, 712, 717, 719, 701, 749, 753, 718, 841]) - self.assertEqual(c.segment[5].exponent, - [10, 10, 10, 10, 9, 9, 9, 8, 8, 8, 7, 7, 7, 6, 6, 6, - 5, 5, 5]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 3) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[6].guard_bits, 6) - self.assertEqual(c.segment[6].mantissa, [0] * 19) - self.assertEqual(c.segment[6].exponent, - [12, 13, 13, 14, 13, 13, 14, 13, 13, 14, 13, 13, 14, - 13, 13, 14, 13, 13, 14]) - - # COC: Coding style component - self.assertEqual(c.segment[7].ccoc, 3) - self.assertEqual(c.segment[7].spcoc[0], 6) # levels - self.assertEqual(tuple(c.segment[7].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[7].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[7].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[7].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[7].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[7].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[7].spcoc[3] & 0x0020) - self.assertEqual(c.segment[7].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # RGN: region of interest - self.assertEqual(c.segment[8].crgn, 0) # component - self.assertEqual(c.segment[8].srgn, 0) # implicit - self.assertEqual(c.segment[8].sprgn, 11) - - # SOT: start of tile part - self.assertEqual(c.segment[9].isot, 0) - self.assertEqual(c.segment[9].psot, 33582) - self.assertEqual(c.segment[9].tpsot, 0) - self.assertEqual(c.segment[9].tnsot, 1) - - # RGN: region of interest - self.assertEqual(c.segment[10].crgn, 0) # component - self.assertEqual(c.segment[10].srgn, 0) # implicit - self.assertEqual(c.segment[10].sprgn, 9) - - # SOD: start of data - # Just one. - self.assertEqual(c.segment[11].marker_id, 'SOD') - - def test_NR_p0_07_dump(self): - jfile = opj_data_file('input/conformance/p0_07.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 2048) - self.assertEqual(c.segment[1].ysiz, 2048) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12, 12, 12)) - # signed - self.assertEqual(c.segment[1].signed, (True, True, True)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (1, 1), (1, 1)]) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) - self.assertTrue(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 8) # 8 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 3) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, [0] * 10) - self.assertEqual(c.segment[3].exponent, - [14, 15, 15, 16, 15, 15, 16, 15, 15, 16]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Kakadu-3.0.7") - - # SOT: start of tile part - self.assertEqual(c.segment[5].isot, 0) - self.assertEqual(c.segment[5].psot, 9951) - self.assertEqual(c.segment[5].tpsot, 0) - self.assertEqual(c.segment[5].tnsot, 0) # unknown - - # POD: progression order change - self.assertEqual(c.segment[6].rspod, (0,)) - self.assertEqual(c.segment[6].cspod, (0,)) - self.assertEqual(c.segment[6].lyepod, (9,)) - self.assertEqual(c.segment[6].repod, (3,)) - self.assertEqual(c.segment[6].cdpod, (3,)) - self.assertEqual(c.segment[6].ppod, (glymur.core.LRCP,)) - - # PLT: packet length, tile part - self.assertEqual(c.segment[7].zplt, 0) - #self.assertEqual(c.segment[7].iplt), 99) - - # SOD: start of data - self.assertEqual(c.segment[8].marker_id, 'SOD') - - def test_NR_p0_08_dump(self): - jfile = opj_data_file('input/conformance/p0_08.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 513) - self.assertEqual(c.segment[1].ysiz, 3072) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (513, 3072)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12, 12, 12)) - # signed - self.assertEqual(c.segment[1].signed, (True, True, True)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (1, 1), (1, 1)]) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) - self.assertTrue(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.CPRL) - self.assertEqual(c.segment[2].layers, 30) # 30 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 7) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # COC: Coding style component - self.assertEqual(c.segment[3].ccoc, 0) - self.assertEqual(c.segment[3].spcoc[0], 6) # levels - self.assertEqual(tuple(c.segment[3].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcoc[3] & 0x0020) - self.assertEqual(c.segment[3].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # COC: Coding style component - self.assertEqual(c.segment[4].ccoc, 1) - self.assertEqual(c.segment[4].spcoc[0], 7) # levels - self.assertEqual(tuple(c.segment[4].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[4].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[4].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[4].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[4].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[4].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[4].spcoc[3] & 0x0020) - self.assertEqual(c.segment[4].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # COC: Coding style component - self.assertEqual(c.segment[5].ccoc, 2) - self.assertEqual(c.segment[5].spcoc[0], 8) # levels - self.assertEqual(tuple(c.segment[5].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[5].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[5].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[5].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[5].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[5].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[5].spcoc[3] & 0x0020) - self.assertEqual(c.segment[5].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[6].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[6].guard_bits, 4) - self.assertEqual(c.segment[6].mantissa, [0] * 22) - self.assertEqual(c.segment[6].exponent, - [11, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, - 12, 12, 13, 12, 12, 13, 12, 12, 13]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[7].cqcc, 0) - # quantization type - self.assertEqual(c.segment[7].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[7].guard_bits, 4) - self.assertEqual(c.segment[7].mantissa, [0] * 19) - self.assertEqual(c.segment[7].exponent, - [11, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, - 12, 12, 13, 12, 12, 13]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[8].cqcc, 2) - # quantization type - self.assertEqual(c.segment[8].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[8].guard_bits, 4) - self.assertEqual(c.segment[8].mantissa, [0] * 25) - self.assertEqual(c.segment[8].exponent, - [11, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, - 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13]) - - # COM: comment - # Registration - self.assertEqual(c.segment[9].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[9].ccme.decode('latin-1'), - "Kakadu-3.0.7") - - # SOT: start of tile part - self.assertEqual(c.segment[10].isot, 0) - self.assertEqual(c.segment[10].psot, 3820593) - self.assertEqual(c.segment[10].tpsot, 0) - self.assertEqual(c.segment[10].tnsot, 1) # unknown - - def test_NR_p0_09_dump(self): - jfile = opj_data_file('input/conformance/p0_09.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "0" means profile 2, or full capabilities - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 17) - self.assertEqual(c.segment[1].ysiz, 37) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (17, 37)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # scalar expounded - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, - [1915, 1884, 1884, 1853, 1884, 1884, 1853, 1962, 1962, - 1986, 53, 53, 120, 26, 26, 1983]) - self.assertEqual(c.segment[3].exponent, - [16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 12, 12, 12, - 11, 11, 12]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Kakadu-3.0.7") - - # SOT: start of tile part - self.assertEqual(c.segment[5].isot, 0) - self.assertEqual(c.segment[5].psot, 478) - self.assertEqual(c.segment[5].tpsot, 0) - self.assertEqual(c.segment[5].tnsot, 1) # unknown - - # SOD: start of data - # Just one. - self.assertEqual(c.segment[6].marker_id, 'SOD') - - # EOC: end of codestream - self.assertEqual(c.segment[7].marker_id, 'EOC') - - def test_NR_p0_10_dump(self): - jfile = opj_data_file('input/conformance/p0_10.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 256) - self.assertEqual(c.segment[1].ysiz, 256) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(4, 4), (4, 4), (4, 4)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 2) # 2 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 3) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[3].guard_bits, 0) - self.assertEqual(c.segment[3].mantissa, [0] * 10) - self.assertEqual(c.segment[3].exponent, - [11, 12, 12, 13, 12, 12, 13, 12, 12, 13]) - - # SOT: start of tile part - self.assertEqual(c.segment[4].isot, 0) - self.assertEqual(c.segment[4].psot, 2453) - self.assertEqual(c.segment[4].tpsot, 0) - self.assertEqual(c.segment[4].tnsot, 0) - - # SOD: start of data - self.assertEqual(c.segment[5].marker_id, 'SOD') - - # SOT: start of tile part - self.assertEqual(c.segment[6].isot, 1) - self.assertEqual(c.segment[6].psot, 2403) - self.assertEqual(c.segment[6].tpsot, 0) - self.assertEqual(c.segment[6].tnsot, 0) - - # SOD: start of data - self.assertEqual(c.segment[7].marker_id, 'SOD') - - # SOT: start of tile part - self.assertEqual(c.segment[8].isot, 2) - self.assertEqual(c.segment[8].psot, 2420) - self.assertEqual(c.segment[8].tpsot, 0) - self.assertEqual(c.segment[8].tnsot, 0) - - # SOD: start of data - self.assertEqual(c.segment[9].marker_id, 'SOD') - - # SOT: start of tile part - self.assertEqual(c.segment[10].isot, 3) - self.assertEqual(c.segment[10].psot, 2472) - self.assertEqual(c.segment[10].tpsot, 0) - self.assertEqual(c.segment[10].tnsot, 0) - - # SOD: start of data - self.assertEqual(c.segment[11].marker_id, 'SOD') - - # SOT: start of tile part - self.assertEqual(c.segment[12].isot, 0) - self.assertEqual(c.segment[12].psot, 1043) - self.assertEqual(c.segment[12].tpsot, 1) - self.assertEqual(c.segment[12].tnsot, 2) - - # SOD: start of data - self.assertEqual(c.segment[13].marker_id, 'SOD') - - # SOT: start of tile part - self.assertEqual(c.segment[14].isot, 1) - self.assertEqual(c.segment[14].psot, 1101) - self.assertEqual(c.segment[14].tpsot, 1) - self.assertEqual(c.segment[14].tnsot, 2) - - # SOD: start of data - self.assertEqual(c.segment[15].marker_id, 'SOD') - - # SOT: start of tile part - self.assertEqual(c.segment[16].isot, 3) - self.assertEqual(c.segment[16].psot, 1054) - self.assertEqual(c.segment[16].tpsot, 1) - self.assertEqual(c.segment[16].tnsot, 2) - - # SOD: start of data - self.assertEqual(c.segment[17].marker_id, 'SOD') - - # SOT: start of tile part - self.assertEqual(c.segment[18].isot, 2) - self.assertEqual(c.segment[18].psot, 14) - self.assertEqual(c.segment[18].tpsot, 1) - self.assertEqual(c.segment[18].tnsot, 0) - - # SOD: start of data - self.assertEqual(c.segment[19].marker_id, 'SOD') - - # SOT: start of tile part - self.assertEqual(c.segment[20].isot, 2) - self.assertEqual(c.segment[20].psot, 1089) - self.assertEqual(c.segment[20].tpsot, 2) - self.assertEqual(c.segment[20].tnsot, 0) - - # SOD: start of data - self.assertEqual(c.segment[21].marker_id, 'SOD') - - # EOC: end of codestream - self.assertEqual(c.segment[22].marker_id, 'EOC') - - def test_NR_p0_11_dump(self): - jfile = opj_data_file('input/conformance/p0_11.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 128) - self.assertEqual(c.segment[1].ysiz, 1) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertTrue(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 0) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertTrue(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(c.segment[2].precinct_size, [(128, 2)]) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].mantissa, [0]) - self.assertEqual(c.segment[3].exponent, [8]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # SOT: start of tile part - self.assertEqual(c.segment[5].isot, 0) - self.assertEqual(c.segment[5].psot, 118) - self.assertEqual(c.segment[5].tpsot, 0) - self.assertEqual(c.segment[5].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[6].marker_id, 'SOD') - - # SOP, EPH - sop = [x.marker_id for x in c.segment if x.marker_id == 'SOP'] - eph = [x.marker_id for x in c.segment if x.marker_id == 'EPH'] - self.assertEqual(len(sop), 0) - self.assertEqual(len(eph), 1) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_p0_12_dump(self): - jfile = opj_data_file('input/conformance/p0_12.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 3) - self.assertEqual(c.segment[1].ysiz, 5) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (3, 5)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 3) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].mantissa, [0] * 10) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # SOT: start of tile part - self.assertEqual(c.segment[5].isot, 0) - self.assertEqual(c.segment[5].psot, 162) - self.assertEqual(c.segment[5].tpsot, 0) - self.assertEqual(c.segment[5].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[6].marker_id, 'SOD') - - # SOP, EPH - sop = [x.marker_id for x in c.segment if x.marker_id == 'SOP'] - eph = [x.marker_id for x in c.segment if x.marker_id == 'EPH'] - self.assertEqual(len(sop), 4) - self.assertEqual(len(eph), 0) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_p0_13_dump(self): - jfile = opj_data_file('input/conformance/p0_13.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1) - self.assertEqual(c.segment[1].ysiz, 1) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (1, 1)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, tuple([8] * 257)) - # signed - self.assertEqual(c.segment[1].signed, tuple([False] * 257)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 257) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 1) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertTrue(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # COC: Coding style component - self.assertEqual(c.segment[3].ccoc, 2) - self.assertEqual(c.segment[3].spcoc[0], 1) # levels - self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcoc[3] & 0x0020) - self.assertEqual(c.segment[3].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[4].guard_bits, 2) - self.assertEqual(c.segment[4].mantissa, [0] * 4) - self.assertEqual(c.segment[4].exponent, - [8, 9, 9, 10]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 1) - self.assertEqual(c.segment[5].guard_bits, 3) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[5].exponent, [9, 10, 10, 11]) - self.assertEqual(c.segment[5].mantissa, [0, 0, 0, 0]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 2) - self.assertEqual(c.segment[6].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[6].exponent, [9, 10, 10, 11]) - self.assertEqual(c.segment[6].mantissa, [0, 0, 0, 0]) - - # RGN: region of interest - self.assertEqual(c.segment[7].crgn, 3) - self.assertEqual(c.segment[7].srgn, 0) - self.assertEqual(c.segment[7].sprgn, 11) - - # POD: progression order change - self.assertEqual(c.segment[8].rspod, (0, 0)) - self.assertEqual(c.segment[8].cspod, (0, 128)) - self.assertEqual(c.segment[8].lyepod, (1, 1)) - self.assertEqual(c.segment[8].repod, (33, 33)) - self.assertEqual(c.segment[8].cdpod, (128, 257)) - self.assertEqual(c.segment[8].ppod, - (glymur.core.RLCP, glymur.core.CPRL)) - - # COM: comment - # Registration - self.assertEqual(c.segment[9].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[9].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # SOT: start of tile part - self.assertEqual(c.segment[10].isot, 0) - self.assertEqual(c.segment[10].psot, 1537) - self.assertEqual(c.segment[10].tpsot, 0) - self.assertEqual(c.segment[10].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[11].marker_id, 'SOD') - - # EOC: end of codestream - self.assertEqual(c.segment[12].marker_id, 'EOC') - - def test_NR_p0_14_dump(self): - jfile = opj_data_file('input/conformance/p0_14.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "0" means profile 2 - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 49) - self.assertEqual(c.segment[1].ysiz, 49) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (49, 49)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # 1 layer - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [10, 11, 11, 12, 11, 11, 12, 11, 11, 12, 11, 11, 12, - 11, 11, 12]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Kakadu-3.0.7") - - # SOT: start of tile part - self.assertEqual(c.segment[5].isot, 0) - self.assertEqual(c.segment[5].psot, 1528) - self.assertEqual(c.segment[5].tpsot, 0) - self.assertEqual(c.segment[5].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[6].marker_id, 'SOD') - - # EOC: end of codestream - self.assertEqual(c.segment[7].marker_id, 'EOC') - - def test_NR_p0_15_dump(self): - jfile = opj_data_file('input/conformance/p0_15.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 256) - self.assertEqual(c.segment[1].ysiz, 256) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (4,)) - # signed - self.assertEqual(c.segment[1].signed, (True,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) - self.assertEqual(c.segment[2].layers, 8) # layers = 8 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 1) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 1) # derived - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0]) - self.assertEqual(c.segment[3].exponent, [0]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[4].cqcc, 0) - self.assertEqual(c.segment[4].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[4].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[4].mantissa, [0] * 4) - self.assertEqual(c.segment[4].exponent, [4, 5, 5, 6]) - - # POD: progression order change - self.assertEqual(c.segment[5].rspod, (0,)) - self.assertEqual(c.segment[5].cspod, (0,)) - self.assertEqual(c.segment[5].lyepod, (8,)) - self.assertEqual(c.segment[5].repod, (33,)) - self.assertEqual(c.segment[5].cdpod, (255,)) - self.assertEqual(c.segment[5].ppod, (glymur.core.LRCP,)) - - # CRG: component registration - self.assertEqual(c.segment[6].xcrg, (65424,)) - self.assertEqual(c.segment[6].ycrg, (32558,)) - - # COM: comment - # Registration - self.assertEqual(c.segment[7].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[7].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # COM: comment - # Registration - self.assertEqual(c.segment[8].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[8].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000," - + "2001 Algo Vision Technology") - - # COM: comment - # Registration - self.assertEqual(c.segment[9].rcme, glymur.core.RCME_BINARY) - # Comment value - self.assertEqual(len(c.segment[9].ccme), 62) - - # TLM: tile-part length - self.assertEqual(c.segment[10].ztlm, 0) - self.assertEqual(c.segment[10].ttlm, (0, 1, 2, 3)) - self.assertEqual(c.segment[10].ptlm, (4267, 2117, 4080, 2081)) - - # SOT: start of tile part - self.assertEqual(c.segment[11].isot, 0) - self.assertEqual(c.segment[11].psot, 4267) - self.assertEqual(c.segment[11].tpsot, 0) - self.assertEqual(c.segment[11].tnsot, 1) - - # RGN: region of interest - self.assertEqual(c.segment[12].crgn, 0) - self.assertEqual(c.segment[12].srgn, 0) - self.assertEqual(c.segment[12].sprgn, 7) - - # SOD: start of data - self.assertEqual(c.segment[13].marker_id, 'SOD') - - # 16 SOP markers would be here if we were looking for them - - # SOT: start of tile part - self.assertEqual(c.segment[31].isot, 1) - self.assertEqual(c.segment[31].psot, 2117) - self.assertEqual(c.segment[31].tpsot, 0) - self.assertEqual(c.segment[31].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[32].marker_id, 'SOD') - - # 16 SOP markers would be here if we were looking for them - - # SOT: start of tile part - self.assertEqual(c.segment[49].isot, 2) - self.assertEqual(c.segment[49].psot, 4080) - self.assertEqual(c.segment[49].tpsot, 0) - self.assertEqual(c.segment[49].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[50].marker_id, 'SOD') - - # 16 SOP markers would be here if we were looking for them - - # SOT: start of tile part - self.assertEqual(c.segment[67].isot, 3) - self.assertEqual(c.segment[67].psot, 2081) - self.assertEqual(c.segment[67].tpsot, 0) - self.assertEqual(c.segment[67].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[68].marker_id, 'SOD') - - # 16 SOP markers would be here if we were looking for them - - # EOC: end of codestream - self.assertEqual(c.segment[85].marker_id, 'EOC') - - def test_NR_p0_16_dump(self): - jfile = opj_data_file('input/conformance/p0_16.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "0" means profile 2 - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 128) - self.assertEqual(c.segment[1].ysiz, 128) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 3) # layers = 3 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 3) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 10) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - # SOT: start of tile part - self.assertEqual(c.segment[4].isot, 0) - self.assertEqual(c.segment[4].psot, 7331) - self.assertEqual(c.segment[4].tpsot, 0) - self.assertEqual(c.segment[4].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[5].marker_id, 'SOD') - - # EOC: end of codestream - self.assertEqual(c.segment[6].marker_id, 'EOC') - - def test_NR_p1_01_dump(self): - jfile = opj_data_file('input/conformance/p1_01.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 1 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 127) - self.assertEqual(c.segment[1].ysiz, 227) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (5, 128)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (127, 126)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (1, 101)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(2, 1)]) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) # SOP - self.assertTrue(c.segment[2].scod & 4) # EPH - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 5) # layers = 5 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 3) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertTrue(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertTrue(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # COC: Coding style component - self.assertEqual(c.segment[3].ccoc, 0) - self.assertEqual(c.segment[3].spcoc[0], 3) # level - self.assertEqual(tuple(c.segment[3].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[3].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcoc[3] & 0x08) - # Predictable termination - self.assertTrue(c.segment[3].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertTrue(c.segment[3].spcoc[3] & 0x0020) - self.assertEqual(c.segment[3].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[4].guard_bits, 3) - self.assertEqual(c.segment[4].mantissa, [0] * 10) - self.assertEqual(c.segment[4].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - # COM: comment - # Registration - self.assertEqual(c.segment[5].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[5].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # SOT: start of tile part - self.assertEqual(c.segment[6].isot, 0) - self.assertEqual(c.segment[6].psot, 4627) - self.assertEqual(c.segment[6].tpsot, 0) - self.assertEqual(c.segment[6].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[7].marker_id, 'SOD') - - # SOP, EPH - sop = [x.marker_id for x in c.segment if x.marker_id == 'SOP'] - eph = [x.marker_id for x in c.segment if x.marker_id == 'EPH'] - self.assertEqual(len(sop), 20) - self.assertEqual(len(eph), 20) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_p1_02_dump(self): - jfile = opj_data_file('input/conformance/p1_02.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 1 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 640) - self.assertEqual(c.segment[1].ysiz, 480) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (640, 480)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, tuple([8] * 3)) - # signed - self.assertEqual(c.segment[1].signed, tuple([False] * 3)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 19) # layers = 19 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 6) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertTrue(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertTrue(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(c.segment[2].precinct_size, - [(128, 128), (256, 256), (512, 512), (1024, 1024), - (2048, 2048), (4096, 4096), (8192, 8192)]) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # expounded - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, 1845, - 1868, 1925, 1925, 2007, 32, 32, 131, 2002, 2002, - 1888]) - self.assertEqual(c.segment[3].exponent, - [16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, - 11, 11, 11, 11, 11, 11]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[4].cqcc, 1) - self.assertEqual(c.segment[4].guard_bits, 3) - # quantization type - self.assertEqual(c.segment[4].sqcc & 0x1f, 2) # expounded - self.assertEqual(c.segment[4].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, 1845, - 1868, 1925, 1925, 2007, 32, 32, 131, 2002, 2002, - 1888]) - self.assertEqual(c.segment[4].exponent, - [14, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, - 9, 9, 9, 9, 9, 9]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 2) - self.assertEqual(c.segment[5].guard_bits, 3) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 2) # expounded - self.assertEqual(c.segment[5].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, 1845, - 1868, 1925, 1925, 2007, 32, 32, 131, 2002, 2002, - 1888]) - self.assertEqual(c.segment[5].exponent, - [14, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, - 9, 9, 9, 9, 9, 9]) - - # COM: comment - # Registration - self.assertEqual(c.segment[6].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[6].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # SOT: start of tile part - self.assertEqual(c.segment[7].isot, 0) - self.assertEqual(c.segment[7].psot, 262838) - self.assertEqual(c.segment[7].tpsot, 0) - self.assertEqual(c.segment[7].tnsot, 1) - - # PPT: packed packet headers, tile-part header - self.assertEqual(c.segment[8].marker_id, 'PPT') - self.assertEqual(c.segment[8].zppt, 0) - - # SOD: start of data - self.assertEqual(c.segment[9].marker_id, 'SOD') - - # EOC: end of codestream - self.assertEqual(c.segment[10].marker_id, 'EOC') - - def test_NR_p1_03_dump(self): - jfile = opj_data_file('input/conformance/p1_03.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 1 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1024) - self.assertEqual(c.segment[1].ysiz, 1024) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1024, 1024)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, tuple([8] * 4)) - # signed - self.assertEqual(c.segment[1].signed, tuple([False] * 4)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (1, 1), (2, 2), (2, 2)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) - self.assertEqual(c.segment[2].layers, 10) # layers = 10 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 6) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertTrue(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # COC: Coding style component - self.assertEqual(c.segment[3].ccoc, 1) - self.assertEqual(c.segment[3].spcoc[0], 3) # level - self.assertEqual(tuple(c.segment[3].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertTrue(c.segment[3].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[3].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcoc[3] & 0x0020) - self.assertEqual(c.segment[3].spcoc[4], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - - # COC: Coding style component - self.assertEqual(c.segment[4].ccoc, 3) - self.assertEqual(c.segment[4].spcoc[0], 6) # level - self.assertEqual(tuple(c.segment[4].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertTrue(c.segment[4].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[4].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[4].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[4].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[4].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[4].spcoc[3] & 0x0020) - self.assertEqual(c.segment[4].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[5].sqcd & 0x1f, 2) # expounded - self.assertEqual(c.segment[5].guard_bits, 3) - self.assertEqual(c.segment[5].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, 1845, - 1868, 1925, 1925, 2007, 32, 32, 131, 2002, 2002, - 1888]) - self.assertEqual(c.segment[5].exponent, - [16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, - 11, 11, 11, 11, 11, 11]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 0) - self.assertEqual(c.segment[6].guard_bits, 3) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 1) # derived - self.assertEqual(c.segment[6].mantissa, [0]) - self.assertEqual(c.segment[6].exponent, [14]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[7].cqcc, 3) - self.assertEqual(c.segment[7].guard_bits, 3) - # quantization type - self.assertEqual(c.segment[7].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[7].mantissa, [0] * 19) - self.assertEqual(c.segment[7].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, - 9, 9, 10]) - - # COM: comment - # Registration - self.assertEqual(c.segment[8].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[8].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # PPM: packed packet headers, main header - self.assertEqual(c.segment[9].marker_id, 'PPM') - self.assertEqual(c.segment[9].zppm, 0) - - # TLM (tile-part length) - self.assertEqual(c.segment[10].ztlm, 0) - self.assertEqual(c.segment[10].ttlm, (0,)) - self.assertEqual(c.segment[10].ptlm, (1366780,)) - - # SOT: start of tile part - self.assertEqual(c.segment[11].isot, 0) - self.assertEqual(c.segment[11].psot, 1366780) - self.assertEqual(c.segment[11].tpsot, 0) - self.assertEqual(c.segment[11].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[12].marker_id, 'SOD') - - # EOC: end of codestream - self.assertEqual(c.segment[13].marker_id, 'EOC') - - def test_NR_p1_04_dump(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 1 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1024) - self.assertEqual(c.segment[1].ysiz, 1024) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 3) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # expounded - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, - [84, 423, 408, 435, 450, 435, 470, 549, 520, 618]) - self.assertEqual(c.segment[3].exponent, - [8, 10, 10, 10, 9, 9, 9, 8, 8, 8]) - - # TLM (tile-part length) - self.assertEqual(c.segment[4].ztlm, 0) - self.assertIsNone(c.segment[4].ttlm) - self.assertEqual(c.segment[4].ptlm, - (350, 356, 402, 245, 402, 564, 675, 283, 317, 299, - 330, 333, 346, 403, 839, 667, 328, 349, 274, 325, - 501, 561, 756, 710, 779, 620, 628, 675, 600, 66195, - 721, 719, 565, 565, 546, 586, 574, 641, 713, 634, - 573, 528, 544, 597, 771, 665, 624, 706, 568, 537, - 554, 546, 542, 635, 826, 667, 617, 606, 813, 586, - 641, 654, 669, 623)) - - # COM: comment - # Registration - self.assertEqual(c.segment[5].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[5].ccme.decode('latin-1'), - "Created by Aware, Inc.") - - # SOT: start of tile part - self.assertEqual(c.segment[6].isot, 0) - self.assertEqual(c.segment[6].psot, 350) - self.assertEqual(c.segment[6].tpsot, 0) - self.assertEqual(c.segment[6].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[7].marker_id, 'SOD') - - # SOT: start of tile part - self.assertEqual(c.segment[8].isot, 1) - self.assertEqual(c.segment[8].psot, 356) - self.assertEqual(c.segment[8].tpsot, 0) - self.assertEqual(c.segment[8].tnsot, 1) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[9].sqcd & 0x1f, 2) # expounded - self.assertEqual(c.segment[9].guard_bits, 2) - self.assertEqual(c.segment[9].mantissa, - [75, 1093, 1098, 1115, 1157, 1134, 1186, 1217, 1245, - 1248]) - self.assertEqual(c.segment[9].exponent, - [8, 10, 10, 10, 9, 9, 9, 8, 8, 8]) - - # SOD: start of data - self.assertEqual(c.segment[10].marker_id, 'SOD') - - # SOT: start of tile part - self.assertEqual(c.segment[11].isot, 2) - self.assertEqual(c.segment[11].psot, 402) - self.assertEqual(c.segment[11].tpsot, 0) - self.assertEqual(c.segment[11].tnsot, 1) - - # and so on - - # There should be 64 SOD, SOT, QCD segments. - ids = [x.marker_id for x in c.segment if x.marker_id == 'SOT'] - self.assertEqual(len(ids), 64) - ids = [x.marker_id for x in c.segment if x.marker_id == 'SOD'] - self.assertEqual(len(ids), 64) - ids = [x.marker_id for x in c.segment if x.marker_id == 'QCD'] - self.assertEqual(len(ids), 64) - - # Tiles should be in order, right? - tiles = [x.isot for x in c.segment if x.marker_id == 'SOT'] - self.assertEqual(tiles, list(range(64))) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_p1_05_dump(self): - jfile = opj_data_file('input/conformance/p1_05.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 1 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 529) - self.assertEqual(c.segment[1].ysiz, 524) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (17, 12)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (37, 37)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (8, 2)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) # sop - self.assertTrue(c.segment[2].scod & 4) # eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) - self.assertEqual(c.segment[2].layers, 2) # levels = 2 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 7) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 8)) # cblk - # Selective arithmetic coding bypass - self.assertTrue(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertTrue(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertTrue(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(c.segment[2].precinct_size, [(16, 16)] * 8) - - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # expounded - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].mantissa, - [1813, 1814, 1814, 1814, 1815, 1815, 1817, 1821, - 1821, 1827, 1845, 1845, 1868, 1925, 1925, 2007, - 32, 32, 131, 2002, 2002, 1888]) - self.assertEqual(c.segment[3].exponent, - [17, 17, 17, 17, 16, 16, 16, 15, 15, 15, 14, 14, - 14, 13, 13, 13, 11, 11, 11, 11, 11, 11]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # 225 consecutive PPM segments. - zppm = [x.zppm for x in c.segment[5:230]] - self.assertEqual(zppm, list(range(225))) - - # SOT: start of tile part - self.assertEqual(c.segment[230].isot, 0) - self.assertEqual(c.segment[230].psot, 580) - self.assertEqual(c.segment[230].tpsot, 0) - self.assertEqual(c.segment[230].tnsot, 1) - - # 225 total SOT segments - isot = [x.isot for x in c.segment if x.marker_id == 'SOT'] - self.assertEqual(isot, list(range(225))) - - # scads of SOP, EPH segments - sop = [x.marker_id for x in c.segment if x.marker_id == 'SOP'] - eph = [x.marker_id for x in c.segment if x.marker_id == 'EPH'] - self.assertEqual(len(sop), 26472) - self.assertEqual(len(eph), 0) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_p1_06_dump(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 1 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 12) - self.assertEqual(c.segment[1].ysiz, 12) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (3, 3)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) # sop - self.assertTrue(c.segment[2].scod & 4) # eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 4) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (32, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertTrue(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertTrue(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # expounded - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].mantissa, - [1821, 1845, 1845, 1868, 1925, 1925, 2007, 32, - 32, 131, 2002, 2002, 1888]) - self.assertEqual(c.segment[3].exponent, - [14, 14, 14, 14, 13, 13, 13, 11, 11, 11, - 11, 11, 11]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # SOT: start of tile part - self.assertEqual(c.segment[5].isot, 0) - self.assertEqual(c.segment[5].psot, 349) - self.assertEqual(c.segment[5].tpsot, 0) - self.assertEqual(c.segment[5].tnsot, 1) - - # PPT: packed packet headers, tile-part header - self.assertEqual(c.segment[6].marker_id, 'PPT') - self.assertEqual(c.segment[6].zppt, 0) - - # scads of SOP, EPH segments - - # 16 SOD segments - sods = [x for x in c.segment if x.marker_id == 'SOD'] - self.assertEqual(len(sods), 16) - - # 16 PPT segments - ppts = [x for x in c.segment if x.marker_id == 'PPT'] - self.assertEqual(len(ppts), 16) - - # 16 SOT segments - isots = [x.isot for x in c.segment if x.marker_id == 'SOT'] - self.assertEqual(isots, list(range(16))) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_p1_07_dump(self): - jfile = opj_data_file('input/conformance/p1_07.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 1 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 12) - self.assertEqual(c.segment[1].ysiz, 12) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (4, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (12, 12)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (4, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(4, 1), (1, 1)]) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) # sop - self.assertTrue(c.segment[2].scod & 4) # eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.RPCL) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 1) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(c.segment[2].precinct_size, [(1, 1), (2, 2)]) - - # COC: Coding style component - self.assertEqual(c.segment[3].ccoc, 1) - self.assertEqual(c.segment[3].spcoc[0], 1) # level - self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcoc[3] & 0x0020) - self.assertEqual(c.segment[3].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(c.segment[3].precinct_size, [(2, 2), (4, 4)]) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[4].guard_bits, 2) - self.assertEqual(c.segment[4].mantissa, [0] * 4) - self.assertEqual(c.segment[4].exponent, [8, 9, 9, 10]) - - # COM: comment - # Registration - self.assertEqual(c.segment[5].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[5].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # SOT: start of tile part - self.assertEqual(c.segment[6].isot, 0) - self.assertEqual(c.segment[6].psot, 434) - self.assertEqual(c.segment[6].tpsot, 0) - self.assertEqual(c.segment[6].tnsot, 1) - - # scads of SOP, EPH segments - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_file1_dump(self): - jfile = opj_data_file('input/conformance/file1.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'xml ', 'jp2h', 'xml ', - 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - - # XML box - tags = [x.tag for x in jp2.box[2].xml.getroot()] - self.assertEqual(tags, - ['{http://www.jpeg.org/jpx/1.0/xml}' - + 'GENERAL_CREATION_INFO']) - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 512) - self.assertEqual(jp2.box[3].box[0].width, 768) - self.assertEqual(jp2.box[3].box[0].num_components, 3) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[1].precedence, 0) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact ?? - self.assertEqual(jp2.box[3].box[1].colorspace, glymur.core.SRGB) - - # XML box - tags = [x.tag for x in jp2.box[4].xml.getroot()] - self.assertEqual(tags, ['{http://www.jpeg.org/jpx/1.0/xml}CAPTION', - '{http://www.jpeg.org/jpx/1.0/xml}LOCATION', - '{http://www.jpeg.org/jpx/1.0/xml}EVENT']) - - def test_NR_file2_dump(self): - jfile = opj_data_file('input/conformance/file2.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr', 'cdef']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 640) - self.assertEqual(jp2.box[2].box[0].width, 480) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact?? - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.YCC) - - # Jp2 Header - # Channel Definition - self.assertEqual(jp2.box[2].box[2].index, (0, 1, 2)) - self.assertEqual(jp2.box[2].box[2].channel_type, (0, 0, 0)) # color - self.assertEqual(jp2.box[2].box[2].association, (3, 2, 1)) # reverse - - def test_NR_file3_dump(self): - # Three 8-bit components in the sRGB-YCC colourspace, with the Cb and - # Cr components being subsampled 2x in both the horizontal and - # vertical directions. The components are stored in the standard - # order. - jfile = opj_data_file('input/conformance/file3.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 640) - self.assertEqual(jp2.box[2].box[0].width, 480) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.YCC) - - # sub-sampling - codestream = jp2.get_codestream() - self.assertEqual(codestream.segment[1].xrsiz[0], 1) - self.assertEqual(codestream.segment[1].yrsiz[0], 1) - self.assertEqual(codestream.segment[1].xrsiz[1], 2) - self.assertEqual(codestream.segment[1].yrsiz[1], 2) - self.assertEqual(codestream.segment[1].xrsiz[2], 2) - self.assertEqual(codestream.segment[1].yrsiz[2], 2) - - def test_NR_file4_dump(self): - # One 8-bit component in the sRGB-grey colourspace. - jfile = opj_data_file('input/conformance/file4.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 512) - self.assertEqual(jp2.box[2].box[0].width, 768) - self.assertEqual(jp2.box[2].box[0].num_components, 1) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact? - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.GREYSCALE) - - def test_NR_file5_dump(self): - # Three 8-bit components in the ROMM-RGB colourspace, encapsulated in a - # JP2 compatible JPX file. The components have been transformed using - # the RCT. The colourspace is specified using both a Restricted ICC - # profile and using the JPX-defined enumerated code for the ROMM-RGB - # colourspace. - jfile = opj_data_file('input/conformance/file5.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jpx ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') - self.assertEqual(jp2.box[1].compatibility_list[3], 'jpxb') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 512) - self.assertEqual(jp2.box[3].box[0].width, 768) - self.assertEqual(jp2.box[3].box[0].num_components, 3) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.RESTRICTED_ICC_PROFILE) # enumerated - self.assertEqual(jp2.box[3].box[1].precedence, 0) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 546) - self.assertIsNone(jp2.box[3].box[1].colorspace) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[2].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[2].precedence, 1) - self.assertEqual(jp2.box[3].box[2].approximation, 1) # JPX exact - self.assertIsNone(jp2.box[3].box[2].icc_profile) - self.assertEqual(jp2.box[3].box[2].colorspace, - glymur.core.ROMM_RGB) - - def test_NR_file6_dump(self): - jfile = opj_data_file('input/conformance/file6.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 512) - self.assertEqual(jp2.box[2].box[0].width, 768) - self.assertEqual(jp2.box[2].box[0].num_components, 1) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 12) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact - self.assertIsNone(jp2.box[2].box[1].icc_profile) - self.assertEqual(jp2.box[2].box[1].colorspace, - glymur.core.GREYSCALE) - - def test_NR_file7_dump(self): - # Three 16-bit components in the e-sRGB colourspace, encapsulated in a - # JP2 compatible JPX file. The components have been transformed using - # the RCT. The colourspace is specified using both a Restricted ICC - # profile and using the JPX-defined enumerated code for the e-sRGB - # colourspace. - jfile = opj_data_file('input/conformance/file7.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jpx ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') - self.assertEqual(jp2.box[1].compatibility_list[3], 'jpxb') - self.assertEqual(jp2.box[1].minor_version, 0) - - # Reader requirements talk. - # e-SRGB enumerated colourspace - self.assertTrue(60 in jp2.box[2].standard_flag) - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 640) - self.assertEqual(jp2.box[3].box[0].width, 480) - self.assertEqual(jp2.box[3].box[0].num_components, 3) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 16) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.RESTRICTED_ICC_PROFILE) - self.assertEqual(jp2.box[3].box[1].precedence, 0) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 13332) - self.assertIsNone(jp2.box[3].box[1].colorspace) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[2].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[2].precedence, 1) - self.assertEqual(jp2.box[3].box[2].approximation, 1) # JPX exact - self.assertIsNone(jp2.box[3].box[2].icc_profile) - self.assertEqual(jp2.box[3].box[2].colorspace, - glymur.core.E_SRGB) - - def test_NR_file8_dump(self): - # One 8-bit component in a gamma 1.8 space. The colourspace is - # specified using a Restricted ICC profile. - jfile = opj_data_file('input/conformance/file8.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'xml ', 'jp2c', - 'xml ']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 400) - self.assertEqual(jp2.box[2].box[0].width, 700) - self.assertEqual(jp2.box[2].box[0].num_components, 1) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.RESTRICTED_ICC_PROFILE) # enumerated - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[2].box[1].icc_profile['Size'], 414) - self.assertIsNone(jp2.box[2].box[1].colorspace) - - # XML box - tags = [x.tag for x in jp2.box[3].xml.getroot()] - self.assertEqual(tags, - ['{http://www.jpeg.org/jpx/1.0/xml}' - + 'GENERAL_CREATION_INFO']) - - # XML box - tags = [x.tag for x in jp2.box[5].xml.getroot()] - self.assertEqual(tags, - ['{http://www.jpeg.org/jpx/1.0/xml}CAPTION', - '{http://www.jpeg.org/jpx/1.0/xml}LOCATION', - '{http://www.jpeg.org/jpx/1.0/xml}THING', - '{http://www.jpeg.org/jpx/1.0/xml}EVENT']) - - def test_NR_file9_dump(self): - # Colormap - jfile = opj_data_file('input/conformance/file9.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'pclr', 'cmap', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 512) - self.assertEqual(jp2.box[2].box[0].width, 768) - self.assertEqual(jp2.box[2].box[0].num_components, 1) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Palette box. - self.assertEqual(len(jp2.box[2].box[1].palette), 3) - self.assertEqual(len(jp2.box[2].box[1].palette[0]), 256) - self.assertEqual(len(jp2.box[2].box[1].palette[1]), 256) - self.assertEqual(len(jp2.box[2].box[1].palette[2]), 256) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[0][0], 0) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[1][0], 0) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[2][0], 0) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[0][128], 73) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[1][128], 92) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[2][128], 53) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[0][-1], 245) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[1][-1], 245) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[2][-1], 245) - - # Component mapping box - self.assertEqual(jp2.box[2].box[2].component_index, (0, 0, 0)) - self.assertEqual(jp2.box[2].box[2].mapping_type, (1, 1, 1)) - self.assertEqual(jp2.box[2].box[2].palette_index, (0, 1, 2)) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[3].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[3].precedence, 0) - self.assertEqual(jp2.box[2].box[3].approximation, 1) # JPX exact - self.assertIsNone(jp2.box[2].box[3].icc_profile) - self.assertEqual(jp2.box[2].box[3].colorspace, glymur.core.SRGB) - - def test_NR_00042_j2k_dump(self): - # Profile 3. - jfile = opj_data_file('input/nonregression/_00042.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "3" means profile 3 - self.assertEqual(c.segment[1].rsiz, 3) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1920) - self.assertEqual(c.segment[1].ysiz, 1080) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1920, 1080)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12, 12, 12)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.CPRL) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(c.segment[2].precinct_size[0], (128, 128)) - self.assertEqual(c.segment[2].precinct_size[1:], [(256, 256)] * 5) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, - [1824, 1776, 1776, 1728, 1792, 1792, 1760, 1872, - 1872, 1896, 5, 5, 71, 2003, 2003, 1890]) - self.assertEqual(c.segment[3].exponent, - [18, 18, 18, 18, 17, 17, 17, 16, - 16, 16, 14, 14, 14, 14, 14, 14]) - - # COC: Coding style component - self.assertEqual(c.segment[4].ccoc, 1) - self.assertEqual(c.segment[4].spcoc[0], 5) # level - self.assertEqual(tuple(c.segment[4].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[4].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[4].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[4].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[4].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[4].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[4].spcoc[3] & 0x0020) - self.assertEqual(c.segment[4].spcoc[4], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 1) - self.assertEqual(c.segment[5].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 2) - self.assertEqual(c.segment[5].mantissa, - [1824, 1776, 1776, 1728, 1792, 1792, 1760, 1872, - 1872, 1896, 5, 5, 71, 2003, 2003, 1890]) - self.assertEqual(c.segment[5].exponent, - [18, 18, 18, 18, 17, 17, 17, 16, 16, 16, 14, 14, 14, - 14, 14, 14]) - - # COC: Coding style component - self.assertEqual(c.segment[6].ccoc, 2) - self.assertEqual(c.segment[6].spcoc[0], 5) # level - self.assertEqual(tuple(c.segment[6].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[6].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[6].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[6].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[6].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[6].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[6].spcoc[3] & 0x0020) - self.assertEqual(c.segment[6].spcoc[4], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[7].cqcc, 2) - self.assertEqual(c.segment[7].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[7].sqcc & 0x1f, 2) # none - self.assertEqual(c.segment[7].mantissa, - [1824, 1776, 1776, 1728, 1792, 1792, 1760, 1872, - 1872, 1896, 5, 5, 71, 2003, 2003, 1890]) - self.assertEqual(c.segment[7].exponent, - [18, 18, 18, 18, 17, 17, 17, 16, 16, 16, 14, 14, - 14, 14, 14, 14]) - - # COM: comment - # Registration - self.assertEqual(c.segment[8].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[8].ccme.decode('latin-1'), - "Created by OpenJPEG version 1.3.0") - - # TLM (tile-part length) - self.assertEqual(c.segment[9].ztlm, 0) - self.assertEqual(c.segment[9].ttlm, (0, 0, 0)) - self.assertEqual(c.segment[9].ptlm, (45274, 20838, 8909)) - - # 3 tiles, one for each component - idx = [x.isot for x in c.segment if x.marker_id == 'SOT'] - self.assertEqual(idx, [0, 0, 0]) - lens = [x.psot for x in c.segment if x.marker_id == 'SOT'] - self.assertEqual(lens, [45274, 20838, 8909]) - tpsot = [x.tpsot for x in c.segment if x.marker_id == 'SOT'] - self.assertEqual(tpsot, [0, 1, 2]) - - sods = [x for x in c.segment if x.marker_id == 'SOD'] - self.assertEqual(len(sods), 3) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_Bretagne2_j2k_dump(self): - # Profile 3. - jfile = opj_data_file('input/nonregression/Bretagne2.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "3" means profile 3 - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 2592) - self.assertEqual(c.segment[1].ysiz, 1944) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (640, 480)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 3) # layers = 3 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(c.segment[2].precinct_size, - [(16, 16), (32, 32), (64, 64), (128, 128), - (128, 128), (128, 128)]) - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] - expected += ['SOT', 'COC', 'QCC', 'COC', 'QCC', 'SOD'] * 25 - expected += ['EOC'] - self.assertEqual(ids, expected) - - def test_NR_buxI_j2k_dump(self): - jfile = opj_data_file('input/nonregression/buxI.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 512) - self.assertEqual(c.segment[1].ysiz, 512) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (512, 512)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 2) # layers = 2 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'SOT', 'SOD', 'EOC'] - self.assertEqual(ids, expected) - - def test_NR_buxR_j2k_dump(self): - jfile = opj_data_file('input/nonregression/buxR.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 512) - self.assertEqual(c.segment[1].ysiz, 512) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (512, 512)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 2) # layers = 2 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'SOT', 'SOD', 'EOC'] - self.assertEqual(ids, expected) - - def test_NR_Cannotreaddatawithnosizeknown_j2k(self): - lst = ['input', 'nonregression', - 'Cannotreaddatawithnosizeknown.j2k'] - path = '/'.join(lst) - - jfile = opj_data_file(path) - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1420) - self.assertEqual(c.segment[1].ysiz, 1416) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1420, 1416)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 11) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 4) - self.assertEqual(c.segment[3].mantissa, [0] * 34) - self.assertEqual(c.segment[3].exponent, [16] + [17, 17, 18] * 11) - - def test_NR_CT_Phillips_JPEG2K_Decompr_Problem_dump(self): - jfile = opj_data_file('input/nonregression/' - + 'CT_Phillips_JPEG2K_Decompr_Problem.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 512) - self.assertEqual(c.segment[1].ysiz, 614) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (512, 614)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, - [442, 422, 422, 403, 422, 422, 403, 472, 472, 487, - 591, 591, 676, 558, 558, 485]) - self.assertEqual(c.segment[3].exponent, - [22, 22, 22, 22, 21, 21, 21, 20, 20, 20, 19, 19, 19, - 18, 18, 18]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), "Kakadu-3.2") - - def test_NR_cthead1_dump(self): - jfile = opj_data_file('input/nonregression/cthead1.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'CME'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 256) - self.assertEqual(c.segment[1].ysiz, 256) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (256, 256)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [9, 10, 10, 11, 10, 10, 11, 10, 10, 11, 10, 10, 10, - 9, 9, 10]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), "Kakadu-v6.3.1") - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), "Kakadu-v6.3.1") - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_illegalcolortransform_dump(self): - jfile = opj_data_file('input/nonregression/illegalcolortransform.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1420) - self.assertEqual(c.segment[1].ysiz, 1416) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1420, 1416)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 11) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 4) - self.assertEqual(c.segment[3].mantissa, [0] * 34) - self.assertEqual(c.segment[3].exponent, [16] + [17, 17, 18] * 11) - - def test_NR_j2k32_dump(self): - jfile = opj_data_file('input/nonregression/j2k32.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 256) - self.assertEqual(c.segment[1].ysiz, 256) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (256, 256)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (True, True, True)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, [8, 9, 9, 10, 9, 9, 10, 9, 9, - 10, 9, 9, 10, 9, 9, 10]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_BINARY) - # Comment value - self.assertEqual(len(c.segment[4].ccme), 36) - - def test_NR_kakadu_v4_4_openjpegv2_broken_dump(self): - jfile = opj_data_file('input/nonregression/' - + 'kakadu_v4-4_openjpegv2_broken.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 2048) - self.assertEqual(c.segment[1].ysiz, 2500) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (2048, 2500)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 12) # layers = 12 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 8) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, [0] * 25) - self.assertEqual(c.segment[3].exponent, - [17, 18, 18, 19, 18, 18, 19, 18, 18, 19, 18, 18, 19, - 18, 18, 19, 18, 18, 19, 18, 18, 19, 18, 18, 19]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), "Kakadu-v4.4") - - # COM: comment - # Registration - self.assertEqual(c.segment[5].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - expected = "Kdu-Layer-Info: log_2{Delta-D(MSE)/[2^16*Delta-L(bytes)]}," - expected += " L(bytes)\n" - expected += " -65.4, 6.8e+004\n" - expected += " -66.3, 1.0e+005\n" - expected += " -67.3, 2.0e+005\n" - expected += " -68.5, 4.1e+005\n" - expected += " -69.0, 5.1e+005\n" - expected += " -69.5, 5.9e+005\n" - expected += " -69.7, 6.8e+005\n" - expected += " -70.3, 8.2e+005\n" - expected += " -70.8, 1.0e+006\n" - expected += " -71.9, 1.4e+006\n" - expected += " -73.8, 2.0e+006\n" - expected += "-256.0, 3.7e+006\n" - self.assertEqual(c.segment[5].ccme.decode('latin-1'), expected) - - def test_NR_MarkerIsNotCompliant_j2k_dump(self): - jfile = opj_data_file('input/nonregression/MarkerIsNotCompliant.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1420) - self.assertEqual(c.segment[1].ysiz, 1416) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1420, 1416)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 11) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 4) - self.assertEqual(c.segment[3].mantissa, [0] * 34) - self.assertEqual(c.segment[3].exponent, - [16, 17, 17, 18, 17, 17, 18, 17, 17, 18, 17, 17, 18, - 17, 17, 18, 17, 17, 18, 17, 17, 18, 17, 17, 18, 17, - 17, 18, 17, 17, 18, 17, 17, 18]) - - def test_NR_movie_00000(self): - jfile = opj_data_file('input/nonregression/movie_00000.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1920) - self.assertEqual(c.segment[1].ysiz, 1080) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1920, 1080)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - def test_NR_movie_00001(self): - jfile = opj_data_file('input/nonregression/movie_00001.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1920) - self.assertEqual(c.segment[1].ysiz, 1080) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1920, 1080)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - def test_NR_movie_00002(self): - jfile = opj_data_file('input/nonregression/movie_00002.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1920) - self.assertEqual(c.segment[1].ysiz, 1080) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1920, 1080)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - def test_NR_orb_blue10_lin_j2k_dump(self): - jfile = opj_data_file('input/nonregression/orb-blue10-lin-j2k.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 117) - self.assertEqual(c.segment[1].ysiz, 117) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (117, 117)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 4) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - def test_NR_orb_blue10_win_j2k_dump(self): - jfile = opj_data_file('input/nonregression/orb-blue10-win-j2k.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 117) - self.assertEqual(c.segment[1].ysiz, 117) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (117, 117)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 4) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - def test_NR_pacs_ge_j2k_dump(self): - jfile = opj_data_file('input/nonregression/pacs.ge.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 512) - self.assertEqual(c.segment[1].ysiz, 512) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (512, 512)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (True,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 16) # layers = 16 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [18, 19, 19, 20, 19, 19, 20, 19, 19, 20, 19, 19, 20, - 19, 19, 20]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Kakadu-2.0.2") - - def test_NR_test_lossless_j2k_dump(self): - jfile = opj_data_file('input/nonregression/test_lossless.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1024) - self.assertEqual(c.segment[1].ysiz, 1024) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1024, 1024)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [12, 13, 13, 14, 13, 13, 14, 13, 13, 14, 13, 13, 14, - 13, 13, 14]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "ClearCanvas DICOM OpenJPEG") - - def test_NR_123_j2c_dump(self): - jfile = opj_data_file('input/nonregression/123.j2c') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1800) - self.assertEqual(c.segment[1].ysiz, 1800) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1800, 1800)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 11) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 4) - self.assertEqual(c.segment[3].mantissa, [0] * 34) - self.assertEqual(c.segment[3].exponent, - [16] + [17, 17, 18] * 11) - - def test_NR_bug_j2c_dump(self): - jfile = opj_data_file('input/nonregression/bug.j2c') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1800) - self.assertEqual(c.segment[1].ysiz, 1800) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1800, 1800)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 11) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 4) - self.assertEqual(c.segment[3].mantissa, [0] * 34) - self.assertEqual(c.segment[3].exponent, - [16] + [17, 17, 18] * 11) - - def test_NR_kodak_2layers_lrcp_j2c_dump(self): - jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 2048) - self.assertEqual(c.segment[1].ysiz, 1556) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (2048, 1556)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12, 12, 12)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 2) # layers = 2 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(c.segment[2].precinct_size, - [(128, 128)] + [(256, 256)] * 5) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [13, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, - 13, 13, 13]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "DCP-Werkstatt") - - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") - def test_NR_broken_jp2_dump(self): - jfile = opj_data_file('input/nonregression/broken.jp2') - with self.assertWarns(UserWarning): - # colr box has bad length. - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 152) - self.assertEqual(jp2.box[2].box[0].width, 203) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # not allowed? - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 203) - self.assertEqual(c.segment[1].ysiz, 152) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (203, 152)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COM: comment - # Registration - self.assertEqual(c.segment[2].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[2].ccme.decode('latin-1'), - "Creator: JasPer Version 1.701.0") - - # COD: Coding style default - self.assertFalse(c.segment[3].scod & 2) # no sop - self.assertFalse(c.segment[3].scod & 4) # no eph - self.assertEqual(c.segment[3].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[3].layers, 1) # layers = 1 - self.assertEqual(c.segment[3].spcod[3], 1) # mct - self.assertEqual(c.segment[3].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[3].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcod[7] & 0x0020) - self.assertEqual(c.segment[3].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[3].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) - self.assertEqual(c.segment[4].guard_bits, 2) - self.assertEqual(c.segment[4].mantissa, [0] * 16) - self.assertEqual(c.segment[4].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 1) - self.assertEqual(c.segment[5].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[5].mantissa, [0] * 16) - self.assertEqual(c.segment[5].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 2) - self.assertEqual(c.segment[6].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[6].mantissa, [0] * 16) - self.assertEqual(c.segment[6].exponent, - [8] + [9, 9, 10] * 5) - - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2, 'assertWarns'.") - def test_NR_broken2_jp2_dump(self): - # Invalid marker ID on codestream. - jfile = opj_data_file('input/nonregression/broken2.jp2') - with self.assertWarns(UserWarning): - jp2 = Jp2k(jfile) - - self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') - - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") - def test_NR_broken3_jp2_dump(self): - jfile = opj_data_file('input/nonregression/broken3.jp2') - with self.assertWarns(UserWarning): - # colr box has bad length. - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 152) - self.assertEqual(jp2.box[2].box[0].width, 203) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 203) - self.assertEqual(c.segment[1].ysiz, 152) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (203, 152)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COM: comment - # Registration - self.assertEqual(c.segment[2].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[2].ccme.decode('latin-1'), - "Creator: JasPer Vers)on 1.701.0") - - # COD: Coding style default - self.assertFalse(c.segment[3].scod & 2) # no sop - self.assertFalse(c.segment[3].scod & 4) # no eph - self.assertEqual(c.segment[3].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[3].layers, 1) # layers = 1 - self.assertEqual(c.segment[3].spcod[3], 1) # mct - self.assertEqual(c.segment[3].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[3].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcod[7] & 0x0020) - self.assertEqual(c.segment[3].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[3].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) - self.assertEqual(c.segment[4].guard_bits, 2) - self.assertEqual(c.segment[4].mantissa, [0] * 16) - self.assertEqual(c.segment[4].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 1) - self.assertEqual(c.segment[5].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[5].mantissa, [0] * 16) - self.assertEqual(c.segment[5].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 2) - self.assertEqual(c.segment[6].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[6].mantissa, [0] * 16) - self.assertEqual(c.segment[6].exponent, - [8] + [9, 9, 10] * 5) - - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2, 'assertWarns'") - def test_NR_broken4_jp2_dump(self): - # Has an invalid marker in the main header - jfile = opj_data_file('input/nonregression/broken4.jp2') - with self.assertWarns(UserWarning): - jp2 = Jp2k(jfile) - - self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') - - def test_NR_file409752(self): - jfile = opj_data_file('input/nonregression/file409752.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 243) - self.assertEqual(jp2.box[2].box[0].width, 720) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.YCC) - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 720) - self.assertEqual(c.segment[1].ysiz, 243) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (720, 243)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (2, 1), (2, 1)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 128)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, - [1816, 1792, 1792, 1724, 1770, 1770, 1724, 1868, - 1868, 1892, 3, 3, 69, 2002, 2002, 1889]) - self.assertEqual(c.segment[3].exponent, - [13] * 4 + [12] * 3 + [11] * 3 + [9] * 6) - - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") - def test_NR_gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc_patch_jp2(self): - lst = ['input', 'nonregression', - 'gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc.patch.jp2'] - jfile = opj_data_file('/'.join(lst)) - with self.assertWarns(UserWarning): - Jp2k(jfile) - - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") - def test_NR_gdal_fuzzer_check_comp_dx_dy_jp2_dump(self): - lst = ['input', 'nonregression', 'gdal_fuzzer_check_comp_dx_dy.jp2'] - jfile = opj_data_file('/'.join(lst)) - with self.assertWarns(UserWarning): - Jp2k(jfile) - - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") - def test_NR_gdal_fuzzer_check_number_of_tiles(self): - # Has an impossible tiling setup. - lst = ['input', 'nonregression', - 'gdal_fuzzer_check_number_of_tiles.jp2'] - jfile = opj_data_file('/'.join(lst)) - with self.assertWarns(UserWarning): - Jp2k(jfile) - - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") - def test_NR_gdal_fuzzer_unchecked_numresolutions_dump(self): - # Has an invalid number of resolutions. - lst = ['input', 'nonregression', - 'gdal_fuzzer_unchecked_numresolutions.jp2'] - jfile = opj_data_file('/'.join(lst)) - with self.assertWarns(UserWarning): - Jp2k(jfile) - - def test_NR_issue104_jpxstream_dump(self): - jfile = opj_data_file('input/nonregression/issue104_jpxstream.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'pclr', 'cmap']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jpxb') - self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') - - # Reader requirements talk. - # unrestricted jpeg 2000 part 1 - self.assertTrue(5 in jp2.box[2].standard_flag) - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 203) - self.assertEqual(jp2.box[3].box[0].width, 479) - self.assertEqual(jp2.box[3].box[0].num_components, 1) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[1].precedence, 2) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # exact - self.assertEqual(jp2.box[3].box[1].colorspace, glymur.core.SRGB) - - # Jp2 Header - # Palette box. - self.assertEqual(len(jp2.box[3].box[2].palette), 3) - self.assertEqual(len(jp2.box[3].box[2].palette[0]), 256) - self.assertEqual(len(jp2.box[3].box[2].palette[1]), 256) - self.assertEqual(len(jp2.box[3].box[2].palette[2]), 256) - - # Jp2 Header - # Component mapping box - self.assertEqual(jp2.box[3].box[3].component_index, (0, 0, 0)) - self.assertEqual(jp2.box[3].box[3].mapping_type, (1, 1, 1)) - self.assertEqual(jp2.box[3].box[3].palette_index, (0, 1, 2)) - - c = jp2.box[4].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 479) - self.assertEqual(c.segment[1].ysiz, 203) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (256, 203)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, [8] + [9, 9, 10] * 5) - - def test_NR_issue188_beach_64bitsbox(self): - lst = ['input', 'nonregression', 'issue188_beach_64bitsbox.jp2'] - jfile = opj_data_file('/'.join(lst)) - with warnings.catch_warnings(): - # There's a warning for an unknown box. We explicitly test for - # that down below. - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'XML ', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 200) - self.assertEqual(jp2.box[2].box[0].width, 200) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, True) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) - - # Skip the 4th box, it is uknown. - - c = jp2.box[4].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'CME'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 200) - self.assertEqual(c.segment[1].ysiz, 200) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (200, 200)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 1) - - def test_NR_issue206_image_000_dump(self): - jfile = opj_data_file('input/nonregression/issue206_image-000.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jpxb') - self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') - - # Reader requirements talk. - # unrestricted jpeg 2000 part 1 - self.assertTrue(5 in jp2.box[2].standard_flag) - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 326) - self.assertEqual(jp2.box[3].box[0].width, 431) - self.assertEqual(jp2.box[3].box[0].num_components, 3) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[1].precedence, 2) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[3].box[1].colorspace, glymur.core.SRGB) - - c = jp2.box[4].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 431) - self.assertEqual(c.segment[1].ysiz, 326) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (256, 256)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, [8] + [9, 9, 10] * 5) - - def test_NR_Marrin_jp2_dump(self): - jfile = opj_data_file('input/nonregression/Marrin.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr', 'cdef', 'res ']) - - ids = [box.box_id for box in jp2.box[2].box[3].box] - self.assertEqual(ids, ['resd']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 135) - self.assertEqual(jp2.box[2].box[0].width, 135) - self.assertEqual(jp2.box[2].box[0].num_components, 2) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, True) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.GREYSCALE) - - # Jp2 Header - # Channel Definition - self.assertEqual(jp2.box[2].box[2].index, (0, 1)) - self.assertEqual(jp2.box[2].box[2].channel_type, (0, 1)) # opacity - self.assertEqual(jp2.box[2].box[2].association, (0, 0)) # both main - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 135) - self.assertEqual(c.segment[1].ysiz, 135) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (135, 135)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 2) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 2) # layers = 2 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, - [1822, 1770, 1770, 1724, 1792, 1792, 1762, 1868, 1868, - 1892, 3, 3, 69, 2002, 2002, 1889]) - self.assertEqual(c.segment[3].exponent, - [14] * 4 + [13] * 3 + [12] * 3 + [10] * 6) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Kakadu-v5.2.1") - - def test_NR_mem_b2ace68c_1381_dump(self): - jfile = opj_data_file('input/nonregression/mem-b2ace68c-1381.jp2') - with warnings.catch_warnings(): - # This file has a bad pclr box, we test for this elsewhere. - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'pclr', 'cmap']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jpxb') - self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') - - # Reader requirements talk. - # cmyk colourspace - self.assertTrue(55 in jp2.box[2].standard_flag) - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 865) - self.assertEqual(jp2.box[3].box[0].width, 649) - self.assertEqual(jp2.box[3].box[0].num_components, 1) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 1) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[1].precedence, 2) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[3].box[1].colorspace, glymur.core.CMYK) - - # Jp2 Header - # Palette box. - self.assertEqual(len(jp2.box[3].box[2].palette), 4) - self.assertEqual(len(jp2.box[3].box[2].palette[0]), 1) - self.assertEqual(len(jp2.box[3].box[2].palette[1]), 1) - self.assertEqual(len(jp2.box[3].box[2].palette[2]), 1) - self.assertEqual(len(jp2.box[3].box[2].palette[3]), 1) - - # Jp2 Header - # Component mapping box - self.assertEqual(jp2.box[3].box[3].component_index, (0, 1, 2)) - self.assertEqual(jp2.box[3].box[3].mapping_type, (1, 1, 0)) - self.assertEqual(jp2.box[3].box[3].palette_index, (0, 0, 1)) - - c = jp2.box[4].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 649) - self.assertEqual(c.segment[1].ysiz, 865) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (256, 256)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (1,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, [1] + [2, 2, 3] * 5) - - def test_NR_mem_b2b86b74_2753_dump(self): - jfile = opj_data_file('input/nonregression/mem-b2b86b74-2753.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'pclr', 'cmap']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jpxb') - self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') - - # Reader requirements talk. - # unrestricted jpeg 2000 part 1 - self.assertTrue(5 in jp2.box[2].standard_flag) - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 46) - self.assertEqual(jp2.box[3].box[0].width, 124) - self.assertEqual(jp2.box[3].box[0].num_components, 1) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 4) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[1].precedence, 2) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[3].box[1].colorspace, glymur.core.SRGB) - - # Jp2 Header - # Palette box. - # 3 columns with 16 entries. - self.assertEqual(len(jp2.box[3].box[2].palette), 3) - self.assertEqual(len(jp2.box[3].box[2].palette[0]), 16) - self.assertEqual(len(jp2.box[3].box[2].palette[1]), 16) - self.assertEqual(len(jp2.box[3].box[2].palette[2]), 16) - - # Jp2 Header - # Component mapping box - self.assertEqual(jp2.box[3].box[3].component_index, (0, 0, 0)) - self.assertEqual(jp2.box[3].box[3].mapping_type, (1, 1, 1)) - self.assertEqual(jp2.box[3].box[3].palette_index, (0, 1, 2)) - - c = jp2.box[4].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 124) - self.assertEqual(c.segment[1].ysiz, 46) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (124, 46)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (4,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, [4] + [5, 5, 6] * 5) - - def test_NR_merged_dump(self): - jfile = opj_data_file('input/nonregression/merged.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 576) - self.assertEqual(jp2.box[2].box[0].width, 766) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.YCC) - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'POD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 766) - self.assertEqual(c.segment[1].ysiz, 576) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (766, 576)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (2, 1), (2, 1)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 128)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, [8] + [9, 9, 10] * 5) - - # POD: progression order change - self.assertEqual(c.segment[4].rspod, (0, 0)) - self.assertEqual(c.segment[4].cspod, (0, 1)) - self.assertEqual(c.segment[4].lyepod, (1, 1)) - self.assertEqual(c.segment[4].repod, (6, 6)) - self.assertEqual(c.segment[4].cdpod, (1, 3)) - - podvals = (glymur.core.LRCP, glymur.core.LRCP) - self.assertEqual(c.segment[4].ppod, podvals) - - def test_NR_orb_blue10_lin_jp2_dump(self): - jfile = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2') - with warnings.catch_warnings(): - # This file has an invalid ICC profile - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 117) - self.assertEqual(jp2.box[2].box[0].width, 117) - self.assertEqual(jp2.box[2].box[0].num_components, 4) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.RESTRICTED_ICC_PROFILE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertIsNone(jp2.box[2].box[1].icc_profile) - self.assertIsNone(jp2.box[2].box[1].colorspace) - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 117) - self.assertEqual(c.segment[1].ysiz, 117) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (117, 117)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 4) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - def test_NR_orb_blue10_win_jp2_dump(self): - jfile = opj_data_file('input/nonregression/orb-blue10-win-jp2.jp2') - with warnings.catch_warnings(): - # This file has an invalid ICC profile - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 117) - self.assertEqual(jp2.box[2].box[0].width, 117) - self.assertEqual(jp2.box[2].box[0].num_components, 4) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.RESTRICTED_ICC_PROFILE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertIsNone(jp2.box[2].box[1].icc_profile) - self.assertIsNone(jp2.box[2].box[1].colorspace) - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 117) - self.assertEqual(c.segment[1].ysiz, 117) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (117, 117)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 4) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - def test_NR_text_GBR_dump(self): - jfile = opj_data_file('input/nonregression/text_GBR.jp2') - with warnings.catch_warnings(): - # brand is 'jp2 ', but has any icc profile. - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - lst = ['jP ', 'ftyp', 'rreq', 'jp2h', - 'uuid', 'uuid', 'uuid', 'uuid', 'jp2c'] - self.assertEqual(ids, lst) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'res ']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Reader requirements. - # Compositing layer uses any icc profile - self.assertTrue(44 in jp2.box[2].standard_flag) - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 400) - self.assertEqual(jp2.box[3].box[0].width, 400) - self.assertEqual(jp2.box[3].box[0].num_components, 3) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.ANY_ICC_PROFILE) - self.assertEqual(jp2.box[3].box[1].precedence, 2) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 1328) - self.assertIsNone(jp2.box[3].box[1].colorspace) - - # UUID boxes. All mentioned in the RREQ box. - self.assertEqual(jp2.box[2].vendor_feature[0], jp2.box[4].uuid) - self.assertEqual(jp2.box[2].vendor_feature[1], jp2.box[5].uuid) - self.assertEqual(jp2.box[2].vendor_feature[2], jp2.box[6].uuid) - self.assertEqual(jp2.box[2].vendor_feature[3], jp2.box[7].uuid) - - c = jp2.box[8].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 400) - self.assertEqual(c.segment[1].ysiz, 400) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 6) # layers = 6 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @@ -6751,19 +527,6 @@ class TestSuite_bands(unittest.TestCase): self.assertTrue(peak_tolerance(jpdata[0], pgxdata) < 54) self.assertTrue(mse(jpdata[0], pgxdata) < 68) - @unittest.skip("8-bit pgx data vs 12-bit j2k data") - def test_ETS_C0P0_p0_06_j2k(self): - jfile = opj_data_file('input/conformance/p0_06.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read_bands(rlevel=3) - - pgxfile = opj_data_file('baseline/conformance/c0p0_06.pgx') - pgxdata = read_pgx(pgxfile) - tol = peak_tolerance(jpdata[0], pgxdata) - self.assertTrue(tol < 109) - m = mse(jpdata[0], pgxdata) - self.assertTrue(m < 743) - def test_ETS_C0P1_p1_03_j2k(self): jfile = opj_data_file('input/conformance/p1_03.j2k') jp2k = Jp2k(jfile) @@ -6919,439 +682,5 @@ class TestSuite2point0(unittest.TestCase): self.assertTrue(True) -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL, - "Test not in done in v2.0.0 official") -@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1, - "Tests not introduced until 2.1") -class TestSuite2point1(unittest.TestCase): - """Runs tests introduced in version 2.0+ or that pass only in 2.0+""" - - def setUp(self): - pass - - def tearDown(self): - pass - - def test_NR_DEC_text_GBR_jp2_29_decode(self): - jfile = opj_data_file('input/nonregression/text_GBR.jp2') - with warnings.catch_warnings(): - # brand is 'jp2 ', but has any icc profile. - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) - jp2.read() - self.assertTrue(True) - - def test_NR_DEC_kodak_2layers_lrcp_j2c_31_decode(self): - jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_kodak_2layers_lrcp_j2c_32_decode(self): - jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c') - Jp2k(jfile).read(layer=2) - self.assertTrue(True) - - def test_NR_DEC_issue104_jpxstream_jp2_33_decode(self): - jfile = opj_data_file('input/nonregression/issue104_jpxstream.jp2') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_mem_b2ace68c_1381_jp2_34_decode(self): - jfile = opj_data_file('input/nonregression/mem-b2ace68c-1381.jp2') - with warnings.catch_warnings(): - # This file has a bad pclr box, we test for this elsewhere. - warnings.simplefilter("ignore") - j = Jp2k(jfile) - j.read() - self.assertTrue(True) - - def test_NR_DEC_mem_b2b86b74_2753_jp2_35_decode(self): - jfile = opj_data_file('input/nonregression/mem-b2b86b74-2753.jp2') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_gdal_fuzzer_unchecked_num_resolutions_jp2_36_decode(self): - f = 'input/nonregression/gdal_fuzzer_unchecked_numresolutions.jp2' - jfile = opj_data_file(f) - with warnings.catch_warnings(): - # Invalid number of resolutions. - warnings.simplefilter("ignore") - j = Jp2k(jfile) - with self.assertRaises(IOError): - j.read() - - def test_NR_DEC_gdal_fuzzer_check_number_of_tiles_jp2_38_decode(self): - relpath = 'input/nonregression/gdal_fuzzer_check_number_of_tiles.jp2' - jfile = opj_data_file(relpath) - with warnings.catch_warnings(): - # Invalid number of tiles. - warnings.simplefilter("ignore") - j = Jp2k(jfile) - with self.assertRaises(IOError): - j.read() - - def test_NR_DEC_gdal_fuzzer_check_comp_dx_dy_jp2_39_decode(self): - relpath = 'input/nonregression/gdal_fuzzer_check_comp_dx_dy.jp2' - jfile = opj_data_file(relpath) - with warnings.catch_warnings(): - # Invalid subsampling value - warnings.simplefilter("ignore") - with self.assertRaises(IOError): - Jp2k(jfile).read() - - def test_NR_DEC_file_409752_jp2_40_decode(self): - jfile = opj_data_file('input/nonregression/file409752.jp2') - with self.assertRaises(RuntimeError): - Jp2k(jfile).read() - - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") - def test_NR_DEC_issue188_beach_64bitsbox_jp2_41_decode(self): - # Has an 'XML ' box instead of 'xml '. Yes that is pedantic, but it - # really does deserve a warning. - relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2' - jfile = opj_data_file(relpath) - with self.assertWarns(UserWarning): - Jp2k(jfile).read() - - def test_NR_DEC_issue206_image_000_jp2_42_decode(self): - jfile = opj_data_file('input/nonregression/issue206_image-000.jp2') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_p1_04_j2k_43_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 0, 1024, 1024)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata) - - def test_NR_DEC_p1_04_j2k_44_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(640, 512, 768, 640)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[640:768, 512:640]) - - def test_NR_DEC_p1_04_j2k_45_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(896, 896, 1024, 1024)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[896:1024, 896:1024]) - - def test_NR_DEC_p1_04_j2k_46_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(500, 100, 800, 300)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[500:800, 100:300]) - - def test_NR_DEC_p1_04_j2k_47_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(520, 260, 600, 360)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[520:600, 260:360]) - - def test_NR_DEC_p1_04_j2k_48_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(520, 260, 660, 360)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[520:660, 260:360]) - - def test_NR_DEC_p1_04_j2k_49_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(520, 360, 600, 400)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[520:600, 360:400]) - - def test_NR_DEC_p1_04_j2k_50_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 0, 1024, 1024), rlevel=2) - odata = jp2k.read(rlevel=2) - - np.testing.assert_array_equal(ssdata, odata[0:256, 0:256]) - - def test_NR_DEC_p1_04_j2k_51_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(640, 512, 768, 640), rlevel=2) - odata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(ssdata, odata[160:192, 128:160]) - - def test_NR_DEC_p1_04_j2k_52_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(896, 896, 1024, 1024), rlevel=2) - odata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(ssdata, odata[224:352, 224:352]) - - def test_NR_DEC_p1_04_j2k_53_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(500, 100, 800, 300), rlevel=2) - odata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(ssdata, odata[125:200, 25:75]) - - def test_NR_DEC_p1_04_j2k_54_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(520, 260, 600, 360), rlevel=2) - odata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(ssdata, odata[130:150, 65:90]) - - def test_NR_DEC_p1_04_j2k_55_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(520, 260, 660, 360), rlevel=2) - odata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(ssdata, odata[130:165, 65:90]) - - def test_NR_DEC_p1_04_j2k_56_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(520, 360, 600, 400), rlevel=2) - odata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(ssdata, odata[130:150, 90:100]) - - def test_NR_DEC_p1_04_j2k_57_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - tdata = jp2k.read(tile=63) # last tile - odata = jp2k.read() - np.testing.assert_array_equal(tdata, odata[896:1024, 896:1024]) - - def test_NR_DEC_p1_04_j2k_58_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - tdata = jp2k.read(tile=63, rlevel=2) # last tile - odata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(tdata, odata[224:256, 224:256]) - - def test_NR_DEC_p1_04_j2k_59_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - tdata = jp2k.read(tile=12) # 2nd row, 5th column - odata = jp2k.read() - np.testing.assert_array_equal(tdata, odata[128:256, 512:640]) - - def test_NR_DEC_p1_04_j2k_60_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - tdata = jp2k.read(tile=12, rlevel=1) # 2nd row, 5th column - odata = jp2k.read(rlevel=1) - np.testing.assert_array_equal(tdata, odata[64:128, 256:320]) - - def test_NR_DEC_jp2_36_decode(self): - lst = ('input', - 'nonregression', - 'gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc.patch.jp2') - jfile = opj_data_file('/'.join(lst)) - with warnings.catch_warnings(): - # Invalid component number. - warnings.simplefilter("ignore") - j = Jp2k(jfile) - with self.assertRaises(IOError): - j.read() - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_61_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 0, 12, 12)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[0:12, 0:12]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_62_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(1, 8, 8, 11)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[1:8, 8:11]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_63_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(9, 9, 12, 12)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[9:12, 9:12]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_64_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(10, 4, 12, 10)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[10:12, 4:10]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_65_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(3, 3, 9, 9)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[3:9, 3:9]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_66_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(4, 4, 7, 7)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[4:7, 4:7]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_67_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(4, 4, 5, 5)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[4:5, 4: 5]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_68_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 0, 12, 12), rlevel=1) - odata = jp2k.read(rlevel=1) - np.testing.assert_array_equal(ssdata, odata[0:6, 0:6]) - - @unittest.skip("fprintf stderr output in r2343.") - def test_NR_DEC_p1_06_j2k_69_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(1, 8, 8, 11), rlevel=1) - self.assertEqual(ssdata.shape, (3, 2, 3)) - - def test_NR_DEC_p1_06_j2k_70_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(9, 9, 12, 12), rlevel=1) - self.assertEqual(ssdata.shape, (1, 1, 3)) - - def test_NR_DEC_p1_06_j2k_71_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(10, 4, 12, 10), rlevel=1) - self.assertEqual(ssdata.shape, (1, 3, 3)) - - def test_NR_DEC_p1_06_j2k_72_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(3, 3, 9, 9), rlevel=1) - self.assertEqual(ssdata.shape, (3, 3, 3)) - - def test_NR_DEC_p1_06_j2k_73_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(4, 4, 7, 7), rlevel=1) - self.assertEqual(ssdata.shape, (2, 2, 3)) - - def test_NR_DEC_p1_06_j2k_74_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(4, 4, 5, 5), rlevel=1) - self.assertEqual(ssdata.shape, (1, 1, 3)) - - def test_NR_DEC_p1_06_j2k_75_decode(self): - # Image size would be 0 x 0. - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - with self.assertRaises((IOError, OSError)): - jp2k.read(area=(9, 9, 12, 12), rlevel=2) - - def test_NR_DEC_p0_04_j2k_85_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 0, 256, 256)) - fulldata = jp2k.read() - np.testing.assert_array_equal(fulldata[0:256, 0:256], ssdata) - - def test_NR_DEC_p0_04_j2k_86_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 128, 128, 256)) - fulldata = jp2k.read() - np.testing.assert_array_equal(fulldata[0:128, 128:256], ssdata) - - def test_NR_DEC_p0_04_j2k_87_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(10, 50, 200, 120)) - fulldata = jp2k.read() - np.testing.assert_array_equal(fulldata[10:200, 50:120], ssdata) - - def test_NR_DEC_p0_04_j2k_88_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(150, 10, 210, 190)) - fulldata = jp2k.read() - np.testing.assert_array_equal(fulldata[150:210, 10:190], ssdata) - - def test_NR_DEC_p0_04_j2k_89_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(80, 100, 150, 200)) - fulldata = jp2k.read() - np.testing.assert_array_equal(fulldata[80:150, 100:200], ssdata) - - def test_NR_DEC_p0_04_j2k_90_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(20, 150, 50, 200)) - fulldata = jp2k.read() - np.testing.assert_array_equal(fulldata[20:50, 150:200], ssdata) - - def test_NR_DEC_p0_04_j2k_91_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 0, 256, 256), rlevel=2) - fulldata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(fulldata[0:64, 0:64], ssdata) - - def test_NR_DEC_p0_04_j2k_92_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 128, 128, 256), rlevel=2) - fulldata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(fulldata[0:32, 32:64], ssdata) - - def test_NR_DEC_p0_04_j2k_93_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(10, 50, 200, 120), rlevel=2) - fulldata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(fulldata[3:50, 13:30], ssdata) - - def test_NR_DEC_p0_04_j2k_94_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(150, 10, 210, 190), rlevel=2) - fulldata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(fulldata[38:53, 3:48], ssdata) - - def test_NR_DEC_p0_04_j2k_95_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(80, 100, 150, 200), rlevel=2) - fulldata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(fulldata[20:38, 25:50], ssdata) - - def test_NR_DEC_p0_04_j2k_96_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(20, 150, 50, 200), rlevel=2) - fulldata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(fulldata[5:13, 38:50], ssdata) - - if __name__ == "__main__": unittest.main() diff --git a/glymur/test/test_opj_suite_2p1.py b/glymur/test/test_opj_suite_2p1.py new file mode 100644 index 0000000..12d19a0 --- /dev/null +++ b/glymur/test/test_opj_suite_2p1.py @@ -0,0 +1,407 @@ +""" +The tests defined here roughly correspond to what is in the OpenJPEG test +suite. +""" + +# Some test names correspond with openjpeg tests. Long names are ok in this +# case. +# pylint: disable=C0103 + +# All of these tests correspond to tests in openjpeg, so no docstring is really +# needed. +# pylint: disable=C0111 + +# This module is very long, cannot be helped. +# pylint: disable=C0302 + +# unittest fools pylint with "too many public methods" +# pylint: disable=R0904 + +# Some tests use numpy test infrastructure, which means the tests never +# reference "self", so pylint claims it should be a function. No, no, no. +# pylint: disable=R0201 + +# Many tests are pretty long and that can't be helped. +# pylint: disable=R0915 + +# asserWarns introduced in python 3.2 (python2.7/pylint issue) +# pylint: disable=E1101 + +# unittest2 is python2.6 only (pylint/python-2.7) +# pylint: disable=F0401 + +import re +import sys + +if sys.hexversion < 0x02070000: + import unittest2 as unittest +else: + import unittest + +import warnings + +import numpy as np + +from glymur import Jp2k +import glymur + +from .fixtures import OPENJP2_IS_V2_OFFICIAL, OPJ_DATA_ROOT +from .fixtures import mse, peak_tolerance, read_pgx, opj_data_file + + +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +@unittest.skipIf(re.match(r'''2.0.0''', glymur.version.openjpeg_version), + "Tests not introduced until 2.0.1") +@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1, + "Tests not introduced until 2.1") +class TestSuite2point1(unittest.TestCase): + """Runs tests introduced in version 2.0+ or that pass only in 2.0+""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_NR_DEC_text_GBR_jp2_29_decode(self): + jfile = opj_data_file('input/nonregression/text_GBR.jp2') + with warnings.catch_warnings(): + # brand is 'jp2 ', but has any icc profile. + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) + jp2.read() + self.assertTrue(True) + + def test_NR_DEC_kodak_2layers_lrcp_j2c_31_decode(self): + jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_kodak_2layers_lrcp_j2c_32_decode(self): + jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c') + Jp2k(jfile).read(layer=2) + self.assertTrue(True) + + def test_NR_DEC_issue104_jpxstream_jp2_33_decode(self): + jfile = opj_data_file('input/nonregression/issue104_jpxstream.jp2') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_mem_b2b86b74_2753_jp2_35_decode(self): + jfile = opj_data_file('input/nonregression/mem-b2b86b74-2753.jp2') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_gdal_fuzzer_unchecked_num_resolutions_jp2_36_decode(self): + f = 'input/nonregression/gdal_fuzzer_unchecked_numresolutions.jp2' + jfile = opj_data_file(f) + with warnings.catch_warnings(): + # Invalid number of resolutions. + warnings.simplefilter("ignore") + j = Jp2k(jfile) + with self.assertRaises(IOError): + j.read() + + def test_NR_DEC_gdal_fuzzer_check_number_of_tiles_jp2_38_decode(self): + relpath = 'input/nonregression/gdal_fuzzer_check_number_of_tiles.jp2' + jfile = opj_data_file(relpath) + with warnings.catch_warnings(): + # Invalid number of tiles. + warnings.simplefilter("ignore") + j = Jp2k(jfile) + with self.assertRaises(IOError): + j.read() + + def test_NR_DEC_gdal_fuzzer_check_comp_dx_dy_jp2_39_decode(self): + relpath = 'input/nonregression/gdal_fuzzer_check_comp_dx_dy.jp2' + jfile = opj_data_file(relpath) + with warnings.catch_warnings(): + # Invalid subsampling value + warnings.simplefilter("ignore") + with self.assertRaises(IOError): + Jp2k(jfile).read() + + def test_NR_DEC_file_409752_jp2_40_decode(self): + jfile = opj_data_file('input/nonregression/file409752.jp2') + with self.assertRaises(RuntimeError): + Jp2k(jfile).read() + + def test_NR_DEC_issue188_beach_64bitsbox_jp2_41_decode(self): + # Has an 'XML ' box instead of 'xml '. Yes that is pedantic, but it + # really does deserve a warning. + relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2' + jfile = opj_data_file(relpath) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + Jp2k(jfile).read() + self.assertEqual(len(w), 1) + + def test_NR_DEC_issue206_image_000_jp2_42_decode(self): + jfile = opj_data_file('input/nonregression/issue206_image-000.jp2') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_p1_04_j2k_43_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 0, 1024, 1024)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata) + + def test_NR_DEC_p1_04_j2k_44_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(640, 512, 768, 640)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[640:768, 512:640]) + + def test_NR_DEC_p1_04_j2k_45_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(896, 896, 1024, 1024)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[896:1024, 896:1024]) + + def test_NR_DEC_p1_04_j2k_46_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(500, 100, 800, 300)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[500:800, 100:300]) + + def test_NR_DEC_p1_04_j2k_47_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(520, 260, 600, 360)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[520:600, 260:360]) + + def test_NR_DEC_p1_04_j2k_48_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(520, 260, 660, 360)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[520:660, 260:360]) + + def test_NR_DEC_p1_04_j2k_49_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(520, 360, 600, 400)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[520:600, 360:400]) + + def test_NR_DEC_p1_04_j2k_50_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 0, 1024, 1024), rlevel=2) + odata = jp2k.read(rlevel=2) + + np.testing.assert_array_equal(ssdata, odata[0:256, 0:256]) + + def test_NR_DEC_p1_04_j2k_51_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(640, 512, 768, 640), rlevel=2) + odata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(ssdata, odata[160:192, 128:160]) + + def test_NR_DEC_p1_04_j2k_52_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(896, 896, 1024, 1024), rlevel=2) + odata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(ssdata, odata[224:352, 224:352]) + + def test_NR_DEC_p1_04_j2k_53_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(500, 100, 800, 300), rlevel=2) + odata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(ssdata, odata[125:200, 25:75]) + + def test_NR_DEC_p1_04_j2k_54_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(520, 260, 600, 360), rlevel=2) + odata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(ssdata, odata[130:150, 65:90]) + + def test_NR_DEC_p1_04_j2k_55_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(520, 260, 660, 360), rlevel=2) + odata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(ssdata, odata[130:165, 65:90]) + + def test_NR_DEC_p1_04_j2k_56_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(520, 360, 600, 400), rlevel=2) + odata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(ssdata, odata[130:150, 90:100]) + + def test_NR_DEC_p1_04_j2k_57_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + tdata = jp2k.read(tile=63) # last tile + odata = jp2k.read() + np.testing.assert_array_equal(tdata, odata[896:1024, 896:1024]) + + def test_NR_DEC_p1_04_j2k_58_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + tdata = jp2k.read(tile=63, rlevel=2) # last tile + odata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(tdata, odata[224:256, 224:256]) + + def test_NR_DEC_p1_04_j2k_59_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + tdata = jp2k.read(tile=12) # 2nd row, 5th column + odata = jp2k.read() + np.testing.assert_array_equal(tdata, odata[128:256, 512:640]) + + def test_NR_DEC_p1_04_j2k_60_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + tdata = jp2k.read(tile=12, rlevel=1) # 2nd row, 5th column + odata = jp2k.read(rlevel=1) + np.testing.assert_array_equal(tdata, odata[64:128, 256:320]) + + def test_NR_DEC_jp2_36_decode(self): + lst = ('input', + 'nonregression', + 'gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc.patch.jp2') + jfile = opj_data_file('/'.join(lst)) + with warnings.catch_warnings(): + # Invalid component number. + warnings.simplefilter("ignore") + j = Jp2k(jfile) + with self.assertRaises(IOError): + j.read() + + def test_NR_DEC_p1_06_j2k_70_decode(self): + jfile = opj_data_file('input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(9, 9, 12, 12), rlevel=1) + self.assertEqual(ssdata.shape, (1, 1, 3)) + + def test_NR_DEC_p1_06_j2k_71_decode(self): + jfile = opj_data_file('input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(10, 4, 12, 10), rlevel=1) + self.assertEqual(ssdata.shape, (1, 3, 3)) + + def test_NR_DEC_p1_06_j2k_72_decode(self): + jfile = opj_data_file('input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(3, 3, 9, 9), rlevel=1) + self.assertEqual(ssdata.shape, (3, 3, 3)) + + def test_NR_DEC_p1_06_j2k_73_decode(self): + jfile = opj_data_file('input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(4, 4, 7, 7), rlevel=1) + self.assertEqual(ssdata.shape, (2, 2, 3)) + + def test_NR_DEC_p1_06_j2k_74_decode(self): + jfile = opj_data_file('input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(4, 4, 5, 5), rlevel=1) + self.assertEqual(ssdata.shape, (1, 1, 3)) + + def test_NR_DEC_p1_06_j2k_75_decode(self): + # Image size would be 0 x 0. + jfile = opj_data_file('input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + with self.assertRaises((IOError, OSError)): + jp2k.read(area=(9, 9, 12, 12), rlevel=2) + + def test_NR_DEC_p0_04_j2k_85_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 0, 256, 256)) + fulldata = jp2k.read() + np.testing.assert_array_equal(fulldata[0:256, 0:256], ssdata) + + def test_NR_DEC_p0_04_j2k_86_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 128, 128, 256)) + fulldata = jp2k.read() + np.testing.assert_array_equal(fulldata[0:128, 128:256], ssdata) + + def test_NR_DEC_p0_04_j2k_87_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(10, 50, 200, 120)) + fulldata = jp2k.read() + np.testing.assert_array_equal(fulldata[10:200, 50:120], ssdata) + + def test_NR_DEC_p0_04_j2k_88_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(150, 10, 210, 190)) + fulldata = jp2k.read() + np.testing.assert_array_equal(fulldata[150:210, 10:190], ssdata) + + def test_NR_DEC_p0_04_j2k_89_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(80, 100, 150, 200)) + fulldata = jp2k.read() + np.testing.assert_array_equal(fulldata[80:150, 100:200], ssdata) + + def test_NR_DEC_p0_04_j2k_90_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(20, 150, 50, 200)) + fulldata = jp2k.read() + np.testing.assert_array_equal(fulldata[20:50, 150:200], ssdata) + + def test_NR_DEC_p0_04_j2k_91_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 0, 256, 256), rlevel=2) + fulldata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(fulldata[0:64, 0:64], ssdata) + + def test_NR_DEC_p0_04_j2k_92_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 128, 128, 256), rlevel=2) + fulldata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(fulldata[0:32, 32:64], ssdata) + + def test_NR_DEC_p0_04_j2k_93_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(10, 50, 200, 120), rlevel=2) + fulldata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(fulldata[3:50, 13:30], ssdata) + + def test_NR_DEC_p0_04_j2k_94_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(150, 10, 210, 190), rlevel=2) + fulldata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(fulldata[38:53, 3:48], ssdata) + + def test_NR_DEC_p0_04_j2k_95_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(80, 100, 150, 200), rlevel=2) + fulldata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(fulldata[20:38, 25:50], ssdata) + + def test_NR_DEC_p0_04_j2k_96_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(20, 150, 50, 200), rlevel=2) + fulldata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(fulldata[5:13, 38:50], ssdata) + + +if __name__ == "__main__": + unittest.main() diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index 3785af9..3e60cea 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -2,9 +2,6 @@ The tests here do not correspond directly to the OpenJPEG test suite, but seem like logical negative tests to add. """ -# E1101: assertWarns introduced in python 3.2 -# pylint: disable=E1101 - # R0904: Not too many methods in unittest. # pylint: disable=R0904 @@ -15,6 +12,7 @@ import os import re import sys import tempfile +import warnings if sys.hexversion < 0x02070000: import unittest2 as unittest @@ -64,15 +62,15 @@ class TestSuiteNegative(unittest.TestCase): jp2k.get_codestream(header_only=False) self.assertTrue(True) - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") def test_nr_illegalclrtransform(self): """EOC marker is bad""" relpath = 'input/nonregression/illegalcolortransform.j2k' jfile = opj_data_file(relpath) jp2k = Jp2k(jfile) - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") codestream = jp2k.get_codestream(header_only=False) + self.assertEqual(len(w), 1) # Verify that the last segment returned in the codestream is SOD, # not EOC. Codestream parsing should stop when we try to jump to @@ -106,8 +104,6 @@ class TestSuiteNegative(unittest.TestCase): with self.assertRaises(IOError): j.write(data, cbsize=(2, 2048)) - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") def test_exceeded_box(self): """should warn if reading past end of a box""" # Verify that a warning is issued if we read past the end of a box @@ -115,7 +111,8 @@ class TestSuiteNegative(unittest.TestCase): # short. infile = os.path.join(OPJ_DATA_ROOT, 'input/nonregression/mem-b2ace68c-1381.jp2') - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("ignore") Jp2k(infile) @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 2f699e6..2b1271b 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -794,7 +794,6 @@ class TestSuiteWrite(unittest.TestCase): glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) - @unittest.skip("Known failure in openjpeg test suite.") def test_NR_ENC_random_issue_0005_tif_12_encode(self): """NR-ENC-random-issue-0005.tif-12-encode""" # opj_decompress has trouble reading it, but that is not an issue here. diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index bb7da3e..3c325f5 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -35,7 +35,9 @@ else: import glymur from glymur import Jp2k +from . import fixtures from .fixtures import OPJ_DATA_ROOT, opj_data_file +from .fixtures import text_gbr_27, text_gbr_33, text_gbr_34 @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @@ -185,31 +187,23 @@ class TestPrintingNeedsLib(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) + @unittest.skipIf(sys.hexversion < 0x02070000, "Do not bother with 2.6") def test_jp2dump(self): """basic jp2dump test""" with patch('sys.stdout', new=StringIO()) as fake_out: - glymur.jp2dump(self._plain_nemo_file) + glymur.jp2dump(glymur.data.nemo()) actual = fake_out.getvalue().strip() # Get rid of the filename line, as it is not set in stone. lst = actual.split('\n') lst = lst[1:] actual = '\n'.join(lst) - 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) - actual = fake_out.getvalue().strip() - - # Get rid of the filename line, as it is not set in stone. - lst = actual.split('\n') - lst = lst[1:] - actual = '\n'.join(lst) - - self.assertEqual(actual, self.expected_plain) + self.maxDiff = None + if sys.hexversion < 0x02080000: + # Ordered dicts are different in 2.7 + self.assertEqual(actual, fixtures.nemo_dump_full_p27) + else: + self.assertEqual(actual, fixtures.nemo_dump_full_opj2) class TestPrinting(unittest.TestCase): @@ -287,70 +281,25 @@ class TestPrinting(unittest.TestCase): @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") def test_icc_profile(self): - """verify printing of colr box with ICC profile""" + """verify icc profile printing with a jpx""" + # ICC profiles may be used in JP2, but the approximation field should + # be zero unless we have jpx. This file does both. filename = opj_data_file('input/nonregression/text_GBR.jp2') with warnings.catch_warnings(): # brand is 'jp2 ', but has any icc profile. warnings.simplefilter("ignore") jp2 = Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: print(jp2.box[3].box[1]) actual = fake_out.getvalue().strip() - lin27 = ["Colour Specification Box (colr) @ (179, 1339)", - " Method: any ICC profile", - " Precedence: 2", - " Approximation: accurately represents correct " - + "colorspace definition", - " ICC Profile:", - " {'Color Space': 'RGB',", - " 'Connection Space': 'XYZ',", - " 'Creator': u'appl',", - " 'Datetime': " - + "datetime.datetime(2009, 2, 25, 11, 26, 11),", - " 'Device Attributes': 'reflective, glossy, " - + "positive media polarity, color media',", - " 'Device Class': 'display device profile',", - " 'Device Manufacturer': u'appl',", - " 'Device Model': '',", - " 'File Signature': u'acsp',", - " 'Flags': " - + "'not embedded, can be used independently',", - " 'Illuminant': " - + "array([ 0.96420288, 1. , 0.8249054 ]),", - " 'Platform': u'APPL',", - " 'Preferred CMM Type': 1634758764,", - " 'Rendering Intent': 'perceptual',", - " 'Size': 1328,", - " 'Version': '2.2.0'}"] - lin33 = ["Colour Specification Box (colr) @ (179, 1339)", - " Method: any ICC profile", - " Precedence: 2", - " Approximation: accurately represents correct " - + "colorspace definition", - " ICC Profile:", - " {'Size': 1328,", - " 'Preferred CMM Type': 1634758764,", - " 'Version': '2.2.0',", - " 'Device Class': 'display device profile',", - " 'Color Space': 'RGB',", - " 'Connection Space': 'XYZ',", - " 'Datetime': " - + "datetime.datetime(2009, 2, 25, 11, 26, 11),", - " 'File Signature': 'acsp',", - " 'Platform': 'APPL',", - " 'Flags': 'not embedded, can be used " - + "independently',", - " 'Device Manufacturer': 'appl',", - " 'Device Model': '',", - " 'Device Attributes': 'reflective, glossy, " - + "positive media polarity, color media',", - " 'Rendering Intent': 'perceptual',", - " 'Illuminant': " - + "array([ 0.96420288, 1. , 0.8249054 ]),", - " 'Creator': 'appl'}"] + if sys.hexversion < 0x03000000: + expected = text_gbr_27 + elif sys.hexversion < 0x03040000: + expected = text_gbr_33 + else: + expected = text_gbr_34 - lines = lin27 if sys.hexversion < 0x03000000 else lin33 - expected = '\n'.join(lines) self.assertEqual(actual, expected) @unittest.skipIf(OPJ_DATA_ROOT is None, @@ -980,7 +929,7 @@ class TestPrinting(unittest.TestCase): @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") def test_jpx_approx_icc_profile(self): - """verify jpx with approx field equal to zero""" + """verify icc profile printing with a jpx""" # ICC profiles may be used in JP2, but the approximation field should # be zero unless we have jpx. This file does both. filename = opj_data_file('input/nonregression/text_GBR.jp2') @@ -992,34 +941,13 @@ class TestPrinting(unittest.TestCase): with patch('sys.stdout', new=StringIO()) as fake_out: print(jp2.box[3].box[1]) actual = fake_out.getvalue().strip() - lines = ["Colour Specification Box (colr) @ (179, 1339)", - " Method: any ICC profile", - " Precedence: 2", - " Approximation: accurately represents " - + "correct colorspace definition", - " ICC Profile:", - " {'Size': 1328,", - " 'Preferred CMM Type': 1634758764,", - " 'Version': '2.2.0',", - " 'Device Class': 'display device profile',", - " 'Color Space': 'RGB',", - " 'Connection Space': 'XYZ',", - " 'Datetime': " - + "datetime.datetime(2009, 2, 25, 11, 26, 11),", - " 'File Signature': 'acsp',", - " 'Platform': 'APPL',", - " 'Flags': 'not embedded, " - + "can be used independently',", - " 'Device Manufacturer': 'appl',", - " 'Device Model': '',", - " 'Device Attributes': 'reflective, glossy, " - + "positive media polarity, color media',", - " 'Rendering Intent': 'perceptual',", - " 'Illuminant': array([ 0.96420288, 1. ," - + " 0.8249054 ]),", - " 'Creator': 'appl'}"] + if sys.hexversion < 0x03000000: + expected = text_gbr_27 + elif sys.hexversion < 0x03040000: + expected = text_gbr_33 + else: + expected = text_gbr_34 - expected = '\n'.join(lines) self.assertEqual(actual, expected) @unittest.skipIf(OPJ_DATA_ROOT is None, diff --git a/glymur/version.py b/glymur/version.py index d3d87db..2e6a8cb 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -15,7 +15,7 @@ from .lib import openjp2 as opj2 # Do not change the format of this next line! Doing so risks breaking # setup.py -version = "0.5.10" +version = "0.5.11" _sv = LooseVersion(version) version_tuple = _sv.version From f37da173e310bf2a387834b4fcf981d39e4e997b Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 15 May 2014 06:58:32 -0400 Subject: [PATCH 223/326] Releasing 0.5.12. Fix a few documentation warts. Added changelog into RST documentation. Restored glymur.lib.openjp2.*_v3 functions removed in 0.5.11 --- CHANGES.txt | 2 + docs/source/conf.py | 2 +- docs/source/detailed_installation.rst | 9 +- docs/source/how_do_i.rst | 23 +- docs/source/index.rst | 4 +- docs/source/introduction.rst | 4 +- docs/source/whatsnew/0.5.rst | 50 +++ docs/source/whatsnew/index.rst | 11 + glymur/lib/openjp2.py | 54 +++- glymur/lib/test/test_openjp2_svn.py | 443 ++++++++++++++++++++++++++ glymur/version.py | 2 +- 11 files changed, 578 insertions(+), 26 deletions(-) create mode 100644 docs/source/whatsnew/0.5.rst create mode 100644 docs/source/whatsnew/index.rst create mode 100644 glymur/lib/test/test_openjp2_svn.py diff --git a/CHANGES.txt b/CHANGES.txt index 4799831..82438ca 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,5 @@ +May 18, 2014 - v0.5.12 Restored _v3 functions removed in 0.5.11 + May 09, 2014 - v0.5.11 Added support for Python 3.4, OpenJPEG 2.0.1, and OpenJPEG 2.1.0. diff --git a/docs/source/conf.py b/docs/source/conf.py index 35fe2c0..53d1c0b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -78,7 +78,7 @@ copyright = u'2013, John Evans' # The short X.Y version. version = '0.5' # The full version, including alpha/beta/rc tags. -release = '0.5.11' +release = '0.5.12' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index 15c5ed4..cf855df 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -1,20 +1,21 @@ ---------------------------------- Advanced Installation Instructions ---------------------------------- -Most users won't need to read this! You've been warned... '''''''''''''''''''''' Glymur Configuration '''''''''''''''''''''' The default glymur installation process relies upon OpenJPEG being -properly installed on your system as a shared library. You need -at least version 1.5 in order to read and write JPEG 2000 files. +properly installed on your system as a shared library. If you have OpenJPEG +installed through your system's package manager on linux or if you use MacPorts +on the mac, you are probably already set to go. But if you have OpenJPEG +installed into a non-standard place or if you use windows, then read on. Glymur uses ctypes to access the openjp2/openjpeg libraries, and because ctypes accesses libraries in a platform-dependent manner, it is recommended that if you compile and install OpenJPEG into a -non-standard location, you should create a configuration file to +non-standard location, you should then create a configuration file to help Glymur properly find the openjpeg or openjp2 libraries (linux users or macports users don't need to bother with this if you are using OpenJPEG as provided by your package manager). The configuration diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index 5cf2c8f..db87774 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -68,10 +68,11 @@ The **append** method can add an XML box as shown below:: ... add metadata in a more general fashion? =========================================== -An existing raw codestream (or JP2 file) can be wrapped (re-wrapped) in a -user-defined set of JP2 boxes. To get just a minimal JP2 jacket on the -codestream provided by `goodstuff.j2k` (a file consisting of a raw codestream), -you can use the **wrap** method with no box argument: :: +An existing raw codestream or JP2 file can be wrapped (re-wrapped in the case +of JP2) in a user-defined set of JP2 boxes. To get just a minimal +JP2 jacket on the codestream provided by `goodstuff.j2k` (a file +consisting of just a raw codestream), you can use the **wrap** method +with no box argument: :: >>> import glymur >>> jfile = glymur.data.goodstuff() @@ -106,10 +107,12 @@ layer (the signature, file type, JP2 header, and contiguous codestream), with two additional boxes (image header and color specification) contained in the JP2 header superbox. -XML boxes are not in the minimal set of box requirements for the JP2 format, so -in order to add an XML box into the mix before the codestream box, we'll need to -re-specify all of the boxes. If you already have a JP2 jacket in place, you can just reuse that, -though. Take the following example content in an XML file `favorites.xml` : :: +XML boxes are not in the minimal set of box requirements for the +JP2 format, so in order to add an XML box into the mix before the +codestream box, we'll need to re-specify all of the boxes. If you +already have a JP2 jacket in place, you can just reuse that, though. +Take the following example content in an XML file `favorites.xml` +: :: @@ -219,8 +222,8 @@ Here's how the Preview application on the mac shows the RGBA image. .. image:: goodstuff_alpha.png -work with XMP UUIDs? -==================== +... work with XMP UUIDs? +======================== The example JP2 file shipped with glymur has an XMP UUID. :: >>> import glymur diff --git a/docs/source/index.rst b/docs/source/index.rst index 8536be8..3c556d7 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,8 +15,10 @@ Contents: introduction detailed_installation how_do_i - roadmap api + roadmap + whatsnew/index + ------------------ Indices and tables diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index bce4598..382f3c3 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -3,7 +3,7 @@ Glymur: a Python interface for JPEG 2000 ---------------------------------------- **Glymur** is an interface to the OpenJPEG library -which allows one to read and write JPEG 2000 files from within Python. +which allows one to read and write JPEG 2000 files from Python. Glymur supports both reading and writing of JPEG 2000 images, but writing JPEG 2000 images is currently limited to images that can fit in memory @@ -18,7 +18,7 @@ Glymur works on Python 2.6, 2.7, 3.3, and 3.4. OpenJPEG Installation ===================== Glymur will read JPEG 2000 images with versions 1.3, 1.4, 1.5, 2.0, and 2.1 of -OpenJPEG. Writing images is only supported with the 1.5 or better, however, +OpenJPEG. Writing images is only supported with OpenJPEG 1.5 or better, however, and version 2.1 is strongly recommended. For more information about OpenJPEG, please consult http://www.openjpeg.org. diff --git a/docs/source/whatsnew/0.5.rst b/docs/source/whatsnew/0.5.rst new file mode 100644 index 0000000..86ce1dd --- /dev/null +++ b/docs/source/whatsnew/0.5.rst @@ -0,0 +1,50 @@ +===================== +Changes in glymur 0.5 +===================== + +Changes in 0.5.12 +================= + +* Minor documentation fixes for grammar and style. +* The functions removed in 0.5.11 due to API changes in OpenJPEG 2.1.0 were + restored for backwards compatibility. They are deprecated, though, and will + be removed in 0.6.0. + + * ``glymur.lib.openjp2.stream_create_default_file_stream_v3`` + * ``glymur.lib.openjp2.opj.stream_destroy_v3`` + + +Changes in 0.5.11 +================= + +* Added support for Python 3.4. +* OpenJPEG 1.5.2 and 2.0.1 are officially supported. +* OpenJPEG 2.1.0 is officially supported, but the ABI changes introduced by + OpenJPEG 2.1.0 required corresponding changes to glymur's ctypes interface. + The functions + + * ``glymur.lib.openjp2.stream_create_default_file_stream_v3`` + * ``glymur.lib.openjp2.opj.stream_destroy_v3`` + + functions were renamed to + + * ``glymur.lib.openjp2.stream_create_default_file_stream`` + * ``glymur.lib.openjp2.opj.stream_destroy`` + + in order to follow OpenJPEG's upstream changes. Unless you were using the + svn version of OpenJPEG, you should not be affected by this. + + +Changes in 0.5.10 +================= + +* Fixed bad warning issued when an unsupported reader requirement box mask + length was encountered. + +Changes in 0.5.9 +================ + +* Fixed bad library load on linux as a result of botched 0.5.8 release. + This release was primarily aimed at supporting SunPy. + + diff --git a/docs/source/whatsnew/index.rst b/docs/source/whatsnew/index.rst new file mode 100644 index 0000000..7a22fac --- /dev/null +++ b/docs/source/whatsnew/index.rst @@ -0,0 +1,11 @@ +.. _whatsnew: + +********************** +"What's new" documents +********************** + +These document the changes between minor (or major) versions of glymur. + +.. toctree:: + + 0.5 diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py index a28ddfd..69589f5 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -1293,7 +1293,7 @@ def start_compress(codec, image, stream): def _stream_create_default_file_stream_2p0(fptr, isa_read_stream): - """Wraps openjp2 library function opj_stream_create_default_vile_stream. + """Wraps openjp2 library function opj_stream_create_default_file_stream. Sets the stream to be a file stream. @@ -1318,7 +1318,7 @@ def _stream_create_default_file_stream_2p0(fptr, isa_read_stream): def _stream_create_default_file_stream_2p1(fname, isa_read_stream): - """Wraps openjp2 library function opj_stream_create_default_vile_stream. + """Wraps openjp2 library function opj_stream_create_default_file_stream. Sets the stream to be a file stream. @@ -1343,11 +1343,6 @@ def _stream_create_default_file_stream_2p1(fname, isa_read_stream): read_stream) return stream -if re.match(r'''2.0''', version()): - stream_create_default_file_stream = _stream_create_default_file_stream_2p0 -else: - stream_create_default_file_stream = _stream_create_default_file_stream_2p1 - def stream_destroy(stream): """Wraps openjp2 library function opj_stream_destroy. @@ -1362,6 +1357,51 @@ def stream_destroy(stream): OPENJP2.opj_stream_destroy.restype = ctypes.c_void_p OPENJP2.opj_stream_destroy(stream) +if re.match(r'''2.0''', version()): + # We must have the 2.0.x + stream_create_default_file_stream = _stream_create_default_file_stream_2p0 +else: + # We must have version 2.1. + stream_create_default_file_stream = _stream_create_default_file_stream_2p1 + +# The _v3 functions existed only in the SVN version of OpenJPEG, before 2.1.0 +# was released +stream_create_default_file_stream_v3 = _stream_create_default_file_stream_2p1 +stream_create_default_file_stream_v3.__doc__ = r""" +Wraps openjp2 library function opj_stream_create_default_file_stream_v3. + + Sets the stream to be a file stream. + + This function is deprecated and will be removed in the 0.6.0 version of + glymur. Please use stream_create_default_file_stream instead. + + Parameters + ---------- + fname : str + Specifies a file. + isa_read_stream: bool + True (read) or False (write) + + Returns + ------- + stream : stream_t + An OpenJPEG file stream. + """ + +stream_destroy_v3 = stream_destroy +stream_destroy_v3.__doc__ = r""" +Wraps openjp2 library function opj_stream_destroy. + + Destroys the stream created by create_stream. + + This function is deprecated and wil be removed in the 0.6.0 version of + glymur. Please use stream_destroy instead. + + Parameters + ---------- + stream : STREAM_TYPE_P + The file stream. + """ def write_tile(codec, tile_index, data, data_size, stream): """Wraps openjp2 library function opj_write_tile. diff --git a/glymur/lib/test/test_openjp2_svn.py b/glymur/lib/test/test_openjp2_svn.py new file mode 100644 index 0000000..e52fe80 --- /dev/null +++ b/glymur/lib/test/test_openjp2_svn.py @@ -0,0 +1,443 @@ +""" +Tests for functions that wrap SVN version of libopenjp2 (before release of +2.1.0) +""" +# 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 re +import sys +import tempfile + +if sys.hexversion < 0x02070000: + import unittest2 as unittest +else: + import unittest + +import numpy as np + +import glymur +from glymur.lib import openjp2 + +if re.match("2.0", glymur.version.openjpeg_version): + OPENJP2_IS_V2_OFFICIAL = True +else: + OPENJP2_IS_V2_OFFICIAL = False + +@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") +@unittest.skipIf(openjp2.OPENJP2 is None, + "Missing openjp2 library.") +@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL, "API followed here specific to V2.1") +class TestOpenJP2(unittest.TestCase): + """Test openjp2 library functionality. + + Some tests correspond to those in the openjpeg test suite. + """ + + 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) + self.assertEqual(cparams.cblockh_init, 64) + self.assertEqual(cparams.numresolution, 6) + self.assertEqual(cparams.subsampling_dx, 1) + self.assertEqual(cparams.subsampling_dy, 1) + self.assertEqual(cparams.mode, 0) + self.assertEqual(cparams.prog_order, glymur.core.LRCP) + self.assertEqual(cparams.roi_shift, 0) + self.assertEqual(cparams.cp_tx0, 0) + self.assertEqual(cparams.cp_ty0, 0) + + self.assertEqual(cparams.irreversible, 0) + + 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) + self.assertEqual(dparams.DA_x1, 0) + self.assertEqual(dparams.DA_y1, 0) + + def tile_macro(self, 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): + """fixture called by the test_rtaX methods""" + dparam = openjp2.set_default_decoder_parameters() + + infile = filename.encode() + nelts = openjp2.PATH_LEN - len(infile) + infile += b'0' * nelts + dparam.infile = infile + + dparam.decod_format = codec_format + + codec = openjp2.create_decompress(codec_format) + + openjp2.set_info_handler(codec, None) + openjp2.set_warning_handler(codec, None) + openjp2.set_error_handler(codec, None) + + stream = openjp2.stream_create_default_file_stream_v3(filename, True) + + openjp2.setup_decoder(codec, dparam) + image = openjp2.read_header(stream, codec) + + cstr_info = openjp2.get_cstr_info(codec) + + tile_ul = 0 + tile_ur = cstr_info.contents.tw - 1 + tile_lr = cstr_info.contents.tw * cstr_info.contents.th - 1 + tile_ll = tile_lr - cstr_info.contents.tw + + self.tile_macro(codec, stream, image, tile_ul) + self.tile_macro(codec, stream, image, tile_ur) + self.tile_macro(codec, stream, image, tile_lr) + self.tile_macro(codec, stream, image, tile_ll) + + openjp2.destroy_cstr_info(cstr_info) + + 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.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + ttx0_setup(tfile.name) + self.assertTrue(True) + + def test_ttd0(self): + """Runs test designated ttd0 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + + # Produce the tte0 output file for ttd0 input. + ttx0_setup(tfile.name) + + kwargs = {'x0': 0, + 'y0': 0, + 'x1': 1000, + 'y1': 1000, + 'filename': tfile.name, + 'codec_format': openjp2.CODEC_J2K} + tile_decoder(**kwargs) + self.assertTrue(True) + + def xtx1_setup(self, filename): + """Runs tests tte1, rta1.""" + kwargs = {'filename': filename, + 'codec': openjp2.CODEC_J2K, + 'comp_prec': 8, + 'irreversible': 1, + 'num_comps': 3, + 'image_height': 256, + 'image_width': 256, + 'tile_height': 128, + 'tile_width': 128} + 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: + self.xtx1_setup(tfile.name) + + def test_ttd1(self): + """Runs test designated ttd1 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + + # Produce the tte0 output file for ttd0 input. + self.xtx1_setup(tfile.name) + + kwargs = {'x0': 0, + 'y0': 0, + 'x1': 128, + 'y1': 128, + 'filename': tfile.name, + '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: + self.xtx1_setup(tfile.name) + + 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.""" + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + xtx2_setup(tfile.name) + self.assertTrue(True) + + def test_ttd2(self): + """Runs test designated ttd2 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + # Produce the tte0 output file for ttd0 input. + xtx2_setup(tfile.name) + + kwargs = {'x0': 0, + 'y0': 0, + 'x1': 128, + 'y1': 128, + 'filename': tfile.name, + '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: + xtx2_setup(tfile.name) + + 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: + xtx3_setup(tfile.name) + self.assertTrue(True) + + def test_rta3(self): + """Runs test designated rta3 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + xtx3_setup(tfile.name) + + 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.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + xtx4_setup(tfile.name) + self.assertTrue(True) + + def test_rta4(self): + """Runs test designated rta4 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + xtx4_setup(tfile.name) + + 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.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + xtx5_setup(tfile.name) + self.assertTrue(True) + + def test_rta5(self): + """Runs test designated rta5 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + xtx5_setup(tfile.name) + + 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/version.py b/glymur/version.py index 2e6a8cb..4a9e4e5 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -15,7 +15,7 @@ from .lib import openjp2 as opj2 # Do not change the format of this next line! Doing so risks breaking # setup.py -version = "0.5.11" +version = "0.5.12" _sv = LooseVersion(version) version_tuple = _sv.version From 4cf9f7841e94b14a68d19f8ba220ade3941cf1f3 Mon Sep 17 00:00:00 2001 From: jevans Date: Sun, 18 May 2014 22:01:28 -0400 Subject: [PATCH 224/326] Refactored test_opj_suite.py --- glymur/test/test_opj_suite.py | 6093 ---------------------------- glymur/test/test_opj_suite_2p1.py | 389 ++ glymur/test/test_opj_suite_dump.py | 5320 ++++++++++++++++++++++++ glymur/test/test_opj_suite_warn.py | 366 ++ 4 files changed, 6075 insertions(+), 6093 deletions(-) create mode 100644 glymur/test/test_opj_suite_2p1.py create mode 100644 glymur/test/test_opj_suite_dump.py create mode 100644 glymur/test/test_opj_suite_warn.py diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 88669ce..1e658a3 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -395,5756 +395,6 @@ class TestSuite(unittest.TestCase): self.assertTrue(True) -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -class TestSuiteWarn(unittest.TestCase): - """ - All these tests issue warnings. - """ - - def setUp(self): - pass - - def tearDown(self): - pass - - def test_NR_text_GBR_dump(self): - # brand is 'jp2 ', but has any icc profile. - # Verify the warning on python3, but ignore it otherwise. - jfile = opj_data_file('input/nonregression/text_GBR.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - lst = ['jP ', 'ftyp', 'rreq', 'jp2h', - 'uuid', 'uuid', 'uuid', 'uuid', 'jp2c'] - self.assertEqual(ids, lst) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'res ']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Reader requirements. - # Compositing layer uses any icc profile - self.assertTrue(44 in jp2.box[2].standard_flag) - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 400) - self.assertEqual(jp2.box[3].box[0].width, 400) - self.assertEqual(jp2.box[3].box[0].num_components, 3) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.ANY_ICC_PROFILE) - self.assertEqual(jp2.box[3].box[1].precedence, 2) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 1328) - self.assertIsNone(jp2.box[3].box[1].colorspace) - - # UUID boxes. All mentioned in the RREQ box. - self.assertEqual(jp2.box[2].vendor_feature[0], jp2.box[4].uuid) - self.assertEqual(jp2.box[2].vendor_feature[1], jp2.box[5].uuid) - self.assertEqual(jp2.box[2].vendor_feature[2], jp2.box[6].uuid) - self.assertEqual(jp2.box[2].vendor_feature[3], jp2.box[7].uuid) - - c = jp2.box[8].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 400) - self.assertEqual(c.segment[1].ysiz, 400) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 6) # layers = 6 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - def test_ETS_C1P0_p0_02_j2k(self): - jfile = opj_data_file('input/conformance/p0_02.j2k') - jp2k = Jp2k(jfile) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - jpdata = jp2k.read(rlevel=0) - - pgxfile = opj_data_file('baseline/conformance/c1p0_02_0.pgx') - pgxdata = read_pgx(pgxfile) - - np.testing.assert_array_equal(jpdata, pgxdata) - - def test_NR_DEC_broken_jp2_4_decode(self): - jfile = opj_data_file('input/nonregression/broken.jp2') - with warnings.catch_warnings(record=True) as w: - # colr box has bad length. - warnings.simplefilter('ignore') - jp2 = Jp2k(jfile) - with self.assertRaises(IOError): - jp2.read() - self.assertTrue(True) - - def test_NR_DEC_broken3_jp2_6_decode(self): - jfile = opj_data_file('input/nonregression/broken3.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - # colr box has bad length. - j = Jp2k(jfile) - - with self.assertRaises(IOError): - j.read() - -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -class TestSuiteDumpWarnings(unittest.TestCase): - - def setUp(self): - pass - - def tearDown(self): - pass - - def test_NR_broken_jp2_dump(self): - jfile = opj_data_file('input/nonregression/broken.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - # colr box has bad length. - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 152) - self.assertEqual(jp2.box[2].box[0].width, 203) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # not allowed? - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 203) - self.assertEqual(c.segment[1].ysiz, 152) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (203, 152)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COM: comment - # Registration - self.assertEqual(c.segment[2].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[2].ccme.decode('latin-1'), - "Creator: JasPer Version 1.701.0") - - # COD: Coding style default - self.assertFalse(c.segment[3].scod & 2) # no sop - self.assertFalse(c.segment[3].scod & 4) # no eph - self.assertEqual(c.segment[3].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[3].layers, 1) # layers = 1 - self.assertEqual(c.segment[3].spcod[3], 1) # mct - self.assertEqual(c.segment[3].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[3].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcod[7] & 0x0020) - self.assertEqual(c.segment[3].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[3].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) - self.assertEqual(c.segment[4].guard_bits, 2) - self.assertEqual(c.segment[4].mantissa, [0] * 16) - self.assertEqual(c.segment[4].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 1) - self.assertEqual(c.segment[5].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[5].mantissa, [0] * 16) - self.assertEqual(c.segment[5].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 2) - self.assertEqual(c.segment[6].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[6].mantissa, [0] * 16) - self.assertEqual(c.segment[6].exponent, - [8] + [9, 9, 10] * 5) - - def test_NR_broken2_jp2_dump(self): - # Invalid marker ID on codestream. - jfile = opj_data_file('input/nonregression/broken2.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - jp2 = Jp2k(jfile) - - self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') - - def test_NR_broken3_jp2_dump(self): - jfile = opj_data_file('input/nonregression/broken3.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 152) - self.assertEqual(jp2.box[2].box[0].width, 203) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 203) - self.assertEqual(c.segment[1].ysiz, 152) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (203, 152)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COM: comment - # Registration - self.assertEqual(c.segment[2].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[2].ccme.decode('latin-1'), - "Creator: JasPer Vers)on 1.701.0") - - # COD: Coding style default - self.assertFalse(c.segment[3].scod & 2) # no sop - self.assertFalse(c.segment[3].scod & 4) # no eph - self.assertEqual(c.segment[3].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[3].layers, 1) # layers = 1 - self.assertEqual(c.segment[3].spcod[3], 1) # mct - self.assertEqual(c.segment[3].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[3].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcod[7] & 0x0020) - self.assertEqual(c.segment[3].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[3].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) - self.assertEqual(c.segment[4].guard_bits, 2) - self.assertEqual(c.segment[4].mantissa, [0] * 16) - self.assertEqual(c.segment[4].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 1) - self.assertEqual(c.segment[5].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[5].mantissa, [0] * 16) - self.assertEqual(c.segment[5].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 2) - self.assertEqual(c.segment[6].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[6].mantissa, [0] * 16) - self.assertEqual(c.segment[6].exponent, - [8] + [9, 9, 10] * 5) - - def test_NR_broken4_jp2_dump(self): - # Has an invalid marker in the main header - jfile = opj_data_file('input/nonregression/broken4.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - jp2 = Jp2k(jfile) - - self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') - - def test_NR_gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc_patch_jp2(self): - lst = ['input', 'nonregression', - 'gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc.patch.jp2'] - jfile = opj_data_file('/'.join(lst)) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - Jp2k(jfile) - - def test_NR_gdal_fuzzer_check_comp_dx_dy_jp2_dump(self): - lst = ['input', 'nonregression', 'gdal_fuzzer_check_comp_dx_dy.jp2'] - jfile = opj_data_file('/'.join(lst)) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - Jp2k(jfile) - - def test_NR_gdal_fuzzer_check_number_of_tiles(self): - # Has an impossible tiling setup. - lst = ['input', 'nonregression', - 'gdal_fuzzer_check_number_of_tiles.jp2'] - jfile = opj_data_file('/'.join(lst)) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - Jp2k(jfile) - - def test_NR_gdal_fuzzer_unchecked_numresolutions_dump(self): - # Has an invalid number of resolutions. - lst = ['input', 'nonregression', - 'gdal_fuzzer_unchecked_numresolutions.jp2'] - jfile = opj_data_file('/'.join(lst)) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - Jp2k(jfile) - - def test_NR_DEC_issue188_beach_64bitsbox_jp2_41_decode(self): - # Has an 'XML ' box instead of 'xml '. Yes that is pedantic, but it - # really does deserve a warning. - relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2' - jfile = opj_data_file(relpath) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - Jp2k(jfile).read() - -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -class TestSuiteDump(unittest.TestCase): - - def setUp(self): - pass - - def tearDown(self): - pass - - def test_NR_file409752(self): - jfile = opj_data_file('input/nonregression/file409752.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 243) - self.assertEqual(jp2.box[2].box[0].width, 720) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.YCC) - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 720) - self.assertEqual(c.segment[1].ysiz, 243) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (720, 243)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (2, 1), (2, 1)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 128)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, - [1816, 1792, 1792, 1724, 1770, 1770, 1724, 1868, - 1868, 1892, 3, 3, 69, 2002, 2002, 1889]) - self.assertEqual(c.segment[3].exponent, - [13] * 4 + [12] * 3 + [11] * 3 + [9] * 6) - - def test_NR_p0_01_dump(self): - jfile = opj_data_file('input/conformance/p0_01.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # Segment IDs. - actual = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'QCD', 'COD', 'SOT', 'SOD', 'EOC'] - self.assertEqual(actual, expected) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 128) - self.assertEqual(c.segment[1].ysiz, 128) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # QCD: Quantization default - self.assertEqual(c.segment[2].sqcd & 0x1f, 0) - self.assertEqual(c.segment[2].guard_bits, 2) - self.assertEqual(c.segment[2].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - self.assertEqual(c.segment[2].mantissa, - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - - # COD: Coding style default - self.assertFalse(c.segment[3].scod & 2) # no sop - self.assertFalse(c.segment[3].scod & 4) # no eph - self.assertEqual(c.segment[3].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[3].layers, 1) # layers = 1 - self.assertEqual(c.segment[3].spcod[3], 0) # mct - self.assertEqual(c.segment[3].spcod[4], 3) # layers - self.assertEqual(tuple(c.segment[3].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcod[7] & 0x0020) - self.assertEqual(c.segment[3].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # SOT: start of tile part - self.assertEqual(c.segment[4].isot, 0) - self.assertEqual(c.segment[4].psot, 7314) - self.assertEqual(c.segment[4].tpsot, 0) - self.assertEqual(c.segment[4].tnsot, 1) - - def test_NR_p0_02_dump(self): - jfile = opj_data_file('input/conformance/p0_02.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 127) - self.assertEqual(c.segment[1].ysiz, 126) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (127, 126)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(2, 1)]) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) # sop - self.assertTrue(c.segment[2].scod & 4) # eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 6) # layers = 6 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 3) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertTrue(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertTrue(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - - # COC: Coding style component - self.assertEqual(c.segment[3].ccoc, 0) - self.assertEqual(c.segment[3].spcoc[0], 3) # levels - self.assertEqual(tuple(c.segment[3].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[3].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcoc[3] & 0x08) - # Predictable termination - self.assertTrue(c.segment[3].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertTrue(c.segment[3].spcoc[3] & 0x0020) - self.assertEqual(c.segment[3].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[4].guard_bits, 3) - self.assertEqual(c.segment[4].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - self.assertEqual(c.segment[4].mantissa, - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - - # COM: comment - # Registration - self.assertEqual(c.segment[5].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[5].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # One unknown marker - self.assertEqual(c.segment[6].marker_id, '0xff30') - - # SOT: start of tile part - self.assertEqual(c.segment[7].isot, 0) - self.assertEqual(c.segment[7].psot, 6047) - self.assertEqual(c.segment[7].tpsot, 0) - self.assertEqual(c.segment[7].tnsot, 1) - - # SOD: start of data - # Just one. - self.assertEqual(c.segment[8].marker_id, 'SOD') - - # SOP, EPH - sop = [x.marker_id for x in c.segment if x.marker_id == 'SOP'] - eph = [x.marker_id for x in c.segment if x.marker_id == 'EPH'] - self.assertEqual(len(sop), 24) - self.assertEqual(len(eph), 24) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_p0_03_dump(self): - jfile = opj_data_file('input/conformance/p0_03.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 256) - self.assertEqual(c.segment[1].ysiz, 256) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (4,)) - # signed - self.assertEqual(c.segment[1].signed, (True,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) - self.assertEqual(c.segment[2].layers, 8) # 8 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 1) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 1) # scalar implicit - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].exponent, [0]) - self.assertEqual(c.segment[3].mantissa, [0]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[4].cqcc, 0) - self.assertEqual(c.segment[4].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[4].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[4].exponent, [4, 5, 5, 6]) - self.assertEqual(c.segment[4].mantissa, [0, 0, 0, 0]) - - # POD: progression order change - self.assertEqual(c.segment[5].rspod, (0,)) - self.assertEqual(c.segment[5].cspod, (0,)) - self.assertEqual(c.segment[5].lyepod, (8,)) - self.assertEqual(c.segment[5].repod, (33,)) - self.assertEqual(c.segment[5].cdpod, (255,)) - self.assertEqual(c.segment[5].ppod, (glymur.core.LRCP,)) - - # CRG: component registration - self.assertEqual(c.segment[6].xcrg, (65424,)) - self.assertEqual(c.segment[6].ycrg, (32558,)) - - # COM: comment - # Registration - self.assertEqual(c.segment[7].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[7].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # COM: comment - # Registration - self.assertEqual(c.segment[8].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[8].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000," - + "2001 Algo Vision Technology") - - # COM: comment - # Registration - self.assertEqual(c.segment[9].rcme, glymur.core.RCME_BINARY) - # Comment value - self.assertEqual(len(c.segment[9].ccme), 62) - - # TLM (tile-part length) - self.assertEqual(c.segment[10].ztlm, 0) - self.assertEqual(c.segment[10].ttlm, (0, 1, 2, 3)) - self.assertEqual(c.segment[10].ptlm, (4267, 2117, 4080, 2081)) - - # SOT: start of tile part - self.assertEqual(c.segment[11].isot, 0) - self.assertEqual(c.segment[11].psot, 4267) - self.assertEqual(c.segment[11].tpsot, 0) - self.assertEqual(c.segment[11].tnsot, 1) - - # RGN: region of interest - self.assertEqual(c.segment[12].crgn, 0) - self.assertEqual(c.segment[12].srgn, 0) - self.assertEqual(c.segment[12].sprgn, 7) - - # SOD: start of data - # Just one. - self.assertEqual(c.segment[13].marker_id, 'SOD') - - def test_NR_p0_04_dump(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 640) - self.assertEqual(c.segment[1].ysiz, 480) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (640, 480)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (1, 1), (1, 1)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 20) # 20 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 6) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(c.segment[2].precinct_size, - [(128, 128), (128, 128), (128, 128), (128, 128), - (128, 128), (128, 128), (128, 128)]) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # scalar expounded - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].exponent, - [16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, - 11, 11, 11, 11, 11, 11]) - self.assertEqual(c.segment[3].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, 1845, - 1868, 1925, 1925, 2007, 32, 32, 131, 2002, 2002, - 1888]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[4].cqcc, 1) - # quantization type - self.assertEqual(c.segment[4].sqcc & 0x1f, 2) # none - self.assertEqual(c.segment[4].guard_bits, 3) - self.assertEqual(c.segment[4].exponent, - [14, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, - 9, 9, 9, 9, 9, 9]) - self.assertEqual(c.segment[4].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, - 1845, 1868, 1925, 1925, 2007, 32, 32, 131, 2002, - 2002, 1888]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 2) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 2) # none - self.assertEqual(c.segment[5].guard_bits, 3) - self.assertEqual(c.segment[5].exponent, - [14, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, - 9, 9, 9, 9, 9, 9]) - self.assertEqual(c.segment[5].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, - 1845, 1868, 1925, 1925, 2007, 32, 32, 131, 2002, - 2002, 1888]) - - # COM: comment - # Registration - self.assertEqual(c.segment[6].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[6].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # SOT: start of tile part - self.assertEqual(c.segment[7].isot, 0) - self.assertEqual(c.segment[7].psot, 264383) - self.assertEqual(c.segment[7].tpsot, 0) - self.assertEqual(c.segment[7].tnsot, 1) - - # SOD: start of data - # Just one. - self.assertEqual(c.segment[8].marker_id, 'SOD') - - def test_NR_p0_05_dump(self): - jfile = opj_data_file('input/conformance/p0_05.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1024) - self.assertEqual(c.segment[1].ysiz, 1024) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1024, 1024)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (1, 1), (2, 2), (2, 2)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) - self.assertEqual(c.segment[2].layers, 7) # 7 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 6) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # COC: Coding style component - self.assertEqual(c.segment[3].ccoc, 1) - self.assertEqual(c.segment[3].spcoc[0], 3) # levels - self.assertEqual(tuple(c.segment[3].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcoc[3] & 0x0020) - self.assertEqual(c.segment[3].spcoc[4], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - - # COC: Coding style component - self.assertEqual(c.segment[4].ccoc, 3) - self.assertEqual(c.segment[4].spcoc[0], 6) # levels - self.assertEqual(tuple(c.segment[4].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[4].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[4].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[4].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[4].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[4].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[4].spcoc[3] & 0x0020) - self.assertEqual(c.segment[4].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[5].sqcd & 0x1f, 2) # scalar expounded - self.assertEqual(c.segment[5].guard_bits, 3) - self.assertEqual(c.segment[5].exponent, - [16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, - 11, 11, 11, 11, 11, 11]) - self.assertEqual(c.segment[5].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, - 1845, 1868, 1925, 1925, 2007, 32, 32, 131, 2002, - 2002, 1888]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 0) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 1) # scalar derived - self.assertEqual(c.segment[6].guard_bits, 3) - self.assertEqual(c.segment[6].exponent, [14]) - self.assertEqual(c.segment[6].mantissa, [0]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[7].cqcc, 3) - # quantization type - self.assertEqual(c.segment[7].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[7].guard_bits, 3) - self.assertEqual(c.segment[7].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, - 9, 9, 10]) - self.assertEqual(c.segment[7].mantissa, [0] * 19) - - # COM: comment - # Registration - self.assertEqual(c.segment[8].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[8].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # TLM (tile-part length) - self.assertEqual(c.segment[9].ztlm, 0) - self.assertEqual(c.segment[9].ttlm, (0,)) - self.assertEqual(c.segment[9].ptlm, (1310540,)) - - # SOT: start of tile part - self.assertEqual(c.segment[10].isot, 0) - self.assertEqual(c.segment[10].psot, 1310540) - self.assertEqual(c.segment[10].tpsot, 0) - self.assertEqual(c.segment[10].tnsot, 1) - - # SOD: start of data - # Just one. - self.assertEqual(c.segment[11].marker_id, 'SOD') - - def test_NR_p0_06_dump(self): - jfile = opj_data_file('input/conformance/p0_06.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 513) - self.assertEqual(c.segment[1].ysiz, 129) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (513, 129)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12, 12, 12, 12)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (2, 1), (1, 2), (2, 2)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.RPCL) - self.assertEqual(c.segment[2].layers, 4) # 4 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 6) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # scalar expounded - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].mantissa, - [512, 518, 522, 524, 516, 524, 522, 527, 523, 549, - 557, 561, 853, 852, 700, 163, 78, 1508, 1831]) - self.assertEqual(c.segment[3].exponent, - [7, 7, 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 2, 1, 2, - 1]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[4].cqcc, 1) - # quantization type - self.assertEqual(c.segment[4].sqcc & 0x1f, 2) # scalar derived - self.assertEqual(c.segment[4].guard_bits, 4) - self.assertEqual(c.segment[4].mantissa, - [1527, 489, 665, 506, 487, 502, 493, 493, 500, 485, - 505, 491, 490, 491, 499, 509, 503, 496, 558]) - self.assertEqual(c.segment[4].exponent, - [10, 10, 10, 10, 9, 9, 9, 8, 8, 8, 7, 7, 7, 6, 6, 6, - 5, 5, 5]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 2) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 2) # scalar derived - self.assertEqual(c.segment[5].guard_bits, 5) - self.assertEqual(c.segment[5].mantissa, - [1337, 728, 890, 719, 716, 726, 700, 718, 704, 704, - 712, 712, 717, 719, 701, 749, 753, 718, 841]) - self.assertEqual(c.segment[5].exponent, - [10, 10, 10, 10, 9, 9, 9, 8, 8, 8, 7, 7, 7, 6, 6, 6, - 5, 5, 5]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 3) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[6].guard_bits, 6) - self.assertEqual(c.segment[6].mantissa, [0] * 19) - self.assertEqual(c.segment[6].exponent, - [12, 13, 13, 14, 13, 13, 14, 13, 13, 14, 13, 13, 14, - 13, 13, 14, 13, 13, 14]) - - # COC: Coding style component - self.assertEqual(c.segment[7].ccoc, 3) - self.assertEqual(c.segment[7].spcoc[0], 6) # levels - self.assertEqual(tuple(c.segment[7].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[7].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[7].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[7].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[7].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[7].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[7].spcoc[3] & 0x0020) - self.assertEqual(c.segment[7].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # RGN: region of interest - self.assertEqual(c.segment[8].crgn, 0) # component - self.assertEqual(c.segment[8].srgn, 0) # implicit - self.assertEqual(c.segment[8].sprgn, 11) - - # SOT: start of tile part - self.assertEqual(c.segment[9].isot, 0) - self.assertEqual(c.segment[9].psot, 33582) - self.assertEqual(c.segment[9].tpsot, 0) - self.assertEqual(c.segment[9].tnsot, 1) - - # RGN: region of interest - self.assertEqual(c.segment[10].crgn, 0) # component - self.assertEqual(c.segment[10].srgn, 0) # implicit - self.assertEqual(c.segment[10].sprgn, 9) - - # SOD: start of data - # Just one. - self.assertEqual(c.segment[11].marker_id, 'SOD') - - def test_NR_p0_07_dump(self): - jfile = opj_data_file('input/conformance/p0_07.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 2048) - self.assertEqual(c.segment[1].ysiz, 2048) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12, 12, 12)) - # signed - self.assertEqual(c.segment[1].signed, (True, True, True)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (1, 1), (1, 1)]) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) - self.assertTrue(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 8) # 8 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 3) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, [0] * 10) - self.assertEqual(c.segment[3].exponent, - [14, 15, 15, 16, 15, 15, 16, 15, 15, 16]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Kakadu-3.0.7") - - # SOT: start of tile part - self.assertEqual(c.segment[5].isot, 0) - self.assertEqual(c.segment[5].psot, 9951) - self.assertEqual(c.segment[5].tpsot, 0) - self.assertEqual(c.segment[5].tnsot, 0) # unknown - - # POD: progression order change - self.assertEqual(c.segment[6].rspod, (0,)) - self.assertEqual(c.segment[6].cspod, (0,)) - self.assertEqual(c.segment[6].lyepod, (9,)) - self.assertEqual(c.segment[6].repod, (3,)) - self.assertEqual(c.segment[6].cdpod, (3,)) - self.assertEqual(c.segment[6].ppod, (glymur.core.LRCP,)) - - # PLT: packet length, tile part - self.assertEqual(c.segment[7].zplt, 0) - #self.assertEqual(c.segment[7].iplt), 99) - - # SOD: start of data - self.assertEqual(c.segment[8].marker_id, 'SOD') - - def test_NR_p0_08_dump(self): - jfile = opj_data_file('input/conformance/p0_08.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 513) - self.assertEqual(c.segment[1].ysiz, 3072) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (513, 3072)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12, 12, 12)) - # signed - self.assertEqual(c.segment[1].signed, (True, True, True)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (1, 1), (1, 1)]) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) - self.assertTrue(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.CPRL) - self.assertEqual(c.segment[2].layers, 30) # 30 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 7) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # COC: Coding style component - self.assertEqual(c.segment[3].ccoc, 0) - self.assertEqual(c.segment[3].spcoc[0], 6) # levels - self.assertEqual(tuple(c.segment[3].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcoc[3] & 0x0020) - self.assertEqual(c.segment[3].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # COC: Coding style component - self.assertEqual(c.segment[4].ccoc, 1) - self.assertEqual(c.segment[4].spcoc[0], 7) # levels - self.assertEqual(tuple(c.segment[4].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[4].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[4].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[4].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[4].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[4].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[4].spcoc[3] & 0x0020) - self.assertEqual(c.segment[4].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # COC: Coding style component - self.assertEqual(c.segment[5].ccoc, 2) - self.assertEqual(c.segment[5].spcoc[0], 8) # levels - self.assertEqual(tuple(c.segment[5].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[5].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[5].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[5].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[5].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[5].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[5].spcoc[3] & 0x0020) - self.assertEqual(c.segment[5].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[6].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[6].guard_bits, 4) - self.assertEqual(c.segment[6].mantissa, [0] * 22) - self.assertEqual(c.segment[6].exponent, - [11, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, - 12, 12, 13, 12, 12, 13, 12, 12, 13]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[7].cqcc, 0) - # quantization type - self.assertEqual(c.segment[7].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[7].guard_bits, 4) - self.assertEqual(c.segment[7].mantissa, [0] * 19) - self.assertEqual(c.segment[7].exponent, - [11, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, - 12, 12, 13, 12, 12, 13]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[8].cqcc, 2) - # quantization type - self.assertEqual(c.segment[8].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[8].guard_bits, 4) - self.assertEqual(c.segment[8].mantissa, [0] * 25) - self.assertEqual(c.segment[8].exponent, - [11, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, - 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13]) - - # COM: comment - # Registration - self.assertEqual(c.segment[9].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[9].ccme.decode('latin-1'), - "Kakadu-3.0.7") - - # SOT: start of tile part - self.assertEqual(c.segment[10].isot, 0) - self.assertEqual(c.segment[10].psot, 3820593) - self.assertEqual(c.segment[10].tpsot, 0) - self.assertEqual(c.segment[10].tnsot, 1) # unknown - - def test_NR_p0_09_dump(self): - jfile = opj_data_file('input/conformance/p0_09.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "0" means profile 2, or full capabilities - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 17) - self.assertEqual(c.segment[1].ysiz, 37) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (17, 37)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # scalar expounded - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, - [1915, 1884, 1884, 1853, 1884, 1884, 1853, 1962, 1962, - 1986, 53, 53, 120, 26, 26, 1983]) - self.assertEqual(c.segment[3].exponent, - [16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 12, 12, 12, - 11, 11, 12]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Kakadu-3.0.7") - - # SOT: start of tile part - self.assertEqual(c.segment[5].isot, 0) - self.assertEqual(c.segment[5].psot, 478) - self.assertEqual(c.segment[5].tpsot, 0) - self.assertEqual(c.segment[5].tnsot, 1) # unknown - - # SOD: start of data - # Just one. - self.assertEqual(c.segment[6].marker_id, 'SOD') - - # EOC: end of codestream - self.assertEqual(c.segment[7].marker_id, 'EOC') - - def test_NR_p0_10_dump(self): - jfile = opj_data_file('input/conformance/p0_10.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 256) - self.assertEqual(c.segment[1].ysiz, 256) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(4, 4), (4, 4), (4, 4)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 2) # 2 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 3) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[3].guard_bits, 0) - self.assertEqual(c.segment[3].mantissa, [0] * 10) - self.assertEqual(c.segment[3].exponent, - [11, 12, 12, 13, 12, 12, 13, 12, 12, 13]) - - # SOT: start of tile part - self.assertEqual(c.segment[4].isot, 0) - self.assertEqual(c.segment[4].psot, 2453) - self.assertEqual(c.segment[4].tpsot, 0) - self.assertEqual(c.segment[4].tnsot, 0) - - # SOD: start of data - self.assertEqual(c.segment[5].marker_id, 'SOD') - - # SOT: start of tile part - self.assertEqual(c.segment[6].isot, 1) - self.assertEqual(c.segment[6].psot, 2403) - self.assertEqual(c.segment[6].tpsot, 0) - self.assertEqual(c.segment[6].tnsot, 0) - - # SOD: start of data - self.assertEqual(c.segment[7].marker_id, 'SOD') - - # SOT: start of tile part - self.assertEqual(c.segment[8].isot, 2) - self.assertEqual(c.segment[8].psot, 2420) - self.assertEqual(c.segment[8].tpsot, 0) - self.assertEqual(c.segment[8].tnsot, 0) - - # SOD: start of data - self.assertEqual(c.segment[9].marker_id, 'SOD') - - # SOT: start of tile part - self.assertEqual(c.segment[10].isot, 3) - self.assertEqual(c.segment[10].psot, 2472) - self.assertEqual(c.segment[10].tpsot, 0) - self.assertEqual(c.segment[10].tnsot, 0) - - # SOD: start of data - self.assertEqual(c.segment[11].marker_id, 'SOD') - - # SOT: start of tile part - self.assertEqual(c.segment[12].isot, 0) - self.assertEqual(c.segment[12].psot, 1043) - self.assertEqual(c.segment[12].tpsot, 1) - self.assertEqual(c.segment[12].tnsot, 2) - - # SOD: start of data - self.assertEqual(c.segment[13].marker_id, 'SOD') - - # SOT: start of tile part - self.assertEqual(c.segment[14].isot, 1) - self.assertEqual(c.segment[14].psot, 1101) - self.assertEqual(c.segment[14].tpsot, 1) - self.assertEqual(c.segment[14].tnsot, 2) - - # SOD: start of data - self.assertEqual(c.segment[15].marker_id, 'SOD') - - # SOT: start of tile part - self.assertEqual(c.segment[16].isot, 3) - self.assertEqual(c.segment[16].psot, 1054) - self.assertEqual(c.segment[16].tpsot, 1) - self.assertEqual(c.segment[16].tnsot, 2) - - # SOD: start of data - self.assertEqual(c.segment[17].marker_id, 'SOD') - - # SOT: start of tile part - self.assertEqual(c.segment[18].isot, 2) - self.assertEqual(c.segment[18].psot, 14) - self.assertEqual(c.segment[18].tpsot, 1) - self.assertEqual(c.segment[18].tnsot, 0) - - # SOD: start of data - self.assertEqual(c.segment[19].marker_id, 'SOD') - - # SOT: start of tile part - self.assertEqual(c.segment[20].isot, 2) - self.assertEqual(c.segment[20].psot, 1089) - self.assertEqual(c.segment[20].tpsot, 2) - self.assertEqual(c.segment[20].tnsot, 0) - - # SOD: start of data - self.assertEqual(c.segment[21].marker_id, 'SOD') - - # EOC: end of codestream - self.assertEqual(c.segment[22].marker_id, 'EOC') - - def test_NR_p0_11_dump(self): - jfile = opj_data_file('input/conformance/p0_11.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 128) - self.assertEqual(c.segment[1].ysiz, 1) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertTrue(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 0) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertTrue(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(c.segment[2].precinct_size, [(128, 2)]) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].mantissa, [0]) - self.assertEqual(c.segment[3].exponent, [8]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # SOT: start of tile part - self.assertEqual(c.segment[5].isot, 0) - self.assertEqual(c.segment[5].psot, 118) - self.assertEqual(c.segment[5].tpsot, 0) - self.assertEqual(c.segment[5].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[6].marker_id, 'SOD') - - # SOP, EPH - sop = [x.marker_id for x in c.segment if x.marker_id == 'SOP'] - eph = [x.marker_id for x in c.segment if x.marker_id == 'EPH'] - self.assertEqual(len(sop), 0) - self.assertEqual(len(eph), 1) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_p0_12_dump(self): - jfile = opj_data_file('input/conformance/p0_12.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 3) - self.assertEqual(c.segment[1].ysiz, 5) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (3, 5)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 3) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].mantissa, [0] * 10) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # SOT: start of tile part - self.assertEqual(c.segment[5].isot, 0) - self.assertEqual(c.segment[5].psot, 162) - self.assertEqual(c.segment[5].tpsot, 0) - self.assertEqual(c.segment[5].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[6].marker_id, 'SOD') - - # SOP, EPH - sop = [x.marker_id for x in c.segment if x.marker_id == 'SOP'] - eph = [x.marker_id for x in c.segment if x.marker_id == 'EPH'] - self.assertEqual(len(sop), 4) - self.assertEqual(len(eph), 0) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_p0_13_dump(self): - jfile = opj_data_file('input/conformance/p0_13.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1) - self.assertEqual(c.segment[1].ysiz, 1) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (1, 1)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, tuple([8] * 257)) - # signed - self.assertEqual(c.segment[1].signed, tuple([False] * 257)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 257) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 1) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertTrue(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # COC: Coding style component - self.assertEqual(c.segment[3].ccoc, 2) - self.assertEqual(c.segment[3].spcoc[0], 1) # levels - self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcoc[3] & 0x0020) - self.assertEqual(c.segment[3].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[4].guard_bits, 2) - self.assertEqual(c.segment[4].mantissa, [0] * 4) - self.assertEqual(c.segment[4].exponent, - [8, 9, 9, 10]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 1) - self.assertEqual(c.segment[5].guard_bits, 3) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[5].exponent, [9, 10, 10, 11]) - self.assertEqual(c.segment[5].mantissa, [0, 0, 0, 0]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 2) - self.assertEqual(c.segment[6].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[6].exponent, [9, 10, 10, 11]) - self.assertEqual(c.segment[6].mantissa, [0, 0, 0, 0]) - - # RGN: region of interest - self.assertEqual(c.segment[7].crgn, 3) - self.assertEqual(c.segment[7].srgn, 0) - self.assertEqual(c.segment[7].sprgn, 11) - - # POD: progression order change - self.assertEqual(c.segment[8].rspod, (0, 0)) - self.assertEqual(c.segment[8].cspod, (0, 128)) - self.assertEqual(c.segment[8].lyepod, (1, 1)) - self.assertEqual(c.segment[8].repod, (33, 33)) - self.assertEqual(c.segment[8].cdpod, (128, 257)) - self.assertEqual(c.segment[8].ppod, - (glymur.core.RLCP, glymur.core.CPRL)) - - # COM: comment - # Registration - self.assertEqual(c.segment[9].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[9].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # SOT: start of tile part - self.assertEqual(c.segment[10].isot, 0) - self.assertEqual(c.segment[10].psot, 1537) - self.assertEqual(c.segment[10].tpsot, 0) - self.assertEqual(c.segment[10].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[11].marker_id, 'SOD') - - # EOC: end of codestream - self.assertEqual(c.segment[12].marker_id, 'EOC') - - def test_NR_p0_14_dump(self): - jfile = opj_data_file('input/conformance/p0_14.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "0" means profile 2 - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 49) - self.assertEqual(c.segment[1].ysiz, 49) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (49, 49)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # 1 layer - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [10, 11, 11, 12, 11, 11, 12, 11, 11, 12, 11, 11, 12, - 11, 11, 12]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Kakadu-3.0.7") - - # SOT: start of tile part - self.assertEqual(c.segment[5].isot, 0) - self.assertEqual(c.segment[5].psot, 1528) - self.assertEqual(c.segment[5].tpsot, 0) - self.assertEqual(c.segment[5].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[6].marker_id, 'SOD') - - # EOC: end of codestream - self.assertEqual(c.segment[7].marker_id, 'EOC') - - def test_NR_p0_15_dump(self): - jfile = opj_data_file('input/conformance/p0_15.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 256) - self.assertEqual(c.segment[1].ysiz, 256) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (4,)) - # signed - self.assertEqual(c.segment[1].signed, (True,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) - self.assertEqual(c.segment[2].layers, 8) # layers = 8 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 1) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 1) # derived - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0]) - self.assertEqual(c.segment[3].exponent, [0]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[4].cqcc, 0) - self.assertEqual(c.segment[4].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[4].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[4].mantissa, [0] * 4) - self.assertEqual(c.segment[4].exponent, [4, 5, 5, 6]) - - # POD: progression order change - self.assertEqual(c.segment[5].rspod, (0,)) - self.assertEqual(c.segment[5].cspod, (0,)) - self.assertEqual(c.segment[5].lyepod, (8,)) - self.assertEqual(c.segment[5].repod, (33,)) - self.assertEqual(c.segment[5].cdpod, (255,)) - self.assertEqual(c.segment[5].ppod, (glymur.core.LRCP,)) - - # CRG: component registration - self.assertEqual(c.segment[6].xcrg, (65424,)) - self.assertEqual(c.segment[6].ycrg, (32558,)) - - # COM: comment - # Registration - self.assertEqual(c.segment[7].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[7].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # COM: comment - # Registration - self.assertEqual(c.segment[8].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[8].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000," - + "2001 Algo Vision Technology") - - # COM: comment - # Registration - self.assertEqual(c.segment[9].rcme, glymur.core.RCME_BINARY) - # Comment value - self.assertEqual(len(c.segment[9].ccme), 62) - - # TLM: tile-part length - self.assertEqual(c.segment[10].ztlm, 0) - self.assertEqual(c.segment[10].ttlm, (0, 1, 2, 3)) - self.assertEqual(c.segment[10].ptlm, (4267, 2117, 4080, 2081)) - - # SOT: start of tile part - self.assertEqual(c.segment[11].isot, 0) - self.assertEqual(c.segment[11].psot, 4267) - self.assertEqual(c.segment[11].tpsot, 0) - self.assertEqual(c.segment[11].tnsot, 1) - - # RGN: region of interest - self.assertEqual(c.segment[12].crgn, 0) - self.assertEqual(c.segment[12].srgn, 0) - self.assertEqual(c.segment[12].sprgn, 7) - - # SOD: start of data - self.assertEqual(c.segment[13].marker_id, 'SOD') - - # 16 SOP markers would be here if we were looking for them - - # SOT: start of tile part - self.assertEqual(c.segment[31].isot, 1) - self.assertEqual(c.segment[31].psot, 2117) - self.assertEqual(c.segment[31].tpsot, 0) - self.assertEqual(c.segment[31].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[32].marker_id, 'SOD') - - # 16 SOP markers would be here if we were looking for them - - # SOT: start of tile part - self.assertEqual(c.segment[49].isot, 2) - self.assertEqual(c.segment[49].psot, 4080) - self.assertEqual(c.segment[49].tpsot, 0) - self.assertEqual(c.segment[49].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[50].marker_id, 'SOD') - - # 16 SOP markers would be here if we were looking for them - - # SOT: start of tile part - self.assertEqual(c.segment[67].isot, 3) - self.assertEqual(c.segment[67].psot, 2081) - self.assertEqual(c.segment[67].tpsot, 0) - self.assertEqual(c.segment[67].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[68].marker_id, 'SOD') - - # 16 SOP markers would be here if we were looking for them - - # EOC: end of codestream - self.assertEqual(c.segment[85].marker_id, 'EOC') - - def test_NR_p0_16_dump(self): - jfile = opj_data_file('input/conformance/p0_16.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "0" means profile 2 - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 128) - self.assertEqual(c.segment[1].ysiz, 128) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 3) # layers = 3 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 3) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 10) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - # SOT: start of tile part - self.assertEqual(c.segment[4].isot, 0) - self.assertEqual(c.segment[4].psot, 7331) - self.assertEqual(c.segment[4].tpsot, 0) - self.assertEqual(c.segment[4].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[5].marker_id, 'SOD') - - # EOC: end of codestream - self.assertEqual(c.segment[6].marker_id, 'EOC') - - def test_NR_p1_01_dump(self): - jfile = opj_data_file('input/conformance/p1_01.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 1 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 127) - self.assertEqual(c.segment[1].ysiz, 227) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (5, 128)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (127, 126)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (1, 101)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(2, 1)]) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) # SOP - self.assertTrue(c.segment[2].scod & 4) # EPH - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 5) # layers = 5 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 3) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertTrue(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertTrue(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # COC: Coding style component - self.assertEqual(c.segment[3].ccoc, 0) - self.assertEqual(c.segment[3].spcoc[0], 3) # level - self.assertEqual(tuple(c.segment[3].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[3].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcoc[3] & 0x08) - # Predictable termination - self.assertTrue(c.segment[3].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertTrue(c.segment[3].spcoc[3] & 0x0020) - self.assertEqual(c.segment[3].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[4].guard_bits, 3) - self.assertEqual(c.segment[4].mantissa, [0] * 10) - self.assertEqual(c.segment[4].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - # COM: comment - # Registration - self.assertEqual(c.segment[5].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[5].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # SOT: start of tile part - self.assertEqual(c.segment[6].isot, 0) - self.assertEqual(c.segment[6].psot, 4627) - self.assertEqual(c.segment[6].tpsot, 0) - self.assertEqual(c.segment[6].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[7].marker_id, 'SOD') - - # SOP, EPH - sop = [x.marker_id for x in c.segment if x.marker_id == 'SOP'] - eph = [x.marker_id for x in c.segment if x.marker_id == 'EPH'] - self.assertEqual(len(sop), 20) - self.assertEqual(len(eph), 20) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_p1_02_dump(self): - jfile = opj_data_file('input/conformance/p1_02.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 1 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 640) - self.assertEqual(c.segment[1].ysiz, 480) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (640, 480)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, tuple([8] * 3)) - # signed - self.assertEqual(c.segment[1].signed, tuple([False] * 3)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 19) # layers = 19 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 6) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertTrue(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertTrue(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(c.segment[2].precinct_size, - [(128, 128), (256, 256), (512, 512), (1024, 1024), - (2048, 2048), (4096, 4096), (8192, 8192)]) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # expounded - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, 1845, - 1868, 1925, 1925, 2007, 32, 32, 131, 2002, 2002, - 1888]) - self.assertEqual(c.segment[3].exponent, - [16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, - 11, 11, 11, 11, 11, 11]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[4].cqcc, 1) - self.assertEqual(c.segment[4].guard_bits, 3) - # quantization type - self.assertEqual(c.segment[4].sqcc & 0x1f, 2) # expounded - self.assertEqual(c.segment[4].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, 1845, - 1868, 1925, 1925, 2007, 32, 32, 131, 2002, 2002, - 1888]) - self.assertEqual(c.segment[4].exponent, - [14, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, - 9, 9, 9, 9, 9, 9]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 2) - self.assertEqual(c.segment[5].guard_bits, 3) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 2) # expounded - self.assertEqual(c.segment[5].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, 1845, - 1868, 1925, 1925, 2007, 32, 32, 131, 2002, 2002, - 1888]) - self.assertEqual(c.segment[5].exponent, - [14, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, - 9, 9, 9, 9, 9, 9]) - - # COM: comment - # Registration - self.assertEqual(c.segment[6].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[6].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # SOT: start of tile part - self.assertEqual(c.segment[7].isot, 0) - self.assertEqual(c.segment[7].psot, 262838) - self.assertEqual(c.segment[7].tpsot, 0) - self.assertEqual(c.segment[7].tnsot, 1) - - # PPT: packed packet headers, tile-part header - self.assertEqual(c.segment[8].marker_id, 'PPT') - self.assertEqual(c.segment[8].zppt, 0) - - # SOD: start of data - self.assertEqual(c.segment[9].marker_id, 'SOD') - - # EOC: end of codestream - self.assertEqual(c.segment[10].marker_id, 'EOC') - - def test_NR_p1_03_dump(self): - jfile = opj_data_file('input/conformance/p1_03.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 1 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1024) - self.assertEqual(c.segment[1].ysiz, 1024) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1024, 1024)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, tuple([8] * 4)) - # signed - self.assertEqual(c.segment[1].signed, tuple([False] * 4)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (1, 1), (2, 2), (2, 2)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) - self.assertEqual(c.segment[2].layers, 10) # layers = 10 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 6) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertTrue(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # COC: Coding style component - self.assertEqual(c.segment[3].ccoc, 1) - self.assertEqual(c.segment[3].spcoc[0], 3) # level - self.assertEqual(tuple(c.segment[3].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertTrue(c.segment[3].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[3].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcoc[3] & 0x0020) - self.assertEqual(c.segment[3].spcoc[4], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - - # COC: Coding style component - self.assertEqual(c.segment[4].ccoc, 3) - self.assertEqual(c.segment[4].spcoc[0], 6) # level - self.assertEqual(tuple(c.segment[4].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertTrue(c.segment[4].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[4].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[4].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[4].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[4].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[4].spcoc[3] & 0x0020) - self.assertEqual(c.segment[4].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[5].sqcd & 0x1f, 2) # expounded - self.assertEqual(c.segment[5].guard_bits, 3) - self.assertEqual(c.segment[5].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, 1845, - 1868, 1925, 1925, 2007, 32, 32, 131, 2002, 2002, - 1888]) - self.assertEqual(c.segment[5].exponent, - [16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, - 11, 11, 11, 11, 11, 11]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 0) - self.assertEqual(c.segment[6].guard_bits, 3) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 1) # derived - self.assertEqual(c.segment[6].mantissa, [0]) - self.assertEqual(c.segment[6].exponent, [14]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[7].cqcc, 3) - self.assertEqual(c.segment[7].guard_bits, 3) - # quantization type - self.assertEqual(c.segment[7].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[7].mantissa, [0] * 19) - self.assertEqual(c.segment[7].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, - 9, 9, 10]) - - # COM: comment - # Registration - self.assertEqual(c.segment[8].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[8].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # PPM: packed packet headers, main header - self.assertEqual(c.segment[9].marker_id, 'PPM') - self.assertEqual(c.segment[9].zppm, 0) - - # TLM (tile-part length) - self.assertEqual(c.segment[10].ztlm, 0) - self.assertEqual(c.segment[10].ttlm, (0,)) - self.assertEqual(c.segment[10].ptlm, (1366780,)) - - # SOT: start of tile part - self.assertEqual(c.segment[11].isot, 0) - self.assertEqual(c.segment[11].psot, 1366780) - self.assertEqual(c.segment[11].tpsot, 0) - self.assertEqual(c.segment[11].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[12].marker_id, 'SOD') - - # EOC: end of codestream - self.assertEqual(c.segment[13].marker_id, 'EOC') - - def test_NR_p1_04_dump(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 1 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1024) - self.assertEqual(c.segment[1].ysiz, 1024) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 3) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # expounded - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, - [84, 423, 408, 435, 450, 435, 470, 549, 520, 618]) - self.assertEqual(c.segment[3].exponent, - [8, 10, 10, 10, 9, 9, 9, 8, 8, 8]) - - # TLM (tile-part length) - self.assertEqual(c.segment[4].ztlm, 0) - self.assertIsNone(c.segment[4].ttlm) - self.assertEqual(c.segment[4].ptlm, - (350, 356, 402, 245, 402, 564, 675, 283, 317, 299, - 330, 333, 346, 403, 839, 667, 328, 349, 274, 325, - 501, 561, 756, 710, 779, 620, 628, 675, 600, 66195, - 721, 719, 565, 565, 546, 586, 574, 641, 713, 634, - 573, 528, 544, 597, 771, 665, 624, 706, 568, 537, - 554, 546, 542, 635, 826, 667, 617, 606, 813, 586, - 641, 654, 669, 623)) - - # COM: comment - # Registration - self.assertEqual(c.segment[5].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[5].ccme.decode('latin-1'), - "Created by Aware, Inc.") - - # SOT: start of tile part - self.assertEqual(c.segment[6].isot, 0) - self.assertEqual(c.segment[6].psot, 350) - self.assertEqual(c.segment[6].tpsot, 0) - self.assertEqual(c.segment[6].tnsot, 1) - - # SOD: start of data - self.assertEqual(c.segment[7].marker_id, 'SOD') - - # SOT: start of tile part - self.assertEqual(c.segment[8].isot, 1) - self.assertEqual(c.segment[8].psot, 356) - self.assertEqual(c.segment[8].tpsot, 0) - self.assertEqual(c.segment[8].tnsot, 1) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[9].sqcd & 0x1f, 2) # expounded - self.assertEqual(c.segment[9].guard_bits, 2) - self.assertEqual(c.segment[9].mantissa, - [75, 1093, 1098, 1115, 1157, 1134, 1186, 1217, 1245, - 1248]) - self.assertEqual(c.segment[9].exponent, - [8, 10, 10, 10, 9, 9, 9, 8, 8, 8]) - - # SOD: start of data - self.assertEqual(c.segment[10].marker_id, 'SOD') - - # SOT: start of tile part - self.assertEqual(c.segment[11].isot, 2) - self.assertEqual(c.segment[11].psot, 402) - self.assertEqual(c.segment[11].tpsot, 0) - self.assertEqual(c.segment[11].tnsot, 1) - - # and so on - - # There should be 64 SOD, SOT, QCD segments. - ids = [x.marker_id for x in c.segment if x.marker_id == 'SOT'] - self.assertEqual(len(ids), 64) - ids = [x.marker_id for x in c.segment if x.marker_id == 'SOD'] - self.assertEqual(len(ids), 64) - ids = [x.marker_id for x in c.segment if x.marker_id == 'QCD'] - self.assertEqual(len(ids), 64) - - # Tiles should be in order, right? - tiles = [x.isot for x in c.segment if x.marker_id == 'SOT'] - self.assertEqual(tiles, list(range(64))) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_p1_05_dump(self): - jfile = opj_data_file('input/conformance/p1_05.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 1 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 529) - self.assertEqual(c.segment[1].ysiz, 524) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (17, 12)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (37, 37)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (8, 2)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) # sop - self.assertTrue(c.segment[2].scod & 4) # eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) - self.assertEqual(c.segment[2].layers, 2) # levels = 2 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 7) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 8)) # cblk - # Selective arithmetic coding bypass - self.assertTrue(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertTrue(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertTrue(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(c.segment[2].precinct_size, [(16, 16)] * 8) - - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # expounded - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].mantissa, - [1813, 1814, 1814, 1814, 1815, 1815, 1817, 1821, - 1821, 1827, 1845, 1845, 1868, 1925, 1925, 2007, - 32, 32, 131, 2002, 2002, 1888]) - self.assertEqual(c.segment[3].exponent, - [17, 17, 17, 17, 16, 16, 16, 15, 15, 15, 14, 14, - 14, 13, 13, 13, 11, 11, 11, 11, 11, 11]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # 225 consecutive PPM segments. - zppm = [x.zppm for x in c.segment[5:230]] - self.assertEqual(zppm, list(range(225))) - - # SOT: start of tile part - self.assertEqual(c.segment[230].isot, 0) - self.assertEqual(c.segment[230].psot, 580) - self.assertEqual(c.segment[230].tpsot, 0) - self.assertEqual(c.segment[230].tnsot, 1) - - # 225 total SOT segments - isot = [x.isot for x in c.segment if x.marker_id == 'SOT'] - self.assertEqual(isot, list(range(225))) - - # scads of SOP, EPH segments - sop = [x.marker_id for x in c.segment if x.marker_id == 'SOP'] - eph = [x.marker_id for x in c.segment if x.marker_id == 'EPH'] - self.assertEqual(len(sop), 26472) - self.assertEqual(len(eph), 0) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_p1_06_dump(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 1 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 12) - self.assertEqual(c.segment[1].ysiz, 12) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (3, 3)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) # sop - self.assertTrue(c.segment[2].scod & 4) # eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 4) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (32, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertTrue(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertTrue(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # expounded - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].mantissa, - [1821, 1845, 1845, 1868, 1925, 1925, 2007, 32, - 32, 131, 2002, 2002, 1888]) - self.assertEqual(c.segment[3].exponent, - [14, 14, 14, 14, 13, 13, 13, 11, 11, 11, - 11, 11, 11]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # SOT: start of tile part - self.assertEqual(c.segment[5].isot, 0) - self.assertEqual(c.segment[5].psot, 349) - self.assertEqual(c.segment[5].tpsot, 0) - self.assertEqual(c.segment[5].tnsot, 1) - - # PPT: packed packet headers, tile-part header - self.assertEqual(c.segment[6].marker_id, 'PPT') - self.assertEqual(c.segment[6].zppt, 0) - - # scads of SOP, EPH segments - - # 16 SOD segments - sods = [x for x in c.segment if x.marker_id == 'SOD'] - self.assertEqual(len(sods), 16) - - # 16 PPT segments - ppts = [x for x in c.segment if x.marker_id == 'PPT'] - self.assertEqual(len(ppts), 16) - - # 16 SOT segments - isots = [x.isot for x in c.segment if x.marker_id == 'SOT'] - self.assertEqual(isots, list(range(16))) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_p1_07_dump(self): - jfile = opj_data_file('input/conformance/p1_07.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "1" means profile 1 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 12) - self.assertEqual(c.segment[1].ysiz, 12) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (4, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (12, 12)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (4, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(4, 1), (1, 1)]) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) # sop - self.assertTrue(c.segment[2].scod & 4) # eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.RPCL) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 1) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(c.segment[2].precinct_size, [(1, 1), (2, 2)]) - - # COC: Coding style component - self.assertEqual(c.segment[3].ccoc, 1) - self.assertEqual(c.segment[3].spcoc[0], 1) # level - self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcoc[3] & 0x0020) - self.assertEqual(c.segment[3].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(c.segment[3].precinct_size, [(2, 2), (4, 4)]) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[4].guard_bits, 2) - self.assertEqual(c.segment[4].mantissa, [0] * 4) - self.assertEqual(c.segment[4].exponent, [8, 9, 9, 10]) - - # COM: comment - # Registration - self.assertEqual(c.segment[5].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[5].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") - - # SOT: start of tile part - self.assertEqual(c.segment[6].isot, 0) - self.assertEqual(c.segment[6].psot, 434) - self.assertEqual(c.segment[6].tpsot, 0) - self.assertEqual(c.segment[6].tnsot, 1) - - # scads of SOP, EPH segments - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_file1_dump(self): - jfile = opj_data_file('input/conformance/file1.jp2') - with warnings.catch_warnings(): - # Bad compatibility list item. - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'xml ', 'jp2h', 'xml ', - 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - - # XML box - tags = [x.tag for x in jp2.box[2].xml.getroot()] - self.assertEqual(tags, - ['{http://www.jpeg.org/jpx/1.0/xml}' - + 'GENERAL_CREATION_INFO']) - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 512) - self.assertEqual(jp2.box[3].box[0].width, 768) - self.assertEqual(jp2.box[3].box[0].num_components, 3) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[1].precedence, 0) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact ?? - self.assertEqual(jp2.box[3].box[1].colorspace, glymur.core.SRGB) - - # XML box - tags = [x.tag for x in jp2.box[4].xml.getroot()] - self.assertEqual(tags, ['{http://www.jpeg.org/jpx/1.0/xml}CAPTION', - '{http://www.jpeg.org/jpx/1.0/xml}LOCATION', - '{http://www.jpeg.org/jpx/1.0/xml}EVENT']) - - def test_NR_file2_dump(self): - jfile = opj_data_file('input/conformance/file2.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr', 'cdef']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 640) - self.assertEqual(jp2.box[2].box[0].width, 480) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact?? - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.YCC) - - # Jp2 Header - # Channel Definition - self.assertEqual(jp2.box[2].box[2].index, (0, 1, 2)) - self.assertEqual(jp2.box[2].box[2].channel_type, (0, 0, 0)) # color - self.assertEqual(jp2.box[2].box[2].association, (3, 2, 1)) # reverse - - def test_NR_file3_dump(self): - # Three 8-bit components in the sRGB-YCC colourspace, with the Cb and - # Cr components being subsampled 2x in both the horizontal and - # vertical directions. The components are stored in the standard - # order. - jfile = opj_data_file('input/conformance/file3.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 640) - self.assertEqual(jp2.box[2].box[0].width, 480) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.YCC) - - # sub-sampling - codestream = jp2.get_codestream() - self.assertEqual(codestream.segment[1].xrsiz[0], 1) - self.assertEqual(codestream.segment[1].yrsiz[0], 1) - self.assertEqual(codestream.segment[1].xrsiz[1], 2) - self.assertEqual(codestream.segment[1].yrsiz[1], 2) - self.assertEqual(codestream.segment[1].xrsiz[2], 2) - self.assertEqual(codestream.segment[1].yrsiz[2], 2) - - def test_NR_file4_dump(self): - # One 8-bit component in the sRGB-grey colourspace. - jfile = opj_data_file('input/conformance/file4.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 512) - self.assertEqual(jp2.box[2].box[0].width, 768) - self.assertEqual(jp2.box[2].box[0].num_components, 1) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact? - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.GREYSCALE) - - def test_NR_file5_dump(self): - # Three 8-bit components in the ROMM-RGB colourspace, encapsulated in a - # JPX file. The components have been transformed using - # the RCT. The colourspace is specified using both a Restricted ICC - # profile and using the JPX-defined enumerated code for the ROMM-RGB - # colourspace. - jfile = opj_data_file('input/conformance/file5.jp2') - with warnings.catch_warnings(): - # There's a warning for an unknown compatibility entry. - # Ignore it here. - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jpx ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 512) - self.assertEqual(jp2.box[3].box[0].width, 768) - self.assertEqual(jp2.box[3].box[0].num_components, 3) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.RESTRICTED_ICC_PROFILE) # enumerated - self.assertEqual(jp2.box[3].box[1].precedence, 0) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 546) - self.assertIsNone(jp2.box[3].box[1].colorspace) - - def test_NR_file6_dump(self): - jfile = opj_data_file('input/conformance/file6.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 512) - self.assertEqual(jp2.box[2].box[0].width, 768) - self.assertEqual(jp2.box[2].box[0].num_components, 1) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 12) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact - self.assertIsNone(jp2.box[2].box[1].icc_profile) - self.assertEqual(jp2.box[2].box[1].colorspace, - glymur.core.GREYSCALE) - - def test_NR_file7_dump(self): - # Three 16-bit components in the e-sRGB colourspace, encapsulated in a - # JP2 compatible JPX file. The components have been transformed using - # the RCT. The colourspace is specified using both a Restricted ICC - # profile and using the JPX-defined enumerated code for the e-sRGB - # colourspace. - jfile = opj_data_file('input/conformance/file7.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jpx ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 640) - self.assertEqual(jp2.box[3].box[0].width, 480) - self.assertEqual(jp2.box[3].box[0].num_components, 3) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 16) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.RESTRICTED_ICC_PROFILE) - self.assertEqual(jp2.box[3].box[1].precedence, 0) - self.assertEqual(jp2.box[3].box[1].approximation, 1) - self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 13332) - self.assertIsNone(jp2.box[3].box[1].colorspace) - - def test_NR_file8_dump(self): - # One 8-bit component in a gamma 1.8 space. The colourspace is - # specified using a Restricted ICC profile. - jfile = opj_data_file('input/conformance/file8.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'xml ', 'jp2c', - 'xml ']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 400) - self.assertEqual(jp2.box[2].box[0].width, 700) - self.assertEqual(jp2.box[2].box[0].num_components, 1) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.RESTRICTED_ICC_PROFILE) # enumerated - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[2].box[1].icc_profile['Size'], 414) - self.assertIsNone(jp2.box[2].box[1].colorspace) - - # XML box - tags = [x.tag for x in jp2.box[3].xml.getroot()] - self.assertEqual(tags, - ['{http://www.jpeg.org/jpx/1.0/xml}' - + 'GENERAL_CREATION_INFO']) - - # XML box - tags = [x.tag for x in jp2.box[5].xml.getroot()] - self.assertEqual(tags, - ['{http://www.jpeg.org/jpx/1.0/xml}CAPTION', - '{http://www.jpeg.org/jpx/1.0/xml}LOCATION', - '{http://www.jpeg.org/jpx/1.0/xml}THING', - '{http://www.jpeg.org/jpx/1.0/xml}EVENT']) - - def test_NR_file9_dump(self): - # Colormap - jfile = opj_data_file('input/conformance/file9.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'pclr', 'cmap', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 512) - self.assertEqual(jp2.box[2].box[0].width, 768) - self.assertEqual(jp2.box[2].box[0].num_components, 1) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Palette box. - self.assertEqual(jp2.box[2].box[1].palette.shape, (256, 3)) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[0, 0], 0) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[0, 1], 0) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[0, 2], 0) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[128, 0], 73) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[128, 1], 92) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[128, 2], 53) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[255, 0], 245) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[255, 1], 245) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[255, 2], 245) - - # Component mapping box - self.assertEqual(jp2.box[2].box[2].component_index, (0, 0, 0)) - self.assertEqual(jp2.box[2].box[2].mapping_type, (1, 1, 1)) - self.assertEqual(jp2.box[2].box[2].palette_index, (0, 1, 2)) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[3].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[3].precedence, 0) - self.assertEqual(jp2.box[2].box[3].approximation, 1) # JPX exact - self.assertIsNone(jp2.box[2].box[3].icc_profile) - self.assertEqual(jp2.box[2].box[3].colorspace, glymur.core.SRGB) - - def test_NR_00042_j2k_dump(self): - # Profile 3. - jfile = opj_data_file('input/nonregression/_00042.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "3" means profile 3 - self.assertEqual(c.segment[1].rsiz, 3) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1920) - self.assertEqual(c.segment[1].ysiz, 1080) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1920, 1080)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12, 12, 12)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.CPRL) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(c.segment[2].precinct_size[0], (128, 128)) - self.assertEqual(c.segment[2].precinct_size[1:], [(256, 256)] * 5) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, - [1824, 1776, 1776, 1728, 1792, 1792, 1760, 1872, - 1872, 1896, 5, 5, 71, 2003, 2003, 1890]) - self.assertEqual(c.segment[3].exponent, - [18, 18, 18, 18, 17, 17, 17, 16, - 16, 16, 14, 14, 14, 14, 14, 14]) - - # COC: Coding style component - self.assertEqual(c.segment[4].ccoc, 1) - self.assertEqual(c.segment[4].spcoc[0], 5) # level - self.assertEqual(tuple(c.segment[4].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[4].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[4].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[4].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[4].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[4].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[4].spcoc[3] & 0x0020) - self.assertEqual(c.segment[4].spcoc[4], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 1) - self.assertEqual(c.segment[5].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 2) - self.assertEqual(c.segment[5].mantissa, - [1824, 1776, 1776, 1728, 1792, 1792, 1760, 1872, - 1872, 1896, 5, 5, 71, 2003, 2003, 1890]) - self.assertEqual(c.segment[5].exponent, - [18, 18, 18, 18, 17, 17, 17, 16, 16, 16, 14, 14, 14, - 14, 14, 14]) - - # COC: Coding style component - self.assertEqual(c.segment[6].ccoc, 2) - self.assertEqual(c.segment[6].spcoc[0], 5) # level - self.assertEqual(tuple(c.segment[6].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[6].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[6].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[6].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[6].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[6].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[6].spcoc[3] & 0x0020) - self.assertEqual(c.segment[6].spcoc[4], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[7].cqcc, 2) - self.assertEqual(c.segment[7].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[7].sqcc & 0x1f, 2) # none - self.assertEqual(c.segment[7].mantissa, - [1824, 1776, 1776, 1728, 1792, 1792, 1760, 1872, - 1872, 1896, 5, 5, 71, 2003, 2003, 1890]) - self.assertEqual(c.segment[7].exponent, - [18, 18, 18, 18, 17, 17, 17, 16, 16, 16, 14, 14, - 14, 14, 14, 14]) - - # COM: comment - # Registration - self.assertEqual(c.segment[8].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[8].ccme.decode('latin-1'), - "Created by OpenJPEG version 1.3.0") - - # TLM (tile-part length) - self.assertEqual(c.segment[9].ztlm, 0) - self.assertEqual(c.segment[9].ttlm, (0, 0, 0)) - self.assertEqual(c.segment[9].ptlm, (45274, 20838, 8909)) - - # 3 tiles, one for each component - idx = [x.isot for x in c.segment if x.marker_id == 'SOT'] - self.assertEqual(idx, [0, 0, 0]) - lens = [x.psot for x in c.segment if x.marker_id == 'SOT'] - self.assertEqual(lens, [45274, 20838, 8909]) - tpsot = [x.tpsot for x in c.segment if x.marker_id == 'SOT'] - self.assertEqual(tpsot, [0, 1, 2]) - - sods = [x for x in c.segment if x.marker_id == 'SOD'] - self.assertEqual(len(sods), 3) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_Bretagne2_j2k_dump(self): - # Profile 3. - jfile = opj_data_file('input/nonregression/Bretagne2.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: "3" means profile 3 - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 2592) - self.assertEqual(c.segment[1].ysiz, 1944) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (640, 480)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 3) # layers = 3 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(c.segment[2].precinct_size, - [(16, 16), (32, 32), (64, 64), (128, 128), - (128, 128), (128, 128)]) - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] - expected += ['SOT', 'COC', 'QCC', 'COC', 'QCC', 'SOD'] * 25 - expected += ['EOC'] - self.assertEqual(ids, expected) - - def test_NR_buxI_j2k_dump(self): - jfile = opj_data_file('input/nonregression/buxI.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 512) - self.assertEqual(c.segment[1].ysiz, 512) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (512, 512)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 2) # layers = 2 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'SOT', 'SOD', 'EOC'] - self.assertEqual(ids, expected) - - def test_NR_buxR_j2k_dump(self): - jfile = opj_data_file('input/nonregression/buxR.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream(header_only=False) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 512) - self.assertEqual(c.segment[1].ysiz, 512) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (512, 512)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 2) # layers = 2 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'SOT', 'SOD', 'EOC'] - self.assertEqual(ids, expected) - - def test_NR_Cannotreaddatawithnosizeknown_j2k(self): - lst = ['input', 'nonregression', - 'Cannotreaddatawithnosizeknown.j2k'] - path = '/'.join(lst) - - jfile = opj_data_file(path) - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1420) - self.assertEqual(c.segment[1].ysiz, 1416) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1420, 1416)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 11) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 4) - self.assertEqual(c.segment[3].mantissa, [0] * 34) - self.assertEqual(c.segment[3].exponent, [16] + [17, 17, 18] * 11) - - def test_NR_CT_Phillips_JPEG2K_Decompr_Problem_dump(self): - jfile = opj_data_file('input/nonregression/' - + 'CT_Phillips_JPEG2K_Decompr_Problem.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 512) - self.assertEqual(c.segment[1].ysiz, 614) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (512, 614)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, - [442, 422, 422, 403, 422, 422, 403, 472, 472, 487, - 591, 591, 676, 558, 558, 485]) - self.assertEqual(c.segment[3].exponent, - [22, 22, 22, 22, 21, 21, 21, 20, 20, 20, 19, 19, 19, - 18, 18, 18]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), "Kakadu-3.2") - - def test_NR_cthead1_dump(self): - jfile = opj_data_file('input/nonregression/cthead1.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'CME'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 256) - self.assertEqual(c.segment[1].ysiz, 256) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (256, 256)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [9, 10, 10, 11, 10, 10, 11, 10, 10, 11, 10, 10, 10, - 9, 9, 10]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), "Kakadu-v6.3.1") - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), "Kakadu-v6.3.1") - - def test_NR_illegalcolortransform_dump(self): - jfile = opj_data_file('input/nonregression/illegalcolortransform.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1420) - self.assertEqual(c.segment[1].ysiz, 1416) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1420, 1416)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 11) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 4) - self.assertEqual(c.segment[3].mantissa, [0] * 34) - self.assertEqual(c.segment[3].exponent, [16] + [17, 17, 18] * 11) - - def test_NR_j2k32_dump(self): - jfile = opj_data_file('input/nonregression/j2k32.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 256) - self.assertEqual(c.segment[1].ysiz, 256) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (256, 256)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (True, True, True)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, [8, 9, 9, 10, 9, 9, 10, 9, 9, - 10, 9, 9, 10, 9, 9, 10]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_BINARY) - # Comment value - self.assertEqual(len(c.segment[4].ccme), 36) - - def test_NR_kakadu_v4_4_openjpegv2_broken_dump(self): - jfile = opj_data_file('input/nonregression/' - + 'kakadu_v4-4_openjpegv2_broken.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 2048) - self.assertEqual(c.segment[1].ysiz, 2500) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (2048, 2500)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 12) # layers = 12 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 8) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, [0] * 25) - self.assertEqual(c.segment[3].exponent, - [17, 18, 18, 19, 18, 18, 19, 18, 18, 19, 18, 18, 19, - 18, 18, 19, 18, 18, 19, 18, 18, 19, 18, 18, 19]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), "Kakadu-v4.4") - - # COM: comment - # Registration - self.assertEqual(c.segment[5].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - expected = "Kdu-Layer-Info: log_2{Delta-D(MSE)/[2^16*Delta-L(bytes)]}," - expected += " L(bytes)\n" - expected += " -65.4, 6.8e+004\n" - expected += " -66.3, 1.0e+005\n" - expected += " -67.3, 2.0e+005\n" - expected += " -68.5, 4.1e+005\n" - expected += " -69.0, 5.1e+005\n" - expected += " -69.5, 5.9e+005\n" - expected += " -69.7, 6.8e+005\n" - expected += " -70.3, 8.2e+005\n" - expected += " -70.8, 1.0e+006\n" - expected += " -71.9, 1.4e+006\n" - expected += " -73.8, 2.0e+006\n" - expected += "-256.0, 3.7e+006\n" - self.assertEqual(c.segment[5].ccme.decode('latin-1'), expected) - - def test_NR_MarkerIsNotCompliant_j2k_dump(self): - jfile = opj_data_file('input/nonregression/MarkerIsNotCompliant.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1420) - self.assertEqual(c.segment[1].ysiz, 1416) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1420, 1416)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 11) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 4) - self.assertEqual(c.segment[3].mantissa, [0] * 34) - self.assertEqual(c.segment[3].exponent, - [16, 17, 17, 18, 17, 17, 18, 17, 17, 18, 17, 17, 18, - 17, 17, 18, 17, 17, 18, 17, 17, 18, 17, 17, 18, 17, - 17, 18, 17, 17, 18, 17, 17, 18]) - - def test_NR_movie_00000(self): - jfile = opj_data_file('input/nonregression/movie_00000.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1920) - self.assertEqual(c.segment[1].ysiz, 1080) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1920, 1080)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - def test_NR_movie_00001(self): - jfile = opj_data_file('input/nonregression/movie_00001.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1920) - self.assertEqual(c.segment[1].ysiz, 1080) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1920, 1080)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - def test_NR_movie_00002(self): - jfile = opj_data_file('input/nonregression/movie_00002.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1920) - self.assertEqual(c.segment[1].ysiz, 1080) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1920, 1080)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - def test_NR_orb_blue10_lin_j2k_dump(self): - jfile = opj_data_file('input/nonregression/orb-blue10-lin-j2k.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 117) - self.assertEqual(c.segment[1].ysiz, 117) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (117, 117)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 4) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - def test_NR_orb_blue10_win_j2k_dump(self): - jfile = opj_data_file('input/nonregression/orb-blue10-win-j2k.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 117) - self.assertEqual(c.segment[1].ysiz, 117) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (117, 117)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 4) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - def test_NR_pacs_ge_j2k_dump(self): - jfile = opj_data_file('input/nonregression/pacs.ge.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 512) - self.assertEqual(c.segment[1].ysiz, 512) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (512, 512)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (True,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 16) # layers = 16 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [18, 19, 19, 20, 19, 19, 20, 19, 19, 20, 19, 19, 20, - 19, 19, 20]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Kakadu-2.0.2") - - def test_NR_test_lossless_j2k_dump(self): - jfile = opj_data_file('input/nonregression/test_lossless.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1024) - self.assertEqual(c.segment[1].ysiz, 1024) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1024, 1024)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [12, 13, 13, 14, 13, 13, 14, 13, 13, 14, 13, 13, 14, - 13, 13, 14]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "ClearCanvas DICOM OpenJPEG") - - def test_NR_123_j2c_dump(self): - jfile = opj_data_file('input/nonregression/123.j2c') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1800) - self.assertEqual(c.segment[1].ysiz, 1800) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1800, 1800)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 11) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 4) - self.assertEqual(c.segment[3].mantissa, [0] * 34) - self.assertEqual(c.segment[3].exponent, - [16] + [17, 17, 18] * 11) - - def test_NR_bug_j2c_dump(self): - jfile = opj_data_file('input/nonregression/bug.j2c') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1800) - self.assertEqual(c.segment[1].ysiz, 1800) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1800, 1800)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 11) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 4) - self.assertEqual(c.segment[3].mantissa, [0] * 34) - self.assertEqual(c.segment[3].exponent, - [16] + [17, 17, 18] * 11) - - def test_NR_kodak_2layers_lrcp_j2c_dump(self): - jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 2048) - self.assertEqual(c.segment[1].ysiz, 1556) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (2048, 1556)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12, 12, 12)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 2) # layers = 2 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(c.segment[2].precinct_size, - [(128, 128)] + [(256, 256)] * 5) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [13, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, - 13, 13, 13]) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "DCP-Werkstatt") - - def test_NR_issue104_jpxstream_dump(self): - jfile = opj_data_file('input/nonregression/issue104_jpxstream.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'pclr', 'cmap']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jpxb') - self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') - - # Reader requirements talk. - # unrestricted jpeg 2000 part 1 - self.assertTrue(5 in jp2.box[2].standard_flag) - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 203) - self.assertEqual(jp2.box[3].box[0].width, 479) - self.assertEqual(jp2.box[3].box[0].num_components, 1) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[1].precedence, 2) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # exact - self.assertEqual(jp2.box[3].box[1].colorspace, glymur.core.SRGB) - - # Jp2 Header - # Palette box. - self.assertEqual(jp2.box[3].box[2].palette.shape, (256, 3)) - - # Jp2 Header - # Component mapping box - self.assertEqual(jp2.box[3].box[3].component_index, (0, 0, 0)) - self.assertEqual(jp2.box[3].box[3].mapping_type, (1, 1, 1)) - self.assertEqual(jp2.box[3].box[3].palette_index, (0, 1, 2)) - - c = jp2.box[4].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 479) - self.assertEqual(c.segment[1].ysiz, 203) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (256, 203)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, [8] + [9, 9, 10] * 5) - - def test_NR_issue188_beach_64bitsbox(self): - lst = ['input', 'nonregression', 'issue188_beach_64bitsbox.jp2'] - jfile = opj_data_file('/'.join(lst)) - with warnings.catch_warnings(): - # There's a warning for an unknown box. We explicitly test for - # that down below. - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', b'XML ', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 200) - self.assertEqual(jp2.box[2].box[0].width, 200) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, True) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) - - # Skip the 4th box, it is uknown. - - c = jp2.box[4].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'CME'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 200) - self.assertEqual(c.segment[1].ysiz, 200) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (200, 200)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 1) - - def test_NR_issue206_image_000_dump(self): - jfile = opj_data_file('input/nonregression/issue206_image-000.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jpxb') - self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') - - # Reader requirements talk. - # unrestricted jpeg 2000 part 1 - self.assertTrue(5 in jp2.box[2].standard_flag) - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 326) - self.assertEqual(jp2.box[3].box[0].width, 431) - self.assertEqual(jp2.box[3].box[0].num_components, 3) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[1].precedence, 2) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[3].box[1].colorspace, glymur.core.SRGB) - - c = jp2.box[4].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 431) - self.assertEqual(c.segment[1].ysiz, 326) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (256, 256)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, [8] + [9, 9, 10] * 5) - - def test_NR_Marrin_jp2_dump(self): - jfile = opj_data_file('input/nonregression/Marrin.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr', 'cdef', 'res ']) - - ids = [box.box_id for box in jp2.box[2].box[3].box] - self.assertEqual(ids, ['resd']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 135) - self.assertEqual(jp2.box[2].box[0].width, 135) - self.assertEqual(jp2.box[2].box[0].num_components, 2) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, True) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.GREYSCALE) - - # Jp2 Header - # Channel Definition - self.assertEqual(jp2.box[2].box[2].index, (0, 1)) - self.assertEqual(jp2.box[2].box[2].channel_type, (0, 1)) # opacity - self.assertEqual(jp2.box[2].box[2].association, (0, 0)) # both main - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 135) - self.assertEqual(c.segment[1].ysiz, 135) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (135, 135)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 2) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 2) # layers = 2 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, - [1822, 1770, 1770, 1724, 1792, 1792, 1762, 1868, 1868, - 1892, 3, 3, 69, 2002, 2002, 1889]) - self.assertEqual(c.segment[3].exponent, - [14] * 4 + [13] * 3 + [12] * 3 + [10] * 6) - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Kakadu-v5.2.1") - - def test_NR_mem_b2b86b74_2753_dump(self): - jfile = opj_data_file('input/nonregression/mem-b2b86b74-2753.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'pclr', 'cmap']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jpxb') - self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') - - # Reader requirements talk. - # unrestricted jpeg 2000 part 1 - self.assertTrue(5 in jp2.box[2].standard_flag) - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 46) - self.assertEqual(jp2.box[3].box[0].width, 124) - self.assertEqual(jp2.box[3].box[0].num_components, 1) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 4) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[1].precedence, 2) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[3].box[1].colorspace, glymur.core.SRGB) - - # Jp2 Header - # Palette box. - # 3 columns with 16 entries. - self.assertEqual(jp2.box[3].box[2].palette.shape, (16, 3)) - - # Jp2 Header - # Component mapping box - self.assertEqual(jp2.box[3].box[3].component_index, (0, 0, 0)) - self.assertEqual(jp2.box[3].box[3].mapping_type, (1, 1, 1)) - self.assertEqual(jp2.box[3].box[3].palette_index, (0, 1, 2)) - - c = jp2.box[4].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 124) - self.assertEqual(c.segment[1].ysiz, 46) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (124, 46)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (4,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, [4] + [5, 5, 6] * 5) - - def test_NR_merged_dump(self): - jfile = opj_data_file('input/nonregression/merged.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 576) - self.assertEqual(jp2.box[2].box[0].width, 766) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.YCC) - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'POD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 766) - self.assertEqual(c.segment[1].ysiz, 576) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (766, 576)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (2, 1), (2, 1)]) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 128)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, [8] + [9, 9, 10] * 5) - - # POD: progression order change - self.assertEqual(c.segment[4].rspod, (0, 0)) - self.assertEqual(c.segment[4].cspod, (0, 1)) - self.assertEqual(c.segment[4].lyepod, (1, 1)) - self.assertEqual(c.segment[4].repod, (6, 6)) - self.assertEqual(c.segment[4].cdpod, (1, 3)) - - podvals = (glymur.core.LRCP, glymur.core.LRCP) - self.assertEqual(c.segment[4].ppod, podvals) - - def test_NR_orb_blue10_lin_jp2_dump(self): - jfile = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2') - with warnings.catch_warnings(): - # This file has an invalid ICC profile - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 117) - self.assertEqual(jp2.box[2].box[0].width, 117) - self.assertEqual(jp2.box[2].box[0].num_components, 4) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.RESTRICTED_ICC_PROFILE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertIsNone(jp2.box[2].box[1].icc_profile) - self.assertIsNone(jp2.box[2].box[1].colorspace) - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 117) - self.assertEqual(c.segment[1].ysiz, 117) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (117, 117)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 4) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - def test_NR_orb_blue10_win_jp2_dump(self): - jfile = opj_data_file('input/nonregression/orb-blue10-win-jp2.jp2') - with warnings.catch_warnings(): - # This file has an invalid ICC profile - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 117) - self.assertEqual(jp2.box[2].box[0].width, 117) - self.assertEqual(jp2.box[2].box[0].num_components, 4) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.RESTRICTED_ICC_PROFILE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertIsNone(jp2.box[2].box[1].icc_profile) - self.assertIsNone(jp2.box[2].box[1].colorspace) - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 117) - self.assertEqual(c.segment[1].ysiz, 117) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (117, 117)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 4) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1, @@ -6306,348 +556,5 @@ class TestSuite2point0(unittest.TestCase): self.assertTrue(True) -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -@unittest.skipIf(re.match(r'''(1|2.0.0)''', - glymur.version.openjpeg_version) is not None, - "Only supported in 2.0.1 or higher") -class TestSuite2point1(unittest.TestCase): - """Runs tests introduced in version 2.0+ or that pass only in 2.0+""" - - def setUp(self): - pass - - def tearDown(self): - pass - - def test_NR_DEC_text_GBR_jp2_29_decode(self): - jfile = opj_data_file('input/nonregression/text_GBR.jp2') - with warnings.catch_warnings(): - # brand is 'jp2 ', but has any icc profile. - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) - jp2.read() - self.assertTrue(True) - - def test_NR_DEC_kodak_2layers_lrcp_j2c_31_decode(self): - jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_kodak_2layers_lrcp_j2c_32_decode(self): - jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c') - Jp2k(jfile).read(layer=2) - self.assertTrue(True) - - def test_NR_DEC_issue104_jpxstream_jp2_33_decode(self): - jfile = opj_data_file('input/nonregression/issue104_jpxstream.jp2') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_mem_b2b86b74_2753_jp2_35_decode(self): - jfile = opj_data_file('input/nonregression/mem-b2b86b74-2753.jp2') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_gdal_fuzzer_unchecked_num_resolutions_jp2_36_decode(self): - f = 'input/nonregression/gdal_fuzzer_unchecked_numresolutions.jp2' - jfile = opj_data_file(f) - with warnings.catch_warnings(): - # Invalid number of resolutions. - warnings.simplefilter("ignore") - j = Jp2k(jfile) - with self.assertRaises(IOError): - j.read() - - def test_NR_DEC_gdal_fuzzer_check_number_of_tiles_jp2_38_decode(self): - relpath = 'input/nonregression/gdal_fuzzer_check_number_of_tiles.jp2' - jfile = opj_data_file(relpath) - with warnings.catch_warnings(): - # Invalid number of tiles. - warnings.simplefilter("ignore") - j = Jp2k(jfile) - with self.assertRaises(IOError): - j.read() - - def test_NR_DEC_gdal_fuzzer_check_comp_dx_dy_jp2_39_decode(self): - relpath = 'input/nonregression/gdal_fuzzer_check_comp_dx_dy.jp2' - jfile = opj_data_file(relpath) - with warnings.catch_warnings(): - # Invalid subsampling value - warnings.simplefilter("ignore") - with self.assertRaises(IOError): - Jp2k(jfile).read() - - def test_NR_DEC_file_409752_jp2_40_decode(self): - jfile = opj_data_file('input/nonregression/file409752.jp2') - with self.assertRaises(RuntimeError): - Jp2k(jfile).read() - - def test_NR_DEC_issue206_image_000_jp2_42_decode(self): - jfile = opj_data_file('input/nonregression/issue206_image-000.jp2') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_p1_04_j2k_43_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 0, 1024, 1024)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata) - - def test_NR_DEC_p1_04_j2k_44_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(640, 512, 768, 640)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[640:768, 512:640]) - - def test_NR_DEC_p1_04_j2k_45_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(896, 896, 1024, 1024)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[896:1024, 896:1024]) - - def test_NR_DEC_p1_04_j2k_46_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(500, 100, 800, 300)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[500:800, 100:300]) - - def test_NR_DEC_p1_04_j2k_47_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(520, 260, 600, 360)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[520:600, 260:360]) - - def test_NR_DEC_p1_04_j2k_48_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(520, 260, 660, 360)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[520:660, 260:360]) - - def test_NR_DEC_p1_04_j2k_49_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(520, 360, 600, 400)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[520:600, 360:400]) - - def test_NR_DEC_p1_04_j2k_50_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 0, 1024, 1024), rlevel=2) - odata = jp2k.read(rlevel=2) - - np.testing.assert_array_equal(ssdata, odata[0:256, 0:256]) - - def test_NR_DEC_p1_04_j2k_51_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(640, 512, 768, 640), rlevel=2) - odata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(ssdata, odata[160:192, 128:160]) - - def test_NR_DEC_p1_04_j2k_52_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(896, 896, 1024, 1024), rlevel=2) - odata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(ssdata, odata[224:352, 224:352]) - - def test_NR_DEC_p1_04_j2k_53_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(500, 100, 800, 300), rlevel=2) - odata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(ssdata, odata[125:200, 25:75]) - - def test_NR_DEC_p1_04_j2k_54_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(520, 260, 600, 360), rlevel=2) - odata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(ssdata, odata[130:150, 65:90]) - - def test_NR_DEC_p1_04_j2k_55_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(520, 260, 660, 360), rlevel=2) - odata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(ssdata, odata[130:165, 65:90]) - - def test_NR_DEC_p1_04_j2k_56_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(520, 360, 600, 400), rlevel=2) - odata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(ssdata, odata[130:150, 90:100]) - - def test_NR_DEC_p1_04_j2k_57_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - tdata = jp2k.read(tile=63) # last tile - odata = jp2k.read() - np.testing.assert_array_equal(tdata, odata[896:1024, 896:1024]) - - def test_NR_DEC_p1_04_j2k_58_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - tdata = jp2k.read(tile=63, rlevel=2) # last tile - odata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(tdata, odata[224:256, 224:256]) - - def test_NR_DEC_p1_04_j2k_59_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - tdata = jp2k.read(tile=12) # 2nd row, 5th column - odata = jp2k.read() - np.testing.assert_array_equal(tdata, odata[128:256, 512:640]) - - def test_NR_DEC_p1_04_j2k_60_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - tdata = jp2k.read(tile=12, rlevel=1) # 2nd row, 5th column - odata = jp2k.read(rlevel=1) - np.testing.assert_array_equal(tdata, odata[64:128, 256:320]) - - def test_NR_DEC_jp2_36_decode(self): - lst = ('input', - 'nonregression', - 'gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc.patch.jp2') - jfile = opj_data_file('/'.join(lst)) - with warnings.catch_warnings(): - # Invalid component number. - warnings.simplefilter("ignore") - j = Jp2k(jfile) - with self.assertRaises(IOError): - j.read() - - def test_NR_DEC_p1_06_j2k_70_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(9, 9, 12, 12), rlevel=1) - self.assertEqual(ssdata.shape, (1, 1, 3)) - - def test_NR_DEC_p1_06_j2k_71_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(10, 4, 12, 10), rlevel=1) - self.assertEqual(ssdata.shape, (1, 3, 3)) - - def test_NR_DEC_p1_06_j2k_72_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(3, 3, 9, 9), rlevel=1) - self.assertEqual(ssdata.shape, (3, 3, 3)) - - def test_NR_DEC_p1_06_j2k_73_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(4, 4, 7, 7), rlevel=1) - self.assertEqual(ssdata.shape, (2, 2, 3)) - - def test_NR_DEC_p1_06_j2k_74_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(4, 4, 5, 5), rlevel=1) - self.assertEqual(ssdata.shape, (1, 1, 3)) - - def test_NR_DEC_p1_06_j2k_75_decode(self): - # Image size would be 0 x 0. - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - with self.assertRaises((IOError, OSError)): - jp2k.read(area=(9, 9, 12, 12), rlevel=2) - - def test_NR_DEC_p0_04_j2k_85_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 0, 256, 256)) - fulldata = jp2k.read() - np.testing.assert_array_equal(fulldata[0:256, 0:256], ssdata) - - def test_NR_DEC_p0_04_j2k_86_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 128, 128, 256)) - fulldata = jp2k.read() - np.testing.assert_array_equal(fulldata[0:128, 128:256], ssdata) - - def test_NR_DEC_p0_04_j2k_87_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(10, 50, 200, 120)) - fulldata = jp2k.read() - np.testing.assert_array_equal(fulldata[10:200, 50:120], ssdata) - - def test_NR_DEC_p0_04_j2k_88_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(150, 10, 210, 190)) - fulldata = jp2k.read() - np.testing.assert_array_equal(fulldata[150:210, 10:190], ssdata) - - def test_NR_DEC_p0_04_j2k_89_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(80, 100, 150, 200)) - fulldata = jp2k.read() - np.testing.assert_array_equal(fulldata[80:150, 100:200], ssdata) - - def test_NR_DEC_p0_04_j2k_90_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(20, 150, 50, 200)) - fulldata = jp2k.read() - np.testing.assert_array_equal(fulldata[20:50, 150:200], ssdata) - - def test_NR_DEC_p0_04_j2k_91_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 0, 256, 256), rlevel=2) - fulldata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(fulldata[0:64, 0:64], ssdata) - - def test_NR_DEC_p0_04_j2k_92_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 128, 128, 256), rlevel=2) - fulldata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(fulldata[0:32, 32:64], ssdata) - - def test_NR_DEC_p0_04_j2k_93_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(10, 50, 200, 120), rlevel=2) - fulldata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(fulldata[3:50, 13:30], ssdata) - - def test_NR_DEC_p0_04_j2k_94_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(150, 10, 210, 190), rlevel=2) - fulldata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(fulldata[38:53, 3:48], ssdata) - - def test_NR_DEC_p0_04_j2k_95_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(80, 100, 150, 200), rlevel=2) - fulldata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(fulldata[20:38, 25:50], ssdata) - - def test_NR_DEC_p0_04_j2k_96_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(20, 150, 50, 200), rlevel=2) - fulldata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(fulldata[5:13, 38:50], ssdata) - - if __name__ == "__main__": unittest.main() diff --git a/glymur/test/test_opj_suite_2p1.py b/glymur/test/test_opj_suite_2p1.py new file mode 100644 index 0000000..ebe5bf8 --- /dev/null +++ b/glymur/test/test_opj_suite_2p1.py @@ -0,0 +1,389 @@ +""" +The tests defined here roughly correspond to what is in the OpenJPEG test +suite. +""" + +# Some test names correspond with openjpeg tests. Long names are ok in this +# case. +# pylint: disable=C0103 + +# All of these tests correspond to tests in openjpeg, so no docstring is really +# needed. +# pylint: disable=C0111 + +# This module is very long, cannot be helped. +# pylint: disable=C0302 + +# unittest fools pylint with "too many public methods" +# pylint: disable=R0904 + +# Some tests use numpy test infrastructure, which means the tests never +# reference "self", so pylint claims it should be a function. No, no, no. +# pylint: disable=R0201 + +# Many tests are pretty long and that can't be helped. +# pylint: disable=R0915 + +# asserWarns introduced in python 3.2 (python2.7/pylint issue) +# pylint: disable=E1101 + +import re +import sys +import unittest + +import warnings + +import numpy as np + +from glymur import Jp2k +import glymur + +from .fixtures import OPJ_DATA_ROOT +from .fixtures import mse, peak_tolerance, read_pgx, opj_data_file + + +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +@unittest.skipIf(re.match(r'''(1|2.0.0)''', + glymur.version.openjpeg_version) is not None, + "Only supported in 2.0.1 or higher") +class TestSuite2point1(unittest.TestCase): + """Runs tests introduced in version 2.0+ or that pass only in 2.0+""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_NR_DEC_text_GBR_jp2_29_decode(self): + jfile = opj_data_file('input/nonregression/text_GBR.jp2') + with warnings.catch_warnings(): + # brand is 'jp2 ', but has any icc profile. + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) + jp2.read() + self.assertTrue(True) + + def test_NR_DEC_kodak_2layers_lrcp_j2c_31_decode(self): + jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_kodak_2layers_lrcp_j2c_32_decode(self): + jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c') + Jp2k(jfile).read(layer=2) + self.assertTrue(True) + + def test_NR_DEC_issue104_jpxstream_jp2_33_decode(self): + jfile = opj_data_file('input/nonregression/issue104_jpxstream.jp2') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_mem_b2b86b74_2753_jp2_35_decode(self): + jfile = opj_data_file('input/nonregression/mem-b2b86b74-2753.jp2') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_gdal_fuzzer_unchecked_num_resolutions_jp2_36_decode(self): + f = 'input/nonregression/gdal_fuzzer_unchecked_numresolutions.jp2' + jfile = opj_data_file(f) + with warnings.catch_warnings(): + # Invalid number of resolutions. + warnings.simplefilter("ignore") + j = Jp2k(jfile) + with self.assertRaises(IOError): + j.read() + + def test_NR_DEC_gdal_fuzzer_check_number_of_tiles_jp2_38_decode(self): + relpath = 'input/nonregression/gdal_fuzzer_check_number_of_tiles.jp2' + jfile = opj_data_file(relpath) + with warnings.catch_warnings(): + # Invalid number of tiles. + warnings.simplefilter("ignore") + j = Jp2k(jfile) + with self.assertRaises(IOError): + j.read() + + def test_NR_DEC_gdal_fuzzer_check_comp_dx_dy_jp2_39_decode(self): + relpath = 'input/nonregression/gdal_fuzzer_check_comp_dx_dy.jp2' + jfile = opj_data_file(relpath) + with warnings.catch_warnings(): + # Invalid subsampling value + warnings.simplefilter("ignore") + with self.assertRaises(IOError): + Jp2k(jfile).read() + + def test_NR_DEC_file_409752_jp2_40_decode(self): + jfile = opj_data_file('input/nonregression/file409752.jp2') + with self.assertRaises(RuntimeError): + Jp2k(jfile).read() + + def test_NR_DEC_issue206_image_000_jp2_42_decode(self): + jfile = opj_data_file('input/nonregression/issue206_image-000.jp2') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_p1_04_j2k_43_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 0, 1024, 1024)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata) + + def test_NR_DEC_p1_04_j2k_44_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(640, 512, 768, 640)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[640:768, 512:640]) + + def test_NR_DEC_p1_04_j2k_45_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(896, 896, 1024, 1024)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[896:1024, 896:1024]) + + def test_NR_DEC_p1_04_j2k_46_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(500, 100, 800, 300)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[500:800, 100:300]) + + def test_NR_DEC_p1_04_j2k_47_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(520, 260, 600, 360)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[520:600, 260:360]) + + def test_NR_DEC_p1_04_j2k_48_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(520, 260, 660, 360)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[520:660, 260:360]) + + def test_NR_DEC_p1_04_j2k_49_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(520, 360, 600, 400)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[520:600, 360:400]) + + def test_NR_DEC_p1_04_j2k_50_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 0, 1024, 1024), rlevel=2) + odata = jp2k.read(rlevel=2) + + np.testing.assert_array_equal(ssdata, odata[0:256, 0:256]) + + def test_NR_DEC_p1_04_j2k_51_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(640, 512, 768, 640), rlevel=2) + odata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(ssdata, odata[160:192, 128:160]) + + def test_NR_DEC_p1_04_j2k_52_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(896, 896, 1024, 1024), rlevel=2) + odata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(ssdata, odata[224:352, 224:352]) + + def test_NR_DEC_p1_04_j2k_53_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(500, 100, 800, 300), rlevel=2) + odata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(ssdata, odata[125:200, 25:75]) + + def test_NR_DEC_p1_04_j2k_54_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(520, 260, 600, 360), rlevel=2) + odata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(ssdata, odata[130:150, 65:90]) + + def test_NR_DEC_p1_04_j2k_55_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(520, 260, 660, 360), rlevel=2) + odata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(ssdata, odata[130:165, 65:90]) + + def test_NR_DEC_p1_04_j2k_56_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(520, 360, 600, 400), rlevel=2) + odata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(ssdata, odata[130:150, 90:100]) + + def test_NR_DEC_p1_04_j2k_57_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + tdata = jp2k.read(tile=63) # last tile + odata = jp2k.read() + np.testing.assert_array_equal(tdata, odata[896:1024, 896:1024]) + + def test_NR_DEC_p1_04_j2k_58_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + tdata = jp2k.read(tile=63, rlevel=2) # last tile + odata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(tdata, odata[224:256, 224:256]) + + def test_NR_DEC_p1_04_j2k_59_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + tdata = jp2k.read(tile=12) # 2nd row, 5th column + odata = jp2k.read() + np.testing.assert_array_equal(tdata, odata[128:256, 512:640]) + + def test_NR_DEC_p1_04_j2k_60_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + tdata = jp2k.read(tile=12, rlevel=1) # 2nd row, 5th column + odata = jp2k.read(rlevel=1) + np.testing.assert_array_equal(tdata, odata[64:128, 256:320]) + + def test_NR_DEC_jp2_36_decode(self): + lst = ('input', + 'nonregression', + 'gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc.patch.jp2') + jfile = opj_data_file('/'.join(lst)) + with warnings.catch_warnings(): + # Invalid component number. + warnings.simplefilter("ignore") + j = Jp2k(jfile) + with self.assertRaises(IOError): + j.read() + + def test_NR_DEC_p1_06_j2k_70_decode(self): + jfile = opj_data_file('input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(9, 9, 12, 12), rlevel=1) + self.assertEqual(ssdata.shape, (1, 1, 3)) + + def test_NR_DEC_p1_06_j2k_71_decode(self): + jfile = opj_data_file('input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(10, 4, 12, 10), rlevel=1) + self.assertEqual(ssdata.shape, (1, 3, 3)) + + def test_NR_DEC_p1_06_j2k_72_decode(self): + jfile = opj_data_file('input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(3, 3, 9, 9), rlevel=1) + self.assertEqual(ssdata.shape, (3, 3, 3)) + + def test_NR_DEC_p1_06_j2k_73_decode(self): + jfile = opj_data_file('input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(4, 4, 7, 7), rlevel=1) + self.assertEqual(ssdata.shape, (2, 2, 3)) + + def test_NR_DEC_p1_06_j2k_74_decode(self): + jfile = opj_data_file('input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(4, 4, 5, 5), rlevel=1) + self.assertEqual(ssdata.shape, (1, 1, 3)) + + def test_NR_DEC_p1_06_j2k_75_decode(self): + # Image size would be 0 x 0. + jfile = opj_data_file('input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + with self.assertRaises((IOError, OSError)): + jp2k.read(area=(9, 9, 12, 12), rlevel=2) + + def test_NR_DEC_p0_04_j2k_85_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 0, 256, 256)) + fulldata = jp2k.read() + np.testing.assert_array_equal(fulldata[0:256, 0:256], ssdata) + + def test_NR_DEC_p0_04_j2k_86_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 128, 128, 256)) + fulldata = jp2k.read() + np.testing.assert_array_equal(fulldata[0:128, 128:256], ssdata) + + def test_NR_DEC_p0_04_j2k_87_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(10, 50, 200, 120)) + fulldata = jp2k.read() + np.testing.assert_array_equal(fulldata[10:200, 50:120], ssdata) + + def test_NR_DEC_p0_04_j2k_88_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(150, 10, 210, 190)) + fulldata = jp2k.read() + np.testing.assert_array_equal(fulldata[150:210, 10:190], ssdata) + + def test_NR_DEC_p0_04_j2k_89_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(80, 100, 150, 200)) + fulldata = jp2k.read() + np.testing.assert_array_equal(fulldata[80:150, 100:200], ssdata) + + def test_NR_DEC_p0_04_j2k_90_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(20, 150, 50, 200)) + fulldata = jp2k.read() + np.testing.assert_array_equal(fulldata[20:50, 150:200], ssdata) + + def test_NR_DEC_p0_04_j2k_91_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 0, 256, 256), rlevel=2) + fulldata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(fulldata[0:64, 0:64], ssdata) + + def test_NR_DEC_p0_04_j2k_92_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 128, 128, 256), rlevel=2) + fulldata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(fulldata[0:32, 32:64], ssdata) + + def test_NR_DEC_p0_04_j2k_93_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(10, 50, 200, 120), rlevel=2) + fulldata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(fulldata[3:50, 13:30], ssdata) + + def test_NR_DEC_p0_04_j2k_94_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(150, 10, 210, 190), rlevel=2) + fulldata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(fulldata[38:53, 3:48], ssdata) + + def test_NR_DEC_p0_04_j2k_95_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(80, 100, 150, 200), rlevel=2) + fulldata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(fulldata[20:38, 25:50], ssdata) + + def test_NR_DEC_p0_04_j2k_96_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(20, 150, 50, 200), rlevel=2) + fulldata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(fulldata[5:13, 38:50], ssdata) + + +if __name__ == "__main__": + unittest.main() diff --git a/glymur/test/test_opj_suite_dump.py b/glymur/test/test_opj_suite_dump.py new file mode 100644 index 0000000..e24f53f --- /dev/null +++ b/glymur/test/test_opj_suite_dump.py @@ -0,0 +1,5320 @@ +""" +The tests defined here roughly correspond to what is in the OpenJPEG test +suite. +""" + +# Some test names correspond with openjpeg tests. Long names are ok in this +# case. +# pylint: disable=C0103 + +# All of these tests correspond to tests in openjpeg, so no docstring is really +# needed. +# pylint: disable=C0111 + +# This module is very long, cannot be helped. +# pylint: disable=C0302 + +# unittest fools pylint with "too many public methods" +# pylint: disable=R0904 + +# Some tests use numpy test infrastructure, which means the tests never +# reference "self", so pylint claims it should be a function. No, no, no. +# pylint: disable=R0201 + +# Many tests are pretty long and that can't be helped. +# pylint: disable=R0915 + +# asserWarns introduced in python 3.2 (python2.7/pylint issue) +# pylint: disable=E1101 + +import re +import sys +import unittest + +import warnings + +import numpy as np + +from glymur import Jp2k +import glymur + +from .fixtures import OPJ_DATA_ROOT +from .fixtures import mse, peak_tolerance, read_pgx, opj_data_file + + +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class TestSuiteDump(unittest.TestCase): + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_NR_file409752(self): + jfile = opj_data_file('input/nonregression/file409752.jp2') + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 243) + self.assertEqual(jp2.box[2].box[0].width, 720) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, + glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 + self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.YCC) + + c = jp2.box[3].main_header + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 720) + self.assertEqual(c.segment[1].ysiz, 243) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (720, 243)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1), (2, 1), (2, 1)]) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), + (32, 128)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 2) + self.assertEqual(c.segment[3].guard_bits, 1) + self.assertEqual(c.segment[3].mantissa, + [1816, 1792, 1792, 1724, 1770, 1770, 1724, 1868, + 1868, 1892, 3, 3, 69, 2002, 2002, 1889]) + self.assertEqual(c.segment[3].exponent, + [13] * 4 + [12] * 3 + [11] * 3 + [9] * 6) + + def test_NR_p0_01_dump(self): + jfile = opj_data_file('input/conformance/p0_01.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # Segment IDs. + actual = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'QCD', 'COD', 'SOT', 'SOD', 'EOC'] + self.assertEqual(actual, expected) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 128) + self.assertEqual(c.segment[1].ysiz, 128) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8,)) + # signed + self.assertEqual(c.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)]) + + # QCD: Quantization default + self.assertEqual(c.segment[2].sqcd & 0x1f, 0) + self.assertEqual(c.segment[2].guard_bits, 2) + self.assertEqual(c.segment[2].exponent, + [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) + self.assertEqual(c.segment[2].mantissa, + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + + # COD: Coding style default + self.assertFalse(c.segment[3].scod & 2) # no sop + self.assertFalse(c.segment[3].scod & 4) # no eph + self.assertEqual(c.segment[3].spcod[0], glymur.core.RLCP) + self.assertEqual(c.segment[3].layers, 1) # layers = 1 + self.assertEqual(c.segment[3].spcod[3], 0) # mct + self.assertEqual(c.segment[3].spcod[4], 3) # layers + self.assertEqual(tuple(c.segment[3].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[3].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[3].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[3].spcod[7] & 0x0020) + self.assertEqual(c.segment[3].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + + # SOT: start of tile part + self.assertEqual(c.segment[4].isot, 0) + self.assertEqual(c.segment[4].psot, 7314) + self.assertEqual(c.segment[4].tpsot, 0) + self.assertEqual(c.segment[4].tnsot, 1) + + def test_NR_p0_02_dump(self): + jfile = opj_data_file('input/conformance/p0_02.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 127) + self.assertEqual(c.segment[1].ysiz, 126) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (127, 126)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8,)) + # signed + self.assertEqual(c.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(2, 1)]) + + # COD: Coding style default + self.assertTrue(c.segment[2].scod & 2) # sop + self.assertTrue(c.segment[2].scod & 4) # eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 6) # layers = 6 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 3) # levels + self.assertEqual(tuple(c.segment[2].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertTrue(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertTrue(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertTrue(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + + # COC: Coding style component + self.assertEqual(c.segment[3].ccoc, 0) + self.assertEqual(c.segment[3].spcoc[0], 3) # levels + self.assertEqual(tuple(c.segment[3].code_block_size), + (32, 32)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].spcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].spcoc[3] & 0x02) + # Termination on each coding pass + self.assertTrue(c.segment[3].spcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].spcoc[3] & 0x08) + # Predictable termination + self.assertTrue(c.segment[3].spcoc[3] & 0x0010) + # Segmentation symbols + self.assertTrue(c.segment[3].spcoc[3] & 0x0020) + self.assertEqual(c.segment[3].spcoc[4], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[4].sqcd & 0x1f, 0) # none + self.assertEqual(c.segment[4].guard_bits, 3) + self.assertEqual(c.segment[4].exponent, + [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) + self.assertEqual(c.segment[4].mantissa, + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + + # COM: comment + # Registration + self.assertEqual(c.segment[5].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[5].ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # One unknown marker + self.assertEqual(c.segment[6].marker_id, '0xff30') + + # SOT: start of tile part + self.assertEqual(c.segment[7].isot, 0) + self.assertEqual(c.segment[7].psot, 6047) + self.assertEqual(c.segment[7].tpsot, 0) + self.assertEqual(c.segment[7].tnsot, 1) + + # SOD: start of data + # Just one. + self.assertEqual(c.segment[8].marker_id, 'SOD') + + # SOP, EPH + sop = [x.marker_id for x in c.segment if x.marker_id == 'SOP'] + eph = [x.marker_id for x in c.segment if x.marker_id == 'EPH'] + self.assertEqual(len(sop), 24) + self.assertEqual(len(eph), 24) + + # EOC: end of codestream + self.assertEqual(c.segment[-1].marker_id, 'EOC') + + def test_NR_p0_03_dump(self): + jfile = opj_data_file('input/conformance/p0_03.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 256) + self.assertEqual(c.segment[1].ysiz, 256) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (4,)) + # signed + self.assertEqual(c.segment[1].signed, (True,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)]) + + # COD: Coding style default + self.assertTrue(c.segment[2].scod & 2) + self.assertFalse(c.segment[2].scod & 4) + self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) + self.assertEqual(c.segment[2].layers, 8) # 8 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 1) # levels + self.assertEqual(tuple(c.segment[2].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[3].sqcd & 0x1f, 1) # scalar implicit + self.assertEqual(c.segment[3].guard_bits, 2) + self.assertEqual(c.segment[3].exponent, [0]) + self.assertEqual(c.segment[3].mantissa, [0]) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[4].cqcc, 0) + self.assertEqual(c.segment[4].guard_bits, 2) + # quantization type + self.assertEqual(c.segment[4].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[4].exponent, [4, 5, 5, 6]) + self.assertEqual(c.segment[4].mantissa, [0, 0, 0, 0]) + + # POD: progression order change + self.assertEqual(c.segment[5].rspod, (0,)) + self.assertEqual(c.segment[5].cspod, (0,)) + self.assertEqual(c.segment[5].lyepod, (8,)) + self.assertEqual(c.segment[5].repod, (33,)) + self.assertEqual(c.segment[5].cdpod, (255,)) + self.assertEqual(c.segment[5].ppod, (glymur.core.LRCP,)) + + # CRG: component registration + self.assertEqual(c.segment[6].xcrg, (65424,)) + self.assertEqual(c.segment[6].ycrg, (32558,)) + + # COM: comment + # Registration + self.assertEqual(c.segment[7].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[7].ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # COM: comment + # Registration + self.assertEqual(c.segment[8].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[8].ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000," + + "2001 Algo Vision Technology") + + # COM: comment + # Registration + self.assertEqual(c.segment[9].rcme, glymur.core.RCME_BINARY) + # Comment value + self.assertEqual(len(c.segment[9].ccme), 62) + + # TLM (tile-part length) + self.assertEqual(c.segment[10].ztlm, 0) + self.assertEqual(c.segment[10].ttlm, (0, 1, 2, 3)) + self.assertEqual(c.segment[10].ptlm, (4267, 2117, 4080, 2081)) + + # SOT: start of tile part + self.assertEqual(c.segment[11].isot, 0) + self.assertEqual(c.segment[11].psot, 4267) + self.assertEqual(c.segment[11].tpsot, 0) + self.assertEqual(c.segment[11].tnsot, 1) + + # RGN: region of interest + self.assertEqual(c.segment[12].crgn, 0) + self.assertEqual(c.segment[12].srgn, 0) + self.assertEqual(c.segment[12].sprgn, 7) + + # SOD: start of data + # Just one. + self.assertEqual(c.segment[13].marker_id, 'SOD') + + def test_NR_p0_04_dump(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 640) + self.assertEqual(c.segment[1].ysiz, 480) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (640, 480)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1), (1, 1), (1, 1)]) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) + self.assertFalse(c.segment[2].scod & 4) + self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) + self.assertEqual(c.segment[2].layers, 20) # 20 + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 6) # levels + self.assertEqual(tuple(c.segment[2].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertTrue(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + self.assertEqual(c.segment[2].precinct_size, + [(128, 128), (128, 128), (128, 128), (128, 128), + (128, 128), (128, 128), (128, 128)]) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # scalar expounded + self.assertEqual(c.segment[3].guard_bits, 3) + self.assertEqual(c.segment[3].exponent, + [16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, + 11, 11, 11, 11, 11, 11]) + self.assertEqual(c.segment[3].mantissa, + [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, 1845, + 1868, 1925, 1925, 2007, 32, 32, 131, 2002, 2002, + 1888]) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[4].cqcc, 1) + # quantization type + self.assertEqual(c.segment[4].sqcc & 0x1f, 2) # none + self.assertEqual(c.segment[4].guard_bits, 3) + self.assertEqual(c.segment[4].exponent, + [14, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, + 9, 9, 9, 9, 9, 9]) + self.assertEqual(c.segment[4].mantissa, + [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, + 1845, 1868, 1925, 1925, 2007, 32, 32, 131, 2002, + 2002, 1888]) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[5].cqcc, 2) + # quantization type + self.assertEqual(c.segment[5].sqcc & 0x1f, 2) # none + self.assertEqual(c.segment[5].guard_bits, 3) + self.assertEqual(c.segment[5].exponent, + [14, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, + 9, 9, 9, 9, 9, 9]) + self.assertEqual(c.segment[5].mantissa, + [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, + 1845, 1868, 1925, 1925, 2007, 32, 32, 131, 2002, + 2002, 1888]) + + # COM: comment + # Registration + self.assertEqual(c.segment[6].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[6].ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # SOT: start of tile part + self.assertEqual(c.segment[7].isot, 0) + self.assertEqual(c.segment[7].psot, 264383) + self.assertEqual(c.segment[7].tpsot, 0) + self.assertEqual(c.segment[7].tnsot, 1) + + # SOD: start of data + # Just one. + self.assertEqual(c.segment[8].marker_id, 'SOD') + + def test_NR_p0_05_dump(self): + jfile = opj_data_file('input/conformance/p0_05.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 1024) + self.assertEqual(c.segment[1].ysiz, 1024) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), + (1024, 1024)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1), (1, 1), (2, 2), (2, 2)]) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) + self.assertFalse(c.segment[2].scod & 4) + self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) + self.assertEqual(c.segment[2].layers, 7) # 7 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 6) # levels + self.assertEqual(tuple(c.segment[2].code_block_size), + (32, 32)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # COC: Coding style component + self.assertEqual(c.segment[3].ccoc, 1) + self.assertEqual(c.segment[3].spcoc[0], 3) # levels + self.assertEqual(tuple(c.segment[3].code_block_size), + (32, 32)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].spcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].spcoc[3] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[3].spcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].spcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[3].spcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[3].spcoc[3] & 0x0020) + self.assertEqual(c.segment[3].spcoc[4], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + + # COC: Coding style component + self.assertEqual(c.segment[4].ccoc, 3) + self.assertEqual(c.segment[4].spcoc[0], 6) # levels + self.assertEqual(tuple(c.segment[4].code_block_size), + (32, 32)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[4].spcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[4].spcoc[3] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[4].spcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[4].spcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[4].spcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[4].spcoc[3] & 0x0020) + self.assertEqual(c.segment[4].spcoc[4], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[5].sqcd & 0x1f, 2) # scalar expounded + self.assertEqual(c.segment[5].guard_bits, 3) + self.assertEqual(c.segment[5].exponent, + [16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, + 11, 11, 11, 11, 11, 11]) + self.assertEqual(c.segment[5].mantissa, + [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, + 1845, 1868, 1925, 1925, 2007, 32, 32, 131, 2002, + 2002, 1888]) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[6].cqcc, 0) + # quantization type + self.assertEqual(c.segment[6].sqcc & 0x1f, 1) # scalar derived + self.assertEqual(c.segment[6].guard_bits, 3) + self.assertEqual(c.segment[6].exponent, [14]) + self.assertEqual(c.segment[6].mantissa, [0]) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[7].cqcc, 3) + # quantization type + self.assertEqual(c.segment[7].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[7].guard_bits, 3) + self.assertEqual(c.segment[7].exponent, + [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, + 9, 9, 10]) + self.assertEqual(c.segment[7].mantissa, [0] * 19) + + # COM: comment + # Registration + self.assertEqual(c.segment[8].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[8].ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # TLM (tile-part length) + self.assertEqual(c.segment[9].ztlm, 0) + self.assertEqual(c.segment[9].ttlm, (0,)) + self.assertEqual(c.segment[9].ptlm, (1310540,)) + + # SOT: start of tile part + self.assertEqual(c.segment[10].isot, 0) + self.assertEqual(c.segment[10].psot, 1310540) + self.assertEqual(c.segment[10].tpsot, 0) + self.assertEqual(c.segment[10].tnsot, 1) + + # SOD: start of data + # Just one. + self.assertEqual(c.segment[11].marker_id, 'SOD') + + def test_NR_p0_06_dump(self): + jfile = opj_data_file('input/conformance/p0_06.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].rsiz, 2) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 513) + self.assertEqual(c.segment[1].ysiz, 129) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (513, 129)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (12, 12, 12, 12)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1), (2, 1), (1, 2), (2, 2)]) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) + self.assertFalse(c.segment[2].scod & 4) + self.assertEqual(c.segment[2].spcod[0], glymur.core.RPCL) + self.assertEqual(c.segment[2].layers, 4) # 4 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 6) # levels + self.assertEqual(tuple(c.segment[2].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # scalar expounded + self.assertEqual(c.segment[3].guard_bits, 3) + self.assertEqual(c.segment[3].mantissa, + [512, 518, 522, 524, 516, 524, 522, 527, 523, 549, + 557, 561, 853, 852, 700, 163, 78, 1508, 1831]) + self.assertEqual(c.segment[3].exponent, + [7, 7, 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 2, 1, 2, + 1]) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[4].cqcc, 1) + # quantization type + self.assertEqual(c.segment[4].sqcc & 0x1f, 2) # scalar derived + self.assertEqual(c.segment[4].guard_bits, 4) + self.assertEqual(c.segment[4].mantissa, + [1527, 489, 665, 506, 487, 502, 493, 493, 500, 485, + 505, 491, 490, 491, 499, 509, 503, 496, 558]) + self.assertEqual(c.segment[4].exponent, + [10, 10, 10, 10, 9, 9, 9, 8, 8, 8, 7, 7, 7, 6, 6, 6, + 5, 5, 5]) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[5].cqcc, 2) + # quantization type + self.assertEqual(c.segment[5].sqcc & 0x1f, 2) # scalar derived + self.assertEqual(c.segment[5].guard_bits, 5) + self.assertEqual(c.segment[5].mantissa, + [1337, 728, 890, 719, 716, 726, 700, 718, 704, 704, + 712, 712, 717, 719, 701, 749, 753, 718, 841]) + self.assertEqual(c.segment[5].exponent, + [10, 10, 10, 10, 9, 9, 9, 8, 8, 8, 7, 7, 7, 6, 6, 6, + 5, 5, 5]) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[6].cqcc, 3) + # quantization type + self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[6].guard_bits, 6) + self.assertEqual(c.segment[6].mantissa, [0] * 19) + self.assertEqual(c.segment[6].exponent, + [12, 13, 13, 14, 13, 13, 14, 13, 13, 14, 13, 13, 14, + 13, 13, 14, 13, 13, 14]) + + # COC: Coding style component + self.assertEqual(c.segment[7].ccoc, 3) + self.assertEqual(c.segment[7].spcoc[0], 6) # levels + self.assertEqual(tuple(c.segment[7].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[7].spcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[7].spcoc[3] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[7].spcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[7].spcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[7].spcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[7].spcoc[3] & 0x0020) + self.assertEqual(c.segment[7].spcoc[4], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + + # RGN: region of interest + self.assertEqual(c.segment[8].crgn, 0) # component + self.assertEqual(c.segment[8].srgn, 0) # implicit + self.assertEqual(c.segment[8].sprgn, 11) + + # SOT: start of tile part + self.assertEqual(c.segment[9].isot, 0) + self.assertEqual(c.segment[9].psot, 33582) + self.assertEqual(c.segment[9].tpsot, 0) + self.assertEqual(c.segment[9].tnsot, 1) + + # RGN: region of interest + self.assertEqual(c.segment[10].crgn, 0) # component + self.assertEqual(c.segment[10].srgn, 0) # implicit + self.assertEqual(c.segment[10].sprgn, 9) + + # SOD: start of data + # Just one. + self.assertEqual(c.segment[11].marker_id, 'SOD') + + def test_NR_p0_07_dump(self): + jfile = opj_data_file('input/conformance/p0_07.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 2048) + self.assertEqual(c.segment[1].ysiz, 2048) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (12, 12, 12)) + # signed + self.assertEqual(c.segment[1].signed, (True, True, True)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1), (1, 1), (1, 1)]) + + # COD: Coding style default + self.assertTrue(c.segment[2].scod & 2) + self.assertTrue(c.segment[2].scod & 4) + self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) + self.assertEqual(c.segment[2].layers, 8) # 8 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 3) # levels + self.assertEqual(tuple(c.segment[2].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none + self.assertEqual(c.segment[3].guard_bits, 1) + self.assertEqual(c.segment[3].mantissa, [0] * 10) + self.assertEqual(c.segment[3].exponent, + [14, 15, 15, 16, 15, 15, 16, 15, 15, 16]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].ccme.decode('latin-1'), + "Kakadu-3.0.7") + + # SOT: start of tile part + self.assertEqual(c.segment[5].isot, 0) + self.assertEqual(c.segment[5].psot, 9951) + self.assertEqual(c.segment[5].tpsot, 0) + self.assertEqual(c.segment[5].tnsot, 0) # unknown + + # POD: progression order change + self.assertEqual(c.segment[6].rspod, (0,)) + self.assertEqual(c.segment[6].cspod, (0,)) + self.assertEqual(c.segment[6].lyepod, (9,)) + self.assertEqual(c.segment[6].repod, (3,)) + self.assertEqual(c.segment[6].cdpod, (3,)) + self.assertEqual(c.segment[6].ppod, (glymur.core.LRCP,)) + + # PLT: packet length, tile part + self.assertEqual(c.segment[7].zplt, 0) + #self.assertEqual(c.segment[7].iplt), 99) + + # SOD: start of data + self.assertEqual(c.segment[8].marker_id, 'SOD') + + def test_NR_p0_08_dump(self): + jfile = opj_data_file('input/conformance/p0_08.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 513) + self.assertEqual(c.segment[1].ysiz, 3072) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (513, 3072)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (12, 12, 12)) + # signed + self.assertEqual(c.segment[1].signed, (True, True, True)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1), (1, 1), (1, 1)]) + + # COD: Coding style default + self.assertTrue(c.segment[2].scod & 2) + self.assertTrue(c.segment[2].scod & 4) + self.assertEqual(c.segment[2].spcod[0], glymur.core.CPRL) + self.assertEqual(c.segment[2].layers, 30) # 30 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 7) # levels + self.assertEqual(tuple(c.segment[2].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # COC: Coding style component + self.assertEqual(c.segment[3].ccoc, 0) + self.assertEqual(c.segment[3].spcoc[0], 6) # levels + self.assertEqual(tuple(c.segment[3].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].spcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].spcoc[3] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[3].spcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].spcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[3].spcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[3].spcoc[3] & 0x0020) + self.assertEqual(c.segment[3].spcoc[4], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + + # COC: Coding style component + self.assertEqual(c.segment[4].ccoc, 1) + self.assertEqual(c.segment[4].spcoc[0], 7) # levels + self.assertEqual(tuple(c.segment[4].code_block_size), + (32, 32)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[4].spcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[4].spcoc[3] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[4].spcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[4].spcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[4].spcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[4].spcoc[3] & 0x0020) + self.assertEqual(c.segment[4].spcoc[4], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + + # COC: Coding style component + self.assertEqual(c.segment[5].ccoc, 2) + self.assertEqual(c.segment[5].spcoc[0], 8) # levels + self.assertEqual(tuple(c.segment[5].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[5].spcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[5].spcoc[3] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[5].spcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[5].spcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[5].spcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[5].spcoc[3] & 0x0020) + self.assertEqual(c.segment[5].spcoc[4], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[6].sqcd & 0x1f, 0) # none + self.assertEqual(c.segment[6].guard_bits, 4) + self.assertEqual(c.segment[6].mantissa, [0] * 22) + self.assertEqual(c.segment[6].exponent, + [11, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, + 12, 12, 13, 12, 12, 13, 12, 12, 13]) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[7].cqcc, 0) + # quantization type + self.assertEqual(c.segment[7].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[7].guard_bits, 4) + self.assertEqual(c.segment[7].mantissa, [0] * 19) + self.assertEqual(c.segment[7].exponent, + [11, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, + 12, 12, 13, 12, 12, 13]) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[8].cqcc, 2) + # quantization type + self.assertEqual(c.segment[8].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[8].guard_bits, 4) + self.assertEqual(c.segment[8].mantissa, [0] * 25) + self.assertEqual(c.segment[8].exponent, + [11, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, + 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13]) + + # COM: comment + # Registration + self.assertEqual(c.segment[9].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[9].ccme.decode('latin-1'), + "Kakadu-3.0.7") + + # SOT: start of tile part + self.assertEqual(c.segment[10].isot, 0) + self.assertEqual(c.segment[10].psot, 3820593) + self.assertEqual(c.segment[10].tpsot, 0) + self.assertEqual(c.segment[10].tnsot, 1) # unknown + + def test_NR_p0_09_dump(self): + jfile = opj_data_file('input/conformance/p0_09.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "0" means profile 2, or full capabilities + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 17) + self.assertEqual(c.segment[1].ysiz, 37) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (17, 37)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8,)) + # signed + self.assertEqual(c.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)]) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) + self.assertFalse(c.segment[2].scod & 4) + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # 1 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # levels + self.assertEqual(tuple(c.segment[2].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # scalar expounded + self.assertEqual(c.segment[3].guard_bits, 1) + self.assertEqual(c.segment[3].mantissa, + [1915, 1884, 1884, 1853, 1884, 1884, 1853, 1962, 1962, + 1986, 53, 53, 120, 26, 26, 1983]) + self.assertEqual(c.segment[3].exponent, + [16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 12, 12, 12, + 11, 11, 12]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].ccme.decode('latin-1'), + "Kakadu-3.0.7") + + # SOT: start of tile part + self.assertEqual(c.segment[5].isot, 0) + self.assertEqual(c.segment[5].psot, 478) + self.assertEqual(c.segment[5].tpsot, 0) + self.assertEqual(c.segment[5].tnsot, 1) # unknown + + # SOD: start of data + # Just one. + self.assertEqual(c.segment[6].marker_id, 'SOD') + + # EOC: end of codestream + self.assertEqual(c.segment[7].marker_id, 'EOC') + + def test_NR_p0_10_dump(self): + jfile = opj_data_file('input/conformance/p0_10.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 256) + self.assertEqual(c.segment[1].ysiz, 256) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(4, 4), (4, 4), (4, 4)]) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) + self.assertFalse(c.segment[2].scod & 4) + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 2) # 2 + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 3) # levels + self.assertEqual(tuple(c.segment[2].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none + self.assertEqual(c.segment[3].guard_bits, 0) + self.assertEqual(c.segment[3].mantissa, [0] * 10) + self.assertEqual(c.segment[3].exponent, + [11, 12, 12, 13, 12, 12, 13, 12, 12, 13]) + + # SOT: start of tile part + self.assertEqual(c.segment[4].isot, 0) + self.assertEqual(c.segment[4].psot, 2453) + self.assertEqual(c.segment[4].tpsot, 0) + self.assertEqual(c.segment[4].tnsot, 0) + + # SOD: start of data + self.assertEqual(c.segment[5].marker_id, 'SOD') + + # SOT: start of tile part + self.assertEqual(c.segment[6].isot, 1) + self.assertEqual(c.segment[6].psot, 2403) + self.assertEqual(c.segment[6].tpsot, 0) + self.assertEqual(c.segment[6].tnsot, 0) + + # SOD: start of data + self.assertEqual(c.segment[7].marker_id, 'SOD') + + # SOT: start of tile part + self.assertEqual(c.segment[8].isot, 2) + self.assertEqual(c.segment[8].psot, 2420) + self.assertEqual(c.segment[8].tpsot, 0) + self.assertEqual(c.segment[8].tnsot, 0) + + # SOD: start of data + self.assertEqual(c.segment[9].marker_id, 'SOD') + + # SOT: start of tile part + self.assertEqual(c.segment[10].isot, 3) + self.assertEqual(c.segment[10].psot, 2472) + self.assertEqual(c.segment[10].tpsot, 0) + self.assertEqual(c.segment[10].tnsot, 0) + + # SOD: start of data + self.assertEqual(c.segment[11].marker_id, 'SOD') + + # SOT: start of tile part + self.assertEqual(c.segment[12].isot, 0) + self.assertEqual(c.segment[12].psot, 1043) + self.assertEqual(c.segment[12].tpsot, 1) + self.assertEqual(c.segment[12].tnsot, 2) + + # SOD: start of data + self.assertEqual(c.segment[13].marker_id, 'SOD') + + # SOT: start of tile part + self.assertEqual(c.segment[14].isot, 1) + self.assertEqual(c.segment[14].psot, 1101) + self.assertEqual(c.segment[14].tpsot, 1) + self.assertEqual(c.segment[14].tnsot, 2) + + # SOD: start of data + self.assertEqual(c.segment[15].marker_id, 'SOD') + + # SOT: start of tile part + self.assertEqual(c.segment[16].isot, 3) + self.assertEqual(c.segment[16].psot, 1054) + self.assertEqual(c.segment[16].tpsot, 1) + self.assertEqual(c.segment[16].tnsot, 2) + + # SOD: start of data + self.assertEqual(c.segment[17].marker_id, 'SOD') + + # SOT: start of tile part + self.assertEqual(c.segment[18].isot, 2) + self.assertEqual(c.segment[18].psot, 14) + self.assertEqual(c.segment[18].tpsot, 1) + self.assertEqual(c.segment[18].tnsot, 0) + + # SOD: start of data + self.assertEqual(c.segment[19].marker_id, 'SOD') + + # SOT: start of tile part + self.assertEqual(c.segment[20].isot, 2) + self.assertEqual(c.segment[20].psot, 1089) + self.assertEqual(c.segment[20].tpsot, 2) + self.assertEqual(c.segment[20].tnsot, 0) + + # SOD: start of data + self.assertEqual(c.segment[21].marker_id, 'SOD') + + # EOC: end of codestream + self.assertEqual(c.segment[22].marker_id, 'EOC') + + def test_NR_p0_11_dump(self): + jfile = opj_data_file('input/conformance/p0_11.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 128) + self.assertEqual(c.segment[1].ysiz, 1) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8,)) + # signed + self.assertEqual(c.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)]) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) + self.assertTrue(c.segment[2].scod & 4) + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # 1 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 0) # levels + self.assertEqual(tuple(c.segment[2].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertTrue(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(c.segment[2].precinct_size, [(128, 2)]) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none + self.assertEqual(c.segment[3].guard_bits, 3) + self.assertEqual(c.segment[3].mantissa, [0]) + self.assertEqual(c.segment[3].exponent, [8]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # SOT: start of tile part + self.assertEqual(c.segment[5].isot, 0) + self.assertEqual(c.segment[5].psot, 118) + self.assertEqual(c.segment[5].tpsot, 0) + self.assertEqual(c.segment[5].tnsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[6].marker_id, 'SOD') + + # SOP, EPH + sop = [x.marker_id for x in c.segment if x.marker_id == 'SOP'] + eph = [x.marker_id for x in c.segment if x.marker_id == 'EPH'] + self.assertEqual(len(sop), 0) + self.assertEqual(len(eph), 1) + + # EOC: end of codestream + self.assertEqual(c.segment[-1].marker_id, 'EOC') + + def test_NR_p0_12_dump(self): + jfile = opj_data_file('input/conformance/p0_12.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 3) + self.assertEqual(c.segment[1].ysiz, 5) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (3, 5)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8,)) + # signed + self.assertEqual(c.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)]) + + # COD: Coding style default + self.assertTrue(c.segment[2].scod & 2) + self.assertFalse(c.segment[2].scod & 4) + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # 1 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 3) # levels + self.assertEqual(tuple(c.segment[2].code_block_size), + (32, 32)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertTrue(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none + self.assertEqual(c.segment[3].guard_bits, 3) + self.assertEqual(c.segment[3].mantissa, [0] * 10) + self.assertEqual(c.segment[3].exponent, + [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # SOT: start of tile part + self.assertEqual(c.segment[5].isot, 0) + self.assertEqual(c.segment[5].psot, 162) + self.assertEqual(c.segment[5].tpsot, 0) + self.assertEqual(c.segment[5].tnsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[6].marker_id, 'SOD') + + # SOP, EPH + sop = [x.marker_id for x in c.segment if x.marker_id == 'SOP'] + eph = [x.marker_id for x in c.segment if x.marker_id == 'EPH'] + self.assertEqual(len(sop), 4) + self.assertEqual(len(eph), 0) + + # EOC: end of codestream + self.assertEqual(c.segment[-1].marker_id, 'EOC') + + def test_NR_p0_13_dump(self): + jfile = opj_data_file('input/conformance/p0_13.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 1) + self.assertEqual(c.segment[1].ysiz, 1) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (1, 1)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, tuple([8] * 257)) + # signed + self.assertEqual(c.segment[1].signed, tuple([False] * 257)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 257) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 1) # levels + self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertTrue(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # COC: Coding style component + self.assertEqual(c.segment[3].ccoc, 2) + self.assertEqual(c.segment[3].spcoc[0], 1) # levels + self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].spcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].spcoc[3] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[3].spcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].spcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[3].spcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[3].spcoc[3] & 0x0020) + self.assertEqual(c.segment[3].spcoc[4], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[4].sqcd & 0x1f, 0) # none + self.assertEqual(c.segment[4].guard_bits, 2) + self.assertEqual(c.segment[4].mantissa, [0] * 4) + self.assertEqual(c.segment[4].exponent, + [8, 9, 9, 10]) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[5].cqcc, 1) + self.assertEqual(c.segment[5].guard_bits, 3) + # quantization type + self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[5].exponent, [9, 10, 10, 11]) + self.assertEqual(c.segment[5].mantissa, [0, 0, 0, 0]) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[6].cqcc, 2) + self.assertEqual(c.segment[6].guard_bits, 2) + # quantization type + self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[6].exponent, [9, 10, 10, 11]) + self.assertEqual(c.segment[6].mantissa, [0, 0, 0, 0]) + + # RGN: region of interest + self.assertEqual(c.segment[7].crgn, 3) + self.assertEqual(c.segment[7].srgn, 0) + self.assertEqual(c.segment[7].sprgn, 11) + + # POD: progression order change + self.assertEqual(c.segment[8].rspod, (0, 0)) + self.assertEqual(c.segment[8].cspod, (0, 128)) + self.assertEqual(c.segment[8].lyepod, (1, 1)) + self.assertEqual(c.segment[8].repod, (33, 33)) + self.assertEqual(c.segment[8].cdpod, (128, 257)) + self.assertEqual(c.segment[8].ppod, + (glymur.core.RLCP, glymur.core.CPRL)) + + # COM: comment + # Registration + self.assertEqual(c.segment[9].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[9].ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # SOT: start of tile part + self.assertEqual(c.segment[10].isot, 0) + self.assertEqual(c.segment[10].psot, 1537) + self.assertEqual(c.segment[10].tpsot, 0) + self.assertEqual(c.segment[10].tnsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[11].marker_id, 'SOD') + + # EOC: end of codestream + self.assertEqual(c.segment[12].marker_id, 'EOC') + + def test_NR_p0_14_dump(self): + jfile = opj_data_file('input/conformance/p0_14.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 49) + self.assertEqual(c.segment[1].ysiz, 49) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (49, 49)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) + self.assertFalse(c.segment[2].scod & 4) + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # 1 layer + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # levels + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none + self.assertEqual(c.segment[3].guard_bits, 1) + self.assertEqual(c.segment[3].mantissa, [0] * 16) + self.assertEqual(c.segment[3].exponent, + [10, 11, 11, 12, 11, 11, 12, 11, 11, 12, 11, 11, 12, + 11, 11, 12]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].ccme.decode('latin-1'), + "Kakadu-3.0.7") + + # SOT: start of tile part + self.assertEqual(c.segment[5].isot, 0) + self.assertEqual(c.segment[5].psot, 1528) + self.assertEqual(c.segment[5].tpsot, 0) + self.assertEqual(c.segment[5].tnsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[6].marker_id, 'SOD') + + # EOC: end of codestream + self.assertEqual(c.segment[7].marker_id, 'EOC') + + def test_NR_p0_15_dump(self): + jfile = opj_data_file('input/conformance/p0_15.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 256) + self.assertEqual(c.segment[1].ysiz, 256) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (4,)) + # signed + self.assertEqual(c.segment[1].signed, (True,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)]) + + # COD: Coding style default + self.assertTrue(c.segment[2].scod & 2) + self.assertFalse(c.segment[2].scod & 4) + self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) + self.assertEqual(c.segment[2].layers, 8) # layers = 8 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 1) # levels + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[3].sqcd & 0x1f, 1) # derived + self.assertEqual(c.segment[3].guard_bits, 2) + self.assertEqual(c.segment[3].mantissa, [0]) + self.assertEqual(c.segment[3].exponent, [0]) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[4].cqcc, 0) + self.assertEqual(c.segment[4].guard_bits, 2) + # quantization type + self.assertEqual(c.segment[4].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[4].mantissa, [0] * 4) + self.assertEqual(c.segment[4].exponent, [4, 5, 5, 6]) + + # POD: progression order change + self.assertEqual(c.segment[5].rspod, (0,)) + self.assertEqual(c.segment[5].cspod, (0,)) + self.assertEqual(c.segment[5].lyepod, (8,)) + self.assertEqual(c.segment[5].repod, (33,)) + self.assertEqual(c.segment[5].cdpod, (255,)) + self.assertEqual(c.segment[5].ppod, (glymur.core.LRCP,)) + + # CRG: component registration + self.assertEqual(c.segment[6].xcrg, (65424,)) + self.assertEqual(c.segment[6].ycrg, (32558,)) + + # COM: comment + # Registration + self.assertEqual(c.segment[7].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[7].ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # COM: comment + # Registration + self.assertEqual(c.segment[8].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[8].ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000," + + "2001 Algo Vision Technology") + + # COM: comment + # Registration + self.assertEqual(c.segment[9].rcme, glymur.core.RCME_BINARY) + # Comment value + self.assertEqual(len(c.segment[9].ccme), 62) + + # TLM: tile-part length + self.assertEqual(c.segment[10].ztlm, 0) + self.assertEqual(c.segment[10].ttlm, (0, 1, 2, 3)) + self.assertEqual(c.segment[10].ptlm, (4267, 2117, 4080, 2081)) + + # SOT: start of tile part + self.assertEqual(c.segment[11].isot, 0) + self.assertEqual(c.segment[11].psot, 4267) + self.assertEqual(c.segment[11].tpsot, 0) + self.assertEqual(c.segment[11].tnsot, 1) + + # RGN: region of interest + self.assertEqual(c.segment[12].crgn, 0) + self.assertEqual(c.segment[12].srgn, 0) + self.assertEqual(c.segment[12].sprgn, 7) + + # SOD: start of data + self.assertEqual(c.segment[13].marker_id, 'SOD') + + # 16 SOP markers would be here if we were looking for them + + # SOT: start of tile part + self.assertEqual(c.segment[31].isot, 1) + self.assertEqual(c.segment[31].psot, 2117) + self.assertEqual(c.segment[31].tpsot, 0) + self.assertEqual(c.segment[31].tnsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[32].marker_id, 'SOD') + + # 16 SOP markers would be here if we were looking for them + + # SOT: start of tile part + self.assertEqual(c.segment[49].isot, 2) + self.assertEqual(c.segment[49].psot, 4080) + self.assertEqual(c.segment[49].tpsot, 0) + self.assertEqual(c.segment[49].tnsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[50].marker_id, 'SOD') + + # 16 SOP markers would be here if we were looking for them + + # SOT: start of tile part + self.assertEqual(c.segment[67].isot, 3) + self.assertEqual(c.segment[67].psot, 2081) + self.assertEqual(c.segment[67].tpsot, 0) + self.assertEqual(c.segment[67].tnsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[68].marker_id, 'SOD') + + # 16 SOP markers would be here if we were looking for them + + # EOC: end of codestream + self.assertEqual(c.segment[85].marker_id, 'EOC') + + def test_NR_p0_16_dump(self): + jfile = opj_data_file('input/conformance/p0_16.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 128) + self.assertEqual(c.segment[1].ysiz, 128) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8,)) + # signed + self.assertEqual(c.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)]) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) + self.assertFalse(c.segment[2].scod & 4) + self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) + self.assertEqual(c.segment[2].layers, 3) # layers = 3 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 3) # levels + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none + self.assertEqual(c.segment[3].guard_bits, 2) + self.assertEqual(c.segment[3].mantissa, [0] * 10) + self.assertEqual(c.segment[3].exponent, + [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) + + # SOT: start of tile part + self.assertEqual(c.segment[4].isot, 0) + self.assertEqual(c.segment[4].psot, 7331) + self.assertEqual(c.segment[4].tpsot, 0) + self.assertEqual(c.segment[4].tnsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[5].marker_id, 'SOD') + + # EOC: end of codestream + self.assertEqual(c.segment[6].marker_id, 'EOC') + + def test_NR_p1_01_dump(self): + jfile = opj_data_file('input/conformance/p1_01.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 1 + self.assertEqual(c.segment[1].rsiz, 2) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 127) + self.assertEqual(c.segment[1].ysiz, 227) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (5, 128)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (127, 126)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (1, 101)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8,)) + # signed + self.assertEqual(c.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(2, 1)]) + + # COD: Coding style default + self.assertTrue(c.segment[2].scod & 2) # SOP + self.assertTrue(c.segment[2].scod & 4) # EPH + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 5) # layers = 5 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 3) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertTrue(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertTrue(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertTrue(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # COC: Coding style component + self.assertEqual(c.segment[3].ccoc, 0) + self.assertEqual(c.segment[3].spcoc[0], 3) # level + self.assertEqual(tuple(c.segment[3].code_block_size), (32, 32)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].spcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].spcoc[3] & 0x02) + # Termination on each coding pass + self.assertTrue(c.segment[3].spcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].spcoc[3] & 0x08) + # Predictable termination + self.assertTrue(c.segment[3].spcoc[3] & 0x0010) + # Segmentation symbols + self.assertTrue(c.segment[3].spcoc[3] & 0x0020) + self.assertEqual(c.segment[3].spcoc[4], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[4].sqcd & 0x1f, 0) # none + self.assertEqual(c.segment[4].guard_bits, 3) + self.assertEqual(c.segment[4].mantissa, [0] * 10) + self.assertEqual(c.segment[4].exponent, + [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) + + # COM: comment + # Registration + self.assertEqual(c.segment[5].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[5].ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # SOT: start of tile part + self.assertEqual(c.segment[6].isot, 0) + self.assertEqual(c.segment[6].psot, 4627) + self.assertEqual(c.segment[6].tpsot, 0) + self.assertEqual(c.segment[6].tnsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[7].marker_id, 'SOD') + + # SOP, EPH + sop = [x.marker_id for x in c.segment if x.marker_id == 'SOP'] + eph = [x.marker_id for x in c.segment if x.marker_id == 'EPH'] + self.assertEqual(len(sop), 20) + self.assertEqual(len(eph), 20) + + # EOC: end of codestream + self.assertEqual(c.segment[-1].marker_id, 'EOC') + + def test_NR_p1_02_dump(self): + jfile = opj_data_file('input/conformance/p1_02.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 1 + self.assertEqual(c.segment[1].rsiz, 2) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 640) + self.assertEqual(c.segment[1].ysiz, 480) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (640, 480)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, tuple([8] * 3)) + # signed + self.assertEqual(c.segment[1].signed, tuple([False] * 3)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 19) # layers = 19 + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 6) # level + self.assertEqual(tuple(c.segment[2].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertTrue(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertTrue(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + self.assertEqual(c.segment[2].precinct_size, + [(128, 128), (256, 256), (512, 512), (1024, 1024), + (2048, 2048), (4096, 4096), (8192, 8192)]) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # expounded + self.assertEqual(c.segment[3].guard_bits, 3) + self.assertEqual(c.segment[3].mantissa, + [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, 1845, + 1868, 1925, 1925, 2007, 32, 32, 131, 2002, 2002, + 1888]) + self.assertEqual(c.segment[3].exponent, + [16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, + 11, 11, 11, 11, 11, 11]) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[4].cqcc, 1) + self.assertEqual(c.segment[4].guard_bits, 3) + # quantization type + self.assertEqual(c.segment[4].sqcc & 0x1f, 2) # expounded + self.assertEqual(c.segment[4].mantissa, + [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, 1845, + 1868, 1925, 1925, 2007, 32, 32, 131, 2002, 2002, + 1888]) + self.assertEqual(c.segment[4].exponent, + [14, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, + 9, 9, 9, 9, 9, 9]) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[5].cqcc, 2) + self.assertEqual(c.segment[5].guard_bits, 3) + # quantization type + self.assertEqual(c.segment[5].sqcc & 0x1f, 2) # expounded + self.assertEqual(c.segment[5].mantissa, + [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, 1845, + 1868, 1925, 1925, 2007, 32, 32, 131, 2002, 2002, + 1888]) + self.assertEqual(c.segment[5].exponent, + [14, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, + 9, 9, 9, 9, 9, 9]) + + # COM: comment + # Registration + self.assertEqual(c.segment[6].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[6].ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # SOT: start of tile part + self.assertEqual(c.segment[7].isot, 0) + self.assertEqual(c.segment[7].psot, 262838) + self.assertEqual(c.segment[7].tpsot, 0) + self.assertEqual(c.segment[7].tnsot, 1) + + # PPT: packed packet headers, tile-part header + self.assertEqual(c.segment[8].marker_id, 'PPT') + self.assertEqual(c.segment[8].zppt, 0) + + # SOD: start of data + self.assertEqual(c.segment[9].marker_id, 'SOD') + + # EOC: end of codestream + self.assertEqual(c.segment[10].marker_id, 'EOC') + + def test_NR_p1_03_dump(self): + jfile = opj_data_file('input/conformance/p1_03.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 1 + self.assertEqual(c.segment[1].rsiz, 2) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 1024) + self.assertEqual(c.segment[1].ysiz, 1024) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), + (1024, 1024)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, tuple([8] * 4)) + # signed + self.assertEqual(c.segment[1].signed, tuple([False] * 4)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1), (1, 1), (2, 2), (2, 2)]) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) + self.assertEqual(c.segment[2].layers, 10) # layers = 10 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 6) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) + # Selective arithmetic coding bypass + self.assertTrue(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertTrue(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # COC: Coding style component + self.assertEqual(c.segment[3].ccoc, 1) + self.assertEqual(c.segment[3].spcoc[0], 3) # level + self.assertEqual(tuple(c.segment[3].code_block_size), (32, 32)) + # Selective arithmetic coding bypass + self.assertTrue(c.segment[3].spcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].spcoc[3] & 0x02) + # Termination on each coding pass + self.assertTrue(c.segment[3].spcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].spcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[3].spcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[3].spcoc[3] & 0x0020) + self.assertEqual(c.segment[3].spcoc[4], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + + # COC: Coding style component + self.assertEqual(c.segment[4].ccoc, 3) + self.assertEqual(c.segment[4].spcoc[0], 6) # level + self.assertEqual(tuple(c.segment[4].code_block_size), (32, 32)) + # Selective arithmetic coding bypass + self.assertTrue(c.segment[4].spcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[4].spcoc[3] & 0x02) + # Termination on each coding pass + self.assertTrue(c.segment[4].spcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[4].spcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[4].spcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[4].spcoc[3] & 0x0020) + self.assertEqual(c.segment[4].spcoc[4], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[5].sqcd & 0x1f, 2) # expounded + self.assertEqual(c.segment[5].guard_bits, 3) + self.assertEqual(c.segment[5].mantissa, + [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, 1845, + 1868, 1925, 1925, 2007, 32, 32, 131, 2002, 2002, + 1888]) + self.assertEqual(c.segment[5].exponent, + [16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, + 11, 11, 11, 11, 11, 11]) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[6].cqcc, 0) + self.assertEqual(c.segment[6].guard_bits, 3) + # quantization type + self.assertEqual(c.segment[6].sqcc & 0x1f, 1) # derived + self.assertEqual(c.segment[6].mantissa, [0]) + self.assertEqual(c.segment[6].exponent, [14]) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[7].cqcc, 3) + self.assertEqual(c.segment[7].guard_bits, 3) + # quantization type + self.assertEqual(c.segment[7].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[7].mantissa, [0] * 19) + self.assertEqual(c.segment[7].exponent, + [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, + 9, 9, 10]) + + # COM: comment + # Registration + self.assertEqual(c.segment[8].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[8].ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # PPM: packed packet headers, main header + self.assertEqual(c.segment[9].marker_id, 'PPM') + self.assertEqual(c.segment[9].zppm, 0) + + # TLM (tile-part length) + self.assertEqual(c.segment[10].ztlm, 0) + self.assertEqual(c.segment[10].ttlm, (0,)) + self.assertEqual(c.segment[10].ptlm, (1366780,)) + + # SOT: start of tile part + self.assertEqual(c.segment[11].isot, 0) + self.assertEqual(c.segment[11].psot, 1366780) + self.assertEqual(c.segment[11].tpsot, 0) + self.assertEqual(c.segment[11].tnsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[12].marker_id, 'SOD') + + # EOC: end of codestream + self.assertEqual(c.segment[13].marker_id, 'EOC') + + def test_NR_p1_04_dump(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 1 + self.assertEqual(c.segment[1].rsiz, 2) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 1024) + self.assertEqual(c.segment[1].ysiz, 1024) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (12,)) + # signed + self.assertEqual(c.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)]) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 3) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # expounded + self.assertEqual(c.segment[3].guard_bits, 2) + self.assertEqual(c.segment[3].mantissa, + [84, 423, 408, 435, 450, 435, 470, 549, 520, 618]) + self.assertEqual(c.segment[3].exponent, + [8, 10, 10, 10, 9, 9, 9, 8, 8, 8]) + + # TLM (tile-part length) + self.assertEqual(c.segment[4].ztlm, 0) + self.assertIsNone(c.segment[4].ttlm) + self.assertEqual(c.segment[4].ptlm, + (350, 356, 402, 245, 402, 564, 675, 283, 317, 299, + 330, 333, 346, 403, 839, 667, 328, 349, 274, 325, + 501, 561, 756, 710, 779, 620, 628, 675, 600, 66195, + 721, 719, 565, 565, 546, 586, 574, 641, 713, 634, + 573, 528, 544, 597, 771, 665, 624, 706, 568, 537, + 554, 546, 542, 635, 826, 667, 617, 606, 813, 586, + 641, 654, 669, 623)) + + # COM: comment + # Registration + self.assertEqual(c.segment[5].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[5].ccme.decode('latin-1'), + "Created by Aware, Inc.") + + # SOT: start of tile part + self.assertEqual(c.segment[6].isot, 0) + self.assertEqual(c.segment[6].psot, 350) + self.assertEqual(c.segment[6].tpsot, 0) + self.assertEqual(c.segment[6].tnsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[7].marker_id, 'SOD') + + # SOT: start of tile part + self.assertEqual(c.segment[8].isot, 1) + self.assertEqual(c.segment[8].psot, 356) + self.assertEqual(c.segment[8].tpsot, 0) + self.assertEqual(c.segment[8].tnsot, 1) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[9].sqcd & 0x1f, 2) # expounded + self.assertEqual(c.segment[9].guard_bits, 2) + self.assertEqual(c.segment[9].mantissa, + [75, 1093, 1098, 1115, 1157, 1134, 1186, 1217, 1245, + 1248]) + self.assertEqual(c.segment[9].exponent, + [8, 10, 10, 10, 9, 9, 9, 8, 8, 8]) + + # SOD: start of data + self.assertEqual(c.segment[10].marker_id, 'SOD') + + # SOT: start of tile part + self.assertEqual(c.segment[11].isot, 2) + self.assertEqual(c.segment[11].psot, 402) + self.assertEqual(c.segment[11].tpsot, 0) + self.assertEqual(c.segment[11].tnsot, 1) + + # and so on + + # There should be 64 SOD, SOT, QCD segments. + ids = [x.marker_id for x in c.segment if x.marker_id == 'SOT'] + self.assertEqual(len(ids), 64) + ids = [x.marker_id for x in c.segment if x.marker_id == 'SOD'] + self.assertEqual(len(ids), 64) + ids = [x.marker_id for x in c.segment if x.marker_id == 'QCD'] + self.assertEqual(len(ids), 64) + + # Tiles should be in order, right? + tiles = [x.isot for x in c.segment if x.marker_id == 'SOT'] + self.assertEqual(tiles, list(range(64))) + + # EOC: end of codestream + self.assertEqual(c.segment[-1].marker_id, 'EOC') + + def test_NR_p1_05_dump(self): + jfile = opj_data_file('input/conformance/p1_05.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 1 + self.assertEqual(c.segment[1].rsiz, 2) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 529) + self.assertEqual(c.segment[1].ysiz, 524) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (17, 12)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (37, 37)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (8, 2)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertTrue(c.segment[2].scod & 2) # sop + self.assertTrue(c.segment[2].scod & 4) # eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) + self.assertEqual(c.segment[2].layers, 2) # levels = 2 + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 7) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 8)) # cblk + # Selective arithmetic coding bypass + self.assertTrue(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertTrue(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertTrue(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + self.assertEqual(c.segment[2].precinct_size, [(16, 16)] * 8) + + self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # expounded + self.assertEqual(c.segment[3].guard_bits, 3) + self.assertEqual(c.segment[3].mantissa, + [1813, 1814, 1814, 1814, 1815, 1815, 1817, 1821, + 1821, 1827, 1845, 1845, 1868, 1925, 1925, 2007, + 32, 32, 131, 2002, 2002, 1888]) + self.assertEqual(c.segment[3].exponent, + [17, 17, 17, 17, 16, 16, 16, 15, 15, 15, 14, 14, + 14, 13, 13, 13, 11, 11, 11, 11, 11, 11]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # 225 consecutive PPM segments. + zppm = [x.zppm for x in c.segment[5:230]] + self.assertEqual(zppm, list(range(225))) + + # SOT: start of tile part + self.assertEqual(c.segment[230].isot, 0) + self.assertEqual(c.segment[230].psot, 580) + self.assertEqual(c.segment[230].tpsot, 0) + self.assertEqual(c.segment[230].tnsot, 1) + + # 225 total SOT segments + isot = [x.isot for x in c.segment if x.marker_id == 'SOT'] + self.assertEqual(isot, list(range(225))) + + # scads of SOP, EPH segments + sop = [x.marker_id for x in c.segment if x.marker_id == 'SOP'] + eph = [x.marker_id for x in c.segment if x.marker_id == 'EPH'] + self.assertEqual(len(sop), 26472) + self.assertEqual(len(eph), 0) + + # EOC: end of codestream + self.assertEqual(c.segment[-1].marker_id, 'EOC') + + def test_NR_p1_06_dump(self): + jfile = opj_data_file('input/conformance/p1_06.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 1 + self.assertEqual(c.segment[1].rsiz, 2) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 12) + self.assertEqual(c.segment[1].ysiz, 12) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (3, 3)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertTrue(c.segment[2].scod & 2) # sop + self.assertTrue(c.segment[2].scod & 4) # eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 4) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (32, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertTrue(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertTrue(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # expounded + self.assertEqual(c.segment[3].guard_bits, 3) + self.assertEqual(c.segment[3].mantissa, + [1821, 1845, 1845, 1868, 1925, 1925, 2007, 32, + 32, 131, 2002, 2002, 1888]) + self.assertEqual(c.segment[3].exponent, + [14, 14, 14, 14, 13, 13, 13, 11, 11, 11, + 11, 11, 11]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # SOT: start of tile part + self.assertEqual(c.segment[5].isot, 0) + self.assertEqual(c.segment[5].psot, 349) + self.assertEqual(c.segment[5].tpsot, 0) + self.assertEqual(c.segment[5].tnsot, 1) + + # PPT: packed packet headers, tile-part header + self.assertEqual(c.segment[6].marker_id, 'PPT') + self.assertEqual(c.segment[6].zppt, 0) + + # scads of SOP, EPH segments + + # 16 SOD segments + sods = [x for x in c.segment if x.marker_id == 'SOD'] + self.assertEqual(len(sods), 16) + + # 16 PPT segments + ppts = [x for x in c.segment if x.marker_id == 'PPT'] + self.assertEqual(len(ppts), 16) + + # 16 SOT segments + isots = [x.isot for x in c.segment if x.marker_id == 'SOT'] + self.assertEqual(isots, list(range(16))) + + # EOC: end of codestream + self.assertEqual(c.segment[-1].marker_id, 'EOC') + + def test_NR_p1_07_dump(self): + jfile = opj_data_file('input/conformance/p1_07.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 1 + self.assertEqual(c.segment[1].rsiz, 2) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 12) + self.assertEqual(c.segment[1].ysiz, 12) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (4, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (12, 12)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (4, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(4, 1), (1, 1)]) + + # COD: Coding style default + self.assertTrue(c.segment[2].scod & 2) # sop + self.assertTrue(c.segment[2].scod & 4) # eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.RPCL) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 1) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(c.segment[2].precinct_size, [(1, 1), (2, 2)]) + + # COC: Coding style component + self.assertEqual(c.segment[3].ccoc, 1) + self.assertEqual(c.segment[3].spcoc[0], 1) # level + self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].spcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].spcoc[3] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[3].spcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].spcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[3].spcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[3].spcoc[3] & 0x0020) + self.assertEqual(c.segment[3].spcoc[4], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(c.segment[3].precinct_size, [(2, 2), (4, 4)]) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[4].sqcd & 0x1f, 0) # none + self.assertEqual(c.segment[4].guard_bits, 2) + self.assertEqual(c.segment[4].mantissa, [0] * 4) + self.assertEqual(c.segment[4].exponent, [8, 9, 9, 10]) + + # COM: comment + # Registration + self.assertEqual(c.segment[5].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[5].ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # SOT: start of tile part + self.assertEqual(c.segment[6].isot, 0) + self.assertEqual(c.segment[6].psot, 434) + self.assertEqual(c.segment[6].tpsot, 0) + self.assertEqual(c.segment[6].tnsot, 1) + + # scads of SOP, EPH segments + + # EOC: end of codestream + self.assertEqual(c.segment[-1].marker_id, 'EOC') + + def test_NR_file1_dump(self): + jfile = opj_data_file('input/conformance/file1.jp2') + with warnings.catch_warnings(): + # Bad compatibility list item. + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'xml ', 'jp2h', 'xml ', + 'jp2c']) + + ids = [box.box_id for box in jp2.box[3].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + + # XML box + tags = [x.tag for x in jp2.box[2].xml.getroot()] + self.assertEqual(tags, + ['{http://www.jpeg.org/jpx/1.0/xml}' + + 'GENERAL_CREATION_INFO']) + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[3].box[0].height, 512) + self.assertEqual(jp2.box[3].box[0].width, 768) + self.assertEqual(jp2.box[3].box[0].num_components, 3) + self.assertEqual(jp2.box[3].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[3].box[0].signed, False) + self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[3].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[3].box[1].method, + glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(jp2.box[3].box[1].precedence, 0) + self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact ?? + self.assertEqual(jp2.box[3].box[1].colorspace, glymur.core.SRGB) + + # XML box + tags = [x.tag for x in jp2.box[4].xml.getroot()] + self.assertEqual(tags, ['{http://www.jpeg.org/jpx/1.0/xml}CAPTION', + '{http://www.jpeg.org/jpx/1.0/xml}LOCATION', + '{http://www.jpeg.org/jpx/1.0/xml}EVENT']) + + def test_NR_file2_dump(self): + jfile = opj_data_file('input/conformance/file2.jp2') + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr', 'cdef']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 640) + self.assertEqual(jp2.box[2].box[0].width, 480) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, + glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact?? + self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.YCC) + + # Jp2 Header + # Channel Definition + self.assertEqual(jp2.box[2].box[2].index, (0, 1, 2)) + self.assertEqual(jp2.box[2].box[2].channel_type, (0, 0, 0)) # color + self.assertEqual(jp2.box[2].box[2].association, (3, 2, 1)) # reverse + + def test_NR_file3_dump(self): + # Three 8-bit components in the sRGB-YCC colourspace, with the Cb and + # Cr components being subsampled 2x in both the horizontal and + # vertical directions. The components are stored in the standard + # order. + jfile = opj_data_file('input/conformance/file3.jp2') + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 640) + self.assertEqual(jp2.box[2].box[0].width, 480) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, + glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact + self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.YCC) + + # sub-sampling + codestream = jp2.get_codestream() + self.assertEqual(codestream.segment[1].xrsiz[0], 1) + self.assertEqual(codestream.segment[1].yrsiz[0], 1) + self.assertEqual(codestream.segment[1].xrsiz[1], 2) + self.assertEqual(codestream.segment[1].yrsiz[1], 2) + self.assertEqual(codestream.segment[1].xrsiz[2], 2) + self.assertEqual(codestream.segment[1].yrsiz[2], 2) + + def test_NR_file4_dump(self): + # One 8-bit component in the sRGB-grey colourspace. + jfile = opj_data_file('input/conformance/file4.jp2') + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 512) + self.assertEqual(jp2.box[2].box[0].width, 768) + self.assertEqual(jp2.box[2].box[0].num_components, 1) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, + glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact? + self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.GREYSCALE) + + def test_NR_file5_dump(self): + # Three 8-bit components in the ROMM-RGB colourspace, encapsulated in a + # JPX file. The components have been transformed using + # the RCT. The colourspace is specified using both a Restricted ICC + # profile and using the JPX-defined enumerated code for the ROMM-RGB + # colourspace. + jfile = opj_data_file('input/conformance/file5.jp2') + with warnings.catch_warnings(): + # There's a warning for an unknown compatibility entry. + # Ignore it here. + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[3].box] + self.assertEqual(ids, ['ihdr', 'colr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jpx ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[3].box[0].height, 512) + self.assertEqual(jp2.box[3].box[0].width, 768) + self.assertEqual(jp2.box[3].box[0].num_components, 3) + self.assertEqual(jp2.box[3].box[0].signed, False) + self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[3].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[3].box[1].method, + glymur.core.RESTRICTED_ICC_PROFILE) # enumerated + self.assertEqual(jp2.box[3].box[1].precedence, 0) + self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact + self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 546) + self.assertIsNone(jp2.box[3].box[1].colorspace) + + def test_NR_file6_dump(self): + jfile = opj_data_file('input/conformance/file6.jp2') + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 512) + self.assertEqual(jp2.box[2].box[0].width, 768) + self.assertEqual(jp2.box[2].box[0].num_components, 1) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 12) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, + glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact + self.assertIsNone(jp2.box[2].box[1].icc_profile) + self.assertEqual(jp2.box[2].box[1].colorspace, + glymur.core.GREYSCALE) + + def test_NR_file7_dump(self): + # Three 16-bit components in the e-sRGB colourspace, encapsulated in a + # JP2 compatible JPX file. The components have been transformed using + # the RCT. The colourspace is specified using both a Restricted ICC + # profile and using the JPX-defined enumerated code for the e-sRGB + # colourspace. + jfile = opj_data_file('input/conformance/file7.jp2') + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[3].box] + self.assertEqual(ids, ['ihdr', 'colr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jpx ') + self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[3].box[0].height, 640) + self.assertEqual(jp2.box[3].box[0].width, 480) + self.assertEqual(jp2.box[3].box[0].num_components, 3) + self.assertEqual(jp2.box[3].box[0].bits_per_component, 16) + self.assertEqual(jp2.box[3].box[0].signed, False) + self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[3].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[3].box[1].method, + glymur.core.RESTRICTED_ICC_PROFILE) + self.assertEqual(jp2.box[3].box[1].precedence, 0) + self.assertEqual(jp2.box[3].box[1].approximation, 1) + self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 13332) + self.assertIsNone(jp2.box[3].box[1].colorspace) + + def test_NR_file8_dump(self): + # One 8-bit component in a gamma 1.8 space. The colourspace is + # specified using a Restricted ICC profile. + jfile = opj_data_file('input/conformance/file8.jp2') + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'xml ', 'jp2c', + 'xml ']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 400) + self.assertEqual(jp2.box[2].box[0].width, 700) + self.assertEqual(jp2.box[2].box[0].num_components, 1) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, + glymur.core.RESTRICTED_ICC_PROFILE) # enumerated + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact + self.assertEqual(jp2.box[2].box[1].icc_profile['Size'], 414) + self.assertIsNone(jp2.box[2].box[1].colorspace) + + # XML box + tags = [x.tag for x in jp2.box[3].xml.getroot()] + self.assertEqual(tags, + ['{http://www.jpeg.org/jpx/1.0/xml}' + + 'GENERAL_CREATION_INFO']) + + # XML box + tags = [x.tag for x in jp2.box[5].xml.getroot()] + self.assertEqual(tags, + ['{http://www.jpeg.org/jpx/1.0/xml}CAPTION', + '{http://www.jpeg.org/jpx/1.0/xml}LOCATION', + '{http://www.jpeg.org/jpx/1.0/xml}THING', + '{http://www.jpeg.org/jpx/1.0/xml}EVENT']) + + def test_NR_file9_dump(self): + # Colormap + jfile = opj_data_file('input/conformance/file9.jp2') + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'pclr', 'cmap', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 512) + self.assertEqual(jp2.box[2].box[0].width, 768) + self.assertEqual(jp2.box[2].box[0].num_components, 1) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Palette box. + self.assertEqual(jp2.box[2].box[1].palette.shape, (256, 3)) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[0, 0], 0) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[0, 1], 0) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[0, 2], 0) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[128, 0], 73) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[128, 1], 92) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[128, 2], 53) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[255, 0], 245) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[255, 1], 245) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[255, 2], 245) + + # Component mapping box + self.assertEqual(jp2.box[2].box[2].component_index, (0, 0, 0)) + self.assertEqual(jp2.box[2].box[2].mapping_type, (1, 1, 1)) + self.assertEqual(jp2.box[2].box[2].palette_index, (0, 1, 2)) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[3].method, + glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(jp2.box[2].box[3].precedence, 0) + self.assertEqual(jp2.box[2].box[3].approximation, 1) # JPX exact + self.assertIsNone(jp2.box[2].box[3].icc_profile) + self.assertEqual(jp2.box[2].box[3].colorspace, glymur.core.SRGB) + + def test_NR_00042_j2k_dump(self): + # Profile 3. + jfile = opj_data_file('input/nonregression/_00042.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "3" means profile 3 + self.assertEqual(c.segment[1].rsiz, 3) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 1920) + self.assertEqual(c.segment[1].ysiz, 1080) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), + (1920, 1080)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (12, 12, 12)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.CPRL) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + self.assertEqual(c.segment[2].precinct_size[0], (128, 128)) + self.assertEqual(c.segment[2].precinct_size[1:], [(256, 256)] * 5) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[3].sqcd & 0x1f, 2) + self.assertEqual(c.segment[3].guard_bits, 2) + self.assertEqual(c.segment[3].mantissa, + [1824, 1776, 1776, 1728, 1792, 1792, 1760, 1872, + 1872, 1896, 5, 5, 71, 2003, 2003, 1890]) + self.assertEqual(c.segment[3].exponent, + [18, 18, 18, 18, 17, 17, 17, 16, + 16, 16, 14, 14, 14, 14, 14, 14]) + + # COC: Coding style component + self.assertEqual(c.segment[4].ccoc, 1) + self.assertEqual(c.segment[4].spcoc[0], 5) # level + self.assertEqual(tuple(c.segment[4].code_block_size), (32, 32)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[4].spcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[4].spcoc[3] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[4].spcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[4].spcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[4].spcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[4].spcoc[3] & 0x0020) + self.assertEqual(c.segment[4].spcoc[4], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[5].cqcc, 1) + self.assertEqual(c.segment[5].guard_bits, 2) + # quantization type + self.assertEqual(c.segment[5].sqcc & 0x1f, 2) + self.assertEqual(c.segment[5].mantissa, + [1824, 1776, 1776, 1728, 1792, 1792, 1760, 1872, + 1872, 1896, 5, 5, 71, 2003, 2003, 1890]) + self.assertEqual(c.segment[5].exponent, + [18, 18, 18, 18, 17, 17, 17, 16, 16, 16, 14, 14, 14, + 14, 14, 14]) + + # COC: Coding style component + self.assertEqual(c.segment[6].ccoc, 2) + self.assertEqual(c.segment[6].spcoc[0], 5) # level + self.assertEqual(tuple(c.segment[6].code_block_size), (32, 32)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[6].spcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[6].spcoc[3] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[6].spcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[6].spcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[6].spcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[6].spcoc[3] & 0x0020) + self.assertEqual(c.segment[6].spcoc[4], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[7].cqcc, 2) + self.assertEqual(c.segment[7].guard_bits, 2) + # quantization type + self.assertEqual(c.segment[7].sqcc & 0x1f, 2) # none + self.assertEqual(c.segment[7].mantissa, + [1824, 1776, 1776, 1728, 1792, 1792, 1760, 1872, + 1872, 1896, 5, 5, 71, 2003, 2003, 1890]) + self.assertEqual(c.segment[7].exponent, + [18, 18, 18, 18, 17, 17, 17, 16, 16, 16, 14, 14, + 14, 14, 14, 14]) + + # COM: comment + # Registration + self.assertEqual(c.segment[8].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[8].ccme.decode('latin-1'), + "Created by OpenJPEG version 1.3.0") + + # TLM (tile-part length) + self.assertEqual(c.segment[9].ztlm, 0) + self.assertEqual(c.segment[9].ttlm, (0, 0, 0)) + self.assertEqual(c.segment[9].ptlm, (45274, 20838, 8909)) + + # 3 tiles, one for each component + idx = [x.isot for x in c.segment if x.marker_id == 'SOT'] + self.assertEqual(idx, [0, 0, 0]) + lens = [x.psot for x in c.segment if x.marker_id == 'SOT'] + self.assertEqual(lens, [45274, 20838, 8909]) + tpsot = [x.tpsot for x in c.segment if x.marker_id == 'SOT'] + self.assertEqual(tpsot, [0, 1, 2]) + + sods = [x for x in c.segment if x.marker_id == 'SOD'] + self.assertEqual(len(sods), 3) + + # EOC: end of codestream + self.assertEqual(c.segment[-1].marker_id, 'EOC') + + def test_Bretagne2_j2k_dump(self): + # Profile 3. + jfile = opj_data_file('input/nonregression/Bretagne2.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "3" means profile 3 + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 2592) + self.assertEqual(c.segment[1].ysiz, 1944) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (640, 480)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 3) # layers = 3 + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(c.segment[2].precinct_size, + [(16, 16), (32, 32), (64, 64), (128, 128), + (128, 128), (128, 128)]) + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] + expected += ['SOT', 'COC', 'QCC', 'COC', 'QCC', 'SOD'] * 25 + expected += ['EOC'] + self.assertEqual(ids, expected) + + def test_NR_buxI_j2k_dump(self): + jfile = opj_data_file('input/nonregression/buxI.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 512) + self.assertEqual(c.segment[1].ysiz, 512) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (512, 512)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (16,)) + # signed + self.assertEqual(c.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 1) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 2) # layers = 2 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'SOT', 'SOD', 'EOC'] + self.assertEqual(ids, expected) + + def test_NR_buxR_j2k_dump(self): + jfile = opj_data_file('input/nonregression/buxR.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 512) + self.assertEqual(c.segment[1].ysiz, 512) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (512, 512)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (16,)) + # signed + self.assertEqual(c.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 1) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 2) # layers = 2 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'SOT', 'SOD', 'EOC'] + self.assertEqual(ids, expected) + + def test_NR_Cannotreaddatawithnosizeknown_j2k(self): + lst = ['input', 'nonregression', + 'Cannotreaddatawithnosizeknown.j2k'] + path = '/'.join(lst) + + jfile = opj_data_file(path) + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 1420) + self.assertEqual(c.segment[1].ysiz, 1416) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), + (1420, 1416)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (16,)) + # signed + self.assertEqual(c.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 1) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 11) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) + self.assertEqual(c.segment[3].guard_bits, 4) + self.assertEqual(c.segment[3].mantissa, [0] * 34) + self.assertEqual(c.segment[3].exponent, [16] + [17, 17, 18] * 11) + + def test_NR_CT_Phillips_JPEG2K_Decompr_Problem_dump(self): + jfile = opj_data_file('input/nonregression/' + + 'CT_Phillips_JPEG2K_Decompr_Problem.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 512) + self.assertEqual(c.segment[1].ysiz, 614) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (512, 614)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (12,)) + # signed + self.assertEqual(c.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 1) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[3].sqcd & 0x1f, 2) + self.assertEqual(c.segment[3].guard_bits, 1) + self.assertEqual(c.segment[3].mantissa, + [442, 422, 422, 403, 422, 422, 403, 472, 472, 487, + 591, 591, 676, 558, 558, 485]) + self.assertEqual(c.segment[3].exponent, + [22, 22, 22, 22, 21, 21, 21, 20, 20, 20, 19, 19, 19, + 18, 18, 18]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].ccme.decode('latin-1'), "Kakadu-3.2") + + def test_NR_cthead1_dump(self): + jfile = opj_data_file('input/nonregression/cthead1.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'CME'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 256) + self.assertEqual(c.segment[1].ysiz, 256) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (256, 256)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8,)) + # signed + self.assertEqual(c.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 1) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) + self.assertEqual(c.segment[3].guard_bits, 1) + self.assertEqual(c.segment[3].mantissa, [0] * 16) + self.assertEqual(c.segment[3].exponent, + [9, 10, 10, 11, 10, 10, 11, 10, 10, 11, 10, 10, 10, + 9, 9, 10]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].ccme.decode('latin-1'), "Kakadu-v6.3.1") + + # COM: comment + # Registration + self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].ccme.decode('latin-1'), "Kakadu-v6.3.1") + + def test_NR_illegalcolortransform_dump(self): + jfile = opj_data_file('input/nonregression/illegalcolortransform.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 1420) + self.assertEqual(c.segment[1].ysiz, 1416) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), + (1420, 1416)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (16,)) + # signed + self.assertEqual(c.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 1) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 11) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) + self.assertEqual(c.segment[3].guard_bits, 4) + self.assertEqual(c.segment[3].mantissa, [0] * 34) + self.assertEqual(c.segment[3].exponent, [16] + [17, 17, 18] * 11) + + def test_NR_j2k32_dump(self): + jfile = opj_data_file('input/nonregression/j2k32.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 256) + self.assertEqual(c.segment[1].ysiz, 256) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (256, 256)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (True, True, True)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) + self.assertEqual(c.segment[3].guard_bits, 2) + self.assertEqual(c.segment[3].mantissa, [0] * 16) + self.assertEqual(c.segment[3].exponent, [8, 9, 9, 10, 9, 9, 10, 9, 9, + 10, 9, 9, 10, 9, 9, 10]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].rcme, glymur.core.RCME_BINARY) + # Comment value + self.assertEqual(len(c.segment[4].ccme), 36) + + def test_NR_kakadu_v4_4_openjpegv2_broken_dump(self): + jfile = opj_data_file('input/nonregression/' + + 'kakadu_v4-4_openjpegv2_broken.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 2048) + self.assertEqual(c.segment[1].ysiz, 2500) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), + (2048, 2500)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (16,)) + # signed + self.assertEqual(c.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 1) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 12) # layers = 12 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 8) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) + self.assertEqual(c.segment[3].guard_bits, 1) + self.assertEqual(c.segment[3].mantissa, [0] * 25) + self.assertEqual(c.segment[3].exponent, + [17, 18, 18, 19, 18, 18, 19, 18, 18, 19, 18, 18, 19, + 18, 18, 19, 18, 18, 19, 18, 18, 19, 18, 18, 19]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].ccme.decode('latin-1'), "Kakadu-v4.4") + + # COM: comment + # Registration + self.assertEqual(c.segment[5].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + expected = "Kdu-Layer-Info: log_2{Delta-D(MSE)/[2^16*Delta-L(bytes)]}," + expected += " L(bytes)\n" + expected += " -65.4, 6.8e+004\n" + expected += " -66.3, 1.0e+005\n" + expected += " -67.3, 2.0e+005\n" + expected += " -68.5, 4.1e+005\n" + expected += " -69.0, 5.1e+005\n" + expected += " -69.5, 5.9e+005\n" + expected += " -69.7, 6.8e+005\n" + expected += " -70.3, 8.2e+005\n" + expected += " -70.8, 1.0e+006\n" + expected += " -71.9, 1.4e+006\n" + expected += " -73.8, 2.0e+006\n" + expected += "-256.0, 3.7e+006\n" + self.assertEqual(c.segment[5].ccme.decode('latin-1'), expected) + + def test_NR_MarkerIsNotCompliant_j2k_dump(self): + jfile = opj_data_file('input/nonregression/MarkerIsNotCompliant.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 1420) + self.assertEqual(c.segment[1].ysiz, 1416) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), + (1420, 1416)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (16,)) + # signed + self.assertEqual(c.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 1) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 11) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) + self.assertEqual(c.segment[3].guard_bits, 4) + self.assertEqual(c.segment[3].mantissa, [0] * 34) + self.assertEqual(c.segment[3].exponent, + [16, 17, 17, 18, 17, 17, 18, 17, 17, 18, 17, 17, 18, + 17, 17, 18, 17, 17, 18, 17, 17, 18, 17, 17, 18, 17, + 17, 18, 17, 17, 18, 17, 17, 18]) + + def test_NR_movie_00000(self): + jfile = opj_data_file('input/nonregression/movie_00000.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 1920) + self.assertEqual(c.segment[1].ysiz, 1080) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), + (1920, 1080)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) + self.assertEqual(c.segment[3].guard_bits, 2) + self.assertEqual(c.segment[3].mantissa, [0] * 16) + self.assertEqual(c.segment[3].exponent, + [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) + + def test_NR_movie_00001(self): + jfile = opj_data_file('input/nonregression/movie_00001.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 1920) + self.assertEqual(c.segment[1].ysiz, 1080) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), + (1920, 1080)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) + self.assertEqual(c.segment[3].guard_bits, 2) + self.assertEqual(c.segment[3].mantissa, [0] * 16) + self.assertEqual(c.segment[3].exponent, + [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) + + def test_NR_movie_00002(self): + jfile = opj_data_file('input/nonregression/movie_00002.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 1920) + self.assertEqual(c.segment[1].ysiz, 1080) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), + (1920, 1080)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) + self.assertEqual(c.segment[3].guard_bits, 2) + self.assertEqual(c.segment[3].mantissa, [0] * 16) + self.assertEqual(c.segment[3].exponent, + [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) + + def test_NR_orb_blue10_lin_j2k_dump(self): + jfile = opj_data_file('input/nonregression/orb-blue10-lin-j2k.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 117) + self.assertEqual(c.segment[1].ysiz, 117) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (117, 117)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 4) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) + self.assertEqual(c.segment[3].guard_bits, 2) + self.assertEqual(c.segment[3].mantissa, [0] * 16) + self.assertEqual(c.segment[3].exponent, + [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) + + def test_NR_orb_blue10_win_j2k_dump(self): + jfile = opj_data_file('input/nonregression/orb-blue10-win-j2k.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 117) + self.assertEqual(c.segment[1].ysiz, 117) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (117, 117)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 4) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) + self.assertEqual(c.segment[3].guard_bits, 2) + self.assertEqual(c.segment[3].mantissa, [0] * 16) + self.assertEqual(c.segment[3].exponent, + [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) + + def test_NR_pacs_ge_j2k_dump(self): + jfile = opj_data_file('input/nonregression/pacs.ge.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 512) + self.assertEqual(c.segment[1].ysiz, 512) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (512, 512)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (16,)) + # signed + self.assertEqual(c.segment[1].signed, (True,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 1) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 16) # layers = 16 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) + self.assertEqual(c.segment[3].guard_bits, 1) + self.assertEqual(c.segment[3].mantissa, [0] * 16) + self.assertEqual(c.segment[3].exponent, + [18, 19, 19, 20, 19, 19, 20, 19, 19, 20, 19, 19, 20, + 19, 19, 20]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].ccme.decode('latin-1'), + "Kakadu-2.0.2") + + def test_NR_test_lossless_j2k_dump(self): + jfile = opj_data_file('input/nonregression/test_lossless.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 1024) + self.assertEqual(c.segment[1].ysiz, 1024) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), + (1024, 1024)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (12,)) + # signed + self.assertEqual(c.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 1) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) + self.assertEqual(c.segment[3].guard_bits, 2) + self.assertEqual(c.segment[3].mantissa, [0] * 16) + self.assertEqual(c.segment[3].exponent, + [12, 13, 13, 14, 13, 13, 14, 13, 13, 14, 13, 13, 14, + 13, 13, 14]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].ccme.decode('latin-1'), + "ClearCanvas DICOM OpenJPEG") + + def test_NR_123_j2c_dump(self): + jfile = opj_data_file('input/nonregression/123.j2c') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 1800) + self.assertEqual(c.segment[1].ysiz, 1800) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), + (1800, 1800)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (16,)) + # signed + self.assertEqual(c.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 1) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 11) # level + self.assertEqual(tuple(c.segment[2].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) + self.assertEqual(c.segment[3].guard_bits, 4) + self.assertEqual(c.segment[3].mantissa, [0] * 34) + self.assertEqual(c.segment[3].exponent, + [16] + [17, 17, 18] * 11) + + def test_NR_bug_j2c_dump(self): + jfile = opj_data_file('input/nonregression/bug.j2c') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 1800) + self.assertEqual(c.segment[1].ysiz, 1800) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), + (1800, 1800)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (16,)) + # signed + self.assertEqual(c.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 1) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 11) # level + self.assertEqual(tuple(c.segment[2].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) + self.assertEqual(c.segment[3].guard_bits, 4) + self.assertEqual(c.segment[3].mantissa, [0] * 34) + self.assertEqual(c.segment[3].exponent, + [16] + [17, 17, 18] * 11) + + def test_NR_kodak_2layers_lrcp_j2c_dump(self): + jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 2048) + self.assertEqual(c.segment[1].ysiz, 1556) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), + (2048, 1556)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (12, 12, 12)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 2) # layers = 2 + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), + (32, 32)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + self.assertEqual(c.segment[2].precinct_size, + [(128, 128)] + [(256, 256)] * 5) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 2) + self.assertEqual(c.segment[3].guard_bits, 2) + self.assertEqual(c.segment[3].mantissa, [0] * 16) + self.assertEqual(c.segment[3].exponent, + [13, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, + 13, 13, 13]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].ccme.decode('latin-1'), + "DCP-Werkstatt") + + def test_NR_issue104_jpxstream_dump(self): + jfile = opj_data_file('input/nonregression/issue104_jpxstream.jp2') + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[3].box] + self.assertEqual(ids, ['ihdr', 'colr', 'pclr', 'cmap']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + self.assertEqual(jp2.box[1].compatibility_list[1], 'jpxb') + self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') + + # Reader requirements talk. + # unrestricted jpeg 2000 part 1 + self.assertTrue(5 in jp2.box[2].standard_flag) + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[3].box[0].height, 203) + self.assertEqual(jp2.box[3].box[0].width, 479) + self.assertEqual(jp2.box[3].box[0].num_components, 1) + self.assertEqual(jp2.box[3].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[3].box[0].signed, False) + self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True) + self.assertEqual(jp2.box[3].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[3].box[1].method, + glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(jp2.box[3].box[1].precedence, 2) + self.assertEqual(jp2.box[3].box[1].approximation, 1) # exact + self.assertEqual(jp2.box[3].box[1].colorspace, glymur.core.SRGB) + + # Jp2 Header + # Palette box. + self.assertEqual(jp2.box[3].box[2].palette.shape, (256, 3)) + + # Jp2 Header + # Component mapping box + self.assertEqual(jp2.box[3].box[3].component_index, (0, 0, 0)) + self.assertEqual(jp2.box[3].box[3].mapping_type, (1, 1, 1)) + self.assertEqual(jp2.box[3].box[3].palette_index, (0, 1, 2)) + + c = jp2.box[4].main_header + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 479) + self.assertEqual(c.segment[1].ysiz, 203) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (256, 203)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8,)) + # signed + self.assertEqual(c.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)]) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), + (32, 32)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) + self.assertEqual(c.segment[3].guard_bits, 2) + self.assertEqual(c.segment[3].mantissa, [0] * 16) + self.assertEqual(c.segment[3].exponent, [8] + [9, 9, 10] * 5) + + def test_NR_issue188_beach_64bitsbox(self): + lst = ['input', 'nonregression', 'issue188_beach_64bitsbox.jp2'] + jfile = opj_data_file('/'.join(lst)) + with warnings.catch_warnings(): + # There's a warning for an unknown box. We explicitly test for + # that down below. + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', b'XML ', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 200) + self.assertEqual(jp2.box[2].box[0].width, 200) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, True) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, + glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) + self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) + + # Skip the 4th box, it is uknown. + + c = jp2.box[4].main_header + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'CME'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 200) + self.assertEqual(c.segment[1].ysiz, 200) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (200, 200)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 2) + self.assertEqual(c.segment[3].guard_bits, 1) + + def test_NR_issue206_image_000_dump(self): + jfile = opj_data_file('input/nonregression/issue206_image-000.jp2') + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[3].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + self.assertEqual(jp2.box[1].compatibility_list[1], 'jpxb') + self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') + + # Reader requirements talk. + # unrestricted jpeg 2000 part 1 + self.assertTrue(5 in jp2.box[2].standard_flag) + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[3].box[0].height, 326) + self.assertEqual(jp2.box[3].box[0].width, 431) + self.assertEqual(jp2.box[3].box[0].num_components, 3) + self.assertEqual(jp2.box[3].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[3].box[0].signed, False) + self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True) + self.assertEqual(jp2.box[3].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[3].box[1].method, + glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(jp2.box[3].box[1].precedence, 2) + self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact + self.assertEqual(jp2.box[3].box[1].colorspace, glymur.core.SRGB) + + c = jp2.box[4].main_header + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 431) + self.assertEqual(c.segment[1].ysiz, 326) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (256, 256)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), + (32, 32)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 2) + self.assertEqual(c.segment[3].guard_bits, 2) + self.assertEqual(c.segment[3].mantissa, [0] * 16) + self.assertEqual(c.segment[3].exponent, [8] + [9, 9, 10] * 5) + + def test_NR_Marrin_jp2_dump(self): + jfile = opj_data_file('input/nonregression/Marrin.jp2') + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr', 'cdef', 'res ']) + + ids = [box.box_id for box in jp2.box[2].box[3].box] + self.assertEqual(ids, ['resd']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 135) + self.assertEqual(jp2.box[2].box[0].width, 135) + self.assertEqual(jp2.box[2].box[0].num_components, 2) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, True) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, + glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 + self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.GREYSCALE) + + # Jp2 Header + # Channel Definition + self.assertEqual(jp2.box[2].box[2].index, (0, 1)) + self.assertEqual(jp2.box[2].box[2].channel_type, (0, 1)) # opacity + self.assertEqual(jp2.box[2].box[2].association, (0, 0)) # both main + + c = jp2.box[3].main_header + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 135) + self.assertEqual(c.segment[1].ysiz, 135) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (135, 135)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 2) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 2) # layers = 2 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 2) + self.assertEqual(c.segment[3].guard_bits, 1) + self.assertEqual(c.segment[3].mantissa, + [1822, 1770, 1770, 1724, 1792, 1792, 1762, 1868, 1868, + 1892, 3, 3, 69, 2002, 2002, 1889]) + self.assertEqual(c.segment[3].exponent, + [14] * 4 + [13] * 3 + [12] * 3 + [10] * 6) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].ccme.decode('latin-1'), + "Kakadu-v5.2.1") + + def test_NR_mem_b2b86b74_2753_dump(self): + jfile = opj_data_file('input/nonregression/mem-b2b86b74-2753.jp2') + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[3].box] + self.assertEqual(ids, ['ihdr', 'colr', 'pclr', 'cmap']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + self.assertEqual(jp2.box[1].compatibility_list[1], 'jpxb') + self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') + + # Reader requirements talk. + # unrestricted jpeg 2000 part 1 + self.assertTrue(5 in jp2.box[2].standard_flag) + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[3].box[0].height, 46) + self.assertEqual(jp2.box[3].box[0].width, 124) + self.assertEqual(jp2.box[3].box[0].num_components, 1) + self.assertEqual(jp2.box[3].box[0].bits_per_component, 4) + self.assertEqual(jp2.box[3].box[0].signed, False) + self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True) + self.assertEqual(jp2.box[3].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[3].box[1].method, + glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(jp2.box[3].box[1].precedence, 2) + self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact + self.assertEqual(jp2.box[3].box[1].colorspace, glymur.core.SRGB) + + # Jp2 Header + # Palette box. + # 3 columns with 16 entries. + self.assertEqual(jp2.box[3].box[2].palette.shape, (16, 3)) + + # Jp2 Header + # Component mapping box + self.assertEqual(jp2.box[3].box[3].component_index, (0, 0, 0)) + self.assertEqual(jp2.box[3].box[3].mapping_type, (1, 1, 1)) + self.assertEqual(jp2.box[3].box[3].palette_index, (0, 1, 2)) + + c = jp2.box[4].main_header + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 124) + self.assertEqual(c.segment[1].ysiz, 46) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (124, 46)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (4,)) + # signed + self.assertEqual(c.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)]) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), + (32, 32)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) + self.assertEqual(c.segment[3].guard_bits, 2) + self.assertEqual(c.segment[3].mantissa, [0] * 16) + self.assertEqual(c.segment[3].exponent, [4] + [5, 5, 6] * 5) + + def test_NR_merged_dump(self): + jfile = opj_data_file('input/nonregression/merged.jp2') + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 576) + self.assertEqual(jp2.box[2].box[0].width, 766) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, + glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 + self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.YCC) + + c = jp2.box[3].main_header + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'POD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 766) + self.assertEqual(c.segment[1].ysiz, 576) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (766, 576)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1), (2, 1), (2, 1)]) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), + (32, 128)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) + self.assertEqual(c.segment[3].guard_bits, 1) + self.assertEqual(c.segment[3].mantissa, [0] * 16) + self.assertEqual(c.segment[3].exponent, [8] + [9, 9, 10] * 5) + + # POD: progression order change + self.assertEqual(c.segment[4].rspod, (0, 0)) + self.assertEqual(c.segment[4].cspod, (0, 1)) + self.assertEqual(c.segment[4].lyepod, (1, 1)) + self.assertEqual(c.segment[4].repod, (6, 6)) + self.assertEqual(c.segment[4].cdpod, (1, 3)) + + podvals = (glymur.core.LRCP, glymur.core.LRCP) + self.assertEqual(c.segment[4].ppod, podvals) + + def test_NR_orb_blue10_lin_jp2_dump(self): + jfile = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2') + with warnings.catch_warnings(): + # This file has an invalid ICC profile + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 117) + self.assertEqual(jp2.box[2].box[0].width, 117) + self.assertEqual(jp2.box[2].box[0].num_components, 4) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, + glymur.core.RESTRICTED_ICC_PROFILE) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 + self.assertIsNone(jp2.box[2].box[1].icc_profile) + self.assertIsNone(jp2.box[2].box[1].colorspace) + + c = jp2.box[3].main_header + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 117) + self.assertEqual(c.segment[1].ysiz, 117) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (117, 117)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 4) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) + self.assertEqual(c.segment[3].guard_bits, 2) + self.assertEqual(c.segment[3].mantissa, [0] * 16) + self.assertEqual(c.segment[3].exponent, + [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) + + def test_NR_orb_blue10_win_jp2_dump(self): + jfile = opj_data_file('input/nonregression/orb-blue10-win-jp2.jp2') + with warnings.catch_warnings(): + # This file has an invalid ICC profile + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 117) + self.assertEqual(jp2.box[2].box[0].width, 117) + self.assertEqual(jp2.box[2].box[0].num_components, 4) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, + glymur.core.RESTRICTED_ICC_PROFILE) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 + self.assertIsNone(jp2.box[2].box[1].icc_profile) + self.assertIsNone(jp2.box[2].box[1].colorspace) + + c = jp2.box[3].main_header + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 117) + self.assertEqual(c.segment[1].ysiz, 117) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (117, 117)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 4) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 0) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 0) + self.assertEqual(c.segment[3].guard_bits, 2) + self.assertEqual(c.segment[3].mantissa, [0] * 16) + self.assertEqual(c.segment[3].exponent, + [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) + + +if __name__ == "__main__": + unittest.main() diff --git a/glymur/test/test_opj_suite_warn.py b/glymur/test/test_opj_suite_warn.py new file mode 100644 index 0000000..c83f938 --- /dev/null +++ b/glymur/test/test_opj_suite_warn.py @@ -0,0 +1,366 @@ +""" +The tests defined here roughly correspond to what is in the OpenJPEG test +suite. +""" + +# Some test names correspond with openjpeg tests. Long names are ok in this +# case. +# pylint: disable=C0103 + +# All of these tests correspond to tests in openjpeg, so no docstring is really +# needed. +# pylint: disable=C0111 + +# This module is very long, cannot be helped. +# pylint: disable=C0302 + +# unittest fools pylint with "too many public methods" +# pylint: disable=R0904 + +# Some tests use numpy test infrastructure, which means the tests never +# reference "self", so pylint claims it should be a function. No, no, no. +# pylint: disable=R0201 + +# Many tests are pretty long and that can't be helped. +# pylint: disable=R0915 + +# asserWarns introduced in python 3.2 (python2.7/pylint issue) +# pylint: disable=E1101 + +import re +import sys +import unittest + +import warnings + +import numpy as np + +from glymur import Jp2k +import glymur + +from .fixtures import OPJ_DATA_ROOT +from .fixtures import mse, peak_tolerance, read_pgx, opj_data_file + + +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class TestSuiteDumpWarnings(unittest.TestCase): + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_NR_broken_jp2_dump(self): + jfile = opj_data_file('input/nonregression/broken.jp2') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') + # colr box has bad length. + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 152) + self.assertEqual(jp2.box[2].box[0].width, 203) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, + glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) # not allowed? + self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) + + c = jp2.box[3].main_header + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 203) + self.assertEqual(c.segment[1].ysiz, 152) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (203, 152)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COM: comment + # Registration + self.assertEqual(c.segment[2].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[2].ccme.decode('latin-1'), + "Creator: JasPer Version 1.701.0") + + # COD: Coding style default + self.assertFalse(c.segment[3].scod & 2) # no sop + self.assertFalse(c.segment[3].scod & 4) # no eph + self.assertEqual(c.segment[3].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[3].layers, 1) # layers = 1 + self.assertEqual(c.segment[3].spcod[3], 1) # mct + self.assertEqual(c.segment[3].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[3].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[3].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[3].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[3].spcod[7] & 0x0020) + self.assertEqual(c.segment[3].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[3].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[4].sqcd & 0x1f, 0) + self.assertEqual(c.segment[4].guard_bits, 2) + self.assertEqual(c.segment[4].mantissa, [0] * 16) + self.assertEqual(c.segment[4].exponent, + [8] + [9, 9, 10] * 5) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[5].cqcc, 1) + self.assertEqual(c.segment[5].guard_bits, 2) + # quantization type + self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[5].mantissa, [0] * 16) + self.assertEqual(c.segment[5].exponent, + [8] + [9, 9, 10] * 5) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[6].cqcc, 2) + self.assertEqual(c.segment[6].guard_bits, 2) + # quantization type + self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[6].mantissa, [0] * 16) + self.assertEqual(c.segment[6].exponent, + [8] + [9, 9, 10] * 5) + + def test_NR_broken2_jp2_dump(self): + # Invalid marker ID on codestream. + jfile = opj_data_file('input/nonregression/broken2.jp2') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') + jp2 = Jp2k(jfile) + + self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') + + def test_NR_broken3_jp2_dump(self): + jfile = opj_data_file('input/nonregression/broken3.jp2') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 152) + self.assertEqual(jp2.box[2].box[0].width, 203) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, + glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 + self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) + + c = jp2.box[3].main_header + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 203) + self.assertEqual(c.segment[1].ysiz, 152) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (203, 152)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COM: comment + # Registration + self.assertEqual(c.segment[2].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[2].ccme.decode('latin-1'), + "Creator: JasPer Vers)on 1.701.0") + + # COD: Coding style default + self.assertFalse(c.segment[3].scod & 2) # no sop + self.assertFalse(c.segment[3].scod & 4) # no eph + self.assertEqual(c.segment[3].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[3].layers, 1) # layers = 1 + self.assertEqual(c.segment[3].spcod[3], 1) # mct + self.assertEqual(c.segment[3].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[3].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[3].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[3].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[3].spcod[7] & 0x0020) + self.assertEqual(c.segment[3].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[3].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[4].sqcd & 0x1f, 0) + self.assertEqual(c.segment[4].guard_bits, 2) + self.assertEqual(c.segment[4].mantissa, [0] * 16) + self.assertEqual(c.segment[4].exponent, + [8] + [9, 9, 10] * 5) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[5].cqcc, 1) + self.assertEqual(c.segment[5].guard_bits, 2) + # quantization type + self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[5].mantissa, [0] * 16) + self.assertEqual(c.segment[5].exponent, + [8] + [9, 9, 10] * 5) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[6].cqcc, 2) + self.assertEqual(c.segment[6].guard_bits, 2) + # quantization type + self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[6].mantissa, [0] * 16) + self.assertEqual(c.segment[6].exponent, + [8] + [9, 9, 10] * 5) + + def test_NR_broken4_jp2_dump(self): + # Has an invalid marker in the main header + jfile = opj_data_file('input/nonregression/broken4.jp2') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') + jp2 = Jp2k(jfile) + + self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') + + def test_NR_gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc_patch_jp2(self): + lst = ['input', 'nonregression', + 'gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc.patch.jp2'] + jfile = opj_data_file('/'.join(lst)) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') + Jp2k(jfile) + + def test_NR_gdal_fuzzer_check_comp_dx_dy_jp2_dump(self): + lst = ['input', 'nonregression', 'gdal_fuzzer_check_comp_dx_dy.jp2'] + jfile = opj_data_file('/'.join(lst)) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') + Jp2k(jfile) + + def test_NR_gdal_fuzzer_check_number_of_tiles(self): + # Has an impossible tiling setup. + lst = ['input', 'nonregression', + 'gdal_fuzzer_check_number_of_tiles.jp2'] + jfile = opj_data_file('/'.join(lst)) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') + Jp2k(jfile) + + def test_NR_gdal_fuzzer_unchecked_numresolutions_dump(self): + # Has an invalid number of resolutions. + lst = ['input', 'nonregression', + 'gdal_fuzzer_unchecked_numresolutions.jp2'] + jfile = opj_data_file('/'.join(lst)) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') + Jp2k(jfile) + + def test_NR_DEC_issue188_beach_64bitsbox_jp2_41_decode(self): + # Has an 'XML ' box instead of 'xml '. Yes that is pedantic, but it + # really does deserve a warning. + relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2' + jfile = opj_data_file(relpath) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('ignore') + Jp2k(jfile).read() + + +if __name__ == "__main__": + unittest.main() From 133205d66bc98669432f1d732abaf2a78fcf4e3b Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 20 May 2014 20:52:27 -0400 Subject: [PATCH 225/326] Changed look and feel of changelog to match 0.5.12. #237 --- docs/source/changelog.rst | 46 ----------------------- docs/source/detailed_installation.rst | 53 ++++++++++++++------------- docs/source/index.rst | 2 +- docs/source/introduction.rst | 6 +-- 4 files changed, 32 insertions(+), 75 deletions(-) delete mode 100644 docs/source/changelog.rst diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst deleted file mode 100644 index a24d1e8..0000000 --- a/docs/source/changelog.rst +++ /dev/null @@ -1,46 +0,0 @@ ---------- -ChangeLog ---------- - -0.6.0 -===== - - * Added support for OpenJPEG 2.1.0, dropped support for 1.3 and 1.4. - * Added Cinema2K, Cinema4K write support. - * Added lxml requirement. - * added set_printoptions, get_printoptions function - * dropped support for Python 2.6, added support for Python 3.4 - * dropped windows support (it might work, it might not, I don't much care) - * added write support for JP2 UUID, dataEntryURL, palette, and component mapping boxes - * added read/write support for JPX free, number list, and data reference boxes - * Added read support for JPX fragment list and fragment table boxes - * incompatible change to channel definition box constructor, channel_type and association are no longer keyword arguments - * incompatible change to palette box constructor, it now takes a 2D numpy array instead of a list of 1D arrays - -0.5.0 (September 16, 2013) -========================== - - * added write support when using OpenJPEG version 1.5 - * added version module - -0.4.0 (August 18, 2013) -========================== - - * added append method - -0.3.0 (July 31, 2013) -========================== - - * added support for OpenJPEG library version 2.0.0 - -0.2.0 (July 11, 2013) -========================== - - * added Python 2.6, Python 2.7 on windows - * read/write using OpenJPEG library version 2.0.0 - * read using OpenJPEG 1.4 - -0.1.0 (May 27, 2013) -==================== - - * first release using development version (2.x) of OpenJPEG diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index e919681..ecf0632 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -6,25 +6,22 @@ Advanced Installation Instructions Glymur Configuration '''''''''''''''''''''' -The default glymur installation process relies upon OpenJPEG -being properly installed on your system. If you have version 1.5 you can -both read and write JPEG 2000 files, but version 2.1 is recommended. -If you compile OpenJPEG yourself, please compile it as a shared library. -You should also download the test data for the purpose of configuring -and running OpenJPEG's test suite, check their instructions for all this. -You should set the **OPJ_DATA_ROOT** environment variable for the purpose -of running Glymur's test suite. :: +The default glymur installation process relies upon OpenJPEG being +properly installed on your system as a shared library. If you have +OpenJPEG installed through your system’s package manager on linux +or if you use MacPorts on the mac, you are probably already set to +go. But if you have OpenJPEG installed into a non-standard place +or if you use windows, then read on. - $ svn co http://openjpeg.googlecode.com/svn/data - $ export OPJ_DATA_ROOT=`pwd`/data - -Glymur uses ctypes to access the openjp2/openjpeg libraries, -and because ctypes accesses libraries in a platform-dependent manner, it is -recommended that you create a configuration file to help Glymur properly find -the openjpeg or openjp2 libraries (linux users don't need to bother with this -if you are using OpenJPEG as provided by your package manager). The -configuration format is the same as used by Python's configparser module, -i.e. :: +Glymur uses ctypes to access the openjp2/openjpeg libraries, and +because ctypes accesses libraries in a platform-dependent manner, +it is recommended that if you compile and install OpenJPEG into a +non-standard location, you should then create a configuration file +to help Glymur properly find the openjpeg or openjp2 libraries +(linux users or macports users don’t need to bother with this if +you are using OpenJPEG as provided by your package manager). The +configuration format is the same as used by Python’s configparser +module, i.e. :: [library] openjp2: /opt/openjp2-svn/lib/libopenjp2.so @@ -41,6 +38,12 @@ the path will be :: $XDG_CONFIG_HOME/glymur/glymurrc +On windows, the path to the configuration file can be determined by starting +up Python and typing :: + + import os + os.path.join(os.path.expanduser('~', 'glymur', 'glymurrc') + You may also include a line for the version 1.x openjpeg library if you have it installed in a non-standard place, i.e. :: @@ -51,14 +54,14 @@ installed in a non-standard place, i.e. :: Testing ''''''' -There are two environment variables you may wish to set before running the -tests. +It is not necessary, but you may wish to download OpenJPEG's test +data for the purpose of configuring and running OpenJPEG's test +suite. Check their instructions on how to do that. You can then +set the **OPJ_DATA_ROOT** environment variable for the purpose of +pointing Glymur to OpenJPEG's test suite. :: - * **OPJ_DATA_ROOT** - points to directory for OpenJPEG test data (see above) - * **FORMAT_CORPUS_DATA_ROOT** - points to directory for format-corpus repository (see https://github.com/openplanets/format-corpus if you wish, but you really don't need to bother with this) - -Setting these two environment variables is not required, as any tests using -either of them will be skipped. + $ svn co http://openjpeg.googlecode.com/svn/data + $ export OPJ_DATA_ROOT=`pwd`/data In order to run the tests, you can either run them from within python as follows ... :: diff --git a/docs/source/index.rst b/docs/source/index.rst index 792f75a..fbc8fce 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -16,8 +16,8 @@ Contents: detailed_installation how_do_i roadmap - changelog api + whatsnew/index ------------------ Indices and tables diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index 10df117..d4eee13 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -3,13 +3,13 @@ Glymur: a Python interface for JPEG 2000 ---------------------------------------- **Glymur** is an interface to the OpenJPEG library -which allows one to read and write JPEG 2000 files from within Python. +which allows one to read and write JPEG 2000 files from Python. Glymur supports both reading and writing of JPEG 2000 images, but writing JPEG 2000 images is currently limited to images that can fit in memory In regards to metadata, most JP2 boxes are properly interpreted. Certain optional JP2 boxes can also be written, including XML boxes and -XMP UUIDs. There is some very limited support for reading JPX metadata. +XMP UUIDs. There is incomplete support for reading JPX metadata. Glymur 0.6 works on Python versions 2.7, 3.3 and 3.4. If you have Python 2.6, you should use the 0.5 series of Glymur. @@ -21,7 +21,7 @@ Glymur Installation You can retrieve the source for Glymur from either of * https://pypi.python.org/pypi/Glymur/ (stable releases) - * http://github.com/quintusdias/glymur (bleeding edge) + * http://github.com/quintusdias/glymur (bleeding edge, use the devel branch) but you should also be able to install Glymur via pip :: From 5d072604f7636d5858c5ca84046fbb8729601c7a Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 21 May 2014 21:04:43 -0400 Subject: [PATCH 226/326] Refactored test_codestream into test_codestream and test_codestream_warnings #236 --- glymur/test/test_codestream.py | 70 ----------------- glymur/test/test_codestream_warnings.py | 99 +++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 70 deletions(-) create mode 100644 glymur/test/test_codestream_warnings.py diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py index 91c1adb..abea7e2 100644 --- a/glymur/test/test_codestream.py +++ b/glymur/test/test_codestream.py @@ -51,44 +51,6 @@ class TestCodestreamOpjData(unittest.TestCase): def tearDown(self): pass - def test_bad_rsiz(self): - """Should warn if RSIZ is bad. Issue196""" - filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - j = Jp2k(filename) - self.assertEqual(len(w), 3) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertTrue('Invalid profile' in str(w[0].message)) - - def test_bad_wavelet_transform(self): - """Should warn if wavelet transform is bad. Issue195""" - filename = opj_data_file('input/nonregression/edf_c2_10025.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - j = Jp2k(filename) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertTrue('Invalid wavelet transform' in str(w[0].message)) - - def test_invalid_progression_order(self): - """Should still be able to parse even if prog order is invalid.""" - jfile = opj_data_file('input/nonregression/2977.pdf.asan.67.2198.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - Jp2k(jfile) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertTrue('Invalid progression order' in str(w[0].message)) - - def test_tile_height_is_zero(self): - """Zero tile height should not cause an exception.""" - filename = opj_data_file('input/nonregression/2539.pdf.SIGFPE.706.1712.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - Jp2k(filename) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertTrue('Invalid tile dimensions' in str(w[0].message)) - - @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_reserved_marker_segment(self): """Reserved marker segments are ok.""" @@ -120,38 +82,6 @@ class TestCodestreamOpjData(unittest.TestCase): self.assertEqual(codestream.segment[2].length, 3) self.assertEqual(codestream.segment[2].data, b'\x00') - @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(OPJ_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. - read_buffer = ifile.read(45) - tfile.write(read_buffer) - - # Write the new marker segment, 0xff79 = 65401 - read_buffer = struct.pack('>HHB', int(65401), int(3), int(0)) - tfile.write(read_buffer) - - # Get the rest of the input file. - read_buffer = ifile.read() - tfile.write(read_buffer) - tfile.flush() - - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - codestream = Jp2k(tfile.name).get_codestream() - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertTrue('Unrecognized marker' in str(w[0].message)) - - 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(OPJ_DATA_ROOT, diff --git a/glymur/test/test_codestream_warnings.py b/glymur/test/test_codestream_warnings.py new file mode 100644 index 0000000..52b3cb0 --- /dev/null +++ b/glymur/test/test_codestream_warnings.py @@ -0,0 +1,99 @@ +""" +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 + +import os +import struct +import sys +import tempfile +import unittest +import warnings + +from glymur import Jp2k +import glymur + +from .fixtures import opj_data_file, OPJ_DATA_ROOT + +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class TestCodestreamOpjDataWarnings(unittest.TestCase): + """Test suite for unusual codestream cases. Uses OPJ_DATA_ROOT""" + + def test_bad_rsiz(self): + """Should warn if RSIZ is bad. Issue196""" + filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + j = Jp2k(filename) + self.assertEqual(len(w), 3) + self.assertTrue(issubclass(w[0].category, UserWarning)) + self.assertTrue('Invalid profile' in str(w[0].message)) + + def test_bad_wavelet_transform(self): + """Should warn if wavelet transform is bad. Issue195""" + filename = opj_data_file('input/nonregression/edf_c2_10025.jp2') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + j = Jp2k(filename) + self.assertTrue(issubclass(w[0].category, UserWarning)) + self.assertTrue('Invalid wavelet transform' in str(w[0].message)) + + def test_invalid_progression_order(self): + """Should still be able to parse even if prog order is invalid.""" + jfile = opj_data_file('input/nonregression/2977.pdf.asan.67.2198.jp2') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + Jp2k(jfile) + self.assertTrue(issubclass(w[0].category, UserWarning)) + self.assertTrue('Invalid progression order' in str(w[0].message)) + + def test_tile_height_is_zero(self): + """Zero tile height should not cause an exception.""" + filename = opj_data_file('input/nonregression/2539.pdf.SIGFPE.706.1712.jp2') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + Jp2k(filename) + self.assertTrue(issubclass(w[0].category, UserWarning)) + self.assertTrue('Invalid tile dimensions' in str(w[0].message)) + + @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(OPJ_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. + read_buffer = ifile.read(45) + tfile.write(read_buffer) + + # Write the new marker segment, 0xff79 = 65401 + read_buffer = struct.pack('>HHB', int(65401), int(3), int(0)) + tfile.write(read_buffer) + + # Get the rest of the input file. + read_buffer = ifile.read() + tfile.write(read_buffer) + tfile.flush() + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + codestream = Jp2k(tfile.name).get_codestream() + self.assertTrue(issubclass(w[0].category, UserWarning)) + self.assertTrue('Unrecognized marker' in str(w[0].message)) + + self.assertEqual(codestream.segment[2].marker_id, '0xff79') + self.assertEqual(codestream.segment[2].length, 3) + self.assertEqual(codestream.segment[2].data, b'\x00') + + +if __name__ == "__main__": + unittest.main() From a719bb4ac9b3700d2d953e970d3b18acb7c9f096 Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 22 May 2014 21:30:30 -0400 Subject: [PATCH 227/326] Added irreversible 9-7 transform support. #238 --- glymur/jp2k.py | 5 +++++ glymur/test/test_jp2k.py | 29 +++++++++++++++++------------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 6bab50b..4ebd678 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -271,6 +271,9 @@ class Jp2k(Jp2kBox): cparams.tcp_numlayers = 1 cparams.cp_disto_alloc = 1 + if 'irreversible' in kwargs and kwargs['irreversible'] is True: + cparams.irreversible = 1 + if 'cinema2k' in kwargs: self._set_cinema_params(cparams, 'cinema2k', kwargs['cinema2k']) return cparams @@ -416,6 +419,8 @@ class Jp2k(Jp2kBox): 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. + irreversible : bool, optional + If true, use the irreversible DWT 9-7 transform. mct : bool, optional Specifies usage of the multi component transform. If not specified, defaults to True if the colorspace is RGB. diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index aba1463..49a491e 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -390,6 +390,7 @@ class TestJp2k(unittest.TestCase): self.assertEqual(data.shape, (1024, 1024, 3)) +@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") class TestJp2k_write(unittest.TestCase): """Write tests, can be run by versions 1.5+""" @@ -400,8 +401,23 @@ class TestJp2k_write(unittest.TestCase): def tearDown(self): pass + def test_irreversible(self): + """Irreversible""" + filename = opj_data_file('input/nonregression/issue141.rawl') + expdata = np.fromfile(filename, dtype=np.uint16) + expdata.resize((2816, 2048)) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(expdata, irreversible=True) + + codestream = j.get_codestream() + self.assertEqual(codestream.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + + actdata = j.read() + self.assertTrue(fixtures.mse(actdata, expdata) < 250) + - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_cblkh_different_than_width(self): """Verify that we can set a code block size where height does not equal width. @@ -418,7 +434,6 @@ class TestJp2k_write(unittest.TestCase): # Code block size is reported as XY in the codestream. self.assertEqual(tuple(codestream.segment[2].spcod[5:7]), (3, 2)) - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_too_many_dimensions(self): """OpenJP2 only allows 2D or 3D images.""" with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: @@ -427,7 +442,6 @@ class TestJp2k_write(unittest.TestCase): data = np.zeros((128, 128, 2, 2), dtype=np.uint8) j.write(data) - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_2d_rgb(self): """RGB must have at least 3 components.""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: @@ -436,7 +450,6 @@ class TestJp2k_write(unittest.TestCase): 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""" with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: @@ -445,7 +458,6 @@ class TestJp2k_write(unittest.TestCase): 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: @@ -454,7 +466,6 @@ class TestJp2k_write(unittest.TestCase): j.write(data, colorspace='rgb') self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB) - @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: @@ -464,7 +475,6 @@ class TestJp2k_write(unittest.TestCase): self.assertEqual(j.box[2].box[1].colorspace, glymur.core.GREYSCALE) - @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: @@ -474,7 +484,6 @@ class TestJp2k_write(unittest.TestCase): self.assertEqual(j.box[2].box[1].colorspace, glymur.core.GREYSCALE) - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_grey_with_two_extra_comps(self): """should be able to write gray + two extra components""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: @@ -495,7 +504,6 @@ class TestJp2k_write(unittest.TestCase): 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_write_with_jp2_in_caps(self): """should be able to write with JP2 suffix.""" j2k = Jp2k(self.j2kfile) @@ -506,7 +514,6 @@ class TestJp2k_write(unittest.TestCase): 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) @@ -520,7 +527,6 @@ class TestJp2k_write(unittest.TestCase): 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.""" j2k = Jp2k(self.j2kfile) @@ -530,7 +536,6 @@ class TestJp2k_write(unittest.TestCase): with self.assertRaises(IOError): ofile.write(expdata[:, :, 0], mct=True) - @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 From 298c493029d6243af91f5650c5a1099d6f699c4c Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 22 May 2014 21:35:20 -0400 Subject: [PATCH 228/326] New "whatsnew" documents, sort of look like h5py. #237 --- docs/source/whatsnew/0.5.rst | 50 ++++++++++++++++++++++++++++++++++ docs/source/whatsnew/0.6.rst | 23 ++++++++++++++++ docs/source/whatsnew/index.rst | 12 ++++++++ 3 files changed, 85 insertions(+) create mode 100644 docs/source/whatsnew/0.5.rst create mode 100644 docs/source/whatsnew/0.6.rst create mode 100644 docs/source/whatsnew/index.rst diff --git a/docs/source/whatsnew/0.5.rst b/docs/source/whatsnew/0.5.rst new file mode 100644 index 0000000..86ce1dd --- /dev/null +++ b/docs/source/whatsnew/0.5.rst @@ -0,0 +1,50 @@ +===================== +Changes in glymur 0.5 +===================== + +Changes in 0.5.12 +================= + +* Minor documentation fixes for grammar and style. +* The functions removed in 0.5.11 due to API changes in OpenJPEG 2.1.0 were + restored for backwards compatibility. They are deprecated, though, and will + be removed in 0.6.0. + + * ``glymur.lib.openjp2.stream_create_default_file_stream_v3`` + * ``glymur.lib.openjp2.opj.stream_destroy_v3`` + + +Changes in 0.5.11 +================= + +* Added support for Python 3.4. +* OpenJPEG 1.5.2 and 2.0.1 are officially supported. +* OpenJPEG 2.1.0 is officially supported, but the ABI changes introduced by + OpenJPEG 2.1.0 required corresponding changes to glymur's ctypes interface. + The functions + + * ``glymur.lib.openjp2.stream_create_default_file_stream_v3`` + * ``glymur.lib.openjp2.opj.stream_destroy_v3`` + + functions were renamed to + + * ``glymur.lib.openjp2.stream_create_default_file_stream`` + * ``glymur.lib.openjp2.opj.stream_destroy`` + + in order to follow OpenJPEG's upstream changes. Unless you were using the + svn version of OpenJPEG, you should not be affected by this. + + +Changes in 0.5.10 +================= + +* Fixed bad warning issued when an unsupported reader requirement box mask + length was encountered. + +Changes in 0.5.9 +================ + +* Fixed bad library load on linux as a result of botched 0.5.8 release. + This release was primarily aimed at supporting SunPy. + + diff --git a/docs/source/whatsnew/0.6.rst b/docs/source/whatsnew/0.6.rst new file mode 100644 index 0000000..8fd49d0 --- /dev/null +++ b/docs/source/whatsnew/0.6.rst @@ -0,0 +1,23 @@ +===================== +Changes in glymur 0.6 +===================== + +Changes in 0.6.0 +================= + +* Added Cinema2K, Cinema4K write support. +* Added irreversible 9-7 transform write support. +* Added set_printoptions, get_printoptions functions. +* Added write support for JP2 UUID, data entry URL, palette, and component + mapping boxes. +* Added read/write support for JPX free, number list, and data reference boxes +* Added read support for JPX fragment list and fragment table boxes +* Incompatible change to channel definition box constructor, channel_type and + association are no longer keyword arguments +* Incompatible change to palette box constructor, it now takes a 2D numpy array + instead of a list of 1D arrays +* Dropped support for 1.3 and 1.4. +* Dropped support for Python 2.6. +* Dropped windows support. It might still work, I don't have access to a windows + box with which to test it. +* Added lxml as a package dependency, replacing ElementTree. diff --git a/docs/source/whatsnew/index.rst b/docs/source/whatsnew/index.rst new file mode 100644 index 0000000..da7e319 --- /dev/null +++ b/docs/source/whatsnew/index.rst @@ -0,0 +1,12 @@ +.. _whatsnew: + +********************** +"What's new" documents +********************** + +These document the changes between minor (or major) versions of glymur. + +.. toctree:: + + 0.5 + 0.6 From 6adbae067bfd40fa93801d60bcab187a9014c948 Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 23 May 2014 06:35:53 -0400 Subject: [PATCH 229/326] Irreversible test needed OPJ_DATA_ROOT decorator. #238 --- glymur/test/test_jp2k.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 49a491e..89d419e 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -401,23 +401,6 @@ class TestJp2k_write(unittest.TestCase): def tearDown(self): pass - def test_irreversible(self): - """Irreversible""" - filename = opj_data_file('input/nonregression/issue141.rawl') - expdata = np.fromfile(filename, dtype=np.uint16) - expdata.resize((2816, 2048)) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - j.write(expdata, irreversible=True) - - codestream = j.get_codestream() - self.assertEqual(codestream.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - - actdata = j.read() - self.assertTrue(fixtures.mse(actdata, expdata) < 250) - - def test_cblkh_different_than_width(self): """Verify that we can set a code block size where height does not equal width. @@ -855,6 +838,23 @@ class TestJp2kOpjDataRootWarnings(unittest.TestCase): class TestJp2kOpjDataRoot(unittest.TestCase): """These tests should be run by just about all configuration.""" + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + def test_irreversible(self): + """Irreversible""" + filename = opj_data_file('input/nonregression/issue141.rawl') + expdata = np.fromfile(filename, dtype=np.uint16) + expdata.resize((2816, 2048)) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(expdata, irreversible=True) + + codestream = j.get_codestream() + self.assertEqual(codestream.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + + actdata = j.read() + self.assertTrue(fixtures.mse(actdata, expdata) < 250) + def test_no_cxform_pclr_jp2(self): """Indices for pclr jpxfile if no color transform""" filename = opj_data_file('input/conformance/file9.jp2') From 83755cef0a5a4ef70a126bc8eb6e6572c6ca3ff1 Mon Sep 17 00:00:00 2001 From: jevans Date: Sun, 25 May 2014 21:44:08 -0400 Subject: [PATCH 230/326] Added another irreversible test to not use OPJ_TEST_SUITE. #238 --- glymur/test/test_jp2k.py | 33 ++++++++++++++--------------- glymur/test/test_opj_suite_write.py | 15 +++++++++++++ 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 49a491e..6e328e0 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -63,6 +63,22 @@ class TestJp2k(unittest.TestCase): def tearDown(self): pass + def test_irreversible(self): + """Irreversible""" + j = Jp2k(self.jp2file) + expdata = j.read() + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j2 = Jp2k(tfile.name, 'wb') + j2.write(expdata, irreversible=True) + + codestream = j2.get_codestream() + self.assertEqual(codestream.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + + actdata = j2.read() + self.assertTrue(fixtures.mse(actdata[0], expdata[0]) < 0.38) + + def test_no_cxform_pclr_jpx(self): """Indices for pclr jpxfile if no color transform""" j = Jp2k(self.jpxfile) @@ -401,23 +417,6 @@ class TestJp2k_write(unittest.TestCase): def tearDown(self): pass - def test_irreversible(self): - """Irreversible""" - filename = opj_data_file('input/nonregression/issue141.rawl') - expdata = np.fromfile(filename, dtype=np.uint16) - expdata.resize((2816, 2048)) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - j.write(expdata, irreversible=True) - - codestream = j.get_codestream() - self.assertEqual(codestream.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - - actdata = j.read() - self.assertTrue(fixtures.mse(actdata, expdata) < 250) - - def test_cblkh_different_than_width(self): """Verify that we can set a code block size where height does not equal width. diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 77ef262..5fdc0ca 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -13,6 +13,8 @@ import tempfile import unittest import warnings +import numpy as np + try: import skimage.io skimage.io.use_plugin('freeimage', 'imread') @@ -271,6 +273,19 @@ class TestSuiteWrite(unittest.TestCase): def tearDown(self): pass + def test_NR_ENC_issue141_rawl_23_encode(self): + filename = opj_data_file('input/nonregression/issue141.rawl') + expdata = np.fromfile(filename, dtype=np.uint16) + expdata.resize((2816, 2048)) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(expdata, irreversible=True) + + codestream = j.get_codestream() + self.assertEqual(codestream.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + + def test_NR_ENC_Bretagne1_ppm_1_encode(self): """NR-ENC-Bretagne1.ppm-1-encode""" infile = opj_data_file('input/nonregression/Bretagne1.ppm') From f88218d48276f30f477bb720f92cb96453f4c870 Mon Sep 17 00:00:00 2001 From: jevans Date: Sun, 3 Aug 2014 21:57:09 -0400 Subject: [PATCH 231/326] Releasing 0.6.0 Skipping a test that segfaults only on 1.5.0 and 2.0.0. The test was test_NR_DEC_issue188_beach_64bitsbox_jp2_41_decode. It was not introduced until the 2.1.x series and the fix was backported to 2.0.1. glymur.test.test_jp2k.TestJp2k.test_no_cxform_pclr_jpx was failing on 1.5.2 only. It's a pretty obscure test, so just skipping it on that release. Some tests for warnings are being skipped. --- docs/source/how_do_i.rst | 18 +++++++++--------- docs/source/index.rst | 2 +- docs/source/introduction.rst | 8 ++++---- docs/source/roadmap.rst | 8 ++++++++ glymur/test/test_codestream_warnings.py | 1 + glymur/test/test_icc.py | 1 + glymur/test/test_jp2box.py | 10 ++++++---- glymur/test/test_jp2box_uuid.py | 2 ++ glymur/test/test_jp2box_xml.py | 1 + glymur/test/test_jp2k.py | 8 +++++++- glymur/test/test_opj_suite_warn.py | 12 +++++++++++- setup.py | 1 + 12 files changed, 52 insertions(+), 20 deletions(-) diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index 4bde854..ebe7a31 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -11,13 +11,13 @@ take a shortcut by supplying -1 as the resolution level. :: >>> import glymur - >>> file = glymur.data.nemo() - >>> jp2 = glymur.Jp2k(file) + >>> jp2file = glymur.data.nemo() + >>> jp2 = glymur.Jp2k(jp2file) >>> thumbnail = jp2.read(rlevel=-1) ... display metadata? ===================== -There are two ways. From the unix command line, the script *jp2dump* is +There are two ways. From the unix command line, the script **jp2dump** is available. :: $ jp2dump /path/to/glymur/installation/data/nemo.jp2 @@ -25,8 +25,8 @@ available. :: From within Python, it is as simple as printing the Jp2k object, i.e. :: >>> import glymur - >>> file = glymur.data.nemo() - >>> jp2 = glymur.Jp2k(file) + >>> jp2file = glymur.data.nemo() + >>> jp2 = glymur.Jp2k(jp2file) >>> print(jp2) File: nemo.jp2 JPEG 2000 Signature Box (jP ) @ (0, 12) @@ -241,8 +241,8 @@ you can use the :py:meth:`wrap` method with no box argument: :: >>> import glymur >>> glymur.set_printoptions(codestream=False) - >>> jfile = glymur.data.goodstuff() - >>> j2k = glymur.Jp2k(jfile) + >>> jp2file = glymur.data.goodstuff() + >>> j2k = glymur.Jp2k(jp2file) >>> jp2 = j2k.wrap("newfile.jp2") >>> print(jp2) File: newfile.jp2 @@ -524,8 +524,8 @@ You can also build up XMP metadata from scratch. For instance, if we try to wrap `goodstuff.j2k` again:: >>> import glymur - >>> jfile = glymur.data.goodstuff() - >>> j2k = glymur.Jp2k(jfile) + >>> j2kfile = glymur.data.goodstuff() + >>> j2k = glymur.Jp2k(j2kfile) >>> jp2 = j2k.wrap("goodstuff.jp2") Now build up the metadata piece-by-piece. It would help to have the XMP diff --git a/docs/source/index.rst b/docs/source/index.rst index fbc8fce..0675eef 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,9 +15,9 @@ Contents: introduction detailed_installation how_do_i - roadmap api whatsnew/index + roadmap ------------------ Indices and tables diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index d4eee13..3ae697e 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -27,10 +27,10 @@ but you should also be able to install Glymur via pip :: $ pip install glymur -This will install the **jp2dump** script that can be used from the unix command -line, so you should adjust your **$PATH** -to take advantage of it. For example, if you install with pip's -`--user` option on linux :: +This will install a script **jp2dump** that can be used from the unix command +line for dumping JP2 metadata, so you should adjust your **$PATH** +environment variable to take advantage of it. For example, if you install +with pip's `--user` option on linux :: $ export PATH=$HOME/.local/bin:$PATH diff --git a/docs/source/roadmap.rst b/docs/source/roadmap.rst index 6651587..f0d7017 100644 --- a/docs/source/roadmap.rst +++ b/docs/source/roadmap.rst @@ -1,3 +1,11 @@ +------------ +Known Issues +------------ + + * Creating a Jp2 file with the irreversible option does not work + on windows. + * Eval-ing a :py:meth:`repr` string does not work on windows. + ------- Roadmap ------- diff --git a/glymur/test/test_codestream_warnings.py b/glymur/test/test_codestream_warnings.py index 52b3cb0..8b50bf2 100644 --- a/glymur/test/test_codestream_warnings.py +++ b/glymur/test/test_codestream_warnings.py @@ -20,6 +20,7 @@ import glymur from .fixtures import opj_data_file, OPJ_DATA_ROOT +@unittest.skipIf(sys.platform.startswith('linux'), 'warnings failing on linux') @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") class TestCodestreamOpjDataWarnings(unittest.TestCase): diff --git a/glymur/test/test_icc.py b/glymur/test/test_icc.py index 3ddc249..734d557 100644 --- a/glymur/test/test_icc.py +++ b/glymur/test/test_icc.py @@ -62,6 +62,7 @@ class TestICC(unittest.TestCase): self.assertEqual(profile['Creator'], 'JPEG') + @unittest.skipIf(sys.platform.startswith('linux'), 'Failing on linux') def test_invalid_profile_header(self): """invalid ICC header data should cause UserWarning""" jfile = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2') diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index da5fea1..e0f12e2 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -572,6 +572,8 @@ class TestPaletteBox(unittest.TestCase): with self.assertRaises(IOError): pclr.write(tfile) + +@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") class TestAppend(unittest.TestCase): """Tests for append method.""" @@ -1292,8 +1294,8 @@ class TestRepr(unittest.TestCase): box = glymur.jp2box.XMLBox(xml=tree) regexp = r"""glymur.jp2box.XMLBox""" - regexp += r"""\(xml=\)""" + regexp += r"""[(]xml=[)]""" if sys.hexversion < 0x03000000: self.assertRegexpMatches(repr(box), regexp) @@ -1357,8 +1359,8 @@ class TestRepr(unittest.TestCase): # Difficult to eval(repr()) this, so just match the general pattern. regexp = "glymur.jp2box.ContiguousCodeStreamBox" - regexp += "\(main_header= Date: Sun, 17 Aug 2014 20:05:27 -0400 Subject: [PATCH 232/326] Releasing 0.6.1. Reverting b24ae781a6738d44c2b11888cd71a982aaf7c03e That commit removed the capitalization, preventing it from being uploaded to pypi. --- docs/source/conf.py | 2 +- glymur/version.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 3e6b26e..1fa9eb8 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -78,7 +78,7 @@ copyright = u'2013, John Evans' # The short X.Y version. version = '0.6' # The full version, including alpha/beta/rc tags. -release = '0.6.0' +release = '0.6.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/glymur/version.py b/glymur/version.py index 8ffd80f..0d5d9ff 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -19,7 +19,7 @@ from .lib import openjp2 as opj2 # Do not change the format of this next line! Doing so risks breaking # setup.py -version = "0.6.0" +version = "0.6.1" _sv = LooseVersion(version) version_tuple = _sv.version diff --git a/setup.py b/setup.py index 94d692e..080907d 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import os import re import sys -kwargs = {'name': 'glymur', +kwargs = {'name': 'Glymur', 'description': 'Tools for accessing JPEG2000 files', 'long_description': open('README.md').read(), 'author': 'John Evans', From af80a9e81abd83069449ecf5986e163f3192a72a Mon Sep 17 00:00:00 2001 From: John Evans Date: Sun, 17 Aug 2014 19:03:29 -0400 Subject: [PATCH 233/326] Reworked warning tests, closes #245. Using 3.x warning infrastructure to verify warnings. Using old 2.x infrastructure to suppress warnings when the point of the test is not the warning itself but something else. All tests for warnings moved into glymur.test.test_glymur_warnings. --- glymur/codestream.py | 2 +- glymur/test/test_codestream_warnings.py | 100 ------- glymur/test/test_glymur_warnings.py | 207 +++++++++++++ glymur/test/test_opj_suite.py | 126 ++++++++ glymur/test/test_opj_suite_dump.py | 168 ++++++++++- glymur/test/test_opj_suite_neg.py | 13 +- glymur/test/test_opj_suite_warn.py | 376 ------------------------ glymur/test/test_printing.py | 25 +- 8 files changed, 513 insertions(+), 504 deletions(-) delete mode 100644 glymur/test/test_codestream_warnings.py create mode 100644 glymur/test/test_glymur_warnings.py delete mode 100644 glymur/test/test_opj_suite_warn.py diff --git a/glymur/codestream.py b/glymur/codestream.py index acc7791..88babe5 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -202,7 +202,7 @@ class Codestream(object): msg = 'Invalid marker id encountered at byte {0:d} ' msg += 'in codestream: "0x{1:x}"' msg = msg.format(self._offset, self._marker_id) - warnings.warn(msg) + warnings.warn(msg, UserWarning) break self.segment.append(segment) diff --git a/glymur/test/test_codestream_warnings.py b/glymur/test/test_codestream_warnings.py deleted file mode 100644 index 8b50bf2..0000000 --- a/glymur/test/test_codestream_warnings.py +++ /dev/null @@ -1,100 +0,0 @@ -""" -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 - -import os -import struct -import sys -import tempfile -import unittest -import warnings - -from glymur import Jp2k -import glymur - -from .fixtures import opj_data_file, OPJ_DATA_ROOT - -@unittest.skipIf(sys.platform.startswith('linux'), 'warnings failing on linux') -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -class TestCodestreamOpjDataWarnings(unittest.TestCase): - """Test suite for unusual codestream cases. Uses OPJ_DATA_ROOT""" - - def test_bad_rsiz(self): - """Should warn if RSIZ is bad. Issue196""" - filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - j = Jp2k(filename) - self.assertEqual(len(w), 3) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertTrue('Invalid profile' in str(w[0].message)) - - def test_bad_wavelet_transform(self): - """Should warn if wavelet transform is bad. Issue195""" - filename = opj_data_file('input/nonregression/edf_c2_10025.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - j = Jp2k(filename) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertTrue('Invalid wavelet transform' in str(w[0].message)) - - def test_invalid_progression_order(self): - """Should still be able to parse even if prog order is invalid.""" - jfile = opj_data_file('input/nonregression/2977.pdf.asan.67.2198.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - Jp2k(jfile) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertTrue('Invalid progression order' in str(w[0].message)) - - def test_tile_height_is_zero(self): - """Zero tile height should not cause an exception.""" - filename = opj_data_file('input/nonregression/2539.pdf.SIGFPE.706.1712.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - Jp2k(filename) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertTrue('Invalid tile dimensions' in str(w[0].message)) - - @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(OPJ_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. - read_buffer = ifile.read(45) - tfile.write(read_buffer) - - # Write the new marker segment, 0xff79 = 65401 - read_buffer = struct.pack('>HHB', int(65401), int(3), int(0)) - tfile.write(read_buffer) - - # Get the rest of the input file. - read_buffer = ifile.read() - tfile.write(read_buffer) - tfile.flush() - - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - codestream = Jp2k(tfile.name).get_codestream() - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertTrue('Unrecognized marker' in str(w[0].message)) - - self.assertEqual(codestream.segment[2].marker_id, '0xff79') - self.assertEqual(codestream.segment[2].length, 3) - self.assertEqual(codestream.segment[2].data, b'\x00') - - -if __name__ == "__main__": - unittest.main() diff --git a/glymur/test/test_glymur_warnings.py b/glymur/test/test_glymur_warnings.py new file mode 100644 index 0000000..622a8af --- /dev/null +++ b/glymur/test/test_glymur_warnings.py @@ -0,0 +1,207 @@ +""" +Test suite for warnings issued by glymur. +""" + +# unittest doesn't work well with R0904. +# pylint: disable=R0904 + +import os +import re +import struct +import sys +import tempfile +import unittest +import warnings + +from glymur import Jp2k +import glymur + +from .fixtures import opj_data_file, OPJ_DATA_ROOT + +@unittest.skipIf(sys.hexversion < 0x03030000, + "assertWarn methods introduced in 3.x") +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class TestWarnings(unittest.TestCase): + """Test suite for warnings issued by glymur.""" + + def test_exceeded_box_length(self): + """ + should warn if reading past end of a box + + Verify that a warning is issued if we read past the end of a box + This file has a palette (pclr) box whose length is impossibly + short. + """ + infile = os.path.join(OPJ_DATA_ROOT, + 'input/nonregression/mem-b2ace68c-1381.jp2') + regex = re.compile(r'''Encountered\san\sunrecoverable\sValueError\s + while\sparsing\sa\spclr\sbox\sat\sbyte\soffset\s + \d+\.\s+The\soriginal\serror\smessage\swas\s + "total\ssize\sof\snew\sarray\smust\sbe\s + unchanged"''', + re.VERBOSE) + with self.assertWarnsRegex(UserWarning, regex): + Jp2k(infile) + + def test_NR_DEC_issue188_beach_64bitsbox_jp2_41_decode(self): + """ + Has an 'XML ' box instead of 'xml '. Yes that is pedantic, but it + really does deserve a warning. + """ + relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2' + jfile = opj_data_file(relpath) + regex = re.compile(r"""Unrecognized\sbox\s\(b'XML\s'\)\sencountered.""", + re.VERBOSE) + with self.assertWarnsRegex(UserWarning, regex): + Jp2k(jfile) + + + def test_NR_gdal_fuzzer_unchecked_numresolutions_dump(self): + """ + Has an invalid number of resolutions. + """ + lst = ['input', 'nonregression', + 'gdal_fuzzer_unchecked_numresolutions.jp2'] + jfile = opj_data_file('/'.join(lst)) + regex = re.compile(r"""Invalid\snumber\sof\sresolutions\s + \(\d+\)\.""", + re.VERBOSE) + with self.assertWarnsRegex(UserWarning, regex): + Jp2k(jfile) + + @unittest.skipIf(re.match("1.5|2.0.0", glymur.version.openjpeg_version), + "Test not passing on 1.5.x, not introduced until 2.x") + def test_NR_gdal_fuzzer_check_number_of_tiles(self): + """ + Has an impossible tiling setup. + """ + lst = ['input', 'nonregression', + 'gdal_fuzzer_check_number_of_tiles.jp2'] + jfile = opj_data_file('/'.join(lst)) + regex = re.compile(r"""Invalid\snumber\sof\stiles\s + \(\d+\)\.""", + re.VERBOSE) + with self.assertWarnsRegex(UserWarning, regex): + Jp2k(jfile) + + def test_NR_gdal_fuzzer_check_comp_dx_dy_jp2_dump(self): + """ + Invalid subsampling value. + """ + lst = ['input', 'nonregression', 'gdal_fuzzer_check_comp_dx_dy.jp2'] + jfile = opj_data_file('/'.join(lst)) + regex = re.compile(r"""Invalid\ssubsampling\svalue\sfor\scomponent\s + \d+:\s+ + dx=\d+,\s*dy=\d+""", + re.VERBOSE) + with self.assertWarnsRegex(UserWarning, regex): + Jp2k(jfile) + + def test_NR_gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc_patch_jp2(self): + lst = ['input', 'nonregression', + 'gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc.patch.jp2'] + jfile = opj_data_file('/'.join(lst)) + regex = re.compile(r"""Invalid\scomponent\snumber\s\(\d+\),\s + number\sof\scomponents\sis\sonly\s\d+""", + re.VERBOSE) + with self.assertWarnsRegex(UserWarning, regex): + Jp2k(jfile) + + def test_NR_broken_jp2_dump(self): + """ + The colr box has a ridiculously incorrect box length. + """ + jfile = opj_data_file('input/nonregression/broken.jp2') + regex = re.compile(r'''b'colr'\sbox\shas\sincorrect\sbox\slength\s + \(\d+\)''', + re.VERBOSE) + with self.assertWarnsRegex(UserWarning, regex): + jp2 = Jp2k(jfile) + + def test_NR_broken2_jp2_dump(self): + """ + Invalid marker ID on codestream. + """ + jfile = opj_data_file('input/nonregression/broken2.jp2') + regex = re.compile(r'''Invalid\smarker\sid\sencountered\sat\sbyte\s + \d+\sin\scodestream:\s*"0x[a-fA-F0-9]{4}"''', + re.VERBOSE) + with self.assertWarnsRegex(UserWarning, regex): + Jp2k(jfile) + + def test_NR_broken4_jp2_dump(self): + """ + Has an invalid marker in the main header + """ + jfile = opj_data_file('input/nonregression/broken4.jp2') + regex = r'Invalid marker id encountered at byte \d+ in codestream' + with self.assertWarnsRegex(UserWarning, regex): + jp2 = Jp2k(jfile) + + @unittest.skipIf(sys.maxsize < 2**32, 'Do not run on 32-bit platforms') + def test_NR_broken3_jp2_dump(self): + """ + Has an impossibly large box length. + + The file in question here has a colr box with an erroneous box + length of over 1GB. Don't run it on 32-bit platforms. + """ + jfile = opj_data_file('input/nonregression/broken3.jp2') + regex = re.compile(r'''b'colr'\sbox\shas\sincorrect\sbox\slength\s + \(\d+\)''', re.VERBOSE) + with self.assertWarnsRegex(UserWarning, regex): + Jp2k(jfile) + + def test_bad_rsiz(self): + """Should warn if RSIZ is bad. Issue196""" + filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') + with self.assertWarnsRegex(UserWarning, 'Invalid profile'): + Jp2k(filename) + + def test_bad_wavelet_transform(self): + """Should warn if wavelet transform is bad. Issue195""" + filename = opj_data_file('input/nonregression/edf_c2_10025.jp2') + with self.assertWarnsRegex(UserWarning, 'Invalid wavelet transform'): + Jp2k(filename) + + def test_invalid_progression_order(self): + """Should still be able to parse even if prog order is invalid.""" + jfile = opj_data_file('input/nonregression/2977.pdf.asan.67.2198.jp2') + with self.assertWarnsRegex(UserWarning, 'Invalid progression order'): + Jp2k(jfile) + + def test_tile_height_is_zero(self): + """Zero tile height should not cause an exception.""" + filename = opj_data_file('input/nonregression/2539.pdf.SIGFPE.706.1712.jp2') + with self.assertWarnsRegex(UserWarning, 'Invalid tile dimensions'): + Jp2k(filename) + + @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(OPJ_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. + read_buffer = ifile.read(45) + tfile.write(read_buffer) + + # Write the new marker segment, 0xff79 = 65401 + read_buffer = struct.pack('>HHB', int(65401), int(3), int(0)) + tfile.write(read_buffer) + + # Get the rest of the input file. + read_buffer = ifile.read() + tfile.write(read_buffer) + tfile.flush() + + with self.assertWarnsRegex(UserWarning, 'Unrecognized marker'): + codestream = Jp2k(tfile.name).get_codestream() + + +if __name__ == "__main__": + unittest.main() diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 1e658a3..9c725c4 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -287,6 +287,132 @@ class TestSuite(unittest.TestCase): jpdata = jp2k.read() self.assertEqual(jpdata.shape, (512, 768, 3)) + def test_NR_broken_jp2_dump(self): + jfile = opj_data_file('input/nonregression/broken.jp2') + + with warnings.catch_warnings(): + # colr box has bad length. + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 152) + self.assertEqual(jp2.box[2].box[0].width, 203) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, + glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) # not allowed? + self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) + + c = jp2.box[3].main_header + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 203) + self.assertEqual(c.segment[1].ysiz, 152) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (203, 152)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COM: comment + # Registration + self.assertEqual(c.segment[2].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[2].ccme.decode('latin-1'), + "Creator: JasPer Version 1.701.0") + + # COD: Coding style default + self.assertFalse(c.segment[3].scod & 2) # no sop + self.assertFalse(c.segment[3].scod & 4) # no eph + self.assertEqual(c.segment[3].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[3].layers, 1) # layers = 1 + self.assertEqual(c.segment[3].spcod[3], 1) # mct + self.assertEqual(c.segment[3].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[3].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[3].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[3].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[3].spcod[7] & 0x0020) + self.assertEqual(c.segment[3].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[3].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[4].sqcd & 0x1f, 0) + self.assertEqual(c.segment[4].guard_bits, 2) + self.assertEqual(c.segment[4].mantissa, [0] * 16) + self.assertEqual(c.segment[4].exponent, + [8] + [9, 9, 10] * 5) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[5].cqcc, 1) + self.assertEqual(c.segment[5].guard_bits, 2) + # quantization type + self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[5].mantissa, [0] * 16) + self.assertEqual(c.segment[5].exponent, + [8] + [9, 9, 10] * 5) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[6].cqcc, 2) + self.assertEqual(c.segment[6].guard_bits, 2) + # quantization type + self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[6].mantissa, [0] * 16) + self.assertEqual(c.segment[6].exponent, + [8] + [9, 9, 10] * 5) + def test_NR_DEC_Bretagne2_j2k_1_decode(self): jfile = opj_data_file('input/nonregression/Bretagne2.j2k') jp2 = Jp2k(jfile) diff --git a/glymur/test/test_opj_suite_dump.py b/glymur/test/test_opj_suite_dump.py index e24f53f..8a831ca 100644 --- a/glymur/test/test_opj_suite_dump.py +++ b/glymur/test/test_opj_suite_dump.py @@ -52,6 +52,171 @@ class TestSuiteDump(unittest.TestCase): def tearDown(self): pass + def test_NR_DEC_issue188_beach_64bitsbox_jp2_41_decode(self): + """ + Has an 'XML ' box instead of 'xml '. Just verify we can read it. + """ + relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2' + jfile = opj_data_file(relpath) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + j = Jp2k(jfile) + d = j.read() + self.assertTrue(True) + + + def test_NR_broken4_jp2_dump(self): + jfile = opj_data_file('input/nonregression/broken4.jp2') + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) + + self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') + + @unittest.skipIf(sys.maxsize < 2**32, 'Do not run on 32-bit platforms') + def test_NR_broken3_jp2_dump(self): + """ + NR_broken3_jp2_dump + + The file in question here has a colr box with an erroneous box + length of over 1GB. Don't run it on 32-bit platforms. + """ + jfile = opj_data_file('input/nonregression/broken3.jp2') + with warnings.catch_warnings(): + # Bad box length. + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 152) + self.assertEqual(jp2.box[2].box[0].width, 203) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, + glymur.core.ENUMERATED_COLORSPACE) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 + self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) + + c = jp2.box[3].main_header + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].xsiz, 203) + self.assertEqual(c.segment[1].ysiz, 152) + # Reference grid offset + self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (203, 152)) + # Tile offset + self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1].signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), + [(1, 1)] * 3) + + # COM: comment + # Registration + self.assertEqual(c.segment[2].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[2].ccme.decode('latin-1'), + "Creator: JasPer Vers)on 1.701.0") + + # COD: Coding style default + self.assertFalse(c.segment[3].scod & 2) # no sop + self.assertFalse(c.segment[3].scod & 4) # no eph + self.assertEqual(c.segment[3].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[3].layers, 1) # layers = 1 + self.assertEqual(c.segment[3].spcod[3], 1) # mct + self.assertEqual(c.segment[3].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[3].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[3].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[3].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[3].spcod[7] & 0x0020) + self.assertEqual(c.segment[3].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[3].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[4].sqcd & 0x1f, 0) + self.assertEqual(c.segment[4].guard_bits, 2) + self.assertEqual(c.segment[4].mantissa, [0] * 16) + self.assertEqual(c.segment[4].exponent, + [8] + [9, 9, 10] * 5) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[5].cqcc, 1) + self.assertEqual(c.segment[5].guard_bits, 2) + # quantization type + self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[5].mantissa, [0] * 16) + self.assertEqual(c.segment[5].exponent, + [8] + [9, 9, 10] * 5) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[6].cqcc, 2) + self.assertEqual(c.segment[6].guard_bits, 2) + # quantization type + self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[6].mantissa, [0] * 16) + self.assertEqual(c.segment[6].exponent, + [8] + [9, 9, 10] * 5) + + def test_NR_broken2_jp2_dump(self): + """ + Invalid marker ID in the codestream. + """ + jfile = opj_data_file('input/nonregression/broken2.jp2') + with warnings.catch_warnings(): + # Invalid marker ID on codestream. + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) + + self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') + def test_NR_file409752(self): jfile = opj_data_file('input/nonregression/file409752.jp2') jp2 = Jp2k(jfile) @@ -4594,8 +4759,7 @@ class TestSuiteDump(unittest.TestCase): lst = ['input', 'nonregression', 'issue188_beach_64bitsbox.jp2'] jfile = opj_data_file('/'.join(lst)) with warnings.catch_warnings(): - # There's a warning for an unknown box. We explicitly test for - # that down below. + # There's a warning for an unknown box. warnings.simplefilter("ignore") jp2 = Jp2k(jfile) diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index 5a475a2..0020803 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -83,7 +83,7 @@ class TestSuiteNegative(unittest.TestCase): relpath = 'input/nonregression/illegalcolortransform.j2k' jfile = opj_data_file(relpath) jp2k = Jp2k(jfile) - with warnings.catch_warnings(record=True) as w: + with warnings.catch_warnings(): warnings.simplefilter('ignore') codestream = jp2k.get_codestream(header_only=False) @@ -119,17 +119,6 @@ class TestSuiteNegative(unittest.TestCase): with self.assertRaises(IOError): j.write(data, cbsize=(2, 2048)) - def test_exceeded_box(self): - """should warn if reading past end of a box""" - # Verify that a warning is issued if we read past the end of a box - # This file has a palette (pclr) box whose length is impossibly - # short. - infile = os.path.join(OPJ_DATA_ROOT, - 'input/nonregression/mem-b2ace68c-1381.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - Jp2k(infile) - @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_precinct_size_not_p2(self): """precinct sizes should be powers of two.""" diff --git a/glymur/test/test_opj_suite_warn.py b/glymur/test/test_opj_suite_warn.py deleted file mode 100644 index c39b810..0000000 --- a/glymur/test/test_opj_suite_warn.py +++ /dev/null @@ -1,376 +0,0 @@ -""" -The tests defined here roughly correspond to what is in the OpenJPEG test -suite. -""" - -# Some test names correspond with openjpeg tests. Long names are ok in this -# case. -# pylint: disable=C0103 - -# All of these tests correspond to tests in openjpeg, so no docstring is really -# needed. -# pylint: disable=C0111 - -# This module is very long, cannot be helped. -# pylint: disable=C0302 - -# unittest fools pylint with "too many public methods" -# pylint: disable=R0904 - -# Some tests use numpy test infrastructure, which means the tests never -# reference "self", so pylint claims it should be a function. No, no, no. -# pylint: disable=R0201 - -# Many tests are pretty long and that can't be helped. -# pylint: disable=R0915 - -# asserWarns introduced in python 3.2 (python2.7/pylint issue) -# pylint: disable=E1101 - -import re -import sys -import unittest - -import warnings - -import numpy as np - -from glymur import Jp2k -import glymur - -from .fixtures import OPJ_DATA_ROOT -from .fixtures import mse, peak_tolerance, read_pgx, opj_data_file - - -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -class TestSuiteDumpWarnings(unittest.TestCase): - - def setUp(self): - pass - - def tearDown(self): - pass - - def test_NR_broken_jp2_dump(self): - jfile = opj_data_file('input/nonregression/broken.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - # colr box has bad length. - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 152) - self.assertEqual(jp2.box[2].box[0].width, 203) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # not allowed? - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 203) - self.assertEqual(c.segment[1].ysiz, 152) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (203, 152)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COM: comment - # Registration - self.assertEqual(c.segment[2].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[2].ccme.decode('latin-1'), - "Creator: JasPer Version 1.701.0") - - # COD: Coding style default - self.assertFalse(c.segment[3].scod & 2) # no sop - self.assertFalse(c.segment[3].scod & 4) # no eph - self.assertEqual(c.segment[3].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[3].layers, 1) # layers = 1 - self.assertEqual(c.segment[3].spcod[3], 1) # mct - self.assertEqual(c.segment[3].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[3].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcod[7] & 0x0020) - self.assertEqual(c.segment[3].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[3].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) - self.assertEqual(c.segment[4].guard_bits, 2) - self.assertEqual(c.segment[4].mantissa, [0] * 16) - self.assertEqual(c.segment[4].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 1) - self.assertEqual(c.segment[5].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[5].mantissa, [0] * 16) - self.assertEqual(c.segment[5].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 2) - self.assertEqual(c.segment[6].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[6].mantissa, [0] * 16) - self.assertEqual(c.segment[6].exponent, - [8] + [9, 9, 10] * 5) - - def test_NR_broken2_jp2_dump(self): - # Invalid marker ID on codestream. - jfile = opj_data_file('input/nonregression/broken2.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - jp2 = Jp2k(jfile) - - self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') - - @unittest.skipIf(sys.maxsize < 2**32, 'Do not run on 32-bit platforms') - def test_NR_broken3_jp2_dump(self): - """ - NR_broken3_jp2_dump - - The file in question here has a colr box with an erroneous box - length of over 1GB. Don't run it on 32-bit platforms. - """ - jfile = opj_data_file('input/nonregression/broken3.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 152) - self.assertEqual(jp2.box[2].box[0].width, 203) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] - self.assertEqual(ids, expected) - - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 203) - self.assertEqual(c.segment[1].ysiz, 152) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (203, 152)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COM: comment - # Registration - self.assertEqual(c.segment[2].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[2].ccme.decode('latin-1'), - "Creator: JasPer Vers)on 1.701.0") - - # COD: Coding style default - self.assertFalse(c.segment[3].scod & 2) # no sop - self.assertFalse(c.segment[3].scod & 4) # no eph - self.assertEqual(c.segment[3].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[3].layers, 1) # layers = 1 - self.assertEqual(c.segment[3].spcod[3], 1) # mct - self.assertEqual(c.segment[3].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[3].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcod[7] & 0x0020) - self.assertEqual(c.segment[3].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[3].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) - self.assertEqual(c.segment[4].guard_bits, 2) - self.assertEqual(c.segment[4].mantissa, [0] * 16) - self.assertEqual(c.segment[4].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 1) - self.assertEqual(c.segment[5].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[5].mantissa, [0] * 16) - self.assertEqual(c.segment[5].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 2) - self.assertEqual(c.segment[6].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[6].mantissa, [0] * 16) - self.assertEqual(c.segment[6].exponent, - [8] + [9, 9, 10] * 5) - - def test_NR_broken4_jp2_dump(self): - # Has an invalid marker in the main header - jfile = opj_data_file('input/nonregression/broken4.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - jp2 = Jp2k(jfile) - - self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') - - def test_NR_gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc_patch_jp2(self): - lst = ['input', 'nonregression', - 'gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc.patch.jp2'] - jfile = opj_data_file('/'.join(lst)) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - Jp2k(jfile) - - def test_NR_gdal_fuzzer_check_comp_dx_dy_jp2_dump(self): - lst = ['input', 'nonregression', 'gdal_fuzzer_check_comp_dx_dy.jp2'] - jfile = opj_data_file('/'.join(lst)) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - Jp2k(jfile) - - def test_NR_gdal_fuzzer_check_number_of_tiles(self): - # Has an impossible tiling setup. - lst = ['input', 'nonregression', - 'gdal_fuzzer_check_number_of_tiles.jp2'] - jfile = opj_data_file('/'.join(lst)) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - Jp2k(jfile) - - def test_NR_gdal_fuzzer_unchecked_numresolutions_dump(self): - # Has an invalid number of resolutions. - lst = ['input', 'nonregression', - 'gdal_fuzzer_unchecked_numresolutions.jp2'] - jfile = opj_data_file('/'.join(lst)) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - Jp2k(jfile) - - @unittest.skipIf(re.match("1.5|2.0.0", glymur.version.openjpeg_version), - "Test not passing on 1.5.x, not introduced until 2.x") - def test_NR_DEC_issue188_beach_64bitsbox_jp2_41_decode(self): - # Has an 'XML ' box instead of 'xml '. Yes that is pedantic, but it - # really does deserve a warning. - relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2' - jfile = opj_data_file(relpath) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('ignore') - j = Jp2k(jfile) - d = j.read() - - -if __name__ == "__main__": - unittest.main() diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index efd1ccf..b893317 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -11,6 +11,7 @@ # pylint: disable=R0904 import os +import re import struct import sys import tempfile @@ -76,7 +77,7 @@ class TestPrinting(unittest.TestCase): with open(self.jpxfile, 'rb') as ifile: tfile.write(ifile.read()) - # Add the header for an unknwon superbox. + # Add the header for an unknown superbox. write_buffer = struct.pack('>I4s', 20, 'grp '.encode()) tfile.write(write_buffer) @@ -86,10 +87,10 @@ class TestPrinting(unittest.TestCase): tfile.write(write_buffer) tfile.flush() - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') + with warnings.catch_warnings(): + # Suppress the warning about the unrecognized box. + warnings.simplefilter("ignore") jpx = Jp2k(tfile.name) - self.assertTrue(len(w), 1) glymur.set_printoptions(short=True) with patch('sys.stdout', new=StringIO()) as fake_out: @@ -725,8 +726,6 @@ class TestPrintingOpjDataRoot(unittest.TestCase): # Reset printoptions for every test. glymur.set_printoptions(short=False, xml=True, codestream=True) - warnings.resetwarnings() - def tearDown(self): pass @@ -744,6 +743,8 @@ class TestPrintingOpjDataRoot(unittest.TestCase): """An invalid colorspace shouldn't cause an error.""" filename = opj_data_file('input/nonregression/edf_c2_1103421.jp2') with warnings.catch_warnings(): + # Bad compatibility list item and bad colorspace warnings. Just + # suppress the warnings. warnings.simplefilter("ignore") jp2 = Jp2k(filename) with patch('sys.stdout', new=StringIO()) as fake_out: @@ -763,14 +764,15 @@ class TestPrintingOpjDataRoot(unittest.TestCase): filename = opj_data_file('input/nonregression/edf_c2_10025.jp2') with warnings.catch_warnings(): warnings.simplefilter("ignore") - j = Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(j) + jp2 = Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(jp2) def test_invalid_progression_order(self): """Should still be able to print even if prog order is invalid.""" jfile = opj_data_file('input/nonregression/2977.pdf.asan.67.2198.jp2') with warnings.catch_warnings(): + # Multiple warnings, actually. warnings.simplefilter("ignore") jp2 = Jp2k(jfile) codestream = jp2.get_codestream() @@ -1058,10 +1060,7 @@ class TestPrintingOpjDataRoot(unittest.TestCase): def test_uuid(self): """verify printing of UUID box""" filename = opj_data_file('input/nonregression/text_GBR.jp2') - with warnings.catch_warnings(): - # brand is 'jp2 ', but has any icc profile. - warnings.simplefilter("ignore") - jp2 = Jp2k(filename) + jp2 = Jp2k(filename) with patch('sys.stdout', new=StringIO()) as fake_out: print(jp2.box[4]) From 20d2f33cfad55b08948a4c7e109f78ebc1def67b Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 26 Aug 2014 20:29:26 -0400 Subject: [PATCH 234/326] Skipping failing test. Closes #249 Test was failing already in openjpeg's own test suite on 1.5.2 and 2.0.0. Best to just skip the test for those configurations. --- glymur/test/test_opj_suite_dump.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/glymur/test/test_opj_suite_dump.py b/glymur/test/test_opj_suite_dump.py index 8a831ca..505169c 100644 --- a/glymur/test/test_opj_suite_dump.py +++ b/glymur/test/test_opj_suite_dump.py @@ -52,6 +52,8 @@ class TestSuiteDump(unittest.TestCase): def tearDown(self): pass + @unittest.skipIf(re.match("1.5|2.0.0", glymur.version.openjpeg_version), + "Test not passing on 1.5, 2.0: not introduced until 2.x") def test_NR_DEC_issue188_beach_64bitsbox_jp2_41_decode(self): """ Has an 'XML ' box instead of 'xml '. Just verify we can read it. From b29aaf3f1924f8b84326662ab659bcd4eb41ed8f Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 27 Aug 2014 20:49:59 -0400 Subject: [PATCH 235/326] Removed duplicated warning tests. Closes #250. --- glymur/test/test_glymur_warnings.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/glymur/test/test_glymur_warnings.py b/glymur/test/test_glymur_warnings.py index 622a8af..07adab7 100644 --- a/glymur/test/test_glymur_warnings.py +++ b/glymur/test/test_glymur_warnings.py @@ -130,29 +130,6 @@ class TestWarnings(unittest.TestCase): with self.assertWarnsRegex(UserWarning, regex): Jp2k(jfile) - def test_NR_broken4_jp2_dump(self): - """ - Has an invalid marker in the main header - """ - jfile = opj_data_file('input/nonregression/broken4.jp2') - regex = r'Invalid marker id encountered at byte \d+ in codestream' - with self.assertWarnsRegex(UserWarning, regex): - jp2 = Jp2k(jfile) - - @unittest.skipIf(sys.maxsize < 2**32, 'Do not run on 32-bit platforms') - def test_NR_broken3_jp2_dump(self): - """ - Has an impossibly large box length. - - The file in question here has a colr box with an erroneous box - length of over 1GB. Don't run it on 32-bit platforms. - """ - jfile = opj_data_file('input/nonregression/broken3.jp2') - regex = re.compile(r'''b'colr'\sbox\shas\sincorrect\sbox\slength\s - \(\d+\)''', re.VERBOSE) - with self.assertWarnsRegex(UserWarning, regex): - Jp2k(jfile) - def test_bad_rsiz(self): """Should warn if RSIZ is bad. Issue196""" filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') From 5304734dfaf23ceb9635d320b951819027467b73 Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 28 Aug 2014 19:45:39 -0400 Subject: [PATCH 236/326] Removed test_ETS_C1P0_p0_07_j2k. Closes #251 Test has always failed, so just remove it. --- glymur/test/test_opj_suite.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 9c725c4..f9f683b 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -95,24 +95,6 @@ class TestSuite(unittest.TestCase): self.assertTrue(peak_tolerance(jpdata[:, :, 2], pgxdata) < 6) self.assertTrue(mse(jpdata[:, :, 2], pgxdata) < 1.07) - @unittest.skip("Known failure in OPENJPEG test suite operation.") - def test_ETS_C1P0_p0_07_j2k(self): - jfile = opj_data_file('input/conformance/p0_07.j2k') - jp2k = Jp2k(jfile) - jpdata = jp2k.read() - - pgxfile = opj_data_file('baseline/conformance/c1p0_07_0.pgx') - pgxdata = read_pgx(pgxfile) - np.testing.assert_array_equal(jpdata[:, :, 0], pgxdata) - - pgxfile = opj_data_file('baseline/conformance/c1p0_07_1.pgx') - pgxdata = read_pgx(pgxfile) - np.testing.assert_array_equal(jpdata[:, : 1], pgxdata) - - pgxfile = opj_data_file('baseline/conformance/c1p0_07_2.pgx') - pgxdata = read_pgx(pgxfile) - np.testing.assert_array_equal(jpdata[:, : 2], pgxdata) - def test_ETS_C1P0_p0_08_j2k(self): jfile = opj_data_file('input/conformance/p0_08.j2k') jp2k = Jp2k(jfile) From 69d3c1a846ba86190b1ef8cc0932e27202d03f4f Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 3 Sep 2014 20:26:11 -0400 Subject: [PATCH 237/326] Need to use extra care on skimage.io.plugin('freeimage'...) --- glymur/test/fixtures.py | 13 +++++++++++++ glymur/test/test_opj_suite_neg.py | 10 ++-------- glymur/test/test_opj_suite_write.py | 14 ++++---------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 0fc5be1..845c342 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -37,6 +37,19 @@ except: raise +# The Cinema2K/4K tests seem to need the freeimage backend to skimage.io +# in order to work. +try: + import skimage.io + if 'freeimage' in skimage.io.find_available_plugins(loaded=True).keys(): + skimage.io.use_plugin('freeimage', 'imread') + NO_SKIMAGE_FREEIMAGE_SUPPORT = False + else: + NO_SKIMAGE_FREEIMAGE_SUPPORT = True +except ((ImportError, RuntimeError)): + NO_SKIMAGE_FREEIMAGE_SUPPORT = True + + def _indent(textstr): """ Indent a string. diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index 0020803..244d244 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -17,15 +17,9 @@ import warnings import numpy as np -try: - import skimage.io - skimage.io.use_plugin('freeimage', 'imread') - _HAS_SKIMAGE_FREEIMAGE_SUPPORT = True -except ((ImportError, RuntimeError)): - _HAS_SKIMAGE_FREEIMAGE_SUPPORT = False - from .fixtures import OPJ_DATA_ROOT, opj_data_file, read_image from .fixtures import NO_READ_BACKEND, NO_READ_BACKEND_MSG +from .fixtures import NO_SKIMAGE_FREEIMAGE_SUPPORT from glymur import Jp2k import glymur @@ -44,7 +38,7 @@ class TestSuiteNegative(unittest.TestCase): pass - @unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, + @unittest.skipIf(NO_SKIMAGE_FREEIMAGE_SUPPORT, "Cannot read input image without scikit-image/freeimage") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_cinema2K_bad_frame_rate(self): diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 5fdc0ca..e3d80ff 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -15,21 +15,15 @@ import warnings import numpy as np -try: - import skimage.io - skimage.io.use_plugin('freeimage', 'imread') - _HAS_SKIMAGE_FREEIMAGE_SUPPORT = True -except ((ImportError, RuntimeError)): - _HAS_SKIMAGE_FREEIMAGE_SUPPORT = False - from .fixtures import read_image, NO_READ_BACKEND, NO_READ_BACKEND_MSG -from .fixtures import OPJ_DATA_ROOT, opj_data_file +from .fixtures import OPJ_DATA_ROOT, NO_SKIMAGE_FREEIMAGE_SUPPORT +from .fixtures import opj_data_file from . import fixtures from glymur import Jp2k import glymur -@unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, +@unittest.skipIf(NO_SKIMAGE_FREEIMAGE_SUPPORT, "Cannot read input image without scikit-image/freeimage") @unittest.skipIf(os.name == "nt", "no write support on windows, period") @unittest.skipIf(re.match(r'''(1|2.0.0)''', @@ -230,7 +224,7 @@ class TestSuiteWriteCinema(unittest.TestCase): codestream = j.get_codestream() self.check_cinema2k_codestream(codestream, (1998, 1080)) -@unittest.skipIf(not _HAS_SKIMAGE_FREEIMAGE_SUPPORT, +@unittest.skipIf(NO_SKIMAGE_FREEIMAGE_SUPPORT, "Cannot read input image without scikit-image/freeimage") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @unittest.skipIf(not re.match("(1.5|2.0.0)", glymur.version.openjpeg_version), From 30219d01bf7058b81b6c8cb3998ecfafb57bc149 Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 8 Sep 2014 20:10:21 -0400 Subject: [PATCH 238/326] Basic functionality and UTs are in. --- glymur/jp2k.py | 45 +++++++++++++++++++++++++ glymur/test/test_jp2k.py | 72 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 4ebd678..8463eb5 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -759,6 +759,51 @@ class Jp2k(Jp2kBox): return boxes + def __getitem__(self, *pargs): + """ + """ + if isinstance(pargs[0], slice): + # Should have a slice object where start = stop = step = None + slc = pargs[0] + if slc.start is None and slc.stop is None and slc.step is None: + return self.read() + else: + raise IndexError("Illegal syntax.") + + if isinstance(pargs[0], tuple): + ridx = pargs[0][0] + cidx = pargs[0][1] + + if ((ridx.start is not None) or + (ridx.stop is not None) or + (cidx.start is not None) or + (cidx.stop is not None)): + msg = "Only strides are supported when slicing a Jp2k object." + raise IndexError(msg) + + if ridx.step is None and cidx.step is None: + step = 1 + elif ridx.step != cidx.step: + msg = "Row and column strides must be the same." + raise IndexError(msg) + else: + step = ridx.step + + if np.log2(step) != np.floor(np.log2(step)): + msg = "Row and column strides must be powers of 2." + raise IndexError(msg) + + data = self.read(rlevel=np.int(np.log2(step))) + if len(pargs[0]) == 2: + return data + + # Ok, 3 arguments in pargs. + if isinstance(pargs[0][2], slice): + return data[:,:,pargs[0][2]] + elif isinstance(pargs[0][2], int): + return data[:,:,pargs[0][2]] + + def read(self, **kwargs): """Read a JPEG 2000 image. diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 63492a5..9058bbf 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -63,6 +63,78 @@ class TestJp2k(unittest.TestCase): def tearDown(self): pass + def test_slice_protocol_negative(self): + """ + """ + j = Jp2k(self.j2kfile) + + with self.assertRaises(IndexError): + # Strides in x/y directions cannot differ. + d = j[::2, ::3] + + with self.assertRaises(IndexError): + # Strides in x/y direction must be powers of 2. + d = j[::3, ::3] + + # start and stop are not supported when slicing on Jp2k object + with self.assertRaises(IndexError): + d = j[2::2, 2::2] + with self.assertRaises(IndexError): + d = j[:8:2, :8:2] + with self.assertRaises(IndexError): + d = j[2:8:2, 2:8:2] + + def test_slice_protocol_3d(self): + """ + """ + j = Jp2k(self.j2kfile) + all = j.read() + + d = j[:,:,0] + np.testing.assert_array_equal(all[:,:,0], d) + + d = j[:,:,1] + np.testing.assert_array_equal(all[:,:,1], d) + + d = j[:,:,2] + np.testing.assert_array_equal(all[:,:,2], d) + + d = j[:,:,1:3] + np.testing.assert_array_equal(all[:,:,1:3], d) + + d = j[::2, ::2, 1:3] + all = j.read(rlevel=1) + np.testing.assert_array_equal(all[:,:,1:3], d) + + def test_slice_protocol_2d(self): + """ + + """ + j = Jp2k(self.j2kfile) + + d = j[:] + self.assertEqual(d.shape, (800, 480, 3)) + + # Stride of one. + d = j[::1, ::1] + self.assertEqual(d.shape, (800, 480, 3)) + + # Stride of 2. + d = j[::2, ::2] + self.assertEqual(d.shape, (400, 240, 3)) + + d = j[::4, ::4] + self.assertEqual(d.shape, (200, 120, 3)) + + d = j[::8, ::8] + self.assertEqual(d.shape, (100, 60, 3)) + + d = j[::16, ::16] + self.assertEqual(d.shape, (50, 30, 3)) + + d = j[::32, ::32] + self.assertEqual(d.shape, (25, 15, 3)) + @unittest.skipIf(os.name == "nt", "Unexplained failure on windows") def test_irreversible(self): """Irreversible""" From 2c8e30e320411926fff60cb49b63d5d20771eebf Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 9 Sep 2014 19:10:31 -0400 Subject: [PATCH 239/326] Documentation for slicing. --- docs/source/how_do_i.rst | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index ebe7a31..a24c1ef 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -3,17 +3,24 @@ How do I...? ------------ -... read the lowest resolution thumbnail? -========================================= -Printing the Jp2k object should reveal the number of resolutions -(look in the COD segment section of the codestream), but you can -take a shortcut by supplying -1 as the -resolution level. :: +... read the lower resolution images? +===================================== +Jp2k implements slicing via the :py:meth:`__getitem__` method so +any lower resolution images in a JPEG 2000 file can easily be +accessed, for example here's how to retrieve the first sub-image :: >>> import glymur >>> jp2file = glymur.data.nemo() >>> jp2 = glymur.Jp2k(jp2file) - >>> thumbnail = jp2.read(rlevel=-1) + >>> fullres = jp2[:] + >>> print(fullres.shape) + (1456, 2592, 3) + >>> thumbnail = jp2[::2, ::2] + >>> print(thumbnail.shape) + (728, 1296, 3) + +The :py:meth:`read` method gives many more options for other JPEG 2000 features +such as quality layers. ... display metadata? ===================== From 81c804e74ce423a758f382d17bf3a661f741150f Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 10 Sep 2014 11:21:48 -0400 Subject: [PATCH 240/326] Must skip warning tests if version of six is < 1.7. --- glymur/test/test_glymur_warnings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/glymur/test/test_glymur_warnings.py b/glymur/test/test_glymur_warnings.py index 07adab7..fc8b3af 100644 --- a/glymur/test/test_glymur_warnings.py +++ b/glymur/test/test_glymur_warnings.py @@ -8,6 +8,7 @@ Test suite for warnings issued by glymur. import os import re import struct +import six import sys import tempfile import unittest @@ -20,6 +21,8 @@ from .fixtures import opj_data_file, OPJ_DATA_ROOT @unittest.skipIf(sys.hexversion < 0x03030000, "assertWarn methods introduced in 3.x") +@unittest.skipIf(re.match('1.[0-6]', six.__version__) is not None, + "Problem with earlier versions of six on python3") @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") class TestWarnings(unittest.TestCase): From 72093f05f8bd1fe072e6bb4acd655c2aab84e600 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 10 Sep 2014 14:43:11 -0400 Subject: [PATCH 241/326] Finesses the check of skimage.io If on Anaconda and python3 and the scikit image version < 0.11, then don't bother. Otherwise, go back to prior try/except code for existance of skimage.io and availability of freeimage backend, which fixes the issue on Linux Mint. --- glymur/test/fixtures.py | 15 ++++++++++----- glymur/test/test_opj_suite_neg.py | 4 ++++ glymur/test/test_opj_suite_write.py | 4 ++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 845c342..b17afc0 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -38,14 +38,19 @@ except: # The Cinema2K/4K tests seem to need the freeimage backend to skimage.io -# in order to work. +# in order to work. Unfortunately, scikit-image/freeimage is about as wonky as +# it gets. Anaconda can get totally weirded out on versions up through 3.6.4 +# on Python3 with scikit-image up through version 0.10.0. +NO_SKIMAGE_FREEIMAGE_SUPPORT = False try: + import skimage import skimage.io - if 'freeimage' in skimage.io.find_available_plugins(loaded=True).keys(): - skimage.io.use_plugin('freeimage', 'imread') - NO_SKIMAGE_FREEIMAGE_SUPPORT = False - else: + if (((sys.hexversion >= 0x03000000) and + ('Anaconda' in sys.version) and + (re.match('0.10', skimage.__version__)))): NO_SKIMAGE_FREEIMAGE_SUPPORT = True + else: + skimage.io.use_plugin('freeimage', 'imread') except ((ImportError, RuntimeError)): NO_SKIMAGE_FREEIMAGE_SUPPORT = True diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index 244d244..bee06f7 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -16,6 +16,10 @@ import unittest import warnings import numpy as np +try: + import skimage.io +except ImportError: + pass from .fixtures import OPJ_DATA_ROOT, opj_data_file, read_image from .fixtures import NO_READ_BACKEND, NO_READ_BACKEND_MSG diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index e3d80ff..2af76d8 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -14,6 +14,10 @@ import unittest import warnings import numpy as np +try: + import skimage.io +except ImportError: + pass from .fixtures import read_image, NO_READ_BACKEND, NO_READ_BACKEND_MSG from .fixtures import OPJ_DATA_ROOT, NO_SKIMAGE_FREEIMAGE_SUPPORT From 7d9a4efdbd665e8a6821d4a7329b125b46910b8e Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 10 Sep 2014 20:40:37 -0400 Subject: [PATCH 242/326] Rewrote jp2dump as an entry point console script. Some printing UT refactoring was done. --- bin/jp2dump | 34 ------- docs/source/how_do_i.rst | 2 +- docs/source/introduction.rst | 9 +- glymur/__init__.py | 1 - glymur/command_line.py | 49 +++++++++++ glymur/test/fixtures.py | 83 +++++++++++++++++- glymur/test/test_printing.py | 166 +++++++++++++++-------------------- setup.py | 4 +- 8 files changed, 205 insertions(+), 143 deletions(-) delete mode 100755 bin/jp2dump create mode 100644 glymur/command_line.py diff --git a/bin/jp2dump b/bin/jp2dump deleted file mode 100755 index 7632aff..0000000 --- a/bin/jp2dump +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python - -import argparse -import sys -import glymur - -description='Print JPEG2000 metadata.' -parser = argparse.ArgumentParser(description=description) -parser.add_argument('-x', '--noxml', help='Suppress XML.', - action='store_true') -parser.add_argument('-s', '--short', help='Only print box id, offset, and length.', - action='store_true') -chelp='Level of codestream information. 0 suppressed all details, 1 prints headers, 2 prints the full codestream' -parser.add_argument('-c', '--codestream', - help=chelp, - nargs=1, - type=int, - default=[0]) -parser.add_argument('filename') -args = parser.parse_args() -if args.noxml: - glymur.set_printoptions(xml=False) -if args.short: - glymur.set_printoptions(short=True) -if args.codestream[0] == 0: - glymur.set_printoptions(codestream=False) - print_full_codestream = False -elif args.codestream[0] == 1: - print_full_codestream = False -else: - print_full_codestream = True - -filename = args.filename -glymur.jp2dump(args.filename, codestream=print_full_codestream) diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index ebe7a31..ef16563 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -17,7 +17,7 @@ resolution level. :: ... display metadata? ===================== -There are two ways. From the unix command line, the script **jp2dump** is +There are two ways. From the command line, the script **jp2dump** is available. :: $ jp2dump /path/to/glymur/installation/data/nemo.jp2 diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index 3ae697e..bfe7174 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -27,10 +27,5 @@ but you should also be able to install Glymur via pip :: $ pip install glymur -This will install a script **jp2dump** that can be used from the unix command -line for dumping JP2 metadata, so you should adjust your **$PATH** -environment variable to take advantage of it. For example, if you install -with pip's `--user` option on linux :: - - $ export PATH=$HOME/.local/bin:$PATH - +In addition to the package, this also gives you a script **jp2dump** that can +be used from the command line line to print JPEG 2000 metadata. diff --git a/glymur/__init__.py b/glymur/__init__.py index f39d6ef..5826f8c 100644 --- a/glymur/__init__.py +++ b/glymur/__init__.py @@ -9,7 +9,6 @@ __version__ = version.version from .jp2k import Jp2k from .jp2dump import jp2dump from .jp2box import get_printoptions, set_printoptions -from .jp2box import get_parseoptions, set_parseoptions from . import data diff --git a/glymur/command_line.py b/glymur/command_line.py new file mode 100644 index 0000000..82b5292 --- /dev/null +++ b/glymur/command_line.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +import argparse +import sys +from . import jp2dump, set_printoptions + +def main(): + + description='Print JPEG2000 metadata.' + parser = argparse.ArgumentParser(description=description) + + parser.add_argument('-x', '--noxml', + help='Suppress XML.', + action='store_true') + parser.add_argument('-s', '--short', + help='Only print box id, offset, and length.', + action='store_true') + + chelp = 'Level of codestream information. 0 suppressed all details, ' + chelp += '1 prints headers, 2 prints the full codestream' + parser.add_argument('-c', '--codestream', + help=chelp, + nargs=1, + type=int, + default=[0]) + + parser.add_argument('filename') + + args = parser.parse_args() + if args.noxml: + set_printoptions(xml=False) + if args.short: + set_printoptions(short=True) + + codestream_level = args.codestream[0] + if codestream_level not in [0, 1, 2]: + raise ValueError("Invalid level of codestream information specified.") + + if codestream_level == 0: + set_printoptions(codestream=False) + print_full_codestream = False + elif codestream_level == 1: + print_full_codestream = False + else: + print_full_codestream = True + + filename = args.filename + jp2dump(args.filename, codestream=print_full_codestream) + diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index b17afc0..d8b28e9 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -455,7 +455,9 @@ Contiguous Codestream Box (jp2c) @ (3223, 1132296) Step size: [(0, 8), (0, 9), (0, 9), (0, 10)] CME marker segment @ (3305, 37) "Created by OpenJPEG version 2.0.0"''' -nemo_dump_full = dump.format(_indent(nemo_xmp)) + +nemo_with_codestream_header = dump.format(_indent(nemo_xmp)) +#nemo_dump_full = dump.format(_indent(nemo_xmp)) nemo_dump_short = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) File Type Box (ftyp) @ (12, 20) @@ -651,7 +653,7 @@ number_list_box = r"""Number List Box (nlst) @ (-1, 0) Association[2]: compositing layer 0""" -goodstuff = r"""Codestream: +goodstuff_codestream_header = r"""Codestream: SOC marker segment @ (0, 0) SIZ marker segment @ (2, 47) Profile: no profile @@ -686,3 +688,80 @@ goodstuff = r"""Codestream: Quantization style: no quantization, 2 guard bits Step size: [(0, 8), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10)]""" +goodstuff_with_full_header = r"""Codestream: + SOC marker segment @ (0, 0) + SIZ marker segment @ (2, 47) + Profile: no profile + Reference Grid Height, Width: (800 x 480) + Vertical, Horizontal Reference Grid Offset: (0 x 0) + Reference Tile Height, Width: (800 x 480) + Vertical, Horizontal Reference Tile Offset: (0 x 0) + Bitdepth: (8, 8, 8) + Signed: (False, False, False) + Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1)) + COD marker segment @ (51, 12) + Coding style: + Entropy coder, without partitions + SOP marker segments: False + EPH marker segments: False + Coding style parameters: + Progression order: LRCP + Number of layers: 1 + Multiple component transformation usage: reversible + Number of resolutions: 6 + Code block height, width: (64 x 64) + Wavelet transform: 5-3 reversible + Precinct size: default, 2^15 x 2^15 + Code block context: + Selective arithmetic coding bypass: False + Reset context probabilities on coding pass boundaries: False + Termination on each coding pass: False + Vertically stripe causal context: False + Predictable termination: False + Segmentation symbols: False + QCD marker segment @ (65, 19) + Quantization style: no quantization, 2 guard bits + Step size: [(0, 8), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10)] + SOT marker segment @ (86, 10) + Tile part index: 0 + Tile part length: 115132 + Tile part instance: 0 + Number of tile parts: 1 + COC marker segment @ (98, 9) + Associated component: 1 + Coding style for this component: Entropy coder, PARTITION = 0 + Coding style parameters: + Number of resolutions: 6 + Code block height, width: (64 x 64) + Wavelet transform: 5-3 reversible + Code block context: + Selective arithmetic coding bypass: False + Reset context probabilities on coding pass boundaries: False + Termination on each coding pass: False + Vertically stripe causal context: False + Predictable termination: False + Segmentation symbols: False + QCC marker segment @ (109, 20) + Associated Component: 1 + Quantization style: no quantization, 2 guard bits + Step size: [(0, 8), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10)] + COC marker segment @ (131, 9) + Associated component: 2 + Coding style for this component: Entropy coder, PARTITION = 0 + Coding style parameters: + Number of resolutions: 6 + Code block height, width: (64 x 64) + Wavelet transform: 5-3 reversible + Code block context: + Selective arithmetic coding bypass: False + Reset context probabilities on coding pass boundaries: False + Termination on each coding pass: False + Vertically stripe causal context: False + Predictable termination: False + Segmentation symbols: False + QCC marker segment @ (142, 20) + Associated Component: 2 + Quantization style: no quantization, 2 guard bits + Step size: [(0, 8), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10)] + SOD marker segment @ (164, 0) + EOC marker segment @ (115218, 0)""" diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index b893317..da2062e 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -31,7 +31,7 @@ else: import lxml.etree as ET import glymur -from glymur import Jp2k +from glymur import Jp2k, command_line from . import fixtures from .fixtures import OPJ_DATA_ROOT, opj_data_file from .fixtures import text_gbr_27, text_gbr_33, text_gbr_34 @@ -107,74 +107,6 @@ class TestPrinting(unittest.TestCase): with self.assertRaises(TypeError): glymur.set_printoptions(hi='low') - def test_propts_no_codestream_then_no_xml(self): - """Verify printed output when codestream=False and xml=False, #162""" - # The print options should be persistent across invocations. - glymur.set_printoptions(codestream=False) - glymur.set_printoptions(xml=False) - with patch('sys.stdout', new=StringIO()) as fake_out: - glymur.jp2dump(self.jp2file) - actual = fake_out.getvalue().strip() - - # Get rid of the filename line, as it is not set in stone. - lst = actual.split('\n') - lst = lst[1:] - actual = '\n'.join(lst) - self.assertEqual(actual, fixtures.nemo_dump_no_codestream_no_xml) - - def test_printopt_no_codestr_or_xml(self): - """Verify printed output when codestream=False and xml=False""" - glymur.set_printoptions(codestream=False, xml=False) - with patch('sys.stdout', new=StringIO()) as fake_out: - glymur.jp2dump(self.jp2file) - actual = fake_out.getvalue().strip() - - # Get rid of the filename line, as it is not set in stone. - lst = actual.split('\n') - lst = lst[1:] - actual = '\n'.join(lst) - self.assertEqual(actual, fixtures.nemo_dump_no_codestream_no_xml) - - def test_printoptions_no_codestream(self): - """Verify printed output when codestream=False""" - glymur.set_printoptions(codestream=False) - with patch('sys.stdout', new=StringIO()) as fake_out: - glymur.jp2dump(self.jp2file) - actual = fake_out.getvalue().strip() - - # Get rid of the filename line, as it is not set in stone. - lst = actual.split('\n') - lst = lst[1:] - actual = '\n'.join(lst) - self.assertEqual(actual, fixtures.nemo_dump_no_codestream) - - def test_printoptions_no_xml(self): - """Verify printed output when xml=False""" - glymur.set_printoptions(xml=False) - with patch('sys.stdout', new=StringIO()) as fake_out: - glymur.jp2dump(self.jp2file) - actual = fake_out.getvalue().strip() - - # Get rid of the filename line, as it is not set in stone. - lst = actual.split('\n') - lst = lst[1:] - actual = '\n'.join(lst) - expected = fixtures.nemo_dump_no_xml - self.assertEqual(actual, expected) - - def test_printoptions_short(self): - """Verify printed output when short=True""" - glymur.set_printoptions(short=True) - with patch('sys.stdout', new=StringIO()) as fake_out: - glymur.jp2dump(self.jp2file) - actual = fake_out.getvalue().strip() - - # Get rid of the filename line, as it is not set in stone. - lst = actual.split('\n') - lst = lst[1:] - actual = '\n'.join(lst) - self.assertEqual(actual, fixtures.nemo_dump_short) - def test_asoc_label_box(self): """verify printing of asoc, label boxes""" # Construct a fake file with an asoc and a label box, as @@ -228,32 +160,6 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) - def test_jp2dump(self): - """basic jp2dump test""" - with patch('sys.stdout', new=StringIO()) as fake_out: - glymur.jp2dump(self.jp2file) - actual = fake_out.getvalue().strip() - - # Get rid of the filename line, as it is not set in stone. - lst = actual.split('\n') - lst = lst[1:] - actual = '\n'.join(lst) - self.assertEqual(actual, fixtures.nemo_dump_full) - - def test_entire_file(self): - """verify output from printing entire file""" - j = glymur.Jp2k(self.jp2file) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(j) - actual = fake_out.getvalue().strip() - - # Get rid of the filename line, as it is not set in stone. - lst = actual.split('\n') - lst = lst[1:] - actual = '\n'.join(lst) - - self.assertEqual(actual, fixtures.nemo_dump_full) - def test_coc_segment(self): """verify printing of COC segment""" j = glymur.Jp2k(self.jp2file) @@ -1113,5 +1019,71 @@ class TestPrintingOpjDataRoot(unittest.TestCase): self.assertTrue(True) -if __name__ == "__main__": - unittest.main() + +class TestJp2dump(unittest.TestCase): + """Tests for verifying how jp2dump console script works.""" + def setUp(self): + self.jpxfile = glymur.data.jpxfile() + self.jp2file = glymur.data.nemo() + self.j2kfile = glymur.data.goodstuff() + + # Reset printoptions for every test. + glymur.set_printoptions(short=False, xml=True, codestream=True) + + def tearDown(self): + pass + + def run_jp2dump(self, args): + sys.argv = args + with patch('sys.stdout', new=StringIO()) as fake_out: + command_line.main() + actual = fake_out.getvalue().strip() + # Remove the file line, as that is filesystem-dependent. + lines = actual.split('\n') + actual = '\n'.join(lines[1:]) + return actual + + def test_default_nemo(self): + """Should be able to dump a JP2 file's metadata with no codestream.""" + actual = self.run_jp2dump(['', self.jp2file]) + + self.assertEqual(actual, fixtures.nemo_dump_no_codestream) + + def test_codestream_0(self): + """Verify dumping with -c 0, supressing all codestream details.""" + actual = self.run_jp2dump(['', '-c', '0', self.jp2file]) + + self.assertEqual(actual, fixtures.nemo_dump_no_codestream) + + def test_codestream_1(self): + """Verify dumping with -c 1, print just the header.""" + actual = self.run_jp2dump(['', '-c', '1', self.jp2file]) + + self.assertEqual(actual, fixtures.nemo_with_codestream_header) + + def test_codestream_2(self): + """Verify dumping with -c 2, full details.""" + with patch('sys.stdout', new=StringIO()) as fake_out: + sys.argv = ['', '-c', '2', self.j2kfile] + command_line.main() + actual = fake_out.getvalue().strip() + + self.assertIn(fixtures.goodstuff_with_full_header, actual) + + def test_codestream_invalid(self): + """Verify dumping with -c 3, not allowd.""" + with self.assertRaises(ValueError): + sys.argv = ['', '-c', '3', self.jp2file] + command_line.main() + + def test_short(self): + """Verify dumping with -s, short option.""" + actual = self.run_jp2dump(['', '-s', self.jp2file]) + + self.assertEqual(actual, fixtures.nemo_dump_short) + + def test_suppress_xml(self): + """Verify dumping with -x, suppress XML.""" + actual = self.run_jp2dump(['', '-x', self.jp2file]) + + self.assertEqual(actual, fixtures.nemo_dump_no_codestream_no_xml) diff --git a/setup.py b/setup.py index 94d692e..5be9368 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,9 @@ kwargs = {'name': 'glymur', 'packages': ['glymur', 'glymur.data', 'glymur.test', 'glymur.lib', 'glymur.lib.test'], 'package_data': {'glymur': ['data/*.jp2', 'data/*.j2k', 'data/*.jpx']}, - 'scripts': ['bin/jp2dump'], + 'entry_points': { + 'console_scripts': ['jp2dump=glymur.command_line:main'], + }, 'license': 'MIT', 'test_suite': 'glymur.test'} From 5c7dd5ebb6a0272458804f70188be3abd42cfde9 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 11 Sep 2014 11:37:36 -0400 Subject: [PATCH 243/326] Restored set_parseoptions, get_parseoptions to glymur/__init__ Was mistakenly removed. --- glymur/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/glymur/__init__.py b/glymur/__init__.py index 5826f8c..f39d6ef 100644 --- a/glymur/__init__.py +++ b/glymur/__init__.py @@ -9,6 +9,7 @@ __version__ = version.version from .jp2k import Jp2k from .jp2dump import jp2dump from .jp2box import get_printoptions, set_printoptions +from .jp2box import get_parseoptions, set_parseoptions from . import data From cde16a062e14d0a2fda32158ac2e90799570a796 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 11 Sep 2014 11:47:01 -0400 Subject: [PATCH 244/326] Add six to travis harness requirements. --- .travis.yml | 6 +++--- glymur/test/test_glymur_warnings.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3a9cb61..406e470 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,9 +12,9 @@ before_install: # command to install dependencies install: - - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install lxml contextlib2 mock; fi - - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install lxml numpy; fi - - if [[ $TRAVIS_PYTHON_VERSION == '3.4' ]]; then pip install lxml numpy; fi + - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install lxml contextlib2 mock six; fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install lxml numpy six; fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.4' ]]; then pip install lxml numpy six; fi # command to run tests script: diff --git a/glymur/test/test_glymur_warnings.py b/glymur/test/test_glymur_warnings.py index fc8b3af..88f1d83 100644 --- a/glymur/test/test_glymur_warnings.py +++ b/glymur/test/test_glymur_warnings.py @@ -8,12 +8,13 @@ Test suite for warnings issued by glymur. import os import re import struct -import six import sys import tempfile import unittest import warnings +import six + from glymur import Jp2k import glymur From b5c4b989130efbe48f896af3856b8ff87bcae142 Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 12 Sep 2014 10:05:40 -0400 Subject: [PATCH 245/326] Added slice protocol support. This includes 1.5.x, which means adding read area support. --- .gitignore | 1 + glymur/jp2k.py | 130 +++++++--- glymur/test/test_jp2k.py | 379 ++++++++++++++++++++++++------ glymur/test/test_opj_suite_2p1.py | 320 +++++++++++-------------- 4 files changed, 534 insertions(+), 296 deletions(-) diff --git a/.gitignore b/.gitignore index 0d20b64..c9b568f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.pyc +*.swp diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 8463eb5..870a6ec 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -759,49 +759,94 @@ class Jp2k(Jp2kBox): return boxes - def __getitem__(self, *pargs): + def __getitem__(self, pargs): """ + Slicing protocol. """ - if isinstance(pargs[0], slice): + codestream = self.get_codestream(header_only=True) + if isinstance(pargs, int): + # Not a very good use of this protocol, but technically legal. + # This retrieves a single row. + row = pargs + area = (row, 0, row + 1, codestream.segment[1].xsiz) + return self.read(area=area).squeeze() + + if isinstance(pargs, slice): + # Case of jp2[:], i.e. retrieve the entire image. + # # Should have a slice object where start = stop = step = None - slc = pargs[0] - if slc.start is None and slc.stop is None and slc.step is None: - return self.read() - else: - raise IndexError("Illegal syntax.") + return self.read() - if isinstance(pargs[0], tuple): - ridx = pargs[0][0] - cidx = pargs[0][1] + if isinstance(pargs, tuple) and all(isinstance(x, int) for x in pargs): + # Retrieve a single pixel. + # Something like jp2[r, c] + row = pargs[0] + col = pargs[1] + area = (row, col, row + 1, col + 1) + pixel = self.read(area=area).squeeze() + + if len(pargs) == 2: + return pixel + elif len(pargs) == 3: + return pixel[pargs[2]] - if ((ridx.start is not None) or - (ridx.stop is not None) or - (cidx.start is not None) or - (cidx.stop is not None)): - msg = "Only strides are supported when slicing a Jp2k object." - raise IndexError(msg) + # Assuming pargs is a tuple of slices from now on. + rows = pargs[0] + cols = pargs[1] + if len(pargs) == 2: + bands = slice(None, None, None) + else: + bands = pargs[2] - if ridx.step is None and cidx.step is None: - step = 1 - elif ridx.step != cidx.step: - msg = "Row and column strides must be the same." - raise IndexError(msg) - else: - step = ridx.step + if rows.step is None: + rows_step = 1 + else: + rows_step = rows.step + + if cols.step is None: + cols_step = 1 + else: + cols_step = cols.step + + if rows_step != cols_step: + msg = "Row and column strides must be the same." + raise IndexError(msg) + + # Ok, reduce layer step is the same in both xy directions, so just take + # one of them. + step = rows_step + + if np.log2(step) != np.floor(np.log2(step)): + msg = "Row and column strides must be powers of 2." + raise IndexError(msg) + + if rows.start is None: + rows_start = 0 + else: + rows_start = rows.start + + if rows.stop is None: + rows_stop = codestream.segment[1].ysiz + else: + rows_stop = rows.stop - if np.log2(step) != np.floor(np.log2(step)): - msg = "Row and column strides must be powers of 2." - raise IndexError(msg) + if cols.start is None: + cols_start = 0 + else: + cols_start = cols.start - data = self.read(rlevel=np.int(np.log2(step))) - if len(pargs[0]) == 2: - return data + if cols.stop is None: + cols_stop = codestream.segment[1].xsiz + else: + cols_stop = cols.stop + + area = (rows_start, cols_start, rows_stop, cols_stop) + data = self.read(area=area, rlevel=np.int(np.log2(step))) + if len(pargs) == 2: + return data - # Ok, 3 arguments in pargs. - if isinstance(pargs[0][2], slice): - return data[:,:,pargs[0][2]] - elif isinstance(pargs[0][2], int): - return data[:,:,pargs[0][2]] + # Ok, 3 arguments in pargs. + return data[:, :, bands] def read(self, **kwargs): @@ -878,7 +923,7 @@ class Jp2k(Jp2kBox): raise RuntimeError(msg) def _read_openjpeg(self, rlevel=0, ignore_pclr_cmap_cdef=False, - verbose=False): + verbose=False, area=None): """Read a JPEG 2000 image using libopenjpeg. Parameters @@ -891,6 +936,9 @@ class Jp2k(Jp2kBox): color transformation. Defaults to False. verbose : bool, optional Print informational messages produced by the OpenJPEG library. + area : tuple, optional + Specifies decoding image area, + (first_row, first_col, last_row, last_col) Returns ------- @@ -943,6 +991,18 @@ class Jp2k(Jp2kBox): # data 2D instead of 3D. data.shape = data.shape[0:2] + if area is not None: + x0, y0, x1, y1 = area + extent = 2 ** rlevel + if x1 - x0 < extent or y1 - y0 < extent: + msg = "Decoded area is too small." + raise IOError(msg) + + area = [int(round(float(x)/extent + 2 ** -20)) for x in area] + rows = slice(area[0], area[2], None) + cols = slice(area[1], area[3], None) + data = data[rows, cols] + return data def _read_openjp2(self, rlevel=0, layer=0, area=None, tile=None, diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 9058bbf..fb86b11 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -52,6 +52,307 @@ def load_tests(loader, tests, ignore): return tests +class TestSliceProtocol(unittest.TestCase): + """ + Test slice protocol, i.e. when using [ ] to read image data. + """ + @classmethod + def setUpClass(self): + + self.jp2 = Jp2k(glymur.data.nemo()) + self.jp2_data = self.jp2.read() + + self.j2k = Jp2k(glymur.data.goodstuff()) + self.j2k_data = self.j2k.read() + + def test_resolution_strides_cannot_differ(self): + with self.assertRaises(IndexError): + # Strides in x/y directions cannot differ. + self.j2k[::2, ::3] + + def test_resolution_strides_cannot_differ(self): + with self.assertRaises(IndexError): + # Strides in x/y directions cannot differ. + self.j2k[::2, ::3] + + def test_resolution_strides_must_be_powers_of_two(self): + with self.assertRaises(IndexError): + self.j2k[::3, ::3] + + def test_integer_index_in_3d(self): + + for j in [0, 1, 2]: + band = self.j2k[:, :, j] + np.testing.assert_array_equal(self.j2k_data[:, :, j], band) + + def test_slice_in_third_dimension(self): + actual = self.j2k[:,:,1:3] + expected = self.j2k_data[:,:,1:3] + np.testing.assert_array_equal(actual, expected) + + def test_reduce_resolution_and_slice_in_third_dimension(self): + d = self.j2k[::2, ::2, 1:3] + all = self.j2k.read(rlevel=1) + np.testing.assert_array_equal(all[:,:,1:3], d) + + def test_retrieve_single_row(self): + actual = self.jp2[0] + expected = self.jp2_data[0] + np.testing.assert_array_equal(actual, expected) + + def test_retrieve_single_pixel(self): + actual = self.jp2[0,0] + expected = self.jp2_data[0, 0] + np.testing.assert_array_equal(actual, expected) + + def test_retrieve_single_component(self): + actual = self.jp2[20,20,2] + expected = self.jp2_data[20, 20, 2] + np.testing.assert_array_equal(actual, expected) + + def test_full_resolution_slicing_by_quarters_upper_left(self): + actual = self.jp2[:728, :1296] + expected = self.jp2_data[:728, :1296] + np.testing.assert_array_equal(actual, expected) + + def test_full_resolution_slicing_by_quarters_lower_left(self): + actual = self.jp2[728:, :1296] + expected = self.jp2_data[728:, :1296] + np.testing.assert_array_equal(actual, expected) + + def test_full_resolution_slicing_by_quarters_upper_right(self): + actual = self.jp2[:728, 1296:] + expected = self.jp2_data[:728, 1296:] + np.testing.assert_array_equal(actual, expected) + + def test_full_resolution_slicing_by_quarters_lower_right(self): + actual = self.jp2[728:, 1296:] + expected = self.jp2_data[728:, 1296:] + np.testing.assert_array_equal(actual, expected) + + def test_full_resolution_slicing_by_quarters_center(self): + actual = self.jp2[364:1092, 648:1942] + expected = self.jp2_data[364:1092, 648:1942] + np.testing.assert_array_equal(actual, expected) + + def test_full_resolution_slicing_by_halves_left(self): + actual = self.jp2[:, :1296] + expected = self.jp2_data[:, :1296] + np.testing.assert_array_equal(actual, expected) + + def test_full_resolution_slicing_by_right_half(self): + actual = self.jp2[:, 1296:] + expected = self.jp2_data[:, 1296:] + np.testing.assert_array_equal(actual, expected) + + def test_full_resolution_slicing_by_top_half(self): + actual = self.jp2[:728, :] + expected = self.jp2_data[:728, :] + np.testing.assert_array_equal(actual, expected) + + def test_full_resolution_slicing_by_bottom_half(self): + actual = self.jp2[728:, :] + expected = self.jp2_data[728:, :] + np.testing.assert_array_equal(actual, expected) + + def test_region_rlevel1(self): + actual = self.jp2[0:201:2, 0:201:2] + expected = self.jp2.read(area=(0, 0, 201, 201), rlevel=1) + np.testing.assert_array_equal(actual, expected) + + def test_region_rlevel1_slice_start_is_none(self): + actual = self.jp2[:201:2, :201:2] + expected = self.jp2.read(area=(0, 0, 201, 201), rlevel=1) + np.testing.assert_array_equal(actual, expected) + + def test_region_rlevel1_slice_stop_is_none(self): + actual = self.jp2[201::2, 201::2] + expected = self.jp2.read(area=(201, 201, 1456, 2592), rlevel=1) + np.testing.assert_array_equal(actual, expected) + + def test_region_rlevel1(self): + actual = self.jp2[0:202:2, 0:202:2] + expected = self.jp2.read(area=(0, 0, 202, 202), rlevel=1) + np.testing.assert_array_equal(actual, expected) + + def test_slice_protocol_2d_reduce_resolution(self): + d = self.j2k[:] + self.assertEqual(d.shape, (800, 480, 3)) + + d = self.j2k[::1, ::1] + self.assertEqual(d.shape, (800, 480, 3)) + + d = self.j2k[::2, ::2] + self.assertEqual(d.shape, (400, 240, 3)) + + d = self.j2k[::4, ::4] + self.assertEqual(d.shape, (200, 120, 3)) + + d = self.j2k[::8, ::8] + self.assertEqual(d.shape, (100, 60, 3)) + + d = self.j2k[::16, ::16] + self.assertEqual(d.shape, (50, 30, 3)) + + d = self.j2k[::32, ::32] + self.assertEqual(d.shape, (25, 15, 3)) + + def test_region_rlevel5(self): + actual = self.j2k[5:533:32, 27:423:32] + expected = self.j2k.read(area=(5, 27, 533, 423), rlevel=5) + np.testing.assert_array_equal(actual, expected) + +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class TestSliceProtocolOpjData(unittest.TestCase): + """ + Test slice protocol, i.e. when using [ ] to read image data. + These correspond to tests for the read method with the area parameter. + """ + @classmethod + def setUpClass(self): + + jfile = opj_data_file('input/conformance/p1_04.j2k') + self.j2k = Jp2k(jfile) + self.j2k_data = self.j2k.read() + self.j2k_half_data = self.j2k.read(rlevel=1) + self.j2k_quarter_data = self.j2k.read(rlevel=2) + + def test_NR_DEC_p1_04_j2k_43_decode(self): + actual = self.j2k[:1024, :1024] + expected = self.j2k_data + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_44_decode(self): + actual = self.j2k[640:768, 512:640] + expected = self.j2k_data[640:768, 512:640] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_45_decode(self): + actual = self.j2k[896:1024, 896:1024] + expected = self.j2k_data[896:1024, 896:1024] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_46_decode(self): + actual = self.j2k[500:800, 100:300] + expected = self.j2k_data[500:800, 100:300] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_47_decode(self): + actual = self.j2k[520:600, 260:360] + expected = self.j2k_data[520:600, 260:360] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_48_decode(self): + actual = self.j2k[520:660, 260:360] + expected = self.j2k_data[520:660, 260:360] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_49_decode(self): + actual = self.j2k[520:600, 360:400] + expected = self.j2k_data[520:600, 360:400] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_50_decode(self): + actual = self.j2k[:1024:4, :1024:4] + expected = self.j2k_quarter_data[:256, :256] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_51_decode(self): + actual = self.j2k[640:768:4, 512:640:4] + expected = self.j2k_quarter_data[160:192, 128:160] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_52_decode(self): + actual = self.j2k[896:1024:4, 896:1024:4] + expected = self.j2k_quarter_data[224:352, 224:352] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_53_decode(self): + actual = self.j2k[500:800:4, 100:300:4] + expected = self.j2k_quarter_data[125:200, 25:75] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_54_decode(self): + actual = self.j2k[520:600:4, 260:360:4] + expected = self.j2k_quarter_data[130:150, 65:90] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_55_decode(self): + actual = self.j2k[520:660:4, 260:360:4] + expected = self.j2k_quarter_data[130:165, 65:90] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_56_decode(self): + actual = self.j2k[520:600:4, 360:400:4] + expected = self.j2k_quarter_data[130:150, 90:100] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_06_j2k_75_decode(self): + # Image size would be 0 x 0. + with self.assertRaises((IOError, OSError)): + self.j2k[9:12:4, 9:12:4] + + def test_NR_DEC_p0_04_j2k_85_decode(self): + actual = self.j2k[:256, :256] + expected = self.j2k_data[:256, :256] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_86_decode(self): + actual = self.j2k[:128, 128:256] + expected = self.j2k_data[:128, 128:256] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_87_decode(self): + actual = self.j2k[10:200, 50:120] + expected = self.j2k_data[10:200, 50:120] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_88_decode(self): + actual = self.j2k[150:210, 10:190] + expected = self.j2k_data[150:210, 10:190] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_89_decode(self): + actual = self.j2k[80:150, 100:200] + expected = self.j2k_data[80:150, 100:200] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_90_decode(self): + actual = self.j2k[20:50, 150:200] + expected = self.j2k_data[20:50, 150:200] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_91_decode(self): + actual = self.j2k[:256:4, :256:4] + expected = self.j2k_quarter_data[0:64, 0:64] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_92_decode(self): + actual = self.j2k[:128:4, 128:256:4] + expected = self.j2k_quarter_data[:32, 32:64] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_93_decode(self): + actual = self.j2k[10:200:4, 50:120:4] + expected = self.j2k_quarter_data[3:50, 13:30] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_94_decode(self): + actual = self.j2k[150:210:4, 10:190:4] + expected = self.j2k_quarter_data[38:53, 3:48] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_95_decode(self): + actual = self.j2k[80:150:4, 100:200:4] + expected = self.j2k_quarter_data[20:38, 25:50] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_96_decode(self): + actual = self.j2k[20:50:4, 150:200:4] + expected = self.j2k_quarter_data[5:13, 38:50] + np.testing.assert_array_equal(actual, expected) + class TestJp2k(unittest.TestCase): """These tests should be run by just about all configuration.""" @@ -63,77 +364,6 @@ class TestJp2k(unittest.TestCase): def tearDown(self): pass - def test_slice_protocol_negative(self): - """ - """ - j = Jp2k(self.j2kfile) - - with self.assertRaises(IndexError): - # Strides in x/y directions cannot differ. - d = j[::2, ::3] - - with self.assertRaises(IndexError): - # Strides in x/y direction must be powers of 2. - d = j[::3, ::3] - - # start and stop are not supported when slicing on Jp2k object - with self.assertRaises(IndexError): - d = j[2::2, 2::2] - with self.assertRaises(IndexError): - d = j[:8:2, :8:2] - with self.assertRaises(IndexError): - d = j[2:8:2, 2:8:2] - - def test_slice_protocol_3d(self): - """ - """ - j = Jp2k(self.j2kfile) - all = j.read() - - d = j[:,:,0] - np.testing.assert_array_equal(all[:,:,0], d) - - d = j[:,:,1] - np.testing.assert_array_equal(all[:,:,1], d) - - d = j[:,:,2] - np.testing.assert_array_equal(all[:,:,2], d) - - d = j[:,:,1:3] - np.testing.assert_array_equal(all[:,:,1:3], d) - - d = j[::2, ::2, 1:3] - all = j.read(rlevel=1) - np.testing.assert_array_equal(all[:,:,1:3], d) - - def test_slice_protocol_2d(self): - """ - - """ - j = Jp2k(self.j2kfile) - - d = j[:] - self.assertEqual(d.shape, (800, 480, 3)) - - # Stride of one. - d = j[::1, ::1] - self.assertEqual(d.shape, (800, 480, 3)) - - # Stride of 2. - d = j[::2, ::2] - self.assertEqual(d.shape, (400, 240, 3)) - - d = j[::4, ::4] - self.assertEqual(d.shape, (200, 120, 3)) - - d = j[::8, ::8] - self.assertEqual(d.shape, (100, 60, 3)) - - d = j[::16, ::16] - self.assertEqual(d.shape, (50, 30, 3)) - - d = j[::32, ::32] - self.assertEqual(d.shape, (25, 15, 3)) @unittest.skipIf(os.name == "nt", "Unexplained failure on windows") def test_irreversible(self): @@ -638,13 +868,6 @@ class TestJp2k_1_x(unittest.TestCase): def tearDown(self): pass - def test_area(self): - """Area option not allowed for 1.x. - """ - j2k = Jp2k(self.j2kfile) - with self.assertRaises(TypeError): - j2k.read(area=(0, 0, 100, 100)) - def test_tile(self): """tile option not allowed for 1.x. """ diff --git a/glymur/test/test_opj_suite_2p1.py b/glymur/test/test_opj_suite_2p1.py index ebe5bf8..1b58d77 100644 --- a/glymur/test/test_opj_suite_2p1.py +++ b/glymur/test/test_opj_suite_2p1.py @@ -124,105 +124,6 @@ class TestSuite2point1(unittest.TestCase): Jp2k(jfile).read() self.assertTrue(True) - def test_NR_DEC_p1_04_j2k_43_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 0, 1024, 1024)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata) - - def test_NR_DEC_p1_04_j2k_44_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(640, 512, 768, 640)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[640:768, 512:640]) - - def test_NR_DEC_p1_04_j2k_45_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(896, 896, 1024, 1024)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[896:1024, 896:1024]) - - def test_NR_DEC_p1_04_j2k_46_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(500, 100, 800, 300)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[500:800, 100:300]) - - def test_NR_DEC_p1_04_j2k_47_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(520, 260, 600, 360)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[520:600, 260:360]) - - def test_NR_DEC_p1_04_j2k_48_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(520, 260, 660, 360)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[520:660, 260:360]) - - def test_NR_DEC_p1_04_j2k_49_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(520, 360, 600, 400)) - odata = jp2k.read() - np.testing.assert_array_equal(ssdata, odata[520:600, 360:400]) - - def test_NR_DEC_p1_04_j2k_50_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 0, 1024, 1024), rlevel=2) - odata = jp2k.read(rlevel=2) - - np.testing.assert_array_equal(ssdata, odata[0:256, 0:256]) - - def test_NR_DEC_p1_04_j2k_51_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(640, 512, 768, 640), rlevel=2) - odata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(ssdata, odata[160:192, 128:160]) - - def test_NR_DEC_p1_04_j2k_52_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(896, 896, 1024, 1024), rlevel=2) - odata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(ssdata, odata[224:352, 224:352]) - - def test_NR_DEC_p1_04_j2k_53_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(500, 100, 800, 300), rlevel=2) - odata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(ssdata, odata[125:200, 25:75]) - - def test_NR_DEC_p1_04_j2k_54_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(520, 260, 600, 360), rlevel=2) - odata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(ssdata, odata[130:150, 65:90]) - - def test_NR_DEC_p1_04_j2k_55_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(520, 260, 660, 360), rlevel=2) - odata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(ssdata, odata[130:165, 65:90]) - - def test_NR_DEC_p1_04_j2k_56_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(520, 360, 600, 400), rlevel=2) - odata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(ssdata, odata[130:150, 90:100]) - def test_NR_DEC_p1_04_j2k_57_decode(self): jfile = opj_data_file('input/conformance/p1_04.j2k') jp2k = Jp2k(jfile) @@ -263,127 +164,180 @@ class TestSuite2point1(unittest.TestCase): with self.assertRaises(IOError): j.read() - def test_NR_DEC_p1_06_j2k_70_decode(self): +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +@unittest.skipIf(re.match(r'''(1|2.0.0)''', + glymur.version.openjpeg_version) is not None, + "Only supported in 2.0.1 or higher") +class TestReadArea(unittest.TestCase): + """ + Runs tests introduced in version 2.0+ or that pass only in 2.0+ + + Specifically for read method with area parameter. + """ + @classmethod + def setUpClass(self): + + jfile = opj_data_file('input/conformance/p1_04.j2k') + self.j2k = Jp2k(jfile) + self.j2k_data = self.j2k.read() + self.j2k_half_data = self.j2k.read(rlevel=1) + self.j2k_quarter_data = self.j2k.read(rlevel=2) + jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(9, 9, 12, 12), rlevel=1) - self.assertEqual(ssdata.shape, (1, 1, 3)) + self.j2k_p1_06 = Jp2k(jfile) + + def test_NR_DEC_p1_04_j2k_43_decode(self): + actual = self.j2k.read(area=(0, 0, 1024, 1024)) + expected = self.j2k_data + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_44_decode(self): + actual = self.j2k.read(area=(640, 512, 768, 640)) + expected = self.j2k_data[640:768, 512:640] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_45_decode(self): + actual = self.j2k.read(area=(896, 896, 1024, 1024)) + expected = self.j2k_data[896:1024, 896:1024] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_46_decode(self): + actual = self.j2k.read(area=(500, 100, 800, 300)) + expected = self.j2k_data[500:800, 100:300] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_47_decode(self): + actual = self.j2k.read(area=(520, 260, 600, 360)) + expected = self.j2k_data[520:600, 260:360] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_48_decode(self): + actual = self.j2k.read(area=(520, 260, 660, 360)) + expected = self.j2k_data[520:660, 260:360] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_49_decode(self): + actual = self.j2k.read(area=(520, 360, 600, 400)) + expected = self.j2k_data[520:600, 360:400] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_50_decode(self): + actual = self.j2k.read(area=(0, 0, 1024, 1024), rlevel=2) + expected = self.j2k_quarter_data + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_51_decode(self): + actual = self.j2k.read(area=(640, 512, 768, 640), rlevel=2) + expected = self.j2k_quarter_data[160:192, 128:160] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_52_decode(self): + actual = self.j2k.read(area=(896, 896, 1024, 1024), rlevel=2) + expected = self.j2k_quarter_data[224:352, 224:352] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_53_decode(self): + actual = self.j2k.read(area=(500, 100, 800, 300), rlevel=2) + expected = self.j2k_quarter_data[125:200, 25:75] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_54_decode(self): + actual = self.j2k.read(area=(520, 260, 600, 360), rlevel=2) + expected = self.j2k_quarter_data[130:150, 65:90] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_55_decode(self): + actual = self.j2k.read(area=(520, 260, 660, 360), rlevel=2) + expected = self.j2k_quarter_data[130:165, 65:90] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_56_decode(self): + actual = self.j2k.read(area=(520, 360, 600, 400), rlevel=2) + expected = self.j2k_quarter_data[130:150, 90:100] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_06_j2k_70_decode(self): + actual = self.j2k_p1_06.read(area=(9, 9, 12, 12), rlevel=1) + self.assertEqual(actual.shape, (1, 1, 3)) def test_NR_DEC_p1_06_j2k_71_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(10, 4, 12, 10), rlevel=1) - self.assertEqual(ssdata.shape, (1, 3, 3)) + actual = self.j2k_p1_06.read(area=(10, 4, 12, 10), rlevel=1) + self.assertEqual(actual.shape, (1, 3, 3)) def test_NR_DEC_p1_06_j2k_72_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(3, 3, 9, 9), rlevel=1) + ssdata = self.j2k_p1_06.read(area=(3, 3, 9, 9), rlevel=1) self.assertEqual(ssdata.shape, (3, 3, 3)) def test_NR_DEC_p1_06_j2k_73_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(4, 4, 7, 7), rlevel=1) + ssdata = self.j2k_p1_06.read(area=(4, 4, 7, 7), rlevel=1) self.assertEqual(ssdata.shape, (2, 2, 3)) def test_NR_DEC_p1_06_j2k_74_decode(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(4, 4, 5, 5), rlevel=1) + ssdata = self.j2k_p1_06.read(area=(4, 4, 5, 5), rlevel=1) self.assertEqual(ssdata.shape, (1, 1, 3)) def test_NR_DEC_p1_06_j2k_75_decode(self): # Image size would be 0 x 0. - jfile = opj_data_file('input/conformance/p1_06.j2k') - jp2k = Jp2k(jfile) with self.assertRaises((IOError, OSError)): - jp2k.read(area=(9, 9, 12, 12), rlevel=2) + self.j2k_p1_06.read(area=(9, 9, 12, 12), rlevel=2) def test_NR_DEC_p0_04_j2k_85_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 0, 256, 256)) - fulldata = jp2k.read() - np.testing.assert_array_equal(fulldata[0:256, 0:256], ssdata) + actual = self.j2k.read(area=(0, 0, 256, 256)) + expected = self.j2k_data[:256, :256] + np.testing.assert_array_equal(actual, expected) def test_NR_DEC_p0_04_j2k_86_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 128, 128, 256)) - fulldata = jp2k.read() - np.testing.assert_array_equal(fulldata[0:128, 128:256], ssdata) + actual = self.j2k.read(area=(0, 128, 128, 256)) + expected = self.j2k_data[:128, 128:256] + np.testing.assert_array_equal(actual, expected) def test_NR_DEC_p0_04_j2k_87_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(10, 50, 200, 120)) - fulldata = jp2k.read() - np.testing.assert_array_equal(fulldata[10:200, 50:120], ssdata) + actual = self.j2k.read(area=(10, 50, 200, 120)) + expected = self.j2k_data[10:200, 50:120] + np.testing.assert_array_equal(actual, expected) def test_NR_DEC_p0_04_j2k_88_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(150, 10, 210, 190)) - fulldata = jp2k.read() - np.testing.assert_array_equal(fulldata[150:210, 10:190], ssdata) + actual = self.j2k.read(area=(150, 10, 210, 190)) + expected = self.j2k_data[150:210, 10:190] + np.testing.assert_array_equal(actual, expected) def test_NR_DEC_p0_04_j2k_89_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(80, 100, 150, 200)) - fulldata = jp2k.read() - np.testing.assert_array_equal(fulldata[80:150, 100:200], ssdata) + actual = self.j2k.read(area=(80, 100, 150, 200)) + expected = self.j2k_data[80:150, 100:200] + np.testing.assert_array_equal(actual, expected) def test_NR_DEC_p0_04_j2k_90_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(20, 150, 50, 200)) - fulldata = jp2k.read() - np.testing.assert_array_equal(fulldata[20:50, 150:200], ssdata) + actual = self.j2k.read(area=(20, 150, 50, 200)) + expected = self.j2k_data[20:50, 150:200] + np.testing.assert_array_equal(actual, expected) def test_NR_DEC_p0_04_j2k_91_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 0, 256, 256), rlevel=2) - fulldata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(fulldata[0:64, 0:64], ssdata) + actual = self.j2k.read(area=(0, 0, 256, 256), rlevel=2) + expected = self.j2k_quarter_data[0:64, 0:64] + np.testing.assert_array_equal(actual, expected) def test_NR_DEC_p0_04_j2k_92_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(0, 128, 128, 256), rlevel=2) - fulldata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(fulldata[0:32, 32:64], ssdata) + actual = self.j2k.read(area=(0, 128, 128, 256), rlevel=2) + expected = self.j2k_quarter_data[:32, 32:64] + np.testing.assert_array_equal(actual, expected) def test_NR_DEC_p0_04_j2k_93_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(10, 50, 200, 120), rlevel=2) - fulldata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(fulldata[3:50, 13:30], ssdata) + actual = self.j2k.read(area=(10, 50, 200, 120), rlevel=2) + expected = self.j2k_quarter_data[3:50, 13:30] + np.testing.assert_array_equal(actual, expected) def test_NR_DEC_p0_04_j2k_94_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(150, 10, 210, 190), rlevel=2) - fulldata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(fulldata[38:53, 3:48], ssdata) + actual = self.j2k.read(area=(150, 10, 210, 190), rlevel=2) + expected = self.j2k_quarter_data[38:53, 3:48] + np.testing.assert_array_equal(actual, expected) def test_NR_DEC_p0_04_j2k_95_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(80, 100, 150, 200), rlevel=2) - fulldata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(fulldata[20:38, 25:50], ssdata) + actual = self.j2k.read(area=(80, 100, 150, 200), rlevel=2) + expected = self.j2k_quarter_data[20:38, 25:50] + np.testing.assert_array_equal(actual, expected) def test_NR_DEC_p0_04_j2k_96_decode(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - jp2k = Jp2k(jfile) - ssdata = jp2k.read(area=(20, 150, 50, 200), rlevel=2) - fulldata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(fulldata[5:13, 38:50], ssdata) - - -if __name__ == "__main__": - unittest.main() + actual = self.j2k.read(area=(20, 150, 50, 200), rlevel=2) + expected = self.j2k_quarter_data[5:13, 38:50] + np.testing.assert_array_equal(actual, expected) From 72a65943a1416917d3d894b3b4514f3a1bd957ad Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 16 Sep 2014 08:11:56 -0400 Subject: [PATCH 246/326] Progress... 1000 lines fewer code. --- glymur/test/test_opj_suite_dump.py | 1879 +++++++--------------------- 1 file changed, 449 insertions(+), 1430 deletions(-) diff --git a/glymur/test/test_opj_suite_dump.py b/glymur/test/test_opj_suite_dump.py index 505169c..705786b 100644 --- a/glymur/test/test_opj_suite_dump.py +++ b/glymur/test/test_opj_suite_dump.py @@ -52,6 +52,58 @@ class TestSuiteDump(unittest.TestCase): def tearDown(self): pass + def verifySignatureBox(self, box): + """ + The signature box is a constant. + """ + self.assertEqual(box.signature, (13, 10, 135, 10)) + + def verifyFileTypeBox(self, box, brand, clist): + """ + All JP2 files should have a brand reading 'jp2 ' and just a single + entry in the compatibility list, also 'jp2 '. JPX files can have more + compatibility items. + """ + self.assertEqual(box.brand, brand) + self.assertEqual(box.minor_version, 0) + for cl in clist: + self.assertIn(cl, box.compatibility_list) + + + def verifySizSegment(self, actual, expected): + """ + Verify the fields of the SIZ segment. + """ + for field in ['rsiz', 'xsiz', 'ysiz', 'xosiz', 'yosiz', 'xtsiz', + 'ytsiz', 'xtosiz', 'ytosiz', 'bitdepth', 'xrsiz', 'yrsiz']: + self.assertEqual(getattr(actual, field), getattr(expected, field)) + + def verifyImageHeaderBox(self, box1, box2): + self.assertEqual(box1.height, box2.height) + self.assertEqual(box1.width, box2.width) + self.assertEqual(box1.num_components, box2.num_components) + self.assertEqual(box1.bits_per_component, box2.bits_per_component) + self.assertEqual(box1.signed, box2.signed) + self.assertEqual(box1.compression, box2.compression) + self.assertEqual(box1.colorspace_unknown, box2.colorspace_unknown) + self.assertEqual(box1.ip_provided, box2.ip_provided) + + def verifyColourSpecificationBox(self, actual, expected): + """ + Does not currently check icc profiles. + """ + self.assertEqual(actual.method, expected.method) + self.assertEqual(actual.precedence, expected.precedence) + self.assertEqual(actual.approximation, expected.approximation) + + if expected.colorspace is None: + self.assertIsNone(actual.colorspace) + self.assertIsNotNone(actual.icc_profile) + else: + self.assertEqual(actual.colorspace, expected.colorspace) + self.assertIsNone(actual.icc_profile) + + @unittest.skipIf(re.match("1.5|2.0.0", glymur.version.openjpeg_version), "Test not passing on 1.5, 2.0: not introduced until 2.x") def test_NR_DEC_issue188_beach_64bitsbox_jp2_41_decode(self): @@ -95,32 +147,14 @@ class TestSuiteDump(unittest.TestCase): ids = [box.box_id for box in jp2.box[2].box] self.assertEqual(ids, ['ihdr', 'colr']) - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + self.verifySignatureBox(jp2.box[0]) + self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + ihdr = glymur.jp2box.ImageHeaderBox(152, 203, num_components=3) + self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 152) - self.assertEqual(jp2.box[2].box[0].width, 203) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.SRGB) + self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) c = jp2.box[3].main_header @@ -128,25 +162,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 203) - self.assertEqual(c.segment[1].ysiz, 152) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (203, 152)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (203, 152), 'xyosiz': (0, 0), + 'xytsiz': (203, 152), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COM: comment # Registration @@ -229,32 +249,14 @@ class TestSuiteDump(unittest.TestCase): ids = [box.box_id for box in jp2.box[2].box] self.assertEqual(ids, ['ihdr', 'colr']) - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + self.verifySignatureBox(jp2.box[0]) + self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + ihdr = glymur.jp2box.ImageHeaderBox(243, 720, num_components=3) + self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 243) - self.assertEqual(jp2.box[2].box[0].width, 720) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.YCC) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.YCC) + self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) c = jp2.box[3].main_header @@ -262,25 +264,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'COD', 'QCD'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 720) - self.assertEqual(c.segment[1].ysiz, 243) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (720, 243)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (2, 1), (2, 1)]) + kwargs = {'rsiz': 0, 'xysiz': (720, 243), 'xyosiz': (0, 0), + 'xytsiz': (720, 243), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 2, 2), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -325,25 +313,10 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'QCD', 'COD', 'SOT', 'SOD', 'EOC'] self.assertEqual(actual, expected) - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 128) - self.assertEqual(c.segment[1].ysiz, 128) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) + kwargs = {'rsiz': 1, 'xysiz': (128, 128), 'xyosiz': (0, 0), + 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # QCD: Quantization default self.assertEqual(c.segment[2].sqcd & 0x1f, 0) @@ -387,25 +360,10 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p0_02.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 127) - self.assertEqual(c.segment[1].ysiz, 126) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (127, 126)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(2, 1)]) + kwargs = {'rsiz': 1, 'xysiz': (127, 126), 'xyosiz': (0, 0), + 'xytsiz': (127, 126), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), 'xyrsiz': [(2,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) # sop @@ -493,25 +451,10 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p0_03.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 256) - self.assertEqual(c.segment[1].ysiz, 256) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (4,)) - # signed - self.assertEqual(c.segment[1].signed, (True,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) + kwargs = {'rsiz': 1, 'xysiz': (256, 256), 'xyosiz': (0, 0), + 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (4,), + 'signed': (True,), 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) @@ -610,25 +553,11 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p0_04.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 640) - self.assertEqual(c.segment[1].ysiz, 480) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (640, 480)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (1, 1), (1, 1)]) + kwargs = {'rsiz': 1, 'xysiz': (640, 480), 'xyosiz': (0, 0), + 'xytsiz': (640, 480), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -718,26 +647,12 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p0_05.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1024) - self.assertEqual(c.segment[1].ysiz, 1024) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1024, 1024)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (1, 1), (2, 2), (2, 2)]) + kwargs = {'rsiz': 1, 'xysiz': (1024, 1024), 'xyosiz': (0, 0), + 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8, 8), + 'signed': (False, False, False, False), + 'xyrsiz': [(1, 1, 2, 2), (1, 1, 2, 2)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -862,25 +777,12 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p0_06.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 513) - self.assertEqual(c.segment[1].ysiz, 129) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (513, 129)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12, 12, 12, 12)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (2, 1), (1, 2), (2, 2)]) + kwargs = {'rsiz': 2, 'xysiz': (513, 129), 'xyosiz': (0, 0), + 'xytsiz': (513, 129), 'xytosiz': (0, 0), + 'bitdepth': (12, 12, 12, 12), + 'signed': (False, False, False, False), + 'xyrsiz': [(1, 2, 1, 2), (1, 1, 2, 2)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -999,25 +901,11 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p0_07.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 2048) - self.assertEqual(c.segment[1].ysiz, 2048) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12, 12, 12)) - # signed - self.assertEqual(c.segment[1].signed, (True, True, True)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (1, 1), (1, 1)]) + kwargs = {'rsiz': 1, 'xysiz': (2048, 2048), 'xyosiz': (0, 0), + 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (12, 12, 12), + 'signed': (True, True, True), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) @@ -1084,25 +972,11 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p0_08.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 513) - self.assertEqual(c.segment[1].ysiz, 3072) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (513, 3072)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12, 12, 12)) - # signed - self.assertEqual(c.segment[1].signed, (True, True, True)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (1, 1), (1, 1)]) + kwargs = {'rsiz': 1, 'xysiz': (513, 3072), 'xyosiz': (0, 0), + 'xytsiz': (513, 3072), 'xytosiz': (0, 0), 'bitdepth': (12, 12, 12), + 'signed': (True, True, True), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) @@ -1237,25 +1111,10 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p0_09.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "0" means profile 2, or full capabilities - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 17) - self.assertEqual(c.segment[1].ysiz, 37) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (17, 37)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) + kwargs = {'rsiz': 0, 'xysiz': (17, 37), 'xyosiz': (0, 0), + 'xytsiz': (17, 37), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -1317,25 +1176,11 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p0_10.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 256) - self.assertEqual(c.segment[1].ysiz, 256) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(4, 4), (4, 4), (4, 4)]) + kwargs = {'rsiz': 1, 'xysiz': (256, 256), 'xyosiz': (0, 0), + 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(4, 4, 4), (4, 4, 4)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -1458,25 +1303,10 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p0_11.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 128) - self.assertEqual(c.segment[1].ysiz, 1) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) + kwargs = {'rsiz': 1, 'xysiz': (128, 1), 'xyosiz': (0, 0), + 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -1539,25 +1369,11 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p0_12.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 3) - self.assertEqual(c.segment[1].ysiz, 5) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (3, 5)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) + kwargs = {'rsiz': 1, 'xysiz': (3, 5), 'xyosiz': (0, 0), + 'xytsiz': (3, 5), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) @@ -1621,25 +1437,11 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p0_13.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1) - self.assertEqual(c.segment[1].ysiz, 1) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (1, 1)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, tuple([8] * 257)) - # signed - self.assertEqual(c.segment[1].signed, tuple([False] * 257)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 257) + kwargs = {'rsiz': 1, 'xysiz': (1, 1), 'xyosiz': (0, 0), + 'xytsiz': (1, 1), 'xytosiz': (0, 0), 'bitdepth': tuple([8] * 257), + 'signed': tuple([False] * 257), + 'xyrsiz': [tuple([1] * 257), tuple([1] * 257)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1747,25 +1549,11 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p0_14.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "0" means profile 2 - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 49) - self.assertEqual(c.segment[1].ysiz, 49) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (49, 49)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (49, 49), 'xyosiz': (0, 0), + 'xytsiz': (49, 49), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -1823,25 +1611,11 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p0_15.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "1" means profile 0 - self.assertEqual(c.segment[1].rsiz, 1) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 256) - self.assertEqual(c.segment[1].ysiz, 256) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (4,)) - # signed - self.assertEqual(c.segment[1].signed, (True,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) + kwargs = {'rsiz': 1, 'xysiz': (256, 256), 'xyosiz': (0, 0), + 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (4,), + 'signed': (True,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) @@ -1977,25 +1751,11 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p0_16.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "0" means profile 2 - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 128) - self.assertEqual(c.segment[1].ysiz, 128) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) + kwargs = {'rsiz': 0, 'xysiz': (128, 128), 'xyosiz': (0, 0), + 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -2045,25 +1805,11 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p1_01.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "1" means profile 1 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 127) - self.assertEqual(c.segment[1].ysiz, 227) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (5, 128)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (127, 126)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (1, 101)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(2, 1)]) + kwargs = {'rsiz': 2, 'xysiz': (127, 227), 'xyosiz': (5, 128), + 'xytsiz': (127, 126), 'xytosiz': (1, 101), 'bitdepth': (8,), + 'signed': (False,), + 'xyrsiz': [(2,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) # SOP @@ -2145,25 +1891,11 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p1_02.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "1" means profile 1 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 640) - self.assertEqual(c.segment[1].ysiz, 480) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (640, 480)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, tuple([8] * 3)) - # signed - self.assertEqual(c.segment[1].signed, tuple([False] * 3)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 2, 'xysiz': (640, 480), 'xyosiz': (0, 0), + 'xytsiz': (640, 480), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2259,26 +1991,12 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p1_03.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "1" means profile 1 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1024) - self.assertEqual(c.segment[1].ysiz, 1024) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1024, 1024)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, tuple([8] * 4)) - # signed - self.assertEqual(c.segment[1].signed, tuple([False] * 4)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (1, 1), (2, 2), (2, 2)]) + kwargs = {'rsiz': 2, 'xysiz': (1024, 1024), 'xyosiz': (0, 0), + 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8, 8), + 'signed': (False, False, False, False), + 'xyrsiz': [(1, 1, 2, 2), (1, 1, 2, 2)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2406,25 +2124,11 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p1_04.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "1" means profile 1 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1024) - self.assertEqual(c.segment[1].ysiz, 1024) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (128, 128)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) + kwargs = {'rsiz': 2, 'xysiz': (1024, 1024), 'xyosiz': (0, 0), + 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (12,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2533,25 +2237,11 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p1_05.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "1" means profile 1 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 529) - self.assertEqual(c.segment[1].ysiz, 524) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (17, 12)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (37, 37)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (8, 2)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 2, 'xysiz': (529, 524), 'xyosiz': (17, 12), + 'xytsiz': (37, 37), 'xytosiz': (8, 2), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) # sop @@ -2621,25 +2311,11 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p1_06.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "1" means profile 1 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 12) - self.assertEqual(c.segment[1].ysiz, 12) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (3, 3)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 2, 'xysiz': (12, 12), 'xyosiz': (0, 0), + 'xytsiz': (3, 3), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) # sop @@ -2714,25 +2390,11 @@ class TestSuiteDump(unittest.TestCase): jfile = opj_data_file('input/conformance/p1_07.j2k') c = Jp2k(jfile).get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "1" means profile 1 - self.assertEqual(c.segment[1].rsiz, 2) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 12) - self.assertEqual(c.segment[1].ysiz, 12) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (4, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (12, 12)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (4, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(4, 1), (1, 1)]) + kwargs = {'rsiz': 2, 'xysiz': (12, 12), 'xyosiz': (4, 0), + 'xytsiz': (12, 12), 'xytosiz': (4, 0), 'bitdepth': (8, 8), + 'signed': (False, False), + 'xyrsiz': [(4, 1), (1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) # sop @@ -2817,13 +2479,8 @@ class TestSuiteDump(unittest.TestCase): ids = [box.box_id for box in jp2.box[3].box] self.assertEqual(ids, ['ihdr', 'colr']) - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + self.verifySignatureBox(jp2.box[0]) + self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) # XML box tags = [x.tag for x in jp2.box[2].xml.getroot()] @@ -2831,24 +2488,12 @@ class TestSuiteDump(unittest.TestCase): ['{http://www.jpeg.org/jpx/1.0/xml}' + 'GENERAL_CREATION_INFO']) - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 512) - self.assertEqual(jp2.box[3].box[0].width, 768) - self.assertEqual(jp2.box[3].box[0].num_components, 3) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) + ihdr = glymur.jp2box.ImageHeaderBox(512, 768, num_components=3) + self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[1].precedence, 0) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact ?? - self.assertEqual(jp2.box[3].box[1].colorspace, glymur.core.SRGB) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.SRGB, + approximation=1) + self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) # XML box tags = [x.tag for x in jp2.box[4].xml.getroot()] @@ -2866,32 +2511,15 @@ class TestSuiteDump(unittest.TestCase): ids = [box.box_id for box in jp2.box[2].box] self.assertEqual(ids, ['ihdr', 'colr', 'cdef']) - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + self.verifySignatureBox(jp2.box[0]) + self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + ihdr = glymur.jp2box.ImageHeaderBox(640, 480, num_components=3) + self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 640) - self.assertEqual(jp2.box[2].box[0].width, 480) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact?? - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.YCC) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.YCC, + approximation=1) + self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) # Jp2 Header # Channel Definition @@ -2913,32 +2541,16 @@ class TestSuiteDump(unittest.TestCase): ids = [box.box_id for box in jp2.box[2].box] self.assertEqual(ids, ['ihdr', 'colr']) - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + self.verifySignatureBox(jp2.box[0]) + self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + ihdr = glymur.jp2box.ImageHeaderBox(640, 480, num_components=3) + self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 640) - self.assertEqual(jp2.box[2].box[0].width, 480) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.YCC) + colr = glymur.jp2box.ColourSpecificationBox( + colorspace=glymur.core.YCC, + approximation=1) + self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) # sub-sampling codestream = jp2.get_codestream() @@ -2960,32 +2572,15 @@ class TestSuiteDump(unittest.TestCase): ids = [box.box_id for box in jp2.box[2].box] self.assertEqual(ids, ['ihdr', 'colr']) - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + self.verifySignatureBox(jp2.box[0]) + self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + ihdr = glymur.jp2box.ImageHeaderBox(512, 768) + self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 512) - self.assertEqual(jp2.box[2].box[0].width, 768) - self.assertEqual(jp2.box[2].box[0].num_components, 1) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact? - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.GREYSCALE) + colr = glymur.jp2box.ColourSpecificationBox( + colorspace=glymur.core.GREYSCALE, approximation=1) + self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) def test_NR_file5_dump(self): # Three 8-bit components in the ROMM-RGB colourspace, encapsulated in a @@ -3006,32 +2601,17 @@ class TestSuiteDump(unittest.TestCase): ids = [box.box_id for box in jp2.box[3].box] self.assertEqual(ids, ['ihdr', 'colr', 'colr']) - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + self.verifySignatureBox(jp2.box[0]) + self.verifyFileTypeBox(jp2.box[1], 'jpx ', ['jp2 ', 'jpx ', 'jpxb']) - # File type box. - self.assertEqual(jp2.box[1].brand, 'jpx ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + ihdr = glymur.jp2box.ImageHeaderBox(512, 768, num_components=3) + self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 512) - self.assertEqual(jp2.box[3].box[0].width, 768) - self.assertEqual(jp2.box[3].box[0].num_components, 3) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.RESTRICTED_ICC_PROFILE) # enumerated - self.assertEqual(jp2.box[3].box[1].precedence, 0) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact + colr = glymur.jp2box.ColourSpecificationBox( + method=glymur.core.RESTRICTED_ICC_PROFILE, + approximation=1, icc_profile=bytes([0] * 546)) + self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 546) - self.assertIsNone(jp2.box[3].box[1].colorspace) def test_NR_file6_dump(self): jfile = opj_data_file('input/conformance/file6.jp2') @@ -3043,34 +2623,17 @@ class TestSuiteDump(unittest.TestCase): ids = [box.box_id for box in jp2.box[2].box] self.assertEqual(ids, ['ihdr', 'colr']) - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + self.verifySignatureBox(jp2.box[0]) + self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + ihdr = glymur.jp2box.ImageHeaderBox(512, 768, bits_per_component=12) + self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 512) - self.assertEqual(jp2.box[2].box[0].width, 768) - self.assertEqual(jp2.box[2].box[0].num_components, 1) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 12) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact - self.assertIsNone(jp2.box[2].box[1].icc_profile) - self.assertEqual(jp2.box[2].box[1].colorspace, - glymur.core.GREYSCALE) + colr = glymur.jp2box.ColourSpecificationBox( + colorspace=glymur.core.GREYSCALE, + method=glymur.core.ENUMERATED_COLORSPACE, + approximation=1) + self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) def test_NR_file7_dump(self): # Three 16-bit components in the e-sRGB colourspace, encapsulated in a @@ -3087,32 +2650,21 @@ class TestSuiteDump(unittest.TestCase): ids = [box.box_id for box in jp2.box[3].box] self.assertEqual(ids, ['ihdr', 'colr', 'colr']) - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + self.verifySignatureBox(jp2.box[0]) # File type box. self.assertEqual(jp2.box[1].brand, 'jpx ') self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 640) - self.assertEqual(jp2.box[3].box[0].width, 480) - self.assertEqual(jp2.box[3].box[0].num_components, 3) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 16) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) + ihdr = glymur.jp2box.ImageHeaderBox(640, 480, + num_components=3, bits_per_component=16) + self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.RESTRICTED_ICC_PROFILE) - self.assertEqual(jp2.box[3].box[1].precedence, 0) - self.assertEqual(jp2.box[3].box[1].approximation, 1) + colr = glymur.jp2box.ColourSpecificationBox( + method=glymur.core.RESTRICTED_ICC_PROFILE, + approximation=1) + self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 13332) - self.assertIsNone(jp2.box[3].box[1].colorspace) def test_NR_file8_dump(self): # One 8-bit component in a gamma 1.8 space. The colourspace is @@ -3127,33 +2679,21 @@ class TestSuiteDump(unittest.TestCase): ids = [box.box_id for box in jp2.box[2].box] self.assertEqual(ids, ['ihdr', 'colr']) - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + self.verifySignatureBox(jp2.box[0]) # File type box. self.assertEqual(jp2.box[1].brand, 'jp2 ') self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') self.assertEqual(jp2.box[1].minor_version, 0) - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 400) - self.assertEqual(jp2.box[2].box[0].width, 700) - self.assertEqual(jp2.box[2].box[0].num_components, 1) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) + ihdr = glymur.jp2box.ImageHeaderBox(400, 700) + self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.RESTRICTED_ICC_PROFILE) # enumerated - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact + colr = glymur.jp2box.ColourSpecificationBox( + method=glymur.core.RESTRICTED_ICC_PROFILE, + approximation=1) + self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) self.assertEqual(jp2.box[2].box[1].icc_profile['Size'], 414) - self.assertIsNone(jp2.box[2].box[1].colorspace) # XML box tags = [x.tag for x in jp2.box[3].xml.getroot()] @@ -3180,24 +2720,15 @@ class TestSuiteDump(unittest.TestCase): ids = [box.box_id for box in jp2.box[2].box] self.assertEqual(ids, ['ihdr', 'pclr', 'cmap', 'colr']) - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + self.verifySignatureBox(jp2.box[0]) # File type box. self.assertEqual(jp2.box[1].brand, 'jp2 ') self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') self.assertEqual(jp2.box[1].minor_version, 0) - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 512) - self.assertEqual(jp2.box[2].box[0].width, 768) - self.assertEqual(jp2.box[2].box[0].num_components, 1) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) + ihdr = glymur.jp2box.ImageHeaderBox(512, 768) + self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) # Palette box. self.assertEqual(jp2.box[2].box[1].palette.shape, (256, 3)) @@ -3216,14 +2747,10 @@ class TestSuiteDump(unittest.TestCase): self.assertEqual(jp2.box[2].box[2].mapping_type, (1, 1, 1)) self.assertEqual(jp2.box[2].box[2].palette_index, (0, 1, 2)) - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[3].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[3].precedence, 0) - self.assertEqual(jp2.box[2].box[3].approximation, 1) # JPX exact - self.assertIsNone(jp2.box[2].box[3].icc_profile) - self.assertEqual(jp2.box[2].box[3].colorspace, glymur.core.SRGB) + colr = glymur.jp2box.ColourSpecificationBox( + colorspace=glymur.core.SRGB, + approximation=1) + self.verifyColourSpecificationBox(jp2.box[2].box[3], colr) def test_NR_00042_j2k_dump(self): # Profile 3. @@ -3231,26 +2758,12 @@ class TestSuiteDump(unittest.TestCase): jp2k = Jp2k(jfile) c = jp2k.get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "3" means profile 3 - self.assertEqual(c.segment[1].rsiz, 3) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1920) - self.assertEqual(c.segment[1].ysiz, 1080) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1920, 1080)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12, 12, 12)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 3, 'xysiz': (1920, 1080), 'xyosiz': (0, 0), + 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), + 'bitdepth': (12, 12, 12), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -3379,30 +2892,15 @@ class TestSuiteDump(unittest.TestCase): self.assertEqual(c.segment[-1].marker_id, 'EOC') def test_Bretagne2_j2k_dump(self): - # Profile 3. jfile = opj_data_file('input/nonregression/Bretagne2.j2k') jp2k = Jp2k(jfile) c = jp2k.get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "3" means profile 3 - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 2592) - self.assertEqual(c.segment[1].ysiz, 1944) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (640, 480)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (2592, 1944), 'xyosiz': (0, 0), + 'xytsiz': (640, 480), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -3441,25 +2939,11 @@ class TestSuiteDump(unittest.TestCase): jp2k = Jp2k(jfile) c = jp2k.get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 512) - self.assertEqual(c.segment[1].ysiz, 512) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (512, 512)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) + kwargs = {'rsiz': 0, 'xysiz': (512, 512), 'xyosiz': (0, 0), + 'xytsiz': (512, 512), 'xytosiz': (0, 0), 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -3494,25 +2978,11 @@ class TestSuiteDump(unittest.TestCase): jp2k = Jp2k(jfile) c = jp2k.get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 512) - self.assertEqual(c.segment[1].ysiz, 512) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (512, 512)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) + kwargs = {'rsiz': 0, 'xysiz': (512, 512), 'xyosiz': (0, 0), + 'xytsiz': (512, 512), 'xytosiz': (0, 0), 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -3555,26 +3025,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'COD', 'QCD'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1420) - self.assertEqual(c.segment[1].ysiz, 1416) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1420, 1416)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) + kwargs = {'rsiz': 0, 'xysiz': (1420, 1416), 'xyosiz': (0, 0), + 'xytsiz': (1420, 1416), 'xytosiz': (0, 0), 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -3617,25 +3072,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 512) - self.assertEqual(c.segment[1].ysiz, 614) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (512, 614)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) + kwargs = {'rsiz': 0, 'xysiz': (512, 614), 'xyosiz': (0, 0), + 'xytsiz': (512, 614), 'xytosiz': (0, 0), 'bitdepth': (12,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -3687,25 +3128,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'CME'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 256) - self.assertEqual(c.segment[1].ysiz, 256) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (256, 256)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) + kwargs = {'rsiz': 0, 'xysiz': (256, 256), 'xyosiz': (0, 0), + 'xytsiz': (256, 256), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -3761,26 +3188,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'COD', 'QCD'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1420) - self.assertEqual(c.segment[1].ysiz, 1416) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1420, 1416)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) + kwargs = {'rsiz': 0, 'xysiz': (1420, 1416), 'xyosiz': (0, 0), + 'xytsiz': (1420, 1416), 'xytosiz': (0, 0), 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -3822,25 +3234,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 256) - self.assertEqual(c.segment[1].ysiz, 256) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (256, 256)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (True, True, True)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (256, 256), 'xyosiz': (0, 0), + 'xytsiz': (256, 256), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (True, True, True), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -3886,26 +3284,11 @@ class TestSuiteDump(unittest.TestCase): jp2k = Jp2k(jfile) c = jp2k.get_codestream() - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 2048) - self.assertEqual(c.segment[1].ysiz, 2500) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (2048, 2500)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) + kwargs = {'rsiz': 0, 'xysiz': (2048, 2500), 'xyosiz': (0, 0), + 'xytsiz': (2048, 2500), 'xytosiz': (0, 0), 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -3970,26 +3353,11 @@ class TestSuiteDump(unittest.TestCase): jp2k = Jp2k(jfile) c = jp2k.get_codestream() - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1420) - self.assertEqual(c.segment[1].ysiz, 1416) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1420, 1416)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) + kwargs = {'rsiz': 0, 'xysiz': (1420, 1416), 'xyosiz': (0, 0), + 'xytsiz': (1420, 1416), 'xytosiz': (0, 0), 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -4029,26 +3397,11 @@ class TestSuiteDump(unittest.TestCase): jp2k = Jp2k(jfile) c = jp2k.get_codestream() - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1920) - self.assertEqual(c.segment[1].ysiz, 1080) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1920, 1080)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (1920, 1080), 'xyosiz': (0, 0), + 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -4086,26 +3439,11 @@ class TestSuiteDump(unittest.TestCase): jp2k = Jp2k(jfile) c = jp2k.get_codestream() - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1920) - self.assertEqual(c.segment[1].ysiz, 1080) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1920, 1080)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (1920, 1080), 'xyosiz': (0, 0), + 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -4143,26 +3481,11 @@ class TestSuiteDump(unittest.TestCase): jp2k = Jp2k(jfile) c = jp2k.get_codestream() - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1920) - self.assertEqual(c.segment[1].ysiz, 1080) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1920, 1080)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (1920, 1080), 'xyosiz': (0, 0), + 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -4204,25 +3527,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'COD', 'QCD'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 117) - self.assertEqual(c.segment[1].ysiz, 117) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (117, 117)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 4) + kwargs = {'rsiz': 0, 'xysiz': (117, 117), 'xyosiz': (0, 0), + 'xytsiz': (117, 117), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8, 8), + 'signed': (False, False, False, False), + 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -4264,25 +3573,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'COD', 'QCD'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 117) - self.assertEqual(c.segment[1].ysiz, 117) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (117, 117)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 4) + kwargs = {'rsiz': 0, 'xysiz': (117, 117), 'xyosiz': (0, 0), + 'xytsiz': (117, 117), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8, 8), + 'signed': (False, False, False, False), + 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -4324,25 +3619,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 512) - self.assertEqual(c.segment[1].ysiz, 512) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (512, 512)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (True,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) + kwargs = {'rsiz': 0, 'xysiz': (512, 512), 'xyosiz': (0, 0), + 'xytsiz': (512, 512), 'xytosiz': (0, 0), 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -4392,26 +3673,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1024) - self.assertEqual(c.segment[1].ysiz, 1024) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1024, 1024)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) + kwargs = {'rsiz': 0, 'xysiz': (1024, 1024), 'xyosiz': (0, 0), + 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), 'bitdepth': (12,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -4461,26 +3727,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'COD', 'QCD'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1800) - self.assertEqual(c.segment[1].ysiz, 1800) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1800, 1800)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) + kwargs = {'rsiz': 0, 'xysiz': (1800, 1800), 'xyosiz': (0, 0), + 'xytsiz': (1800, 1800), 'xytosiz': (0, 0), 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -4523,26 +3774,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'COD', 'QCD'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 1800) - self.assertEqual(c.segment[1].ysiz, 1800) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (1800, 1800)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 1) + kwargs = {'rsiz': 0, 'xysiz': (1800, 1800), 'xyosiz': (0, 0), + 'xytsiz': (1800, 1800), 'xytosiz': (0, 0), 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -4585,26 +3821,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 2048) - self.assertEqual(c.segment[1].ysiz, 1556) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), - (2048, 1556)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (12, 12, 12)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (2048, 1556), 'xyosiz': (0, 0), + 'xytsiz': (2048, 1556), 'xytosiz': (0, 0), 'bitdepth': (12, 12, 12), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -4657,8 +3878,7 @@ class TestSuiteDump(unittest.TestCase): ids = [box.box_id for box in jp2.box[3].box] self.assertEqual(ids, ['ihdr', 'colr', 'pclr', 'cmap']) - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + self.verifySignatureBox(jp2.box[0]) # File type box. self.assertEqual(jp2.box[1].brand, 'jp2 ') @@ -4671,24 +3891,13 @@ class TestSuiteDump(unittest.TestCase): # unrestricted jpeg 2000 part 1 self.assertTrue(5 in jp2.box[2].standard_flag) - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 203) - self.assertEqual(jp2.box[3].box[0].width, 479) - self.assertEqual(jp2.box[3].box[0].num_components, 1) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) + ihdr = glymur.jp2box.ImageHeaderBox(203, 479, colorspace_unknown=True) + self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[1].precedence, 2) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # exact - self.assertEqual(jp2.box[3].box[1].colorspace, glymur.core.SRGB) + colr = glymur.jp2box.ColourSpecificationBox( + colorspace=glymur.core.SRGB, + approximation=1, precedence=2) + self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) # Jp2 Header # Palette box. @@ -4706,25 +3915,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'COD', 'QCD'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 479) - self.assertEqual(c.segment[1].ysiz, 203) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (256, 203)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) + kwargs = {'rsiz': 0, 'xysiz': (479, 203), 'xyosiz': (0, 0), + 'xytsiz': (256, 203), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -4771,32 +3966,19 @@ class TestSuiteDump(unittest.TestCase): ids = [box.box_id for box in jp2.box[2].box] self.assertEqual(ids, ['ihdr', 'colr']) - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + self.verifySignatureBox(jp2.box[0]) # File type box. self.assertEqual(jp2.box[1].brand, 'jp2 ') self.assertEqual(jp2.box[1].minor_version, 0) self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 200) - self.assertEqual(jp2.box[2].box[0].width, 200) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, True) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) + ihdr = glymur.jp2box.ImageHeaderBox(200, 200, + num_components=3, colorspace_unknown=True) + self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.SRGB) + self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) # Skip the 4th box, it is uknown. @@ -4806,25 +3988,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'CME'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 200) - self.assertEqual(c.segment[1].ysiz, 200) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (200, 200)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (200, 200), 'xyosiz': (0, 0), + 'xytsiz': (200, 200), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -4865,8 +4033,7 @@ class TestSuiteDump(unittest.TestCase): ids = [box.box_id for box in jp2.box[3].box] self.assertEqual(ids, ['ihdr', 'colr']) - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + self.verifySignatureBox(jp2.box[0]) # File type box. self.assertEqual(jp2.box[1].brand, 'jp2 ') @@ -4879,24 +4046,14 @@ class TestSuiteDump(unittest.TestCase): # unrestricted jpeg 2000 part 1 self.assertTrue(5 in jp2.box[2].standard_flag) - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 326) - self.assertEqual(jp2.box[3].box[0].width, 431) - self.assertEqual(jp2.box[3].box[0].num_components, 3) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) + ihdr = glymur.jp2box.ImageHeaderBox(326, 431, + num_components=3, colorspace_unknown=True) + self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[1].precedence, 2) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[3].box[1].colorspace, glymur.core.SRGB) + colr = glymur.jp2box.ColourSpecificationBox( + colorspace=glymur.core.SRGB, + approximation=1, precedence=2) + self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) c = jp2.box[4].main_header @@ -4904,25 +4061,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'COD', 'QCD'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 431) - self.assertEqual(c.segment[1].ysiz, 326) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (256, 256)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (431, 326), 'xyosiz': (0, 0), + 'xytsiz': (256, 256), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -4968,32 +4111,20 @@ class TestSuiteDump(unittest.TestCase): ids = [box.box_id for box in jp2.box[2].box[3].box] self.assertEqual(ids, ['resd']) - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + self.verifySignatureBox(jp2.box[0]) # File type box. self.assertEqual(jp2.box[1].brand, 'jp2 ') self.assertEqual(jp2.box[1].minor_version, 0) self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 135) - self.assertEqual(jp2.box[2].box[0].width, 135) - self.assertEqual(jp2.box[2].box[0].num_components, 2) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, True) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) + ihdr = glymur.jp2box.ImageHeaderBox(135, 135, num_components=2, + colorspace_unknown=True) + self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.GREYSCALE) + colr = glymur.jp2box.ColourSpecificationBox( + colorspace=glymur.core.GREYSCALE) + self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) # Jp2 Header # Channel Definition @@ -5007,25 +4138,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 135) - self.assertEqual(c.segment[1].ysiz, 135) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (135, 135)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 2) + kwargs = {'rsiz': 0, 'xysiz': (135, 135), 'xyosiz': (0, 0), + 'xytsiz': (135, 135), 'xytosiz': (0, 0), 'bitdepth': (8, 8), + 'signed': (False, False), + 'xyrsiz': [(1, 1), (1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -5078,8 +4195,7 @@ class TestSuiteDump(unittest.TestCase): ids = [box.box_id for box in jp2.box[3].box] self.assertEqual(ids, ['ihdr', 'colr', 'pclr', 'cmap']) - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + self.verifySignatureBox(jp2.box[0]) # File type box. self.assertEqual(jp2.box[1].brand, 'jp2 ') @@ -5092,24 +4208,15 @@ class TestSuiteDump(unittest.TestCase): # unrestricted jpeg 2000 part 1 self.assertTrue(5 in jp2.box[2].standard_flag) - # Jp2 Header - # Image header - self.assertEqual(jp2.box[3].box[0].height, 46) - self.assertEqual(jp2.box[3].box[0].width, 124) - self.assertEqual(jp2.box[3].box[0].num_components, 1) - self.assertEqual(jp2.box[3].box[0].bits_per_component, 4) - self.assertEqual(jp2.box[3].box[0].signed, False) - self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[3].box[0].colorspace_unknown, True) - self.assertEqual(jp2.box[3].box[0].ip_provided, False) + ihdr = glymur.jp2box.ImageHeaderBox(46, 124, bits_per_component=4, + colorspace_unknown=True) + self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[3].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[3].box[1].precedence, 2) - self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact - self.assertEqual(jp2.box[3].box[1].colorspace, glymur.core.SRGB) + colr = glymur.jp2box.ColourSpecificationBox( + colorspace=glymur.core.SRGB, + method=glymur.core.ENUMERATED_COLORSPACE, + approximation=1, precedence=2) + self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) # Jp2 Header # Palette box. @@ -5128,25 +4235,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'COD', 'QCD'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 124) - self.assertEqual(c.segment[1].ysiz, 46) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (124, 46)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (4,)) - # signed - self.assertEqual(c.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)]) + kwargs = {'rsiz': 0, 'xysiz': (124, 46), 'xyosiz': (0, 0), + 'xytsiz': (124, 46), 'xytosiz': (0, 0), 'bitdepth': (4,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -5189,32 +4282,18 @@ class TestSuiteDump(unittest.TestCase): ids = [box.box_id for box in jp2.box[2].box] self.assertEqual(ids, ['ihdr', 'colr']) - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + self.verifySignatureBox(jp2.box[0]) # File type box. self.assertEqual(jp2.box[1].brand, 'jp2 ') self.assertEqual(jp2.box[1].minor_version, 0) self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 576) - self.assertEqual(jp2.box[2].box[0].width, 766) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) + ihdr = glymur.jp2box.ImageHeaderBox(576, 766, num_components=3) + self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.YCC) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.YCC) + self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) c = jp2.box[3].main_header @@ -5222,25 +4301,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'COD', 'QCD', 'POD'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 766) - self.assertEqual(c.segment[1].ysiz, 576) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (766, 576)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1), (2, 1), (2, 1)]) + kwargs = {'rsiz': 0, 'xysiz': (766, 576), 'xyosiz': (0, 0), + 'xytsiz': (766, 576), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 2, 2), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -5296,24 +4361,15 @@ class TestSuiteDump(unittest.TestCase): ids = [box.box_id for box in jp2.box[2].box] self.assertEqual(ids, ['ihdr', 'colr']) - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + self.verifySignatureBox(jp2.box[0]) # File type box. self.assertEqual(jp2.box[1].brand, 'jp2 ') self.assertEqual(jp2.box[1].minor_version, 0) self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 117) - self.assertEqual(jp2.box[2].box[0].width, 117) - self.assertEqual(jp2.box[2].box[0].num_components, 4) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) + ihdr = glymur.jp2box.ImageHeaderBox(117, 117, num_components=4) + self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) # Jp2 Header # Colour specification @@ -5330,25 +4386,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'COD', 'QCD'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 117) - self.assertEqual(c.segment[1].ysiz, 117) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (117, 117)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 4) + kwargs = {'rsiz': 0, 'xysiz': (117, 117), 'xyosiz': (0, 0), + 'xytsiz': (117, 117), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8, 8), + 'signed': (False, False, False, False), + 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -5395,24 +4437,15 @@ class TestSuiteDump(unittest.TestCase): ids = [box.box_id for box in jp2.box[2].box] self.assertEqual(ids, ['ihdr', 'colr']) - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + self.verifySignatureBox(jp2.box[0]) # File type box. self.assertEqual(jp2.box[1].brand, 'jp2 ') self.assertEqual(jp2.box[1].minor_version, 0) self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 117) - self.assertEqual(jp2.box[2].box[0].width, 117) - self.assertEqual(jp2.box[2].box[0].num_components, 4) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) + ihdr = glymur.jp2box.ImageHeaderBox(117, 117, num_components=4) + self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) # Jp2 Header # Colour specification @@ -5429,25 +4462,11 @@ class TestSuiteDump(unittest.TestCase): expected = ['SOC', 'SIZ', 'COD', 'QCD'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 117) - self.assertEqual(c.segment[1].ysiz, 117) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (117, 117)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 4) + kwargs = {'rsiz': 0, 'xysiz': (117, 117), 'xyosiz': (0, 0), + 'xytsiz': (117, 117), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8, 8), + 'signed': (False, False, False, False), + 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop From 2afb7590dcd9e8fcf57c9a5dcd790cd0f7f71497 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 16 Sep 2014 10:50:48 -0400 Subject: [PATCH 247/326] reorganized test infrastructure, only runs on 3.x Three tests skipped due to unexplained failures. Several tests had to be skipped where the python3-six version was earlier than 1.7.0. Tested on 32-bit Fedora, Mac 10.6, Linux Mint 32 and 64-bit, Anaconda3. --- glymur/test/fixtures.py | 12 + glymur/test/test_callbacks.py | 6 +- glymur/test/test_codestream.py | 4 - glymur/test/test_config.py | 9 +- glymur/test/test_glymur_warnings.py | 10 +- glymur/test/test_icc.py | 13 +- glymur/test/test_jp2box.py | 57 +- glymur/test/test_jp2box_jpx.py | 25 +- glymur/test/test_jp2box_uuid.py | 95 ++- glymur/test/test_jp2box_xml.py | 37 +- glymur/test/test_jp2k.py | 60 +- glymur/test/test_opj_suite.py | 265 +++---- glymur/test/test_opj_suite_2p1.py | 23 +- glymur/test/test_opj_suite_dump.py | 1011 ++++++++++++++------------- glymur/test/test_opj_suite_neg.py | 6 +- glymur/test/test_opj_suite_write.py | 125 ++-- glymur/test/test_printing.py | 250 +++---- 17 files changed, 1006 insertions(+), 1002 deletions(-) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index d8b28e9..9f9301b 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -8,9 +8,21 @@ import textwrap import warnings import numpy as np +import six import glymur +# Some versions of "six" on python3 cause problems when verifying warnings. +# Only use when the version is 1.7 or higher. +# And moreover, we only test using the 3.x infrastructure, never on 2.x. +WARNING_INFRASTRUCTURE_ISSUE = False +WARNING_INFRASTRUCTURE_MSG = "" +if sys.hexversion < 0x03000000: + WARNING_INFRASTRUCTURE_ISSUE = True + WARNING_INFRASTRUCTURE_MSG = "3.x warning infrastructure only" +elif re.match('1.[0-6]', six.__version__) is not None: + WARNING_INFRASTRUCTURE_ISSUE = True + WARNING_INFRASTRUCTURE_MSG = "Cannot use with this version of six" # The Python XMP Toolkit may be used for XMP UUIDs, but only if available and # if the version is at least 2.0.0. diff --git a/glymur/test/test_callbacks.py b/glymur/test/test_callbacks.py index 5b6bd74..e87b3af 100644 --- a/glymur/test/test_callbacks.py +++ b/glymur/test/test_callbacks.py @@ -11,7 +11,6 @@ import os import re import sys import tempfile -import warnings import unittest @@ -24,6 +23,7 @@ else: import glymur +from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG @unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None, "Missing openjp2 library.") @@ -37,12 +37,12 @@ class TestCallbacks(unittest.TestCase): def tearDown(self): pass + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_info_callback_on_write(self): """Verify messages printed when writing an image in verbose mode.""" j = glymur.Jp2k(self.jp2file) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") + with self.assertWarns(UserWarning): tiledata = j.read(tile=0) with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: j = glymur.Jp2k(tfile.name, 'wb') diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py index abea7e2..e520713 100644 --- a/glymur/test/test_codestream.py +++ b/glymur/test/test_codestream.py @@ -5,15 +5,11 @@ 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 - import os import struct import sys import tempfile import unittest -import warnings from glymur import Jp2k import glymur diff --git a/glymur/test/test_config.py b/glymur/test/test_config.py index b8c97ae..7196d35 100644 --- a/glymur/test/test_config.py +++ b/glymur/test/test_config.py @@ -16,7 +16,6 @@ import os import sys import tempfile import unittest -import warnings if sys.hexversion <= 0x03030000: from mock import patch @@ -26,6 +25,7 @@ else: import glymur from glymur import Jp2k +from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG @unittest.skipIf(sys.hexversion < 0x03020000, "TemporaryDirectory introduced in 3.2.") @@ -70,6 +70,7 @@ class TestSuite(unittest.TestCase): imp.reload(glymur.lib.openjp2) Jp2k(self.jp2file) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_xdg_env_config_file_is_bad(self): """A non-existant library location should be rejected.""" with tempfile.TemporaryDirectory() as tdir: @@ -84,11 +85,9 @@ class TestSuite(unittest.TestCase): with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}): # Misconfigured new configuration file should # be rejected. - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') + regex = 'could not be loaded' + with self.assertWarnsRegex(UserWarning, regex): imp.reload(glymur.lib.openjp2) - self.assertTrue(issubclass(w[0].category,UserWarning)) - self.assertTrue('could not be loaded' in str(w[0].message)) @unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None and diff --git a/glymur/test/test_glymur_warnings.py b/glymur/test/test_glymur_warnings.py index 88f1d83..ceb8319 100644 --- a/glymur/test/test_glymur_warnings.py +++ b/glymur/test/test_glymur_warnings.py @@ -5,13 +5,13 @@ Test suite for warnings issued by glymur. # unittest doesn't work well with R0904. # pylint: disable=R0904 +import platform import os import re import struct import sys import tempfile import unittest -import warnings import six @@ -19,13 +19,13 @@ from glymur import Jp2k import glymur from .fixtures import opj_data_file, OPJ_DATA_ROOT +from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG -@unittest.skipIf(sys.hexversion < 0x03030000, - "assertWarn methods introduced in 3.x") -@unittest.skipIf(re.match('1.[0-6]', six.__version__) is not None, - "Problem with earlier versions of six on python3") +@unittest.skipIf(sys.hexversion < 0x03040000 and platform.system() == 'Linux', + "inexplicable failures on 3.3 and linux") @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") +@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) class TestWarnings(unittest.TestCase): """Test suite for warnings issued by glymur.""" diff --git a/glymur/test/test_icc.py b/glymur/test/test_icc.py index 734d557..c6b63e8 100644 --- a/glymur/test/test_icc.py +++ b/glymur/test/test_icc.py @@ -9,14 +9,15 @@ import datetime import os import sys import unittest -import warnings import numpy as np from glymur import Jp2k from .fixtures import OPJ_DATA_ROOT, opj_data_file +from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG +@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") class TestICC(unittest.TestCase): @@ -31,9 +32,8 @@ class TestICC(unittest.TestCase): def test_file5(self): """basic ICC profile""" filename = opj_data_file('input/conformance/file5.jp2') - with warnings.catch_warnings(): + with self.assertWarns(UserWarning): # The file has a bad compatibility list entry. Not important here. - warnings.simplefilter("ignore") j = Jp2k(filename) profile = j.box[3].box[1].icc_profile self.assertEqual(profile['Size'], 546) @@ -62,18 +62,15 @@ class TestICC(unittest.TestCase): self.assertEqual(profile['Creator'], 'JPEG') - @unittest.skipIf(sys.platform.startswith('linux'), 'Failing on linux') def test_invalid_profile_header(self): """invalid ICC header data should cause UserWarning""" jfile = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2') # assertWarns in Python 3.3 (python2.7/pylint issue) # pylint: disable=E1101 - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') + regex = 'ICC profile header is corrupt' + with self.assertWarnsRegex(UserWarning, regex): Jp2k(jfile) - self.assertTrue(issubclass(w[0].category,UserWarning)) - self.assertTrue('ICC profile header is corrupt' in str(w[0].message)) if __name__ == "__main__": unittest.main() diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index e0f12e2..3dcf704 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -23,7 +23,6 @@ import tempfile import uuid from uuid import UUID import unittest -import warnings import lxml.etree as ET import numpy as np @@ -37,6 +36,7 @@ from glymur.core import COLOR, OPACITY from glymur.core import RED, GREEN, BLUE, GREY, WHOLE_IMAGE from .fixtures import opj_data_file +from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG try: FORMAT_CORPUS_DATA_ROOT = os.environ['FORMAT_CORPUS_DATA_ROOT'] @@ -357,6 +357,7 @@ class TestChannelDefinition(unittest.TestCase): with self.assertRaises((IOError, OSError)): j2k.wrap(tfile.name, boxes=boxes) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) 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 @@ -365,25 +366,20 @@ class TestChannelDefinition(unittest.TestCase): channel_type = (COLOR, COLOR, 3) association = (RED, GREEN, BLUE) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') + with self.assertWarns(UserWarning): glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type, association=association) - self.assertEqual(len(w), 1) - self.assertTrue(issubclass(w[0].category, UserWarning)) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_wrong_lengths(self): """Should reject if not all of index, channel_type, association the same length. """ channel_type = (COLOR, COLOR) association = (RED, GREEN, BLUE) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') + with self.assertWarns(UserWarning): glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type, association=association) - self.assertEqual(len(w), 1) - self.assertTrue(issubclass(w[0].category, UserWarning)) class TestFileTypeBox(unittest.TestCase): @@ -395,20 +391,20 @@ class TestFileTypeBox(unittest.TestCase): def tearDown(self): pass + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_brand_unknown(self): """A ftyp box brand must be 'jp2 ' or 'jpx '.""" - with warnings.catch_warnings(): - warnings.simplefilter("ignore") + with self.assertWarns(UserWarning): ftyp = glymur.jp2box.FileTypeBox(brand='jp3') with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: ftyp.write(tfile) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_cl_entry_unknown(self): """A ftyp box cl list can only contain 'jp2 ', 'jpx ', or 'jpxb'.""" - with warnings.catch_warnings(): + with self.assertWarns(UserWarning): # Bad compatibility list item. - warnings.simplefilter("ignore") ftyp = glymur.jp2box.FileTypeBox(compatibility_list=['jp3']) with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: @@ -479,41 +475,34 @@ class TestColourSpecificationBox(unittest.TestCase): self.assertEqual(colr.colorspace, glymur.core.SRGB) self.assertIsNone(colr.icc_profile) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_colr_with_cspace_and_icc(self): """Colour specification boxes can't have both.""" - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') + regex = 'Colorspace and icc_profile cannot both be set' + with self.assertWarnsRegex(UserWarning, regex): colorspace = glymur.core.SRGB rawb = b'\x01\x02\x03\x04' glymur.jp2box.ColourSpecificationBox(colorspace=colorspace, icc_profile=rawb) - self.assertTrue(issubclass(w[0].category,UserWarning)) - msg = 'Colorspace and icc_profile cannot both be set' - self.assertTrue(msg in str(w[0].message)) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_colr_with_bad_method(self): """colr must have a valid method field""" colorspace = glymur.core.SRGB method = -1 - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') + regex = 'Invalid method' + with self.assertWarnsRegex(UserWarning, regex): glymur.jp2box.ColourSpecificationBox(colorspace=colorspace, method=method) - self.assertTrue(issubclass(w[0].category,UserWarning)) - msg = 'Invalid method' - self.assertTrue(msg in str(w[0].message)) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_colr_with_bad_approx(self): """colr should have a valid approximation field""" colorspace = glymur.core.SRGB approx = -1 - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') + with self.assertWarnsRegex(UserWarning, 'Invalid approximation'): glymur.jp2box.ColourSpecificationBox(colorspace=colorspace, approximation=approx) - self.assertTrue(issubclass(w[0].category,UserWarning)) - msg = 'Invalid approximation' - self.assertTrue(msg in str(w[0].message)) def test_colr_with_bad_color(self): """colr must have a valid color, strange as though that may sound.""" @@ -537,29 +526,25 @@ class TestPaletteBox(unittest.TestCase): def tearDown(self): pass + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_mismatched_bitdepth_signed(self): """bitdepth and signed arguments must have equal length""" palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint8) bps = (8, 8, 8) signed = (False, False) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') + with self.assertWarns(UserWarning): pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps, signed=signed) - self.assertEqual(len(w), 1) - self.assertTrue(issubclass(w[0].category, UserWarning)) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_mismatched_signed_palette(self): """bitdepth and signed arguments must have equal length""" palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint8) bps = (8, 8, 8, 8) signed = (False, False, False, False) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') + with self.assertWarns(UserWarning): pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps, signed=signed) - self.assertEqual(len(w), 1) - self.assertTrue(issubclass(w[0].category, UserWarning)) def test_writing_with_different_bitdepths(self): """Bitdepths must be the same when writing.""" diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 3a5d42e..ce676f3 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -9,7 +9,6 @@ import struct import sys import tempfile import unittest -import warnings import lxml.etree as ET @@ -19,6 +18,8 @@ from glymur.jp2box import DataEntryURLBox, FileTypeBox, JPEG2000SignatureBox from glymur.jp2box import DataReferenceBox, FragmentListBox, FragmentTableBox from glymur.jp2box import ColourSpecificationBox +from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") class TestJPXWrap(unittest.TestCase): """Test suite for wrapping JPX files.""" @@ -305,17 +306,15 @@ class TestJPXWrap(unittest.TestCase): with self.assertRaises(IOError): jp2.wrap(tfile.name, boxes=boxes) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_deurl_child_of_dtbl(self): """Data reference boxes can only contain data entry url boxes.""" jp2 = Jp2k(self.jp2file) boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] ftyp = glymur.jp2box.FileTypeBox() - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') + with self.assertWarns(UserWarning): dref = glymur.jp2box.DataReferenceBox([ftyp]) - self.assertEqual(len(w), 1) - self.assertTrue(issubclass(w[0].category, UserWarning)) # Try to get around it by appending the ftyp box after creation. dref = glymur.jp2box.DataReferenceBox() @@ -433,37 +432,37 @@ class TestJPX(unittest.TestCase): def tearDown(self): pass + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_flst_lens_not_the_same(self): """A fragment list box items must be the same length.""" offset = [89] length = [1132288] reference = [0, 0] - with warnings.catch_warnings(): - warnings.simplefilter("ignore") + with self.assertWarns(UserWarning): flst = glymur.jp2box.FragmentListBox(offset, length, reference) with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: flst.write(tfile) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_flst_offsets_not_positive(self): """A fragment list box offsets must be positive.""" offset = [0] length = [1132288] reference = [0] - with warnings.catch_warnings(): - warnings.simplefilter("ignore") + with self.assertWarns(UserWarning): flst = glymur.jp2box.FragmentListBox(offset, length, reference) with self.assertRaises((IOError, OSError)): with tempfile.TemporaryFile() as tfile: flst.write(tfile) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_flst_lengths_not_positive(self): """A fragment list box lengths must be positive.""" offset = [89] length = [0] reference = [0] - with warnings.catch_warnings(): - warnings.simplefilter("ignore") + with self.assertWarns(UserWarning): flst = glymur.jp2box.FragmentListBox(offset, length, reference) with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: @@ -471,9 +470,7 @@ class TestJPX(unittest.TestCase): def test_ftbl_boxes_empty(self): """A fragment table box must have at least one child box.""" - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - ftbl = glymur.jp2box.FragmentTableBox() + ftbl = glymur.jp2box.FragmentTableBox() with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: ftbl.write(tfile) diff --git a/glymur/test/test_jp2box_uuid.py b/glymur/test/test_jp2box_uuid.py index 44808bb..8ec35a2 100644 --- a/glymur/test/test_jp2box_uuid.py +++ b/glymur/test/test_jp2box_uuid.py @@ -17,7 +17,6 @@ import struct import sys import tempfile import uuid -import warnings if sys.hexversion < 0x02070000: import unittest2 as unittest @@ -37,6 +36,8 @@ else: import lxml.etree from .fixtures import HAS_PYTHON_XMP_TOOLKIT, OPJ_DATA_ROOT +from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG + if HAS_PYTHON_XMP_TOOLKIT: from libxmp import XMPMeta @@ -46,8 +47,8 @@ from .fixtures import OPJ_DATA_ROOT, opj_data_file, SimpleRDF @unittest.skipIf(os.name == "nt", "Unexplained failure on windows") -class TestUUIDXMP(unittest.TestCase): - """Tests for UUIDs of XMP type.""" +class TestSuite(unittest.TestCase): + """Tests for XMP, Exif UUIDs.""" def setUp(self): self.jp2file = glymur.data.nemo() @@ -55,7 +56,7 @@ class TestUUIDXMP(unittest.TestCase): def tearDown(self): pass - def test_append(self): + def test_append_xmp_uuid(self): """Should be able to append an XMP UUID box.""" the_uuid = uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac') raw_data = SimpleRDF.encode('utf-8') @@ -75,16 +76,42 @@ class TestUUIDXMP(unittest.TestCase): self.assertTrue(isinstance(jp2.box[-1].data, lxml.etree._ElementTree)) + def test_big_endian_exif(self): + """Verify read of Exif big-endian IFD.""" + with tempfile.NamedTemporaryFile(suffix='.jp2', mode='wb') as tfile: + + with open(self.jp2file, 'rb') as ifptr: + tfile.write(ifptr.read()) + + # Write L, T, UUID identifier. + tfile.write(struct.pack('>I4s', 52, b'uuid')) + tfile.write(b'JpgTiffExif->JP2') + + tfile.write(b'Exif\x00\x00') + xbuffer = struct.pack('>BBHI', 77, 77, 42, 8) + tfile.write(xbuffer) + + # We will write just a single tag. + tfile.write(struct.pack('>H', 1)) + + # The "Make" tag is tag no. 271. + tfile.write(struct.pack('>HHI4s', 271, 2, 3, b'HTC\x00')) + tfile.flush() + + jp2 = glymur.Jp2k(tfile.name) + self.assertEqual(jp2.box[-1].data['Make'], "HTC") + +@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @unittest.skipIf(os.name == "nt", "Unexplained failure on windows") -class TestUUIDExif(unittest.TestCase): - """Tests for UUIDs of Exif type.""" +class TestSuiteWarns(unittest.TestCase): + """Tests for XMP, Exif UUIDs, issues warnings.""" def setUp(self): self.jp2file = glymur.data.nemo() def tearDown(self): pass - + def test_unrecognized_exif_tag(self): """Verify warning in case of unrecognized tag.""" with tempfile.NamedTemporaryFile(suffix='.jp2', mode='wb') as tfile: @@ -107,12 +134,8 @@ class TestUUIDExif(unittest.TestCase): tfile.write(struct.pack('I4s', 52, b'uuid')) - tfile.write(b'JpgTiffExif->JP2') - - tfile.write(b'Exif\x00\x00') - xbuffer = struct.pack('>BBHI', 77, 77, 42, 8) - tfile.write(xbuffer) - - # We will write just a single tag. - tfile.write(struct.pack('>H', 1)) - - # The "Make" tag is tag no. 271. - tfile.write(struct.pack('>HHI4s', 271, 2, 3, b'HTC\x00')) - tfile.flush() - - jp2 = glymur.Jp2k(tfile.name) - self.assertEqual(jp2.box[-1].data['Make'], "HTC") + self.assertEqual(jp2.box[-1].box_id, 'uuid') if __name__ == "__main__": unittest.main() diff --git a/glymur/test/test_jp2box_xml.py b/glymur/test/test_jp2box_xml.py index 91de04a..eeb1fa8 100644 --- a/glymur/test/test_jp2box_xml.py +++ b/glymur/test/test_jp2box_xml.py @@ -15,11 +15,11 @@ Test suite specifically targeting JP2 box layout. # pylint: disable=W0613 import os +import re import struct import sys import tempfile import unittest -import warnings if sys.hexversion < 0x03000000: from StringIO import StringIO @@ -40,6 +40,7 @@ from glymur.jp2box import FileTypeBox, ImageHeaderBox, JP2HeaderBox from glymur.jp2box import JPEG2000SignatureBox from .fixtures import OPJ_DATA_ROOT, opj_data_file +from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") class TestXML(unittest.TestCase): @@ -166,7 +167,6 @@ class TestXML(unittest.TestCase): -@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") class TestJp2kBadXmlFile(unittest.TestCase): """Test suite for bad XML box situations""" @@ -207,14 +207,11 @@ class TestJp2kBadXmlFile(unittest.TestCase): def tearDown(self): pass + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_invalid_xml_box(self): """Should be able to recover info from xml box with bad xml.""" - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") + with self.assertWarns(UserWarning): jp2k = Jp2k(self._bad_xml_file) - self.assertTrue(issubclass(w[0].category, UserWarning)) - msg = 'No XML was retrieved' - self.assertTrue(msg in str(w[0].message)) self.assertEqual(jp2k.box[3].box_id, 'xml ') self.assertEqual(jp2k.box[3].offset, 77) @@ -263,19 +260,17 @@ class TestBadButRecoverableXmlFile(unittest.TestCase): def tearDownClass(cls): os.unlink(cls._bad_xml_file) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_bad_xml_box_warning(self): """Should warn in case of bad XML""" - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') + regex = 'A UnicodeDecodeError was encountered parsing an XML box' + with self.assertWarnsRegex(UserWarning, regex): Jp2k(self._bad_xml_file) - self.assertTrue(issubclass(w[0].category, UserWarning)) - msg = 'A UnicodeDecodeError was encountered parsing an XML box' - self.assertTrue(msg in str(w[0].message)) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_recover_from_bad_xml(self): """Should be able to recover info from xml box with bad xml.""" - with warnings.catch_warnings(): - warnings.simplefilter("ignore") + with self.assertWarns(UserWarning): jp2 = Jp2k(self._bad_xml_file) self.assertEqual(jp2.box[3].box_id, 'xml ') @@ -285,23 +280,21 @@ class TestBadButRecoverableXmlFile(unittest.TestCase): b'this is a test') +@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") class TestXML_OpjDataRoot(unittest.TestCase): """Test suite for XML boxes, requires OPJ_DATA_ROOT.""" - @unittest.skipIf(sys.platform.startswith('linux'), 'Failing on linux') def test_bom(self): """Byte order markers are illegal in UTF-8. Issue 185""" filename = opj_data_file(os.path.join('input', 'nonregression', 'issue171.jp2')) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") + msg = 'An illegal BOM \(byte order marker\) was detected and removed ' + msg += 'from the XML contents in the box starting at byte offset \d+' + with self.assertWarnsRegex(UserWarning, re.compile(msg)): jp2 = Jp2k(filename) - self.assertTrue(issubclass(w[0].category, UserWarning)) - msg = 'An illegal BOM (byte order marker) was detected and removed' - self.assertTrue(msg in str(w[0].message)) self.assertIsNotNone(jp2.box[3].xml) @@ -311,10 +304,8 @@ class TestXML_OpjDataRoot(unittest.TestCase): filename = opj_data_file(os.path.join('input', 'nonregression', '26ccf3651020967f7778238ef5af08af.SIGFPE.d25.527.jp2')) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") + with self.assertWarns((UserWarning, UserWarning)): jp2 = Jp2k(filename) - self.assertTrue(issubclass(w[0].category, UserWarning)) self.assertIsNone(jp2.box[3].box[1].box[1].xml) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index fb86b11..e755de5 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -21,8 +21,6 @@ import unittest import uuid from xml.etree import cElementTree as ET -import warnings - import numpy as np import pkg_resources @@ -30,6 +28,8 @@ import glymur from glymur import Jp2k from .fixtures import HAS_PYTHON_XMP_TOOLKIT +from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG + if HAS_PYTHON_XMP_TOOLKIT: import libxmp from libxmp import XMPMeta @@ -1020,6 +1020,7 @@ class TestJp2k_2_1(unittest.TestCase): self.assertEqual(j.box[2].box[0].num_components, 4) self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_openjpeg_library_message(self): """Verify the error message produced by the openjpeg library""" @@ -1044,8 +1045,7 @@ class TestJp2k_2_1(unittest.TestCase): tfile.write(data[offset+59:]) #tfile.write(data[3186:]) tfile.flush() - with warnings.catch_warnings(): - warnings.simplefilter("ignore") + with self.assertWarns(UserWarning): j = Jp2k(tfile.name) regexp = re.compile(r'''OpenJPEG\slibrary\serror:\s+ Invalid\svalues\sfor\scomp\s=\s0\s+ @@ -1069,21 +1069,16 @@ class TestParsing(unittest.TestCase): def tearDown(self): pass - @unittest.skipIf(sys.platform.startswith('linux'), 'Failing on linux') + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_bad_rsiz(self): """Should not warn if RSIZ when parsing is turned off.""" - # Actually there are three warning triggered by this codestream. filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') glymur.set_parseoptions(codestream=False) - with warnings.catch_warnings(record=True) as w: - j = Jp2k(filename) - self.assertEqual(len(w), 0) + j = Jp2k(filename) glymur.set_parseoptions(codestream=True) - with warnings.catch_warnings(record=True) as w: + with self.assertWarnsRegex(UserWarning, 'Invalid profile'): jp2 = Jp2k(filename) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertTrue('Invalid profile' in str(w[0].message)) def test_main_header(self): """Verify that the main header is not loaded when parsing turned off.""" @@ -1095,6 +1090,7 @@ class TestParsing(unittest.TestCase): main_header = jp2c.main_header self.assertIsNotNone(jp2c._main_header) +@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") class TestJp2kOpjDataRootWarnings(unittest.TestCase): @@ -1103,11 +1099,8 @@ class TestJp2kOpjDataRootWarnings(unittest.TestCase): def test_undecodeable_box_id(self): """Should warn in case of undecodeable box ID but not error out.""" filename = opj_data_file('input/nonregression/edf_c2_1013627.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') + with self.assertWarnsRegex(UserWarning, 'Unrecognized box'): jp2 = Jp2k(filename) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertTrue('Unrecognized box' in str(w[0].message)) # Now make sure we got all of the boxes. Ignore the last, which was # bad. @@ -1117,37 +1110,30 @@ class TestJp2kOpjDataRootWarnings(unittest.TestCase): def test_bad_ftyp_brand(self): """Should warn in case of bad ftyp brand.""" filename = opj_data_file('input/nonregression/edf_c2_1000290.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') + with self.assertWarns(UserWarning): jp2 = Jp2k(filename) - self.assertTrue(issubclass(w[0].category, UserWarning)) def test_invalid_approximation(self): """Should warn in case of invalid approximation.""" filename = opj_data_file('input/nonregression/edf_c2_1015644.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') + with self.assertWarnsRegex(UserWarning, 'Invalid approximation'): jp2 = Jp2k(filename) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertTrue('Invalid approximation' in str(w[0].message)) - @unittest.skipIf(sys.platform.startswith('linux'), 'Failing on linux') def test_invalid_colorspace(self): - """Should warn in case of invalid colorspace.""" + """ + Should warn in case of invalid colorspace. + + There are multiple warnings, so there's no good way to regex them all. + """ filename = opj_data_file('input/nonregression/edf_c2_1103421.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') + with self.assertWarns(UserWarning): jp2 = Jp2k(filename) - self.assertTrue(issubclass(w[1].category, UserWarning)) - self.assertTrue('Unrecognized colorspace' in str(w[1].message)) def test_stupid_windows_eol_at_end(self): """Garbage characters at the end of the file.""" filename = opj_data_file('input/nonregression/issue211.jp2') - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') + with self.assertWarns(UserWarning): jp2 = Jp2k(filename) - self.assertTrue(issubclass(w[1].category, UserWarning)) @unittest.skipIf(OPJ_DATA_ROOT is None, @@ -1172,10 +1158,12 @@ class TestJp2kOpjDataRoot(unittest.TestCase): actdata = j.read() self.assertTrue(fixtures.mse(actdata, expdata) < 250) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_no_cxform_pclr_jp2(self): """Indices for pclr jpxfile if no color transform""" filename = opj_data_file('input/conformance/file9.jp2') - j = Jp2k(filename) + with self.assertWarns(UserWarning): + j = Jp2k(filename) rgb = j.read() idx = j.read(ignore_pclr_cmap_cdef=True) self.assertEqual(rgb.shape, (512, 768, 3)) @@ -1200,15 +1188,15 @@ class TestJp2kOpjDataRoot(unittest.TestCase): j = Jp2k(filename) with self.assertRaises(RuntimeError): j.read() - + + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_no_cxform_cmap(self): """Bands as physically ordered, not as physically intended""" # This file has the components physically reversed. The cmap box # tells the decoder how to order them, but this flag prevents that. filename = opj_data_file('input/conformance/file2.jp2') - with warnings.catch_warnings(): + with self.assertWarns(UserWarning): # The file has a bad compatibility list entry. Not important here. - warnings.simplefilter("ignore") j = Jp2k(filename) ycbcr = j.read() crcby = j.read(ignore_pclr_cmap_cdef=True) diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index f9f683b..4519024 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -31,20 +31,16 @@ import re import sys import unittest -import warnings - import numpy as np from glymur import Jp2k import glymur from .fixtures import OPJ_DATA_ROOT +from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG from .fixtures import mse, peak_tolerance, read_pgx, opj_data_file -@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None and - glymur.lib.openjpeg.OPENJPEG is None, - "Missing openjpeg libraries.") @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") class TestSuite(unittest.TestCase): @@ -204,18 +200,127 @@ class TestSuite(unittest.TestCase): self.assertTrue(peak_tolerance(jpdata, pgxdata) < 624) self.assertTrue(mse(jpdata, pgxdata) < 3080) + def test_NR_DEC_Bretagne2_j2k_1_decode(self): + jfile = opj_data_file('input/nonregression/Bretagne2.j2k') + jp2 = Jp2k(jfile) + jp2.read() + self.assertTrue(True) + + def test_NR_DEC__00042_j2k_2_decode(self): + jfile = opj_data_file('input/nonregression/_00042.j2k') + jp2 = Jp2k(jfile) + jp2.read() + self.assertTrue(True) + + def test_NR_DEC_buxI_j2k_9_decode(self): + jfile = opj_data_file('input/nonregression/buxI.j2k') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_buxR_j2k_10_decode(self): + jfile = opj_data_file('input/nonregression/buxR.j2k') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_Cannotreaddatawithnosizeknown_j2k_11_decode(self): + relpath = 'input/nonregression/Cannotreaddatawithnosizeknown.j2k' + jfile = opj_data_file(relpath) + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_cthead1_j2k_12_decode(self): + jfile = opj_data_file('input/nonregression/cthead1.j2k') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_CT_Phillips_JPEG2K_Decompr_Problem_j2k_13_decode(self): + relpath = 'input/nonregression/CT_Phillips_JPEG2K_Decompr_Problem.j2k' + jfile = opj_data_file(relpath) + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_j2k32_j2k_15_decode(self): + jfile = opj_data_file('input/nonregression/j2k32.j2k') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_MarkerIsNotCompliant_j2k_17_decode(self): + jfile = opj_data_file('input/nonregression/MarkerIsNotCompliant.j2k') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_Marrin_jp2_18_decode(self): + jfile = opj_data_file('input/nonregression/Marrin.jp2') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_movie_00000_j2k_20_decode(self): + jfile = opj_data_file('input/nonregression/movie_00000.j2k') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_movie_00001_j2k_21_decode(self): + jfile = opj_data_file('input/nonregression/movie_00001.j2k') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_movie_00002_j2k_22_decode(self): + jfile = opj_data_file('input/nonregression/movie_00002.j2k') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_orb_blue_lin_j2k_j2k_23_decode(self): + jfile = opj_data_file('input/nonregression/orb-blue10-lin-j2k.j2k') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_orb_blue_win_j2k_j2k_24_decode(self): + jfile = opj_data_file('input/nonregression/orb-blue10-win-j2k.j2k') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_relax_jp2_27_decode(self): + jfile = opj_data_file('input/nonregression/relax.jp2') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_test_lossless_j2k_28_decode(self): + jfile = opj_data_file('input/nonregression/test_lossless.j2k') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_pacs_ge_j2k_30_decode(self): + jfile = opj_data_file('input/nonregression/pacs.ge.j2k') + Jp2k(jfile).read() + self.assertTrue(True) + + +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) +class TestSuiteWarns(unittest.TestCase): + """ + Identical setup to above, but these tests issue warnings. + """ + + def setUp(self): + pass + + def tearDown(self): + pass + def test_ETS_JP2_file1(self): jfile = opj_data_file('input/conformance/file1.jp2') - with warnings.catch_warnings(): + with self.assertWarns(UserWarning): # Bad compatibility list item. - warnings.simplefilter("ignore") jp2k = Jp2k(jfile) jpdata = jp2k.read() self.assertEqual(jpdata.shape, (512, 768, 3)) def test_ETS_JP2_file2(self): jfile = opj_data_file('input/conformance/file2.jp2') - jp2k = Jp2k(jfile) + with self.assertWarns(UserWarning): + jp2k = Jp2k(jfile) jpdata = jp2k.read() self.assertEqual(jpdata.shape, (640, 480, 3)) @@ -223,7 +328,8 @@ class TestSuite(unittest.TestCase): "Functionality not implemented for 1.x") def test_ETS_JP2_file3(self): jfile = opj_data_file('input/conformance/file3.jp2') - jp2k = Jp2k(jfile) + with self.assertWarns(UserWarning): + jp2k = Jp2k(jfile) jpdata = jp2k.read_bands() self.assertEqual(jpdata[0].shape, (640, 480)) self.assertEqual(jpdata[1].shape, (320, 240)) @@ -231,50 +337,53 @@ class TestSuite(unittest.TestCase): def test_ETS_JP2_file4(self): jfile = opj_data_file('input/conformance/file4.jp2') - jp2k = Jp2k(jfile) + with self.assertWarns(UserWarning): + jp2k = Jp2k(jfile) jpdata = jp2k.read() self.assertEqual(jpdata.shape, (512, 768)) def test_ETS_JP2_file5(self): jfile = opj_data_file('input/conformance/file5.jp2') - with warnings.catch_warnings(): + with self.assertWarns(UserWarning): # There's a warning for an unknown compatibility entry. # Ignore it here. - warnings.simplefilter("ignore") jp2k = Jp2k(jfile) jpdata = jp2k.read() self.assertEqual(jpdata.shape, (512, 768, 3)) def test_ETS_JP2_file6(self): jfile = opj_data_file('input/conformance/file6.jp2') - jp2k = Jp2k(jfile) + with self.assertWarns(UserWarning): + jp2k = Jp2k(jfile) jpdata = jp2k.read() self.assertEqual(jpdata.shape, (512, 768)) def test_ETS_JP2_file7(self): jfile = opj_data_file('input/conformance/file7.jp2') - jp2k = Jp2k(jfile) + with self.assertWarns(UserWarning): + jp2k = Jp2k(jfile) jpdata = jp2k.read() self.assertEqual(jpdata.shape, (640, 480, 3)) def test_ETS_JP2_file8(self): jfile = opj_data_file('input/conformance/file8.jp2') - jp2k = Jp2k(jfile) + with self.assertWarns(UserWarning): + jp2k = Jp2k(jfile) jpdata = jp2k.read() self.assertEqual(jpdata.shape, (400, 700)) def test_ETS_JP2_file9(self): jfile = opj_data_file('input/conformance/file9.jp2') - jp2k = Jp2k(jfile) + with self.assertWarns(UserWarning): + jp2k = Jp2k(jfile) jpdata = jp2k.read() self.assertEqual(jpdata.shape, (512, 768, 3)) def test_NR_broken_jp2_dump(self): jfile = opj_data_file('input/nonregression/broken.jp2') - with warnings.catch_warnings(): + with self.assertWarns(UserWarning): # colr box has bad length. - warnings.simplefilter("ignore") jp2 = Jp2k(jfile) ids = [box.box_id for box in jp2.box] @@ -395,111 +504,17 @@ class TestSuite(unittest.TestCase): self.assertEqual(c.segment[6].exponent, [8] + [9, 9, 10] * 5) - def test_NR_DEC_Bretagne2_j2k_1_decode(self): - jfile = opj_data_file('input/nonregression/Bretagne2.j2k') - jp2 = Jp2k(jfile) - jp2.read() - self.assertTrue(True) - - def test_NR_DEC__00042_j2k_2_decode(self): - jfile = opj_data_file('input/nonregression/_00042.j2k') - jp2 = Jp2k(jfile) - jp2.read() - self.assertTrue(True) - - def test_NR_DEC_buxI_j2k_9_decode(self): - jfile = opj_data_file('input/nonregression/buxI.j2k') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_buxR_j2k_10_decode(self): - jfile = opj_data_file('input/nonregression/buxR.j2k') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_Cannotreaddatawithnosizeknown_j2k_11_decode(self): - relpath = 'input/nonregression/Cannotreaddatawithnosizeknown.j2k' - jfile = opj_data_file(relpath) - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_cthead1_j2k_12_decode(self): - jfile = opj_data_file('input/nonregression/cthead1.j2k') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_CT_Phillips_JPEG2K_Decompr_Problem_j2k_13_decode(self): - relpath = 'input/nonregression/CT_Phillips_JPEG2K_Decompr_Problem.j2k' - jfile = opj_data_file(relpath) - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_j2k32_j2k_15_decode(self): - jfile = opj_data_file('input/nonregression/j2k32.j2k') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_MarkerIsNotCompliant_j2k_17_decode(self): - jfile = opj_data_file('input/nonregression/MarkerIsNotCompliant.j2k') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_Marrin_jp2_18_decode(self): - jfile = opj_data_file('input/nonregression/Marrin.jp2') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_movie_00000_j2k_20_decode(self): - jfile = opj_data_file('input/nonregression/movie_00000.j2k') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_movie_00001_j2k_21_decode(self): - jfile = opj_data_file('input/nonregression/movie_00001.j2k') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_movie_00002_j2k_22_decode(self): - jfile = opj_data_file('input/nonregression/movie_00002.j2k') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_orb_blue_lin_j2k_j2k_23_decode(self): - jfile = opj_data_file('input/nonregression/orb-blue10-lin-j2k.j2k') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_orb_blue_win_j2k_j2k_24_decode(self): - jfile = opj_data_file('input/nonregression/orb-blue10-win-j2k.j2k') - Jp2k(jfile).read() - self.assertTrue(True) - def test_NR_DEC_orb_blue_lin_jp2_25_decode(self): jfile = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2') - with warnings.catch_warnings(): + with self.assertWarns(UserWarning): # This file has an invalid ICC profile - warnings.simplefilter("ignore") Jp2k(jfile).read() self.assertTrue(True) def test_NR_DEC_orb_blue_win_jp2_26_decode(self): jfile = opj_data_file('input/nonregression/orb-blue10-win-jp2.jp2') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_relax_jp2_27_decode(self): - jfile = opj_data_file('input/nonregression/relax.jp2') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_test_lossless_j2k_28_decode(self): - jfile = opj_data_file('input/nonregression/test_lossless.j2k') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_pacs_ge_j2k_30_decode(self): - jfile = opj_data_file('input/nonregression/pacs.ge.j2k') - Jp2k(jfile).read() + with self.assertWarns(UserWarning): + Jp2k(jfile).read() self.assertTrue(True) @@ -507,11 +522,9 @@ class TestSuite(unittest.TestCase): "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1, "Feature not supported in glymur until openjpeg 2.0") -class TestSuite_bands(unittest.TestCase): - """Runs tests introduced in version 1.x but only pass in glymur with 2.0 - - The deal here is that the feature works with 1.x, but glymur only supports - it with version 2.0. +class TestSuiteBands(unittest.TestCase): + """ + Test the read_bands method. """ def setUp(self): @@ -630,34 +643,34 @@ class TestSuite2point0(unittest.TestCase): pgxdata = read_pgx(pgxfile) np.testing.assert_array_equal(jpdata[:, :, 2], pgxdata) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_NR_DEC_broken2_jp2_5_decode(self): # Null pointer access jfile = opj_data_file('input/nonregression/broken2.jp2') with self.assertRaises(IOError): - with warnings.catch_warnings(): + with self.assertWarns(UserWarning): # Invalid marker ID. - warnings.simplefilter("ignore") Jp2k(jfile).read() self.assertTrue(True) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_NR_DEC_broken4_jp2_7_decode(self): jfile = opj_data_file('input/nonregression/broken4.jp2') with self.assertRaises(IOError): - with warnings.catch_warnings(): + with self.assertWarns(UserWarning): # invalid number of subbands, bad marker ID - warnings.simplefilter("ignore") Jp2k(jfile).read() self.assertTrue(True) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_NR_DEC_kakadu_v4_4_openjpegv2_broken_j2k_16_decode(self): # This test actually passes in 1.5, but produces unpleasant warning # messages that cannot be turned off? relpath = 'input/nonregression/kakadu_v4-4_openjpegv2_broken.j2k' jfile = opj_data_file(relpath) if glymur.version.openjpeg_version_tuple[0] < 2: - with warnings.catch_warnings(): + with self.assertWarns(UserWarning): # Incorrect warning issued about tile parts. - warnings.simplefilter("ignore") Jp2k(jfile).read() else: Jp2k(jfile).read() diff --git a/glymur/test/test_opj_suite_2p1.py b/glymur/test/test_opj_suite_2p1.py index 1b58d77..addc48b 100644 --- a/glymur/test/test_opj_suite_2p1.py +++ b/glymur/test/test_opj_suite_2p1.py @@ -31,14 +31,13 @@ import re import sys import unittest -import warnings - import numpy as np from glymur import Jp2k import glymur from .fixtures import OPJ_DATA_ROOT +from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG from .fixtures import mse, peak_tolerance, read_pgx, opj_data_file @@ -56,11 +55,11 @@ class TestSuite2point1(unittest.TestCase): def tearDown(self): pass + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_NR_DEC_text_GBR_jp2_29_decode(self): jfile = opj_data_file('input/nonregression/text_GBR.jp2') - with warnings.catch_warnings(): + with self.assertWarns(UserWarning): # brand is 'jp2 ', but has any icc profile. - warnings.simplefilter("ignore") jp2 = Jp2k(jfile) jp2.read() self.assertTrue(True) @@ -85,32 +84,32 @@ class TestSuite2point1(unittest.TestCase): Jp2k(jfile).read() self.assertTrue(True) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_NR_DEC_gdal_fuzzer_unchecked_num_resolutions_jp2_36_decode(self): f = 'input/nonregression/gdal_fuzzer_unchecked_numresolutions.jp2' jfile = opj_data_file(f) - with warnings.catch_warnings(): + with self.assertWarns(UserWarning): # Invalid number of resolutions. - warnings.simplefilter("ignore") j = Jp2k(jfile) with self.assertRaises(IOError): j.read() + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_NR_DEC_gdal_fuzzer_check_number_of_tiles_jp2_38_decode(self): relpath = 'input/nonregression/gdal_fuzzer_check_number_of_tiles.jp2' jfile = opj_data_file(relpath) - with warnings.catch_warnings(): + with self.assertWarns(UserWarning): # Invalid number of tiles. - warnings.simplefilter("ignore") j = Jp2k(jfile) with self.assertRaises(IOError): j.read() + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_NR_DEC_gdal_fuzzer_check_comp_dx_dy_jp2_39_decode(self): relpath = 'input/nonregression/gdal_fuzzer_check_comp_dx_dy.jp2' jfile = opj_data_file(relpath) - with warnings.catch_warnings(): + with self.assertWarns(UserWarning): # Invalid subsampling value - warnings.simplefilter("ignore") with self.assertRaises(IOError): Jp2k(jfile).read() @@ -152,14 +151,14 @@ class TestSuite2point1(unittest.TestCase): odata = jp2k.read(rlevel=1) np.testing.assert_array_equal(tdata, odata[64:128, 256:320]) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_NR_DEC_jp2_36_decode(self): lst = ('input', 'nonregression', 'gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc.patch.jp2') jfile = opj_data_file('/'.join(lst)) - with warnings.catch_warnings(): + with self.assertWarns(UserWarning): # Invalid component number. - warnings.simplefilter("ignore") j = Jp2k(jfile) with self.assertRaises(IOError): j.read() diff --git a/glymur/test/test_opj_suite_dump.py b/glymur/test/test_opj_suite_dump.py index 705786b..60df1e7 100644 --- a/glymur/test/test_opj_suite_dump.py +++ b/glymur/test/test_opj_suite_dump.py @@ -31,20 +31,17 @@ import re import sys import unittest -import warnings - import numpy as np from glymur import Jp2k import glymur from .fixtures import OPJ_DATA_ROOT +from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG from .fixtures import mse, peak_tolerance, read_pgx, opj_data_file -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -class TestSuiteDump(unittest.TestCase): +class TestSuiteBase(unittest.TestCase): def setUp(self): pass @@ -69,7 +66,6 @@ class TestSuiteDump(unittest.TestCase): for cl in clist: self.assertIn(cl, box.compatibility_list) - def verifySizSegment(self, actual, expected): """ Verify the fields of the SIZ segment. @@ -104,140 +100,15 @@ class TestSuiteDump(unittest.TestCase): self.assertIsNone(actual.icc_profile) - @unittest.skipIf(re.match("1.5|2.0.0", glymur.version.openjpeg_version), - "Test not passing on 1.5, 2.0: not introduced until 2.x") - def test_NR_DEC_issue188_beach_64bitsbox_jp2_41_decode(self): - """ - Has an 'XML ' box instead of 'xml '. Just verify we can read it. - """ - relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2' - jfile = opj_data_file(relpath) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - j = Jp2k(jfile) - d = j.read() - self.assertTrue(True) +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class TestSuite(TestSuiteBase): + def setUp(self): + pass - def test_NR_broken4_jp2_dump(self): - jfile = opj_data_file('input/nonregression/broken4.jp2') - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) - - self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') - - @unittest.skipIf(sys.maxsize < 2**32, 'Do not run on 32-bit platforms') - def test_NR_broken3_jp2_dump(self): - """ - NR_broken3_jp2_dump - - The file in question here has a colr box with an erroneous box - length of over 1GB. Don't run it on 32-bit platforms. - """ - jfile = opj_data_file('input/nonregression/broken3.jp2') - with warnings.catch_warnings(): - # Bad box length. - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) - - ihdr = glymur.jp2box.ImageHeaderBox(152, 203, num_components=3) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.SRGB) - self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (203, 152), 'xyosiz': (0, 0), - 'xytsiz': (203, 152), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) - - # COM: comment - # Registration - self.assertEqual(c.segment[2].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[2].ccme.decode('latin-1'), - "Creator: JasPer Vers)on 1.701.0") - - # COD: Coding style default - self.assertFalse(c.segment[3].scod & 2) # no sop - self.assertFalse(c.segment[3].scod & 4) # no eph - self.assertEqual(c.segment[3].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[3].layers, 1) # layers = 1 - self.assertEqual(c.segment[3].spcod[3], 1) # mct - self.assertEqual(c.segment[3].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[3].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcod[7] & 0x0020) - self.assertEqual(c.segment[3].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[3].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) - self.assertEqual(c.segment[4].guard_bits, 2) - self.assertEqual(c.segment[4].mantissa, [0] * 16) - self.assertEqual(c.segment[4].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 1) - self.assertEqual(c.segment[5].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[5].mantissa, [0] * 16) - self.assertEqual(c.segment[5].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 2) - self.assertEqual(c.segment[6].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[6].mantissa, [0] * 16) - self.assertEqual(c.segment[6].exponent, - [8] + [9, 9, 10] * 5) - - def test_NR_broken2_jp2_dump(self): - """ - Invalid marker ID in the codestream. - """ - jfile = opj_data_file('input/nonregression/broken2.jp2') - with warnings.catch_warnings(): - # Invalid marker ID on codestream. - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) - - self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') + def tearDown(self): + pass def test_NR_file409752(self): jfile = opj_data_file('input/nonregression/file409752.jp2') @@ -2465,293 +2336,6 @@ class TestSuiteDump(unittest.TestCase): # EOC: end of codestream self.assertEqual(c.segment[-1].marker_id, 'EOC') - def test_NR_file1_dump(self): - jfile = opj_data_file('input/conformance/file1.jp2') - with warnings.catch_warnings(): - # Bad compatibility list item. - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'xml ', 'jp2h', 'xml ', - 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) - - # XML box - tags = [x.tag for x in jp2.box[2].xml.getroot()] - self.assertEqual(tags, - ['{http://www.jpeg.org/jpx/1.0/xml}' - + 'GENERAL_CREATION_INFO']) - - ihdr = glymur.jp2box.ImageHeaderBox(512, 768, num_components=3) - self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - - colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.SRGB, - approximation=1) - self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) - - # XML box - tags = [x.tag for x in jp2.box[4].xml.getroot()] - self.assertEqual(tags, ['{http://www.jpeg.org/jpx/1.0/xml}CAPTION', - '{http://www.jpeg.org/jpx/1.0/xml}LOCATION', - '{http://www.jpeg.org/jpx/1.0/xml}EVENT']) - - def test_NR_file2_dump(self): - jfile = opj_data_file('input/conformance/file2.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr', 'cdef']) - - self.verifySignatureBox(jp2.box[0]) - self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) - - ihdr = glymur.jp2box.ImageHeaderBox(640, 480, num_components=3) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.YCC, - approximation=1) - self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) - - # Jp2 Header - # Channel Definition - self.assertEqual(jp2.box[2].box[2].index, (0, 1, 2)) - self.assertEqual(jp2.box[2].box[2].channel_type, (0, 0, 0)) # color - self.assertEqual(jp2.box[2].box[2].association, (3, 2, 1)) # reverse - - def test_NR_file3_dump(self): - # Three 8-bit components in the sRGB-YCC colourspace, with the Cb and - # Cr components being subsampled 2x in both the horizontal and - # vertical directions. The components are stored in the standard - # order. - jfile = opj_data_file('input/conformance/file3.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) - - ihdr = glymur.jp2box.ImageHeaderBox(640, 480, num_components=3) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - colr = glymur.jp2box.ColourSpecificationBox( - colorspace=glymur.core.YCC, - approximation=1) - self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) - - # sub-sampling - codestream = jp2.get_codestream() - self.assertEqual(codestream.segment[1].xrsiz[0], 1) - self.assertEqual(codestream.segment[1].yrsiz[0], 1) - self.assertEqual(codestream.segment[1].xrsiz[1], 2) - self.assertEqual(codestream.segment[1].yrsiz[1], 2) - self.assertEqual(codestream.segment[1].xrsiz[2], 2) - self.assertEqual(codestream.segment[1].yrsiz[2], 2) - - def test_NR_file4_dump(self): - # One 8-bit component in the sRGB-grey colourspace. - jfile = opj_data_file('input/conformance/file4.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) - - ihdr = glymur.jp2box.ImageHeaderBox(512, 768) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - colr = glymur.jp2box.ColourSpecificationBox( - colorspace=glymur.core.GREYSCALE, approximation=1) - self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) - - def test_NR_file5_dump(self): - # Three 8-bit components in the ROMM-RGB colourspace, encapsulated in a - # JPX file. The components have been transformed using - # the RCT. The colourspace is specified using both a Restricted ICC - # profile and using the JPX-defined enumerated code for the ROMM-RGB - # colourspace. - jfile = opj_data_file('input/conformance/file5.jp2') - with warnings.catch_warnings(): - # There's a warning for an unknown compatibility entry. - # Ignore it here. - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - self.verifyFileTypeBox(jp2.box[1], 'jpx ', ['jp2 ', 'jpx ', 'jpxb']) - - ihdr = glymur.jp2box.ImageHeaderBox(512, 768, num_components=3) - self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - - colr = glymur.jp2box.ColourSpecificationBox( - method=glymur.core.RESTRICTED_ICC_PROFILE, - approximation=1, icc_profile=bytes([0] * 546)) - self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) - self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 546) - - def test_NR_file6_dump(self): - jfile = opj_data_file('input/conformance/file6.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) - - ihdr = glymur.jp2box.ImageHeaderBox(512, 768, bits_per_component=12) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - colr = glymur.jp2box.ColourSpecificationBox( - colorspace=glymur.core.GREYSCALE, - method=glymur.core.ENUMERATED_COLORSPACE, - approximation=1) - self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) - - def test_NR_file7_dump(self): - # Three 16-bit components in the e-sRGB colourspace, encapsulated in a - # JP2 compatible JPX file. The components have been transformed using - # the RCT. The colourspace is specified using both a Restricted ICC - # profile and using the JPX-defined enumerated code for the e-sRGB - # colourspace. - jfile = opj_data_file('input/conformance/file7.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jpx ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - - ihdr = glymur.jp2box.ImageHeaderBox(640, 480, - num_components=3, bits_per_component=16) - self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - - colr = glymur.jp2box.ColourSpecificationBox( - method=glymur.core.RESTRICTED_ICC_PROFILE, - approximation=1) - self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) - self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 13332) - - def test_NR_file8_dump(self): - # One 8-bit component in a gamma 1.8 space. The colourspace is - # specified using a Restricted ICC profile. - jfile = opj_data_file('input/conformance/file8.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'xml ', 'jp2c', - 'xml ']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - - ihdr = glymur.jp2box.ImageHeaderBox(400, 700) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - colr = glymur.jp2box.ColourSpecificationBox( - method=glymur.core.RESTRICTED_ICC_PROFILE, - approximation=1) - self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) - self.assertEqual(jp2.box[2].box[1].icc_profile['Size'], 414) - - # XML box - tags = [x.tag for x in jp2.box[3].xml.getroot()] - self.assertEqual(tags, - ['{http://www.jpeg.org/jpx/1.0/xml}' - + 'GENERAL_CREATION_INFO']) - - # XML box - tags = [x.tag for x in jp2.box[5].xml.getroot()] - self.assertEqual(tags, - ['{http://www.jpeg.org/jpx/1.0/xml}CAPTION', - '{http://www.jpeg.org/jpx/1.0/xml}LOCATION', - '{http://www.jpeg.org/jpx/1.0/xml}THING', - '{http://www.jpeg.org/jpx/1.0/xml}EVENT']) - - def test_NR_file9_dump(self): - # Colormap - jfile = opj_data_file('input/conformance/file9.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'pclr', 'cmap', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - - ihdr = glymur.jp2box.ImageHeaderBox(512, 768) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - # Palette box. - self.assertEqual(jp2.box[2].box[1].palette.shape, (256, 3)) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[0, 0], 0) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[0, 1], 0) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[0, 2], 0) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[128, 0], 73) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[128, 1], 92) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[128, 2], 53) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[255, 0], 245) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[255, 1], 245) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[255, 2], 245) - - # Component mapping box - self.assertEqual(jp2.box[2].box[2].component_index, (0, 0, 0)) - self.assertEqual(jp2.box[2].box[2].mapping_type, (1, 1, 1)) - self.assertEqual(jp2.box[2].box[2].palette_index, (0, 1, 2)) - - colr = glymur.jp2box.ColourSpecificationBox( - colorspace=glymur.core.SRGB, - approximation=1) - self.verifyColourSpecificationBox(jp2.box[2].box[3], colr) - def test_NR_00042_j2k_dump(self): # Profile 3. jfile = opj_data_file('input/nonregression/_00042.j2k') @@ -3952,77 +3536,6 @@ class TestSuiteDump(unittest.TestCase): self.assertEqual(c.segment[3].mantissa, [0] * 16) self.assertEqual(c.segment[3].exponent, [8] + [9, 9, 10] * 5) - def test_NR_issue188_beach_64bitsbox(self): - lst = ['input', 'nonregression', 'issue188_beach_64bitsbox.jp2'] - jfile = opj_data_file('/'.join(lst)) - with warnings.catch_warnings(): - # There's a warning for an unknown box. - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', b'XML ', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - - ihdr = glymur.jp2box.ImageHeaderBox(200, 200, - num_components=3, colorspace_unknown=True) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.SRGB) - self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) - - # Skip the 4th box, it is uknown. - - c = jp2.box[4].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'CME'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (200, 200), 'xyosiz': (0, 0), - 'xytsiz': (200, 200), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 1) - def test_NR_issue206_image_000_dump(self): jfile = opj_data_file('input/nonregression/issue206_image-000.jp2') jp2 = Jp2k(jfile) @@ -4348,11 +3861,510 @@ class TestSuiteDump(unittest.TestCase): podvals = (glymur.core.LRCP, glymur.core.LRCP) self.assertEqual(c.segment[4].ppod, podvals) + +@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class TestSuiteWarns(TestSuiteBase): + + @unittest.skipIf(re.match("1.5|2.0.0", glymur.version.openjpeg_version), + "Test not passing on 1.5, 2.0: not introduced until 2.x") + def test_NR_DEC_issue188_beach_64bitsbox_jp2_41_decode(self): + """ + Has an 'XML ' box instead of 'xml '. Just verify we can read it. + """ + relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2' + jfile = opj_data_file(relpath) + with self.assertWarns(UserWarning): + j = Jp2k(jfile) + d = j.read() + self.assertTrue(True) + + @unittest.skip("unexplained failure") + def test_NR_broken4_jp2_dump(self): + jfile = opj_data_file('input/nonregression/broken4.jp2') + with self.assertWarns(UserWarning): + jp2 = Jp2k(jfile) + + self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') + + @unittest.skipIf(sys.maxsize < 2**32, 'Do not run on 32-bit platforms') + def test_NR_broken3_jp2_dump(self): + """ + NR_broken3_jp2_dump + + The file in question here has a colr box with an erroneous box + length of over 1GB. Don't run it on 32-bit platforms. + """ + jfile = opj_data_file('input/nonregression/broken3.jp2') + with self.assertWarns(UserWarning): + # Bad box length. + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + self.verifySignatureBox(jp2.box[0]) + self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) + + ihdr = glymur.jp2box.ImageHeaderBox(152, 203, num_components=3) + self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) + + colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.SRGB) + self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) + + c = jp2.box[3].main_header + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] + self.assertEqual(ids, expected) + + kwargs = {'rsiz': 0, 'xysiz': (203, 152), 'xyosiz': (0, 0), + 'xytsiz': (203, 152), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + + # COM: comment + # Registration + self.assertEqual(c.segment[2].rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[2].ccme.decode('latin-1'), + "Creator: JasPer Vers)on 1.701.0") + + # COD: Coding style default + self.assertFalse(c.segment[3].scod & 2) # no sop + self.assertFalse(c.segment[3].scod & 4) # no eph + self.assertEqual(c.segment[3].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[3].layers, 1) # layers = 1 + self.assertEqual(c.segment[3].spcod[3], 1) # mct + self.assertEqual(c.segment[3].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[3].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[3].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[3].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[3].spcod[7] & 0x0020) + self.assertEqual(c.segment[3].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[3].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[4].sqcd & 0x1f, 0) + self.assertEqual(c.segment[4].guard_bits, 2) + self.assertEqual(c.segment[4].mantissa, [0] * 16) + self.assertEqual(c.segment[4].exponent, + [8] + [9, 9, 10] * 5) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[5].cqcc, 1) + self.assertEqual(c.segment[5].guard_bits, 2) + # quantization type + self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[5].mantissa, [0] * 16) + self.assertEqual(c.segment[5].exponent, + [8] + [9, 9, 10] * 5) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[6].cqcc, 2) + self.assertEqual(c.segment[6].guard_bits, 2) + # quantization type + self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none + self.assertEqual(c.segment[6].mantissa, [0] * 16) + self.assertEqual(c.segment[6].exponent, + [8] + [9, 9, 10] * 5) + + @unittest.skip("unexplained failure") + def test_NR_broken2_jp2_dump(self): + """ + Invalid marker ID in the codestream. + """ + jfile = opj_data_file('input/nonregression/broken2.jp2') + with self.assertWarns(UserWarning): + # Invalid marker ID on codestream. + jp2 = Jp2k(jfile) + + self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') + + def test_NR_file1_dump(self): + jfile = opj_data_file('input/conformance/file1.jp2') + with self.assertWarns(UserWarning): + # Bad compatibility list item. + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'xml ', 'jp2h', 'xml ', + 'jp2c']) + + ids = [box.box_id for box in jp2.box[3].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + self.verifySignatureBox(jp2.box[0]) + self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) + + # XML box + tags = [x.tag for x in jp2.box[2].xml.getroot()] + self.assertEqual(tags, + ['{http://www.jpeg.org/jpx/1.0/xml}' + + 'GENERAL_CREATION_INFO']) + + ihdr = glymur.jp2box.ImageHeaderBox(512, 768, num_components=3) + self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) + + colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.SRGB, + approximation=1) + self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) + + # XML box + tags = [x.tag for x in jp2.box[4].xml.getroot()] + self.assertEqual(tags, ['{http://www.jpeg.org/jpx/1.0/xml}CAPTION', + '{http://www.jpeg.org/jpx/1.0/xml}LOCATION', + '{http://www.jpeg.org/jpx/1.0/xml}EVENT']) + + def test_NR_file2_dump(self): + jfile = opj_data_file('input/conformance/file2.jp2') + with self.assertWarns(UserWarning): + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr', 'cdef']) + + self.verifySignatureBox(jp2.box[0]) + self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) + + ihdr = glymur.jp2box.ImageHeaderBox(640, 480, num_components=3) + self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) + + colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.YCC, + approximation=1) + self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) + + # Jp2 Header + # Channel Definition + self.assertEqual(jp2.box[2].box[2].index, (0, 1, 2)) + self.assertEqual(jp2.box[2].box[2].channel_type, (0, 0, 0)) # color + self.assertEqual(jp2.box[2].box[2].association, (3, 2, 1)) # reverse + + def test_NR_file3_dump(self): + # Three 8-bit components in the sRGB-YCC colourspace, with the Cb and + # Cr components being subsampled 2x in both the horizontal and + # vertical directions. The components are stored in the standard + # order. + jfile = opj_data_file('input/conformance/file3.jp2') + with self.assertWarns(UserWarning): + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + self.verifySignatureBox(jp2.box[0]) + self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) + + ihdr = glymur.jp2box.ImageHeaderBox(640, 480, num_components=3) + self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) + + colr = glymur.jp2box.ColourSpecificationBox( + colorspace=glymur.core.YCC, + approximation=1) + self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) + + # sub-sampling + codestream = jp2.get_codestream() + self.assertEqual(codestream.segment[1].xrsiz[0], 1) + self.assertEqual(codestream.segment[1].yrsiz[0], 1) + self.assertEqual(codestream.segment[1].xrsiz[1], 2) + self.assertEqual(codestream.segment[1].yrsiz[1], 2) + self.assertEqual(codestream.segment[1].xrsiz[2], 2) + self.assertEqual(codestream.segment[1].yrsiz[2], 2) + + def test_NR_file4_dump(self): + # One 8-bit component in the sRGB-grey colourspace. + jfile = opj_data_file('input/conformance/file4.jp2') + with self.assertWarns(UserWarning): + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + self.verifySignatureBox(jp2.box[0]) + self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) + + ihdr = glymur.jp2box.ImageHeaderBox(512, 768) + self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) + + colr = glymur.jp2box.ColourSpecificationBox( + colorspace=glymur.core.GREYSCALE, approximation=1) + self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) + + def test_NR_file5_dump(self): + # Three 8-bit components in the ROMM-RGB colourspace, encapsulated in a + # JPX file. The components have been transformed using + # the RCT. The colourspace is specified using both a Restricted ICC + # profile and using the JPX-defined enumerated code for the ROMM-RGB + # colourspace. + jfile = opj_data_file('input/conformance/file5.jp2') + with self.assertWarns(UserWarning): + # There's a warning for an unknown compatibility entry. + # Ignore it here. + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[3].box] + self.assertEqual(ids, ['ihdr', 'colr', 'colr']) + + self.verifySignatureBox(jp2.box[0]) + self.verifyFileTypeBox(jp2.box[1], 'jpx ', ['jp2 ', 'jpx ', 'jpxb']) + + ihdr = glymur.jp2box.ImageHeaderBox(512, 768, num_components=3) + self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) + + colr = glymur.jp2box.ColourSpecificationBox( + method=glymur.core.RESTRICTED_ICC_PROFILE, + approximation=1, icc_profile=bytes([0] * 546)) + self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) + self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 546) + + def test_NR_file6_dump(self): + jfile = opj_data_file('input/conformance/file6.jp2') + with self.assertWarns(UserWarning): + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + self.verifySignatureBox(jp2.box[0]) + self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) + + ihdr = glymur.jp2box.ImageHeaderBox(512, 768, bits_per_component=12) + self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) + + colr = glymur.jp2box.ColourSpecificationBox( + colorspace=glymur.core.GREYSCALE, + method=glymur.core.ENUMERATED_COLORSPACE, + approximation=1) + self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) + + def test_NR_file7_dump(self): + # Three 16-bit components in the e-sRGB colourspace, encapsulated in a + # JP2 compatible JPX file. The components have been transformed using + # the RCT. The colourspace is specified using both a Restricted ICC + # profile and using the JPX-defined enumerated code for the e-sRGB + # colourspace. + jfile = opj_data_file('input/conformance/file7.jp2') + with self.assertWarns(UserWarning): + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[3].box] + self.assertEqual(ids, ['ihdr', 'colr', 'colr']) + + self.verifySignatureBox(jp2.box[0]) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jpx ') + self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + + ihdr = glymur.jp2box.ImageHeaderBox(640, 480, + num_components=3, bits_per_component=16) + self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) + + colr = glymur.jp2box.ColourSpecificationBox( + method=glymur.core.RESTRICTED_ICC_PROFILE, + approximation=1) + self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) + self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 13332) + + def test_NR_file8_dump(self): + # One 8-bit component in a gamma 1.8 space. The colourspace is + # specified using a Restricted ICC profile. + jfile = opj_data_file('input/conformance/file8.jp2') + with self.assertWarns(UserWarning): + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'xml ', 'jp2c', + 'xml ']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + self.verifySignatureBox(jp2.box[0]) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + + ihdr = glymur.jp2box.ImageHeaderBox(400, 700) + self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) + + colr = glymur.jp2box.ColourSpecificationBox( + method=glymur.core.RESTRICTED_ICC_PROFILE, + approximation=1) + self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) + self.assertEqual(jp2.box[2].box[1].icc_profile['Size'], 414) + + # XML box + tags = [x.tag for x in jp2.box[3].xml.getroot()] + self.assertEqual(tags, + ['{http://www.jpeg.org/jpx/1.0/xml}' + + 'GENERAL_CREATION_INFO']) + + # XML box + tags = [x.tag for x in jp2.box[5].xml.getroot()] + self.assertEqual(tags, + ['{http://www.jpeg.org/jpx/1.0/xml}CAPTION', + '{http://www.jpeg.org/jpx/1.0/xml}LOCATION', + '{http://www.jpeg.org/jpx/1.0/xml}THING', + '{http://www.jpeg.org/jpx/1.0/xml}EVENT']) + + def test_NR_file9_dump(self): + # Colormap + jfile = opj_data_file('input/conformance/file9.jp2') + with self.assertWarns(UserWarning): + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'pclr', 'cmap', 'colr']) + + self.verifySignatureBox(jp2.box[0]) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + + ihdr = glymur.jp2box.ImageHeaderBox(512, 768) + self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) + + # Palette box. + self.assertEqual(jp2.box[2].box[1].palette.shape, (256, 3)) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[0, 0], 0) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[0, 1], 0) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[0, 2], 0) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[128, 0], 73) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[128, 1], 92) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[128, 2], 53) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[255, 0], 245) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[255, 1], 245) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[255, 2], 245) + + # Component mapping box + self.assertEqual(jp2.box[2].box[2].component_index, (0, 0, 0)) + self.assertEqual(jp2.box[2].box[2].mapping_type, (1, 1, 1)) + self.assertEqual(jp2.box[2].box[2].palette_index, (0, 1, 2)) + + colr = glymur.jp2box.ColourSpecificationBox( + colorspace=glymur.core.SRGB, + approximation=1) + self.verifyColourSpecificationBox(jp2.box[2].box[3], colr) + + def test_NR_issue188_beach_64bitsbox(self): + lst = ['input', 'nonregression', 'issue188_beach_64bitsbox.jp2'] + jfile = opj_data_file('/'.join(lst)) + with self.assertWarns(UserWarning): + # There's a warning for an unknown box. + jp2 = Jp2k(jfile) + + ids = [box.box_id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', b'XML ', 'jp2c']) + + ids = [box.box_id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + self.verifySignatureBox(jp2.box[0]) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + ihdr = glymur.jp2box.ImageHeaderBox(200, 200, + num_components=3, colorspace_unknown=True) + self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) + + colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.SRGB) + self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) + + # Skip the 4th box, it is uknown. + + c = jp2.box[4].main_header + + ids = [x.marker_id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'CME'] + self.assertEqual(ids, expected) + + kwargs = {'rsiz': 0, 'xysiz': (200, 200), 'xyosiz': (0, 0), + 'xytsiz': (200, 200), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 1) # layers = 1 + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # level + self.assertEqual(tuple(c.segment[2].code_block_size), + (64, 64)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[3].sqcd & 0x1f, 2) + self.assertEqual(c.segment[3].guard_bits, 1) + def test_NR_orb_blue10_lin_jp2_dump(self): jfile = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2') - with warnings.catch_warnings(): + with self.assertWarns(UserWarning): # This file has an invalid ICC profile - warnings.simplefilter("ignore") jp2 = Jp2k(jfile) ids = [box.box_id for box in jp2.box] @@ -4426,9 +4438,8 @@ class TestSuiteDump(unittest.TestCase): def test_NR_orb_blue10_win_jp2_dump(self): jfile = opj_data_file('input/nonregression/orb-blue10-win-jp2.jp2') - with warnings.catch_warnings(): + with self.assertWarns(UserWarning): # This file has an invalid ICC profile - warnings.simplefilter("ignore") jp2 = Jp2k(jfile) ids = [box.box_id for box in jp2.box] diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index bee06f7..058be42 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -13,7 +13,6 @@ import re import sys import tempfile import unittest -import warnings import numpy as np try: @@ -24,6 +23,7 @@ except ImportError: from .fixtures import OPJ_DATA_ROOT, opj_data_file, read_image from .fixtures import NO_READ_BACKEND, NO_READ_BACKEND_MSG from .fixtures import NO_SKIMAGE_FREEIMAGE_SUPPORT +from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG from glymur import Jp2k import glymur @@ -76,13 +76,13 @@ class TestSuiteNegative(unittest.TestCase): jp2k.get_codestream(header_only=False) self.assertTrue(True) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_nr_illegalclrtransform(self): """EOC marker is bad""" relpath = 'input/nonregression/illegalcolortransform.j2k' jfile = opj_data_file(relpath) jp2k = Jp2k(jfile) - with warnings.catch_warnings(): - warnings.simplefilter('ignore') + with self.assertWarns(UserWarning): codestream = jp2k.get_codestream(header_only=False) # Verify that the last segment returned in the codestream is SOD, diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 2af76d8..53b71cb 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -11,7 +11,6 @@ import re import sys import tempfile import unittest -import warnings import numpy as np try: @@ -22,50 +21,13 @@ except ImportError: from .fixtures import read_image, NO_READ_BACKEND, NO_READ_BACKEND_MSG from .fixtures import OPJ_DATA_ROOT, NO_SKIMAGE_FREEIMAGE_SUPPORT from .fixtures import opj_data_file +from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG from . import fixtures from glymur import Jp2k import glymur -@unittest.skipIf(NO_SKIMAGE_FREEIMAGE_SUPPORT, - "Cannot read input image without scikit-image/freeimage") -@unittest.skipIf(os.name == "nt", "no write support on windows, period") -@unittest.skipIf(re.match(r'''(1|2.0.0)''', - glymur.version.openjpeg_version) is not None, - "Uses features not supported until 2.0.1") -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -class TestSuiteWriteCinema(unittest.TestCase): - """Tests for writing with openjp2 backend. - - These tests either roughly correspond with those tests with similar names - in the OpenJPEG test suite or are closely associated. - """ - def setUp(self): - pass - - def tearDown(self): - pass - - def test_cinema2K_with_others(self): - """Can't specify cinema2k with any other options.""" - relfile = 'input/nonregression/X_5_2K_24_235_CBR_STEM24_000.tif' - infile = opj_data_file(relfile) - data = skimage.io.imread(infile) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - with self.assertRaises(IOError): - j.write(data, cinema2k=48, cratios=[200, 100, 50]) - - def test_cinema4K_with_others(self): - """Can't specify cinema4k with any other options.""" - relfile = 'input/nonregression/ElephantDream_4K.tif' - infile = opj_data_file(relfile) - data = skimage.io.imread(infile) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - with self.assertRaises(IOError): - j.write(data, cinema4k=True, cratios=[200, 100, 50]) +class CinemaBase(unittest.TestCase): def check_cinema4k_codestream(self, codestream, image_size): """Common out for cinema2k tests.""" @@ -151,83 +113,136 @@ class TestSuiteWriteCinema(unittest.TestCase): +@unittest.skipIf(NO_SKIMAGE_FREEIMAGE_SUPPORT, + "Cannot read input image without scikit-image/freeimage") +@unittest.skipIf(os.name == "nt", "no write support on windows, period") +@unittest.skipIf(re.match(r'''(1|2.0.0)''', + glymur.version.openjpeg_version) is not None, + "Uses features not supported until 2.0.1") +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class WriteCinema(CinemaBase): + """Tests for writing with openjp2 backend. + + These tests either roughly correspond with those tests with similar names + in the OpenJPEG test suite or are closely associated. + """ + def test_cinema2K_with_others(self): + """Can't specify cinema2k with any other options.""" + relfile = 'input/nonregression/X_5_2K_24_235_CBR_STEM24_000.tif' + infile = opj_data_file(relfile) + data = skimage.io.imread(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError): + j.write(data, cinema2k=48, cratios=[200, 100, 50]) + + def test_cinema4K_with_others(self): + """Can't specify cinema4k with any other options.""" + relfile = 'input/nonregression/ElephantDream_4K.tif' + infile = opj_data_file(relfile) + data = skimage.io.imread(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError): + j.write(data, cinema4k=True, cratios=[200, 100, 50]) + + +@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) +@unittest.skipIf(NO_SKIMAGE_FREEIMAGE_SUPPORT, + "Cannot read input image without scikit-image/freeimage") +@unittest.skipIf(os.name == "nt", "no write support on windows, period") +@unittest.skipIf(re.match(r'''(1|2.0.0)''', + glymur.version.openjpeg_version) is not None, + "Uses features not supported until 2.0.1") +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class WriteCinemaWarns(CinemaBase): + """Tests for writing with openjp2 backend. + + These tests either roughly correspond with those tests with similar names + in the OpenJPEG test suite or are closely associated. These tests issue + warnings. + """ def test_NR_ENC_ElephantDream_4K_tif_21_encode(self): relfile = 'input/nonregression/ElephantDream_4K.tif' infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, 'wb') - with warnings.catch_warnings(): - # Just turn off warnings. - warnings.simplefilter("ignore") + regex = 'OpenJPEG library warning:.*' + with self.assertWarnsRegex(UserWarning, re.compile(regex)): j.write(data, cinema4k=True) codestream = j.get_codestream() self.check_cinema4k_codestream(codestream, (4096, 2160)) - def test_NR_ENC_X_5_2K_24_235_CBR_STEM24_000_tif_19_encode(self): relfile = 'input/nonregression/X_5_2K_24_235_CBR_STEM24_000.tif' infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, 'wb') - j.write(data, cinema2k=48) + with self.assertWarnsRegex(UserWarning, 'OpenJPEG library warning'): + j.write(data, cinema2k=48) codestream = j.get_codestream() self.check_cinema2k_codestream(codestream, (2048, 857)) - def test_NR_ENC_X_6_2K_24_FULL_CBR_CIRCLE_000_tif_20_encode(self): relfile = 'input/nonregression/X_6_2K_24_FULL_CBR_CIRCLE_000.tif' infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, 'wb') - j.write(data, cinema2k=48) + with self.assertWarnsRegex(UserWarning, 'OpenJPEG library warning'): + j.write(data, cinema2k=48) codestream = j.get_codestream() self.check_cinema2k_codestream(codestream, (2048, 1080)) - def test_NR_ENC_X_6_2K_24_FULL_CBR_CIRCLE_000_tif_17_encode(self): relfile = 'input/nonregression/X_6_2K_24_FULL_CBR_CIRCLE_000.tif' infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, 'wb') - j.write(data, cinema2k=24) + with self.assertWarnsRegex(UserWarning, 'OpenJPEG library warning'): + j.write(data, cinema2k=24) codestream = j.get_codestream() self.check_cinema2k_codestream(codestream, (2048, 1080)) - def test_NR_ENC_X_5_2K_24_235_CBR_STEM24_000_tif_16_encode(self): relfile = 'input/nonregression/X_5_2K_24_235_CBR_STEM24_000.tif' infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, 'wb') - j.write(data, cinema2k=24) + with self.assertWarnsRegex(UserWarning, 'OpenJPEG library warning'): + # OpenJPEG library warning: The desired maximum codestream + # size has limited at least one of the desired quality layers + j.write(data, cinema2k=24) codestream = j.get_codestream() self.check_cinema2k_codestream(codestream, (2048, 857)) - def test_NR_ENC_X_4_2K_24_185_CBR_WB_000_tif_18_encode(self): relfile = 'input/nonregression/X_4_2K_24_185_CBR_WB_000.tif' infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, 'wb') - with warnings.catch_warnings(): - # Just turn off warnings. - warnings.simplefilter("ignore") + regex = 'OpenJPEG library warning' + with self.assertWarnsRegex(UserWarning, regex): + # OpenJPEG library warning: The desired maximum codestream + # size has limited at least one of the desired quality layers j.write(data, cinema2k=48) codestream = j.get_codestream() self.check_cinema2k_codestream(codestream, (1998, 1080)) + @unittest.skipIf(NO_SKIMAGE_FREEIMAGE_SUPPORT, "Cannot read input image without scikit-image/freeimage") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index da2062e..66d82d0 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -15,7 +15,6 @@ import re import struct import sys import tempfile -import warnings import unittest if sys.hexversion < 0x03000000: @@ -34,6 +33,7 @@ import glymur from glymur import Jp2k, command_line from . import fixtures from .fixtures import OPJ_DATA_ROOT, opj_data_file +from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG from .fixtures import text_gbr_27, text_gbr_33, text_gbr_34 @@ -71,6 +71,7 @@ class TestPrinting(unittest.TestCase): self.assertTrue(True) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_unknown_superbox(self): """Verify that we can handle an unknown superbox.""" with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile: @@ -87,9 +88,7 @@ class TestPrinting(unittest.TestCase): tfile.write(write_buffer) tfile.flush() - with warnings.catch_warnings(): - # Suppress the warning about the unrecognized box. - warnings.simplefilter("ignore") + with self.assertWarns(UserWarning): jpx = Jp2k(tfile.name) glymur.set_printoptions(short=True) @@ -645,48 +644,6 @@ class TestPrintingOpjDataRoot(unittest.TestCase): actual = fake_out.getvalue().strip() self.assertEqual(actual, fixtures.cinema2k_profile) - def test_invalid_colorspace(self): - """An invalid colorspace shouldn't cause an error.""" - filename = opj_data_file('input/nonregression/edf_c2_1103421.jp2') - with warnings.catch_warnings(): - # Bad compatibility list item and bad colorspace warnings. Just - # suppress the warnings. - warnings.simplefilter("ignore") - jp2 = Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(jp2) - - def test_bad_rsiz(self): - """Should still be able to print if rsiz is bad, issue196""" - filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - j = Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(j) - - def test_bad_wavelet_transform(self): - """Should still be able to print if wavelet xform is bad, issue195""" - filename = opj_data_file('input/nonregression/edf_c2_10025.jp2') - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - jp2 = Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(jp2) - - def test_invalid_progression_order(self): - """Should still be able to print even if prog order is invalid.""" - jfile = opj_data_file('input/nonregression/2977.pdf.asan.67.2198.jp2') - with warnings.catch_warnings(): - # Multiple warnings, actually. - warnings.simplefilter("ignore") - jp2 = Jp2k(jfile) - codestream = jp2.get_codestream() - with patch('sys.stdout', new=StringIO()) as fake_out: - print(codestream.segment[2]) - actual = fake_out.getvalue().strip() - self.assertEqual(actual, fixtures.issue_186_progression_order) - def test_crg(self): """verify printing of CRG segment""" filename = opj_data_file('input/conformance/p0_03.j2k') @@ -836,46 +793,6 @@ class TestPrintingOpjDataRoot(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) - def test_xml(self): - """verify printing of XML box""" - filename = opj_data_file('input/conformance/file1.jp2') - j = glymur.Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(j.box[2]) - actual = fake_out.getvalue().strip() - self.assertEqual(actual, fixtures.file1_xml) - - def test_channel_definition(self): - """verify printing of cdef box""" - filename = opj_data_file('input/conformance/file2.jp2') - with warnings.catch_warnings(): - # Bad compatibility list item. - warnings.simplefilter("ignore") - j = glymur.Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(j.box[2].box[2]) - actual = fake_out.getvalue().strip() - lines = ['Channel Definition Box (cdef) @ (81, 28)', - ' Channel 0 (color) ==> (3)', - ' Channel 1 (color) ==> (2)', - ' Channel 2 (color) ==> (1)'] - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - - def test_component_mapping(self): - """verify printing of cmap box""" - filename = opj_data_file('input/conformance/file9.jp2') - j = glymur.Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(j.box[2].box[2]) - actual = fake_out.getvalue().strip() - lines = ['Component Mapping Box (cmap) @ (848, 20)', - ' Component 0 ==> palette column 0', - ' Component 0 ==> palette column 1', - ' Component 0 ==> palette column 2'] - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - def test_componentmapping_box_alpha(self): """Verify __repr__ method on cmap box.""" cmap = glymur.jp2box.ComponentMappingBox(component_index=(0, 0, 0), @@ -887,27 +804,6 @@ class TestPrintingOpjDataRoot(unittest.TestCase): self.assertEqual(newbox.mapping_type, (1, 1, 1)) self.assertEqual(newbox.palette_index, (0, 1, 2)) - def test_palette7(self): - """verify printing of pclr box""" - filename = opj_data_file('input/conformance/file9.jp2') - j = glymur.Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(j.box[2].box[1]) - actual = fake_out.getvalue().strip() - lines = ['Palette Box (pclr) @ (66, 782)', - ' Size: (256 x 3)'] - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - - def test_rreq(self): - """verify printing of reader requirements box""" - filename = opj_data_file('input/nonregression/text_GBR.jp2') - j = glymur.Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(j.box[2]) - actual = fake_out.getvalue().strip() - self.assertEqual(actual, fixtures.text_GBR_rreq) - def test_differing_subsamples(self): """verify printing of SIZ with different subsampling... Issue 86.""" filename = opj_data_file('input/conformance/p0_05.j2k') @@ -929,10 +825,133 @@ class TestPrintingOpjDataRoot(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") +@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) +class TestPrintingOpjDataRootWarns(unittest.TestCase): + """ + Tests for verifying printing. restricted to OPJ_DATA_ROOT files. + + These tests issue warnings. + """ + def setUp(self): + self.jpxfile = glymur.data.jpxfile() + self.jp2file = glymur.data.nemo() + self.j2kfile = glymur.data.goodstuff() + + # Reset printoptions for every test. + glymur.set_printoptions(short=False, xml=True, codestream=True) + + def tearDown(self): + pass + + def test_invalid_colorspace(self): + """An invalid colorspace shouldn't cause an error.""" + filename = opj_data_file('input/nonregression/edf_c2_1103421.jp2') + with self.assertWarns(UserWarning): + jp2 = Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(jp2) + + @unittest.skip("unexplained failure") + def test_bad_rsiz(self): + """Should still be able to print if rsiz is bad, issue196""" + filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') + with self.assertWarns(UserWarning): + j = Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(j) + + def test_bad_wavelet_transform(self): + """Should still be able to print if wavelet xform is bad, issue195""" + filename = opj_data_file('input/nonregression/edf_c2_10025.jp2') + with self.assertWarns(UserWarning): + jp2 = Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(jp2) + + def test_invalid_progression_order(self): + """Should still be able to print even if prog order is invalid.""" + jfile = opj_data_file('input/nonregression/2977.pdf.asan.67.2198.jp2') + with self.assertWarns(UserWarning): + # Multiple warnings, actually. + jp2 = Jp2k(jfile) + codestream = jp2.get_codestream() + with patch('sys.stdout', new=StringIO()) as fake_out: + print(codestream.segment[2]) + actual = fake_out.getvalue().strip() + self.assertEqual(actual, fixtures.issue_186_progression_order) + + def test_xml(self): + """verify printing of XML box""" + filename = opj_data_file('input/conformance/file1.jp2') + with self.assertWarns(UserWarning): + j = glymur.Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(j.box[2]) + actual = fake_out.getvalue().strip() + self.assertEqual(actual, fixtures.file1_xml) + + def test_channel_definition(self): + """verify printing of cdef box""" + filename = opj_data_file('input/conformance/file2.jp2') + with self.assertWarns(UserWarning): + # Bad compatibility list item. + j = glymur.Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(j.box[2].box[2]) + actual = fake_out.getvalue().strip() + lines = ['Channel Definition Box (cdef) @ (81, 28)', + ' Channel 0 (color) ==> (3)', + ' Channel 1 (color) ==> (2)', + ' Channel 2 (color) ==> (1)'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + def test_component_mapping(self): + """verify printing of cmap box""" + filename = opj_data_file('input/conformance/file9.jp2') + with self.assertWarns(UserWarning): + j = glymur.Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(j.box[2].box[2]) + actual = fake_out.getvalue().strip() + lines = ['Component Mapping Box (cmap) @ (848, 20)', + ' Component 0 ==> palette column 0', + ' Component 0 ==> palette column 1', + ' Component 0 ==> palette column 2'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + def test_palette7(self): + """verify printing of pclr box""" + filename = opj_data_file('input/conformance/file9.jp2') + with self.assertWarns(UserWarning): + j = glymur.Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(j.box[2].box[1]) + actual = fake_out.getvalue().strip() + lines = ['Palette Box (pclr) @ (66, 782)', + ' Size: (256 x 3)'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + def test_rreq(self): + """verify printing of reader requirements box""" + filename = opj_data_file('input/nonregression/text_GBR.jp2') + with self.assertWarns(UserWarning): + j = glymur.Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(j.box[2]) + actual = fake_out.getvalue().strip() + self.assertEqual(actual, fixtures.text_GBR_rreq) + def test_palette_box(self): """Verify that palette (pclr) boxes are printed without error.""" filename = opj_data_file('input/conformance/file9.jp2') - j = glymur.Jp2k(filename) + with self.assertWarns(UserWarning): + j = glymur.Jp2k(filename) with patch('sys.stdout', new=StringIO()) as fake_out: print(j.box[2].box[1]) actual = fake_out.getvalue().strip() @@ -946,9 +965,8 @@ class TestPrintingOpjDataRoot(unittest.TestCase): # ICC profiles may be used in JP2, but the approximation field should # be zero unless we have jpx. This file does both. filename = opj_data_file('input/nonregression/text_GBR.jp2') - with warnings.catch_warnings(): + with self.assertWarns(UserWarning): # brand is 'jp2 ', but has any icc profile. - warnings.simplefilter("ignore") jp2 = Jp2k(filename) with patch('sys.stdout', new=StringIO()) as fake_out: @@ -966,7 +984,8 @@ class TestPrintingOpjDataRoot(unittest.TestCase): def test_uuid(self): """verify printing of UUID box""" filename = opj_data_file('input/nonregression/text_GBR.jp2') - jp2 = Jp2k(filename) + with self.assertWarns(UserWarning): + jp2 = Jp2k(filename) with patch('sys.stdout', new=StringIO()) as fake_out: print(jp2.box[4]) @@ -984,9 +1003,8 @@ class TestPrintingOpjDataRoot(unittest.TestCase): # Format strings like %d were showing up in the output. filename = opj_data_file('input/nonregression/mem-b2ace68c-1381.jp2') - with warnings.catch_warnings(): + with self.assertWarns(UserWarning): # Ignore warning about bad pclr box. - warnings.simplefilter("ignore") jp2 = Jp2k(filename) with patch('sys.stdout', new=StringIO()) as fake_out: print(jp2.box[3].box[3]) @@ -996,9 +1014,8 @@ class TestPrintingOpjDataRoot(unittest.TestCase): def test_issue183(self): filename = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2') - with warnings.catch_warnings(): + with self.assertWarns(UserWarning): # Ignore warning about bad pclr box. - warnings.simplefilter("ignore") jp2 = Jp2k(filename) with patch('sys.stdout', new=StringIO()) as fake_out: print(jp2.box[2].box[1]) @@ -1010,8 +1027,7 @@ class TestPrintingOpjDataRoot(unittest.TestCase): filename = opj_data_file(os.path.join('input', 'nonregression', 'issue171.jp2')) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") + with self.assertWarns(UserWarning): jp2 = Jp2k(filename) with patch('sys.stdout', new=StringIO()) as fake_out: # No need to verify, it's enough that we don't error out. From a6a9b5215b7018808b67f3b6c109e6604bdd4441 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 18 Sep 2014 19:08:14 -0400 Subject: [PATCH 248/326] folded jp2dump module into command_line module --- glymur/__init__.py | 1 - glymur/command_line.py | 30 ++++++++++++++++++++++++++---- glymur/jp2dump.py | 36 ------------------------------------ 3 files changed, 26 insertions(+), 41 deletions(-) delete mode 100644 glymur/jp2dump.py diff --git a/glymur/__init__.py b/glymur/__init__.py index f39d6ef..eb0139c 100644 --- a/glymur/__init__.py +++ b/glymur/__init__.py @@ -7,7 +7,6 @@ from glymur import version __version__ = version.version from .jp2k import Jp2k -from .jp2dump import jp2dump from .jp2box import get_printoptions, set_printoptions from .jp2box import get_parseoptions, set_parseoptions diff --git a/glymur/command_line.py b/glymur/command_line.py index 82b5292..62b9057 100644 --- a/glymur/command_line.py +++ b/glymur/command_line.py @@ -1,10 +1,15 @@ -#!/usr/bin/env python - +""" +Entry point for console script jp2dump. +""" import argparse import sys -from . import jp2dump, set_printoptions +import warnings +from . import Jp2k, set_printoptions def main(): + """ + Entry point for console script jp2dump. + """ description='Print JPEG2000 metadata.' parser = argparse.ArgumentParser(description=description) @@ -45,5 +50,22 @@ def main(): print_full_codestream = True filename = args.filename - jp2dump(args.filename, codestream=print_full_codestream) + with warnings.catch_warnings(record=True) as wctx: + + # JP2 metadata can be extensive, so don't print any warnings until we + # are done with the metadata. + j = Jp2k(filename) + if print_full_codestream: + print(j.get_codestream(header_only=False)) + else: + print(j) + + # Re-emit any warnings that may have been suppressed. + if len(wctx) > 0: + print("\n") + for warning in wctx: + print("{0}:{1}: {2}: {3}".format(warning.filename, + warning.lineno, + warning.category.__name__, + warning.message)) diff --git a/glymur/jp2dump.py b/glymur/jp2dump.py deleted file mode 100644 index c0f0f7f..0000000 --- a/glymur/jp2dump.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -Entry point for jp2dump script. -""" -import warnings - -from .jp2k import Jp2k - - -def jp2dump(filename, codestream=False): - """Prints JPEG2000 metadata. - - Parameters - ---------- - filename : string - The input JPEG2000 file. - codestream : optional, logical scalar - Whether or not to dump codestream contents. - """ - with warnings.catch_warnings(record=True) as wctx: - - # JP2 metadata can be extensive, so don't print any warnings until we - # are done with the metadata. - j = Jp2k(filename) - if codestream: - print(j.get_codestream(header_only=False)) - else: - print(j) - - # Re-emit any warnings that may have been suppressed. - if len(wctx) > 0: - print("\n") - for warning in wctx: - print("{0}:{1}: {2}: {3}".format(warning.filename, - warning.lineno, - warning.category.__name__, - warning.message)) From a580fe50971171b239535f675024a5077dda0bb1 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 18 Sep 2014 20:14:13 -0400 Subject: [PATCH 249/326] UTs written for __setitem__ support --- glymur/test/test_jp2k.py | 49 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index e755de5..90717ff 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -52,7 +52,7 @@ def load_tests(loader, tests, ignore): return tests -class TestSliceProtocol(unittest.TestCase): +class SliceProtocolBase(unittest.TestCase): """ Test slice protocol, i.e. when using [ ] to read image data. """ @@ -65,6 +65,53 @@ class TestSliceProtocol(unittest.TestCase): self.j2k = Jp2k(glymur.data.goodstuff()) self.j2k_data = self.j2k.read() + +@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") +class TestSliceProtocolBaseWrite(SliceProtocolBase): + + def test_basic_write(self): + expected = self.j2k_data + + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j[:] = self.j2k_data + expected = j.read() + + np.testing.assert_array_equal(actual, expected) + + def test_cannot_write_a_row(self): + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError): + j[5] = self.j2k_data + + def test_cannot_write_a_pixel(self): + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError): + j[25, 35] = self.j2k_data[25, 35] + + def test_cannot_write_a_column(self): + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError): + j[:, 25, :] = self.j2k_data[:, :25, :] + + def test_cannot_write_a_band(self): + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError): + j[:, :, 0] = self.j2k_data[:, :, 0] + + def test_cannot_write_a_subarray(self): + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError): + j[:25, :45, :] = self.j2k_data[:25, :25, :] + + +class TestSliceProtocolRead(SliceProtocolBase): + def test_resolution_strides_cannot_differ(self): with self.assertRaises(IndexError): # Strides in x/y directions cannot differ. From b1cd14c6a5530ac37fdc4a151b3cb91c462da68c Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 19 Sep 2014 08:17:15 -0400 Subject: [PATCH 250/326] __setitem__ working for writing an entire image at once --- docs/source/how_do_i.rst | 4 ++-- glymur/jp2k.py | 16 ++++++++++++++++ glymur/test/test_jp2k.py | 24 ++++++++++++++++++------ 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index ea48652..a9da7c1 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -347,7 +347,7 @@ image isn't square. :: >>> alpha[mask] = 0 >>> rgba = np.concatenate((rgb, alpha), axis=2) >>> jp2 = Jp2k('tmp.jp2', 'wb') - >>> jp2.write(rgba) + >>> jp2[:] = rgba Next we need to specify what types of channels we have. The first three channels are color channels, but we identify the fourth as @@ -447,7 +447,7 @@ http://photojournal.jpl.nasa.gov/tiff/PIA17145.tif info JPEG 2000:: >>> image = skimage.io.imread('PIA17145.tif') >>> from glymur import Jp2k >>> jp2 = Jp2k('PIA17145.jp2', 'wb') - >>> jp2.write(image) + >>> jp2[:] = image Next you can extract the XMP metadata. diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 870a6ec..66b79c9 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -759,6 +759,22 @@ class Jp2k(Jp2kBox): return boxes + def __setitem__(self, index, data): + """ + Slicing protocol. + """ + if isinstance(index, slice) and ( + index.start == None and + index.stop == None and + index.step == None): + # Case of jp2[:] = data, i.e. write the entire image. + # + # Should have a slice object where start = stop = step = None + self.write(data) + else: + msg = "Images currently must be written entirely at once." + raise TypeError(msg) + def __getitem__(self, pargs): """ Slicing protocol. diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 90717ff..98f4607 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -75,38 +75,50 @@ class TestSliceProtocolBaseWrite(SliceProtocolBase): with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, 'wb') j[:] = self.j2k_data - expected = j.read() + actual = j.read() np.testing.assert_array_equal(actual, expected) + def test_cannot_write_with_non_default_single_slice(self): + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(TypeError): + j[slice(None, 0)] = self.j2k_data + with self.assertRaises(TypeError): + j[slice(0, None)] = self.j2k_data + with self.assertRaises(TypeError): + j[slice(0, 0, None)] = self.j2k_data + with self.assertRaises(TypeError): + j[slice(0, 640)] = self.j2k_data + def test_cannot_write_a_row(self): with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, 'wb') - with self.assertRaises(IOError): + with self.assertRaises(TypeError): j[5] = self.j2k_data def test_cannot_write_a_pixel(self): with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, 'wb') - with self.assertRaises(IOError): + with self.assertRaises(TypeError): j[25, 35] = self.j2k_data[25, 35] def test_cannot_write_a_column(self): with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, 'wb') - with self.assertRaises(IOError): + with self.assertRaises(TypeError): j[:, 25, :] = self.j2k_data[:, :25, :] def test_cannot_write_a_band(self): with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, 'wb') - with self.assertRaises(IOError): + with self.assertRaises(TypeError): j[:, :, 0] = self.j2k_data[:, :, 0] def test_cannot_write_a_subarray(self): with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, 'wb') - with self.assertRaises(IOError): + with self.assertRaises(TypeError): j[:25, :45, :] = self.j2k_data[:25, :25, :] From 3366812936435f6546a6eb87d3f84eee8ecc554e Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 19 Sep 2014 09:17:40 -0400 Subject: [PATCH 251/326] refactor of CME testing --- glymur/codestream.py | 2 +- glymur/test/test_opj_suite_dump.py | 318 +++++++++-------------------- 2 files changed, 99 insertions(+), 221 deletions(-) diff --git a/glymur/codestream.py b/glymur/codestream.py index 88babe5..a156513 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -1074,7 +1074,7 @@ class CMEsegment(Segment): 15444-1:2004 - Information technology -- JPEG 2000 image coding system: Core coding system """ - def __init__(self, rcme, ccme, length, offset): + def __init__(self, rcme, ccme, length=-1, offset=-1): Segment.__init__(self, marker_id='CME') self.rcme = rcme self.ccme = ccme diff --git a/glymur/test/test_opj_suite_dump.py b/glymur/test/test_opj_suite_dump.py index 60df1e7..2039295 100644 --- a/glymur/test/test_opj_suite_dump.py +++ b/glymur/test/test_opj_suite_dump.py @@ -33,8 +33,10 @@ import unittest import numpy as np -from glymur import Jp2k import glymur +from glymur import Jp2k +from glymur.codestream import CMEsegment +from glymur.core import RCME_ISO_8859_1, RCME_BINARY from .fixtures import OPJ_DATA_ROOT from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG @@ -66,6 +68,13 @@ class TestSuiteBase(unittest.TestCase): for cl in clist: self.assertIn(cl, box.compatibility_list) + def verifyCMEsegment(self, actual, expected): + """ + verify the fields of a CME (comment) segment + """ + self.assertEqual(actual.rcme, expected.rcme) + self.assertEqual(actual.ccme, expected.ccme) + def verifySizSegment(self, actual, expected): """ Verify the fields of the SIZ segment. @@ -289,12 +298,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[4].mantissa, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - # COM: comment - # Registration - self.assertEqual(c.segment[5].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[5].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") + pargs = RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode() + self.verifyCMEsegment(c.segment[5], CMEsegment(*pargs)) # One unknown marker self.assertEqual(c.segment[6].marker_id, '0xff30') @@ -379,26 +384,15 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[6].xcrg, (65424,)) self.assertEqual(c.segment[6].ycrg, (32558,)) - # COM: comment - # Registration - self.assertEqual(c.segment[7].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[7].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") + pargs = RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode() + self.verifyCMEsegment(c.segment[7], CMEsegment(*pargs)) - # COM: comment - # Registration - self.assertEqual(c.segment[8].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[8].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000," - + "2001 Algo Vision Technology") + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision Technology".encode()) + self.verifyCMEsegment(c.segment[8], CMEsegment(*pargs)) - # COM: comment - # Registration - self.assertEqual(c.segment[9].rcme, glymur.core.RCME_BINARY) - # Comment value - self.assertEqual(len(c.segment[9].ccme), 62) + pargs = (RCME_BINARY, c.segment[9].ccme) + self.verifyCMEsegment(c.segment[9], CMEsegment(*pargs)) # TLM (tile-part length) self.assertEqual(c.segment[10].ztlm, 0) @@ -497,12 +491,9 @@ class TestSuite(TestSuiteBase): 1845, 1868, 1925, 1925, 2007, 32, 32, 131, 2002, 2002, 1888]) - # COM: comment - # Registration - self.assertEqual(c.segment[6].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[6].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + self.verifyCMEsegment(c.segment[6], CMEsegment(*pargs)) # SOT: start of tile part self.assertEqual(c.segment[7].isot, 0) @@ -622,12 +613,9 @@ class TestSuite(TestSuiteBase): 9, 9, 10]) self.assertEqual(c.segment[7].mantissa, [0] * 19) - # COM: comment - # Registration - self.assertEqual(c.segment[8].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[8].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + self.verifyCMEsegment(c.segment[8], CMEsegment(*pargs)) # TLM (tile-part length) self.assertEqual(c.segment[9].ztlm, 0) @@ -811,12 +799,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[3].exponent, [14, 15, 15, 16, 15, 15, 16, 15, 15, 16]) - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Kakadu-3.0.7") + pargs = (RCME_ISO_8859_1, "Kakadu-3.0.7".encode()) + self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) # SOT: start of tile part self.assertEqual(c.segment[5].isot, 0) @@ -965,12 +949,8 @@ class TestSuite(TestSuiteBase): [11, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13]) - # COM: comment - # Registration - self.assertEqual(c.segment[9].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[9].ccme.decode('latin-1'), - "Kakadu-3.0.7") + pargs = (RCME_ISO_8859_1, "Kakadu-3.0.7".encode()) + self.verifyCMEsegment(c.segment[9], CMEsegment(*pargs)) # SOT: start of tile part self.assertEqual(c.segment[10].isot, 0) @@ -1023,12 +1003,8 @@ class TestSuite(TestSuiteBase): [16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 12, 12, 12, 11, 11, 12]) - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Kakadu-3.0.7") + pargs = (RCME_ISO_8859_1, "Kakadu-3.0.7".encode()) + self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) # SOT: start of tile part self.assertEqual(c.segment[5].isot, 0) @@ -1211,12 +1187,9 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[3].mantissa, [0]) self.assertEqual(c.segment[3].exponent, [8]) - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) # SOT: start of tile part self.assertEqual(c.segment[5].isot, 0) @@ -1279,12 +1252,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[3].exponent, [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") + pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) # SOT: start of tile part self.assertEqual(c.segment[5].isot, 0) @@ -1397,12 +1366,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[8].ppod, (glymur.core.RLCP, glymur.core.CPRL)) - # COM: comment - # Registration - self.assertEqual(c.segment[9].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[9].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") + pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + self.verifyCMEsegment(c.segment[9], CMEsegment(*pargs)) # SOT: start of tile part self.assertEqual(c.segment[10].isot, 0) @@ -1459,12 +1424,8 @@ class TestSuite(TestSuiteBase): [10, 11, 11, 12, 11, 11, 12, 11, 11, 12, 11, 11, 12, 11, 11, 12]) - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Kakadu-3.0.7") + pargs = (RCME_ISO_8859_1, "Kakadu-3.0.7".encode()) + self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) # SOT: start of tile part self.assertEqual(c.segment[5].isot, 0) @@ -1540,26 +1501,15 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[6].xcrg, (65424,)) self.assertEqual(c.segment[6].ycrg, (32558,)) - # COM: comment - # Registration - self.assertEqual(c.segment[7].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[7].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") + pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + self.verifyCMEsegment(c.segment[7], CMEsegment(*pargs)) - # COM: comment - # Registration - self.assertEqual(c.segment[8].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[8].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000," - + "2001 Algo Vision Technology") + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision Technology".encode()) + self.verifyCMEsegment(c.segment[8], CMEsegment(*pargs)) - # COM: comment - # Registration - self.assertEqual(c.segment[9].rcme, glymur.core.RCME_BINARY) - # Comment value - self.assertEqual(len(c.segment[9].ccme), 62) + pargs = (RCME_BINARY, c.segment[9].ccme) + self.verifyCMEsegment(c.segment[9], CMEsegment(*pargs)) # TLM: tile-part length self.assertEqual(c.segment[10].ztlm, 0) @@ -1733,12 +1683,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[4].exponent, [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - # COM: comment - # Registration - self.assertEqual(c.segment[5].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[5].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") + pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + self.verifyCMEsegment(c.segment[5], CMEsegment(*pargs)) # SOT: start of tile part self.assertEqual(c.segment[6].isot, 0) @@ -1835,12 +1781,8 @@ class TestSuite(TestSuiteBase): [14, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, 9, 9, 9, 9, 9, 9]) - # COM: comment - # Registration - self.assertEqual(c.segment[6].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[6].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") + pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + self.verifyCMEsegment(c.segment[6], CMEsegment(*pargs)) # SOT: start of tile part self.assertEqual(c.segment[7].isot, 0) @@ -1963,12 +1905,8 @@ class TestSuite(TestSuiteBase): [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - # COM: comment - # Registration - self.assertEqual(c.segment[8].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[8].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") + pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + self.verifyCMEsegment(c.segment[8], CMEsegment(*pargs)) # PPM: packed packet headers, main header self.assertEqual(c.segment[9].marker_id, 'PPM') @@ -2046,12 +1984,8 @@ class TestSuite(TestSuiteBase): 554, 546, 542, 635, 826, 667, 617, 606, 813, 586, 641, 654, 669, 623)) - # COM: comment - # Registration - self.assertEqual(c.segment[5].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[5].ccme.decode('latin-1'), - "Created by Aware, Inc.") + pargs = (RCME_ISO_8859_1, "Created by Aware, Inc.".encode()) + self.verifyCMEsegment(c.segment[5], CMEsegment(*pargs)) # SOT: start of tile part self.assertEqual(c.segment[6].isot, 0) @@ -2148,12 +2082,8 @@ class TestSuite(TestSuiteBase): [17, 17, 17, 17, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, 11, 11, 11, 11, 11, 11]) - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") + pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) # 225 consecutive PPM segments. zppm = [x.zppm for x in c.segment[5:230]] @@ -2223,12 +2153,8 @@ class TestSuite(TestSuiteBase): [14, 14, 14, 14, 13, 13, 13, 11, 11, 11, 11, 11, 11]) - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") + pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) # SOT: start of tile part self.assertEqual(c.segment[5].isot, 0) @@ -2318,12 +2244,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[4].mantissa, [0] * 4) self.assertEqual(c.segment[4].exponent, [8, 9, 9, 10]) - # COM: comment - # Registration - self.assertEqual(c.segment[5].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[5].ccme.decode('latin-1'), - "Creator: AV-J2K (c) 2000,2001 Algo Vision") + pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + self.verifyCMEsegment(c.segment[5], CMEsegment(*pargs)) # SOT: start of tile part self.assertEqual(c.segment[6].isot, 0) @@ -2449,12 +2371,8 @@ class TestSuite(TestSuiteBase): [18, 18, 18, 18, 17, 17, 17, 16, 16, 16, 14, 14, 14, 14, 14, 14]) - # COM: comment - # Registration - self.assertEqual(c.segment[8].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[8].ccme.decode('latin-1'), - "Created by OpenJPEG version 1.3.0") + pargs = (RCME_ISO_8859_1, "Created by OpenJPEG version 1.3.0".encode()) + self.verifyCMEsegment(c.segment[8], CMEsegment(*pargs)) # TLM (tile-part length) self.assertEqual(c.segment[9].ztlm, 0) @@ -2697,11 +2615,8 @@ class TestSuite(TestSuiteBase): [22, 22, 22, 22, 21, 21, 21, 20, 20, 20, 19, 19, 19, 18, 18, 18]) - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), "Kakadu-3.2") + pargs = (RCME_ISO_8859_1, "Kakadu-3.2".encode()) + self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) def test_NR_cthead1_dump(self): jfile = opj_data_file('input/nonregression/cthead1.j2k') @@ -2751,17 +2666,8 @@ class TestSuite(TestSuiteBase): [9, 10, 10, 11, 10, 10, 11, 10, 10, 11, 10, 10, 10, 9, 9, 10]) - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), "Kakadu-v6.3.1") - - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), "Kakadu-v6.3.1") + pargs = (RCME_ISO_8859_1, "Kakadu-v6.3.1".encode()) + self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) def test_NR_illegalcolortransform_dump(self): jfile = opj_data_file('input/nonregression/illegalcolortransform.j2k') @@ -2856,11 +2762,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[3].exponent, [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_BINARY) - # Comment value - self.assertEqual(len(c.segment[4].ccme), 36) + pargs = (RCME_BINARY, c.segment[4].ccme) + self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) def test_NR_kakadu_v4_4_openjpegv2_broken_dump(self): jfile = opj_data_file('input/nonregression/' @@ -2906,31 +2809,26 @@ class TestSuite(TestSuiteBase): [17, 18, 18, 19, 18, 18, 19, 18, 18, 19, 18, 18, 19, 18, 18, 19, 18, 18, 19, 18, 18, 19, 18, 18, 19]) - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), "Kakadu-v4.4") + pargs = (RCME_ISO_8859_1, "Kakadu-v4.4".encode()) + self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) + + ccme = "Kdu-Layer-Info: log_2{Delta-D(MSE)/[2^16*Delta-L(bytes)]}," + ccme += " L(bytes)\n" + ccme += " -65.4, 6.8e+004\n" + ccme += " -66.3, 1.0e+005\n" + ccme += " -67.3, 2.0e+005\n" + ccme += " -68.5, 4.1e+005\n" + ccme += " -69.0, 5.1e+005\n" + ccme += " -69.5, 5.9e+005\n" + ccme += " -69.7, 6.8e+005\n" + ccme += " -70.3, 8.2e+005\n" + ccme += " -70.8, 1.0e+006\n" + ccme += " -71.9, 1.4e+006\n" + ccme += " -73.8, 2.0e+006\n" + ccme += "-256.0, 3.7e+006\n" + pargs = (RCME_ISO_8859_1, ccme.encode()) + self.verifyCMEsegment(c.segment[5], CMEsegment(*pargs)) - # COM: comment - # Registration - self.assertEqual(c.segment[5].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - expected = "Kdu-Layer-Info: log_2{Delta-D(MSE)/[2^16*Delta-L(bytes)]}," - expected += " L(bytes)\n" - expected += " -65.4, 6.8e+004\n" - expected += " -66.3, 1.0e+005\n" - expected += " -67.3, 2.0e+005\n" - expected += " -68.5, 4.1e+005\n" - expected += " -69.0, 5.1e+005\n" - expected += " -69.5, 5.9e+005\n" - expected += " -69.7, 6.8e+005\n" - expected += " -70.3, 8.2e+005\n" - expected += " -70.8, 1.0e+006\n" - expected += " -71.9, 1.4e+006\n" - expected += " -73.8, 2.0e+006\n" - expected += "-256.0, 3.7e+006\n" - self.assertEqual(c.segment[5].ccme.decode('latin-1'), expected) def test_NR_MarkerIsNotCompliant_j2k_dump(self): jfile = opj_data_file('input/nonregression/MarkerIsNotCompliant.j2k') @@ -3241,12 +3139,8 @@ class TestSuite(TestSuiteBase): [18, 19, 19, 20, 19, 19, 20, 19, 19, 20, 19, 19, 20, 19, 19, 20]) - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Kakadu-2.0.2") + pargs = (RCME_ISO_8859_1, "Kakadu-2.0.2".encode()) + self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) def test_NR_test_lossless_j2k_dump(self): jfile = opj_data_file('input/nonregression/test_lossless.j2k') @@ -3295,12 +3189,8 @@ class TestSuite(TestSuiteBase): [12, 13, 13, 14, 13, 13, 14, 13, 13, 14, 13, 13, 14, 13, 13, 14]) - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "ClearCanvas DICOM OpenJPEG") + pargs = (RCME_ISO_8859_1, "ClearCanvas DICOM OpenJPEG".encode()) + self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) def test_NR_123_j2c_dump(self): jfile = opj_data_file('input/nonregression/123.j2c') @@ -3445,12 +3335,8 @@ class TestSuite(TestSuiteBase): [13, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13]) - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "DCP-Werkstatt") + pargs = (RCME_ISO_8859_1, "DCP-Werkstatt".encode()) + self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) def test_NR_issue104_jpxstream_dump(self): jfile = opj_data_file('input/nonregression/issue104_jpxstream.jp2') @@ -3691,12 +3577,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[3].exponent, [14] * 4 + [13] * 3 + [12] * 3 + [10] * 6) - # COM: comment - # Registration - self.assertEqual(c.segment[4].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[4].ccme.decode('latin-1'), - "Kakadu-v5.2.1") + pargs = (RCME_ISO_8859_1, "Kakadu-v5.2.1".encode()) + self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) def test_NR_mem_b2b86b74_2753_dump(self): jfile = opj_data_file('input/nonregression/mem-b2b86b74-2753.jp2') @@ -3928,12 +3810,8 @@ class TestSuiteWarns(TestSuiteBase): 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) - # COM: comment - # Registration - self.assertEqual(c.segment[2].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[2].ccme.decode('latin-1'), - "Creator: JasPer Vers)on 1.701.0") + pargs = RCME_ISO_8859_1, "Creator: JasPer Vers)on 1.701.0".encode() + self.verifyCMEsegment(c.segment[2], CMEsegment(*pargs)) # COD: Coding style default self.assertFalse(c.segment[3].scod & 2) # no sop From 82466f2b8056bd1a1174599ebef6e3839b659a9a Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 19 Sep 2014 13:59:45 -0400 Subject: [PATCH 252/326] refactor of test suite --- glymur/codestream.py | 6 +- glymur/test/fixtures.py | 119 +++ glymur/test/test_jp2box.py | 13 +- glymur/test/test_opj_suite.py | 89 +- glymur/test/test_opj_suite_dump.py | 1387 ++++----------------------- glymur/test/test_opj_suite_write.py | 681 +++---------- 6 files changed, 483 insertions(+), 1812 deletions(-) diff --git a/glymur/codestream.py b/glymur/codestream.py index a156513..c87c2d2 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -642,7 +642,7 @@ class Codestream(object): srgn = data[1] sprgn = data[2] - return RGNsegment(length, offset, crgn, srgn, sprgn) + return RGNsegment(crgn, srgn, sprgn, length, offset) def _parse_siz_segment(self, fptr): """Parse the SIZ segment. @@ -1463,7 +1463,7 @@ class RGNsegment(Segment): 15444-1:2004 - Information technology -- JPEG 2000 image coding system: Core coding system """ - def __init__(self, length, offset, crgn, srgn, sprgn): + def __init__(self, crgn, srgn, sprgn, length=-1, offset=-1): Segment.__init__(self, marker_id='RGN') self.length = length self.offset = offset @@ -1726,7 +1726,7 @@ class SOTsegment(Segment): 15444-1:2004 - Information technology -- JPEG 2000 image coding system: Core coding system """ - def __init__(self, isot, psot, tpsot, tnsot, length, offset): + def __init__(self, isot, psot, tpsot, tnsot, length=-1, offset=-1): Segment.__init__(self, marker_id='SOT') self.isot = isot self.psot = psot diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 9f9301b..98554d6 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -5,6 +5,7 @@ import os import re import sys import textwrap +import unittest import warnings import numpy as np @@ -24,6 +25,124 @@ elif re.match('1.[0-6]', six.__version__) is not None: WARNING_INFRASTRUCTURE_ISSUE = True WARNING_INFRASTRUCTURE_MSG = "Cannot use with this version of six" +class MetadataBase(unittest.TestCase): + """ + Base class for testing metadata. + + This class has helper routines defined for testing metadata so that it can + be subclassed and used easily. + """ + + def setUp(self): + pass + + def tearDown(self): + pass + + def verify_codeblock_style(self, actual, style): + """ + Verify the code-block style for SPcod and SPcoc parameters. + + This information is stored in a single byte. Please reference + Table A-17 in FCD15444-1 + """ + expected = 0 + if style[0]: + # Selective arithmetic coding bypass + expected |= 0x01 + if style[1]: + # Reset context probabilities + expected |= 0x02 + if style[2]: + # Termination on each coding pass + expected |= 0x04 + if style[3]: + # Vertically causal context + expected |= 0x08 + if style[4]: + # Predictable termination + expected |= 0x10 + if style[5]: + # Segmentation symbols + expected |= 0x20 + self.assertEqual(actual, expected) + + def verifySignatureBox(self, box): + """ + The signature box is a constant. + """ + self.assertEqual(box.signature, (13, 10, 135, 10)) + + def verify_filetype_box(self, actual, expected): + """ + All JP2 files should have a brand reading 'jp2 ' and just a single + entry in the compatibility list, also 'jp2 '. JPX files can have more + compatibility items. + """ + self.assertEqual(actual.brand, expected.brand) + self.assertEqual(actual.minor_version, expected.minor_version) + self.assertEqual(actual.minor_version, 0) + for cl in expected.compatibility_list: + self.assertIn(cl, actual.compatibility_list) + + def verifyRGNsegment(self, actual, expected): + """ + verify the fields of a RGN segment + """ + self.assertEqual(actual.crgn, expected.crgn) # 0 = component + self.assertEqual(actual.srgn, expected.srgn) # 0 = implicit + self.assertEqual(actual.sprgn, expected.sprgn) + + def verifySOTsegment(self, actual, expected): + """ + verify the fields of a SOT (start of tile) segment + """ + self.assertEqual(actual.isot, expected.isot) + self.assertEqual(actual.psot, expected.psot) + self.assertEqual(actual.tpsot, expected.tpsot) + self.assertEqual(actual.tnsot, expected.tnsot) + + def verifyCMEsegment(self, actual, expected): + """ + verify the fields of a CME (comment) segment + """ + self.assertEqual(actual.rcme, expected.rcme) + self.assertEqual(actual.ccme, expected.ccme) + + def verifySizSegment(self, actual, expected): + """ + Verify the fields of the SIZ segment. + """ + for field in ['rsiz', 'xsiz', 'ysiz', 'xosiz', 'yosiz', 'xtsiz', + 'ytsiz', 'xtosiz', 'ytosiz', 'bitdepth', 'xrsiz', 'yrsiz']: + self.assertEqual(getattr(actual, field), getattr(expected, field)) + + def verifyImageHeaderBox(self, box1, box2): + self.assertEqual(box1.height, box2.height) + self.assertEqual(box1.width, box2.width) + self.assertEqual(box1.num_components, box2.num_components) + self.assertEqual(box1.bits_per_component, box2.bits_per_component) + self.assertEqual(box1.signed, box2.signed) + self.assertEqual(box1.compression, box2.compression) + self.assertEqual(box1.colorspace_unknown, box2.colorspace_unknown) + self.assertEqual(box1.ip_provided, box2.ip_provided) + + def verifyColourSpecificationBox(self, actual, expected): + """ + Does not currently check icc profiles. + """ + self.assertEqual(actual.method, expected.method) + self.assertEqual(actual.precedence, expected.precedence) + self.assertEqual(actual.approximation, expected.approximation) + + if expected.colorspace is None: + self.assertIsNone(actual.colorspace) + self.assertIsNotNone(actual.icc_profile) + else: + self.assertEqual(actual.colorspace, expected.colorspace) + self.assertIsNone(actual.icc_profile) + + # The Python XMP Toolkit may be used for XMP UUIDs, but only if available and # if the version is at least 2.0.0. try: diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 3dcf704..378855c 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -35,8 +35,10 @@ from glymur.jp2box import JPEG2000SignatureBox from glymur.core import COLOR, OPACITY from glymur.core import RED, GREEN, BLUE, GREY, WHOLE_IMAGE -from .fixtures import opj_data_file -from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG +from .fixtures import ( + WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG, + MetadataBase +) try: FORMAT_CORPUS_DATA_ROOT = os.environ['FORMAT_CORPUS_DATA_ROOT'] @@ -1039,7 +1041,7 @@ class TestJp2Boxes(unittest.TestCase): j.box[5].offset + 8) -class TestRepr(unittest.TestCase): +class TestRepr(MetadataBase): """Tests for __repr__ methods.""" def test_default_jp2k(self): """Should be able to eval a JPEG2000SignatureBox""" @@ -1109,10 +1111,7 @@ class TestRepr(unittest.TestCase): # Test the representation instantiation. newbox = eval(repr(ftyp)) - self.assertTrue(isinstance(newbox, glymur.jp2box.FileTypeBox)) - self.assertEqual(newbox.brand, 'jp2 ') - self.assertEqual(newbox.minor_version, 0) - self.assertEqual(newbox.compatibility_list, ['jp2 ']) + self.verify_filetype_box(newbox, FileTypeBox()) def test_colourspecification_box(self): """Verify __repr__ method on colr box.""" diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 4519024..a033aae 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -33,12 +33,15 @@ import unittest import numpy as np -from glymur import Jp2k import glymur +from glymur import Jp2k +from glymur.jp2box import FileTypeBox, ImageHeaderBox, ColourSpecificationBox -from .fixtures import OPJ_DATA_ROOT -from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG -from .fixtures import mse, peak_tolerance, read_pgx, opj_data_file +from .fixtures import ( + OPJ_DATA_ROOT, MetadataBase, + WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG, + mse, peak_tolerance, read_pgx, opj_data_file +) @unittest.skipIf(OPJ_DATA_ROOT is None, @@ -298,7 +301,7 @@ class TestSuite(unittest.TestCase): @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) -class TestSuiteWarns(unittest.TestCase): +class TestSuiteWarns(MetadataBase): """ Identical setup to above, but these tests issue warnings. """ @@ -394,30 +397,13 @@ class TestSuiteWarns(unittest.TestCase): # Signature box. Check for corruption. self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + self.verify_filetype_box(jp2.box[1], FileTypeBox()) - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + expected = ImageHeaderBox(152, 203, num_components=3) + self.verifyImageHeaderBox(jp2.box[2].box[0], expected) - # Jp2 Header - # Image header - self.assertEqual(jp2.box[2].box[0].height, 152) - self.assertEqual(jp2.box[2].box[0].width, 203) - self.assertEqual(jp2.box[2].box[0].num_components, 3) - self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) - self.assertEqual(jp2.box[2].box[0].signed, False) - self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet - self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False) - self.assertEqual(jp2.box[2].box[0].ip_provided, False) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # not allowed? - self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) + expected = ColourSpecificationBox(colorspace=glymur.core.SRGB) + self.verifyColourSpecificationBox(jp2.box[2].box[1], expected) c = jp2.box[3].main_header @@ -425,32 +411,17 @@ class TestSuiteWarns(unittest.TestCase): expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] self.assertEqual(ids, expected) - # SIZ: Image and tile size - # Profile: - self.assertEqual(c.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual(c.segment[1].xsiz, 203) - self.assertEqual(c.segment[1].ysiz, 152) - # Reference grid offset - self.assertEqual((c.segment[1].xosiz, c.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((c.segment[1].xtsiz, c.segment[1].ytsiz), (203, 152)) - # Tile offset - self.assertEqual((c.segment[1].xtosiz, c.segment[1].ytosiz), (0, 0)) - # bitdepth - self.assertEqual(c.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(c.segment[1].signed, (False, False, False)) - # subsampling - self.assertEqual(list(zip(c.segment[1].xrsiz, c.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (203, 152), 'xyosiz': (0, 0), + 'xytsiz': (203, 152), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) - # COM: comment - # Registration - self.assertEqual(c.segment[2].rcme, glymur.core.RCME_ISO_8859_1) - # Comment value - self.assertEqual(c.segment[2].ccme.decode('latin-1'), - "Creator: JasPer Version 1.701.0") + pargs = (glymur.core.RCME_ISO_8859_1, + "Creator: JasPer Version 1.701.0".encode()) + self.verifyCMEsegment(c.segment[2], + glymur.codestream.CMEsegment(*pargs)) # COD: Coding style default self.assertFalse(c.segment[3].scod & 2) # no sop @@ -461,18 +432,8 @@ class TestSuiteWarns(unittest.TestCase): self.assertEqual(c.segment[3].spcod[4], 5) # level self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[3].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[3].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[3].spcod), 9) diff --git a/glymur/test/test_opj_suite_dump.py b/glymur/test/test_opj_suite_dump.py index 2039295..cab7732 100644 --- a/glymur/test/test_opj_suite_dump.py +++ b/glymur/test/test_opj_suite_dump.py @@ -35,83 +35,20 @@ import numpy as np import glymur from glymur import Jp2k -from glymur.codestream import CMEsegment +from glymur.codestream import CMEsegment, SOTsegment, RGNsegment from glymur.core import RCME_ISO_8859_1, RCME_BINARY +from glymur.jp2box import FileTypeBox -from .fixtures import OPJ_DATA_ROOT -from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG -from .fixtures import mse, peak_tolerance, read_pgx, opj_data_file +from .fixtures import ( + MetadataBase, OPJ_DATA_ROOT, + WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG, + mse, peak_tolerance, read_pgx, opj_data_file +) -class TestSuiteBase(unittest.TestCase): - - def setUp(self): - pass - - def tearDown(self): - pass - - def verifySignatureBox(self, box): - """ - The signature box is a constant. - """ - self.assertEqual(box.signature, (13, 10, 135, 10)) - - def verifyFileTypeBox(self, box, brand, clist): - """ - All JP2 files should have a brand reading 'jp2 ' and just a single - entry in the compatibility list, also 'jp2 '. JPX files can have more - compatibility items. - """ - self.assertEqual(box.brand, brand) - self.assertEqual(box.minor_version, 0) - for cl in clist: - self.assertIn(cl, box.compatibility_list) - - def verifyCMEsegment(self, actual, expected): - """ - verify the fields of a CME (comment) segment - """ - self.assertEqual(actual.rcme, expected.rcme) - self.assertEqual(actual.ccme, expected.ccme) - - def verifySizSegment(self, actual, expected): - """ - Verify the fields of the SIZ segment. - """ - for field in ['rsiz', 'xsiz', 'ysiz', 'xosiz', 'yosiz', 'xtsiz', - 'ytsiz', 'xtosiz', 'ytosiz', 'bitdepth', 'xrsiz', 'yrsiz']: - self.assertEqual(getattr(actual, field), getattr(expected, field)) - - def verifyImageHeaderBox(self, box1, box2): - self.assertEqual(box1.height, box2.height) - self.assertEqual(box1.width, box2.width) - self.assertEqual(box1.num_components, box2.num_components) - self.assertEqual(box1.bits_per_component, box2.bits_per_component) - self.assertEqual(box1.signed, box2.signed) - self.assertEqual(box1.compression, box2.compression) - self.assertEqual(box1.colorspace_unknown, box2.colorspace_unknown) - self.assertEqual(box1.ip_provided, box2.ip_provided) - - def verifyColourSpecificationBox(self, actual, expected): - """ - Does not currently check icc profiles. - """ - self.assertEqual(actual.method, expected.method) - self.assertEqual(actual.precedence, expected.precedence) - self.assertEqual(actual.approximation, expected.approximation) - - if expected.colorspace is None: - self.assertIsNone(actual.colorspace) - self.assertIsNotNone(actual.icc_profile) - else: - self.assertEqual(actual.colorspace, expected.colorspace) - self.assertIsNone(actual.icc_profile) - - @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") -class TestSuite(TestSuiteBase): +class TestSuite(MetadataBase): def setUp(self): pass @@ -130,7 +67,7 @@ class TestSuite(TestSuiteBase): self.assertEqual(ids, ['ihdr', 'colr']) self.verifySignatureBox(jp2.box[0]) - self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) + self.verify_filetype_box(jp2.box[1], FileTypeBox()) ihdr = glymur.jp2box.ImageHeaderBox(243, 720, num_components=3) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) @@ -159,18 +96,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (32, 128)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -215,26 +142,12 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[3].spcod[4], 3) # layers self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[3].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[3].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - # SOT: start of tile part - self.assertEqual(c.segment[4].isot, 0) - self.assertEqual(c.segment[4].psot, 7314) - self.assertEqual(c.segment[4].tpsot, 0) - self.assertEqual(c.segment[4].tnsot, 1) + self.verifySOTsegment(c.segment[4], SOTsegment(0, 7314, 0, 1)) def test_NR_p0_02_dump(self): jfile = opj_data_file('input/conformance/p0_02.j2k') @@ -254,18 +167,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 3) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertTrue(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertTrue(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, True, False, True, True]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) @@ -274,18 +177,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[3].spcoc[0], 3) # levels self.assertEqual(tuple(c.segment[3].code_block_size), (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[3].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcoc[3] & 0x08) - # Predictable termination - self.assertTrue(c.segment[3].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertTrue(c.segment[3].spcoc[3] & 0x0020) + self.verify_codeblock_style(c.segment[3].spcoc[3], + [False, False, True, False, True, True]) self.assertEqual(c.segment[3].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -304,11 +197,7 @@ class TestSuite(TestSuiteBase): # One unknown marker self.assertEqual(c.segment[6].marker_id, '0xff30') - # SOT: start of tile part - self.assertEqual(c.segment[7].isot, 0) - self.assertEqual(c.segment[7].psot, 6047) - self.assertEqual(c.segment[7].tpsot, 0) - self.assertEqual(c.segment[7].tnsot, 1) + self.verifySOTsegment(c.segment[7], SOTsegment(0, 6047, 0, 1)) # SOD: start of data # Just one. @@ -341,18 +230,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 1) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -399,16 +278,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[10].ttlm, (0, 1, 2, 3)) self.assertEqual(c.segment[10].ptlm, (4267, 2117, 4080, 2081)) - # SOT: start of tile part - self.assertEqual(c.segment[11].isot, 0) - self.assertEqual(c.segment[11].psot, 4267) - self.assertEqual(c.segment[11].tpsot, 0) - self.assertEqual(c.segment[11].tnsot, 1) - - # RGN: region of interest - self.assertEqual(c.segment[12].crgn, 0) - self.assertEqual(c.segment[12].srgn, 0) - self.assertEqual(c.segment[12].sprgn, 7) + self.verifySOTsegment(c.segment[11], SOTsegment(0, 4267, 0, 1)) + self.verifyRGNsegment(c.segment[12], RGNsegment(0, 0, 7)) # SOD: start of data # Just one. @@ -433,18 +304,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 6) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, True, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(c.segment[2].precinct_size, @@ -495,11 +356,7 @@ class TestSuite(TestSuiteBase): "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[6], CMEsegment(*pargs)) - # SOT: start of tile part - self.assertEqual(c.segment[7].isot, 0) - self.assertEqual(c.segment[7].psot, 264383) - self.assertEqual(c.segment[7].tpsot, 0) - self.assertEqual(c.segment[7].tnsot, 1) + self.verifySOTsegment(c.segment[7], SOTsegment(0, 264383, 0, 1)) # SOD: start of data # Just one. @@ -525,18 +382,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 6) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -546,18 +393,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[3].spcoc[0], 3) # levels self.assertEqual(tuple(c.segment[3].code_block_size), (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcoc[3] & 0x0020) + self.verify_codeblock_style(c.segment[3].spcoc[3], + [False, False, False, False, False, False]) self.assertEqual(c.segment[3].spcoc[4], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) @@ -566,18 +403,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[4].spcoc[0], 6) # levels self.assertEqual(tuple(c.segment[4].code_block_size), (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[4].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[4].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[4].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[4].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[4].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[4].spcoc[3] & 0x0020) + self.verify_codeblock_style(c.segment[4].spcoc[3], + [False, False, False, False, False, False]) self.assertEqual(c.segment[4].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -622,11 +449,7 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[9].ttlm, (0,)) self.assertEqual(c.segment[9].ptlm, (1310540,)) - # SOT: start of tile part - self.assertEqual(c.segment[10].isot, 0) - self.assertEqual(c.segment[10].psot, 1310540) - self.assertEqual(c.segment[10].tpsot, 0) - self.assertEqual(c.segment[10].tnsot, 1) + self.verifySOTsegment(c.segment[10], SOTsegment(0, 1310540, 0, 1)) # SOD: start of data # Just one. @@ -652,18 +475,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 6) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -721,36 +534,14 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[7].spcoc[0], 6) # levels self.assertEqual(tuple(c.segment[7].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[7].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[7].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[7].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[7].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[7].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[7].spcoc[3] & 0x0020) + self.verify_codeblock_style(c.segment[7].spcoc[3], + [False, False, False, False, False, False]) self.assertEqual(c.segment[7].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - # RGN: region of interest - self.assertEqual(c.segment[8].crgn, 0) # component - self.assertEqual(c.segment[8].srgn, 0) # implicit - self.assertEqual(c.segment[8].sprgn, 11) - - # SOT: start of tile part - self.assertEqual(c.segment[9].isot, 0) - self.assertEqual(c.segment[9].psot, 33582) - self.assertEqual(c.segment[9].tpsot, 0) - self.assertEqual(c.segment[9].tnsot, 1) - - # RGN: region of interest - self.assertEqual(c.segment[10].crgn, 0) # component - self.assertEqual(c.segment[10].srgn, 0) # implicit - self.assertEqual(c.segment[10].sprgn, 9) + self.verifyRGNsegment(c.segment[8], RGNsegment(0, 0, 11)) + self.verifySOTsegment(c.segment[9], SOTsegment(0, 33582, 0, 1)) + self.verifyRGNsegment(c.segment[10], RGNsegment(0, 0, 9)) # SOD: start of data # Just one. @@ -775,18 +566,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 3) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -802,11 +583,7 @@ class TestSuite(TestSuiteBase): pargs = (RCME_ISO_8859_1, "Kakadu-3.0.7".encode()) self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) - # SOT: start of tile part - self.assertEqual(c.segment[5].isot, 0) - self.assertEqual(c.segment[5].psot, 9951) - self.assertEqual(c.segment[5].tpsot, 0) - self.assertEqual(c.segment[5].tnsot, 0) # unknown + self.verifySOTsegment(c.segment[5], SOTsegment(0, 9951, 0, 0)) # POD: progression order change self.assertEqual(c.segment[6].rspod, (0,)) @@ -842,18 +619,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 7) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -863,18 +630,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[3].spcoc[0], 6) # levels self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcoc[3] & 0x0020) + self.verify_codeblock_style(c.segment[3].spcoc[3], + [False, False, False, False, False, False]) self.assertEqual(c.segment[3].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -883,18 +640,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[4].spcoc[0], 7) # levels self.assertEqual(tuple(c.segment[4].code_block_size), (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[4].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[4].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[4].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[4].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[4].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[4].spcoc[3] & 0x0020) + self.verify_codeblock_style(c.segment[4].spcoc[3], + [False, False, False, False, False, False]) self.assertEqual(c.segment[4].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -903,18 +650,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[5].spcoc[0], 8) # levels self.assertEqual(tuple(c.segment[5].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[5].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[5].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[5].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[5].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[5].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[5].spcoc[3] & 0x0020) + self.verify_codeblock_style(c.segment[5].spcoc[3], + [False, False, False, False, False, False]) self.assertEqual(c.segment[5].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -952,11 +689,7 @@ class TestSuite(TestSuiteBase): pargs = (RCME_ISO_8859_1, "Kakadu-3.0.7".encode()) self.verifyCMEsegment(c.segment[9], CMEsegment(*pargs)) - # SOT: start of tile part - self.assertEqual(c.segment[10].isot, 0) - self.assertEqual(c.segment[10].psot, 3820593) - self.assertEqual(c.segment[10].tpsot, 0) - self.assertEqual(c.segment[10].tnsot, 1) # unknown + self.verifySOTsegment(c.segment[10], SOTsegment(0, 3820593, 0, 1)) def test_NR_p0_09_dump(self): jfile = opj_data_file('input/conformance/p0_09.j2k') @@ -976,18 +709,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1006,11 +729,7 @@ class TestSuite(TestSuiteBase): pargs = (RCME_ISO_8859_1, "Kakadu-3.0.7".encode()) self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) - # SOT: start of tile part - self.assertEqual(c.segment[5].isot, 0) - self.assertEqual(c.segment[5].psot, 478) - self.assertEqual(c.segment[5].tpsot, 0) - self.assertEqual(c.segment[5].tnsot, 1) # unknown + self.verifySOTsegment(c.segment[5], SOTsegment(0, 478, 0, 1)) # SOD: start of data # Just one. @@ -1038,18 +757,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 3) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1062,88 +771,33 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[3].exponent, [11, 12, 12, 13, 12, 12, 13, 12, 12, 13]) - # SOT: start of tile part - self.assertEqual(c.segment[4].isot, 0) - self.assertEqual(c.segment[4].psot, 2453) - self.assertEqual(c.segment[4].tpsot, 0) - self.assertEqual(c.segment[4].tnsot, 0) + self.verifySOTsegment(c.segment[4], SOTsegment(0, 2453, 0, 0)) - # SOD: start of data self.assertEqual(c.segment[5].marker_id, 'SOD') + self.verifySOTsegment(c.segment[6], SOTsegment(1, 2403, 0, 0)) - # SOT: start of tile part - self.assertEqual(c.segment[6].isot, 1) - self.assertEqual(c.segment[6].psot, 2403) - self.assertEqual(c.segment[6].tpsot, 0) - self.assertEqual(c.segment[6].tnsot, 0) - - # SOD: start of data self.assertEqual(c.segment[7].marker_id, 'SOD') + self.verifySOTsegment(c.segment[8], SOTsegment(2, 2420, 0, 0)) - # SOT: start of tile part - self.assertEqual(c.segment[8].isot, 2) - self.assertEqual(c.segment[8].psot, 2420) - self.assertEqual(c.segment[8].tpsot, 0) - self.assertEqual(c.segment[8].tnsot, 0) - - # SOD: start of data self.assertEqual(c.segment[9].marker_id, 'SOD') + self.verifySOTsegment(c.segment[10], SOTsegment(3, 2472, 0, 0)) - # SOT: start of tile part - self.assertEqual(c.segment[10].isot, 3) - self.assertEqual(c.segment[10].psot, 2472) - self.assertEqual(c.segment[10].tpsot, 0) - self.assertEqual(c.segment[10].tnsot, 0) - - # SOD: start of data self.assertEqual(c.segment[11].marker_id, 'SOD') + self.verifySOTsegment(c.segment[12], SOTsegment(0, 1043, 1, 2)) - # SOT: start of tile part - self.assertEqual(c.segment[12].isot, 0) - self.assertEqual(c.segment[12].psot, 1043) - self.assertEqual(c.segment[12].tpsot, 1) - self.assertEqual(c.segment[12].tnsot, 2) - - # SOD: start of data self.assertEqual(c.segment[13].marker_id, 'SOD') + self.verifySOTsegment(c.segment[14], SOTsegment(1, 1101, 1, 2)) - # SOT: start of tile part - self.assertEqual(c.segment[14].isot, 1) - self.assertEqual(c.segment[14].psot, 1101) - self.assertEqual(c.segment[14].tpsot, 1) - self.assertEqual(c.segment[14].tnsot, 2) - - # SOD: start of data self.assertEqual(c.segment[15].marker_id, 'SOD') + self.verifySOTsegment(c.segment[16], SOTsegment(3, 1054, 1, 2)) - # SOT: start of tile part - self.assertEqual(c.segment[16].isot, 3) - self.assertEqual(c.segment[16].psot, 1054) - self.assertEqual(c.segment[16].tpsot, 1) - self.assertEqual(c.segment[16].tnsot, 2) - - # SOD: start of data self.assertEqual(c.segment[17].marker_id, 'SOD') + self.verifySOTsegment(c.segment[18], SOTsegment(2, 14, 1, 0)) - # SOT: start of tile part - self.assertEqual(c.segment[18].isot, 2) - self.assertEqual(c.segment[18].psot, 14) - self.assertEqual(c.segment[18].tpsot, 1) - self.assertEqual(c.segment[18].tnsot, 0) - - # SOD: start of data self.assertEqual(c.segment[19].marker_id, 'SOD') + self.verifySOTsegment(c.segment[20], SOTsegment(2, 1089, 2, 0)) - # SOT: start of tile part - self.assertEqual(c.segment[20].isot, 2) - self.assertEqual(c.segment[20].psot, 1089) - self.assertEqual(c.segment[20].tpsot, 2) - self.assertEqual(c.segment[20].tnsot, 0) - - # SOD: start of data self.assertEqual(c.segment[21].marker_id, 'SOD') - - # EOC: end of codestream self.assertEqual(c.segment[22].marker_id, 'EOC') def test_NR_p0_11_dump(self): @@ -1164,18 +818,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 0) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertTrue(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, True]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(c.segment[2].precinct_size, [(128, 2)]) @@ -1191,11 +835,7 @@ class TestSuite(TestSuiteBase): "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) - # SOT: start of tile part - self.assertEqual(c.segment[5].isot, 0) - self.assertEqual(c.segment[5].psot, 118) - self.assertEqual(c.segment[5].tpsot, 0) - self.assertEqual(c.segment[5].tnsot, 1) + self.verifySOTsegment(c.segment[5], SOTsegment(0, 118, 0, 1)) # SOD: start of data self.assertEqual(c.segment[6].marker_id, 'SOD') @@ -1228,18 +868,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 3) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, True, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1255,11 +885,7 @@ class TestSuite(TestSuiteBase): pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) - # SOT: start of tile part - self.assertEqual(c.segment[5].isot, 0) - self.assertEqual(c.segment[5].psot, 162) - self.assertEqual(c.segment[5].tpsot, 0) - self.assertEqual(c.segment[5].tnsot, 1) + self.verifySOTsegment(c.segment[5], SOTsegment(0, 162, 0, 1)) # SOD: start of data self.assertEqual(c.segment[6].marker_id, 'SOD') @@ -1291,18 +917,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 1) # mct self.assertEqual(c.segment[2].spcod[4], 1) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertTrue(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, True, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1311,18 +927,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[3].ccoc, 2) self.assertEqual(c.segment[3].spcoc[0], 1) # levels self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcoc[3] & 0x0020) + self.verify_codeblock_style(c.segment[3].spcoc[3], + [False, False, False, False, False, False]) self.assertEqual(c.segment[3].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -1352,10 +958,7 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[6].exponent, [9, 10, 10, 11]) self.assertEqual(c.segment[6].mantissa, [0, 0, 0, 0]) - # RGN: region of interest - self.assertEqual(c.segment[7].crgn, 3) - self.assertEqual(c.segment[7].srgn, 0) - self.assertEqual(c.segment[7].sprgn, 11) + self.verifyRGNsegment(c.segment[7], RGNsegment(3, 0, 11)) # POD: progression order change self.assertEqual(c.segment[8].rspod, (0, 0)) @@ -1369,11 +972,7 @@ class TestSuite(TestSuiteBase): pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[9], CMEsegment(*pargs)) - # SOT: start of tile part - self.assertEqual(c.segment[10].isot, 0) - self.assertEqual(c.segment[10].psot, 1537) - self.assertEqual(c.segment[10].tpsot, 0) - self.assertEqual(c.segment[10].tnsot, 1) + self.verifySOTsegment(c.segment[10], SOTsegment(0, 1537, 0, 1)) # SOD: start of data self.assertEqual(c.segment[11].marker_id, 'SOD') @@ -1399,18 +998,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 1) # mct self.assertEqual(c.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1427,16 +1016,8 @@ class TestSuite(TestSuiteBase): pargs = (RCME_ISO_8859_1, "Kakadu-3.0.7".encode()) self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) - # SOT: start of tile part - self.assertEqual(c.segment[5].isot, 0) - self.assertEqual(c.segment[5].psot, 1528) - self.assertEqual(c.segment[5].tpsot, 0) - self.assertEqual(c.segment[5].tnsot, 1) - - # SOD: start of data + self.verifySOTsegment(c.segment[5], SOTsegment(0, 1528, 0, 1)) self.assertEqual(c.segment[6].marker_id, 'SOD') - - # EOC: end of codestream self.assertEqual(c.segment[7].marker_id, 'EOC') def test_NR_p0_15_dump(self): @@ -1457,18 +1038,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 0) # mct self.assertEqual(c.segment[2].spcod[4], 1) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1516,49 +1087,30 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[10].ttlm, (0, 1, 2, 3)) self.assertEqual(c.segment[10].ptlm, (4267, 2117, 4080, 2081)) - # SOT: start of tile part - self.assertEqual(c.segment[11].isot, 0) - self.assertEqual(c.segment[11].psot, 4267) - self.assertEqual(c.segment[11].tpsot, 0) - self.assertEqual(c.segment[11].tnsot, 1) + self.verifySOTsegment(c.segment[11], SOTsegment(0, 4267, 0, 1)) - # RGN: region of interest - self.assertEqual(c.segment[12].crgn, 0) - self.assertEqual(c.segment[12].srgn, 0) - self.assertEqual(c.segment[12].sprgn, 7) + self.verifyRGNsegment(c.segment[12], RGNsegment(0, 0, 7)) # SOD: start of data self.assertEqual(c.segment[13].marker_id, 'SOD') # 16 SOP markers would be here if we were looking for them - # SOT: start of tile part - self.assertEqual(c.segment[31].isot, 1) - self.assertEqual(c.segment[31].psot, 2117) - self.assertEqual(c.segment[31].tpsot, 0) - self.assertEqual(c.segment[31].tnsot, 1) + self.verifySOTsegment(c.segment[31], SOTsegment(1, 2117, 0, 1)) # SOD: start of data self.assertEqual(c.segment[32].marker_id, 'SOD') # 16 SOP markers would be here if we were looking for them - # SOT: start of tile part - self.assertEqual(c.segment[49].isot, 2) - self.assertEqual(c.segment[49].psot, 4080) - self.assertEqual(c.segment[49].tpsot, 0) - self.assertEqual(c.segment[49].tnsot, 1) + self.verifySOTsegment(c.segment[49], SOTsegment(2, 4080, 0, 1)) # SOD: start of data self.assertEqual(c.segment[50].marker_id, 'SOD') # 16 SOP markers would be here if we were looking for them - # SOT: start of tile part - self.assertEqual(c.segment[67].isot, 3) - self.assertEqual(c.segment[67].psot, 2081) - self.assertEqual(c.segment[67].tpsot, 0) - self.assertEqual(c.segment[67].tnsot, 1) + self.verifySOTsegment(c.segment[67], SOTsegment(3, 2081, 0, 1)) # SOD: start of data self.assertEqual(c.segment[68].marker_id, 'SOD') @@ -1586,18 +1138,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 0) # mct self.assertEqual(c.segment[2].spcod[4], 3) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1610,11 +1152,7 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[3].exponent, [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - # SOT: start of tile part - self.assertEqual(c.segment[4].isot, 0) - self.assertEqual(c.segment[4].psot, 7331) - self.assertEqual(c.segment[4].tpsot, 0) - self.assertEqual(c.segment[4].tnsot, 1) + self.verifySOTsegment(c.segment[4], SOTsegment(0, 7331, 0, 1)) # SOD: start of data self.assertEqual(c.segment[5].marker_id, 'SOD') @@ -1640,18 +1178,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 0) # mct self.assertEqual(c.segment[2].spcod[4], 3) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertTrue(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertTrue(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, True, False, True, True]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1660,18 +1188,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[3].ccoc, 0) self.assertEqual(c.segment[3].spcoc[0], 3) # level self.assertEqual(tuple(c.segment[3].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[3].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcoc[3] & 0x08) - # Predictable termination - self.assertTrue(c.segment[3].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertTrue(c.segment[3].spcoc[3] & 0x0020) + self.verify_codeblock_style(c.segment[3].spcoc[3], + [False, False, True, False, True, True]) self.assertEqual(c.segment[3].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -1686,11 +1204,7 @@ class TestSuite(TestSuiteBase): pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[5], CMEsegment(*pargs)) - # SOT: start of tile part - self.assertEqual(c.segment[6].isot, 0) - self.assertEqual(c.segment[6].psot, 4627) - self.assertEqual(c.segment[6].tpsot, 0) - self.assertEqual(c.segment[6].tnsot, 1) + self.verifySOTsegment(c.segment[6], SOTsegment(0, 4627, 0, 1)) # SOD: start of data self.assertEqual(c.segment[7].marker_id, 'SOD') @@ -1723,18 +1237,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 6) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertTrue(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertTrue(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, True, False, True, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(c.segment[2].precinct_size, @@ -1784,11 +1288,7 @@ class TestSuite(TestSuiteBase): pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[6], CMEsegment(*pargs)) - # SOT: start of tile part - self.assertEqual(c.segment[7].isot, 0) - self.assertEqual(c.segment[7].psot, 262838) - self.assertEqual(c.segment[7].tpsot, 0) - self.assertEqual(c.segment[7].tnsot, 1) + self.verifySOTsegment(c.segment[7], SOTsegment(0, 262838, 0, 1)) # PPT: packed packet headers, tile-part header self.assertEqual(c.segment[8].marker_id, 'PPT') @@ -1819,18 +1319,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 0) # mct self.assertEqual(c.segment[2].spcod[4], 6) # level self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertTrue(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [True, False, True, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1839,18 +1329,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[3].ccoc, 1) self.assertEqual(c.segment[3].spcoc[0], 3) # level self.assertEqual(tuple(c.segment[3].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertTrue(c.segment[3].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[3].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcoc[3] & 0x0020) + self.verify_codeblock_style(c.segment[3].spcoc[3], + [True, False, True, False, False, False]) self.assertEqual(c.segment[3].spcoc[4], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) @@ -1858,18 +1338,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[4].ccoc, 3) self.assertEqual(c.segment[4].spcoc[0], 6) # level self.assertEqual(tuple(c.segment[4].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertTrue(c.segment[4].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[4].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertTrue(c.segment[4].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[4].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[4].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[4].spcoc[3] & 0x0020) + self.verify_codeblock_style(c.segment[4].spcoc[3], + [True, False, True, False, False, False]) self.assertEqual(c.segment[4].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -1917,11 +1387,7 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[10].ttlm, (0,)) self.assertEqual(c.segment[10].ptlm, (1366780,)) - # SOT: start of tile part - self.assertEqual(c.segment[11].isot, 0) - self.assertEqual(c.segment[11].psot, 1366780) - self.assertEqual(c.segment[11].tpsot, 0) - self.assertEqual(c.segment[11].tnsot, 1) + self.verifySOTsegment(c.segment[11], SOTsegment(0, 1366780, 0, 1)) # SOD: start of data self.assertEqual(c.segment[12].marker_id, 'SOD') @@ -1947,18 +1413,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 0) # mct self.assertEqual(c.segment[2].spcod[4], 3) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1987,20 +1443,12 @@ class TestSuite(TestSuiteBase): pargs = (RCME_ISO_8859_1, "Created by Aware, Inc.".encode()) self.verifyCMEsegment(c.segment[5], CMEsegment(*pargs)) - # SOT: start of tile part - self.assertEqual(c.segment[6].isot, 0) - self.assertEqual(c.segment[6].psot, 350) - self.assertEqual(c.segment[6].tpsot, 0) - self.assertEqual(c.segment[6].tnsot, 1) + self.verifySOTsegment(c.segment[6], SOTsegment(0, 350, 0, 1)) # SOD: start of data self.assertEqual(c.segment[7].marker_id, 'SOD') - # SOT: start of tile part - self.assertEqual(c.segment[8].isot, 1) - self.assertEqual(c.segment[8].psot, 356) - self.assertEqual(c.segment[8].tpsot, 0) - self.assertEqual(c.segment[8].tnsot, 1) + self.verifySOTsegment(c.segment[8], SOTsegment(1, 356, 0, 1)) # QCD: Quantization default # quantization type @@ -2015,11 +1463,7 @@ class TestSuite(TestSuiteBase): # SOD: start of data self.assertEqual(c.segment[10].marker_id, 'SOD') - # SOT: start of tile part - self.assertEqual(c.segment[11].isot, 2) - self.assertEqual(c.segment[11].psot, 402) - self.assertEqual(c.segment[11].tpsot, 0) - self.assertEqual(c.segment[11].tnsot, 1) + self.verifySOTsegment(c.segment[11], SOTsegment(2, 402, 0, 1)) # and so on @@ -2056,18 +1500,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 1) # mct self.assertEqual(c.segment[2].spcod[4], 7) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 8)) # cblk - # Selective arithmetic coding bypass - self.assertTrue(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertTrue(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertTrue(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [True, False, False, True, True, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(c.segment[2].precinct_size, [(16, 16)] * 8) @@ -2089,11 +1523,7 @@ class TestSuite(TestSuiteBase): zppm = [x.zppm for x in c.segment[5:230]] self.assertEqual(zppm, list(range(225))) - # SOT: start of tile part - self.assertEqual(c.segment[230].isot, 0) - self.assertEqual(c.segment[230].psot, 580) - self.assertEqual(c.segment[230].tpsot, 0) - self.assertEqual(c.segment[230].tnsot, 1) + self.verifySOTsegment(c.segment[230], SOTsegment(0, 580, 0, 1)) # 225 total SOT segments isot = [x.isot for x in c.segment if x.marker_id == 'SOT'] @@ -2126,18 +1556,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 1) # mct self.assertEqual(c.segment[2].spcod[4], 4) # level self.assertEqual(tuple(c.segment[2].code_block_size), (32, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertTrue(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertTrue(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, True, False, True]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2156,11 +1576,7 @@ class TestSuite(TestSuiteBase): pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) - # SOT: start of tile part - self.assertEqual(c.segment[5].isot, 0) - self.assertEqual(c.segment[5].psot, 349) - self.assertEqual(c.segment[5].tpsot, 0) - self.assertEqual(c.segment[5].tnsot, 1) + self.verifySOTsegment(c.segment[5], SOTsegment(0, 349, 0, 1)) # PPT: packed packet headers, tile-part header self.assertEqual(c.segment[6].marker_id, 'PPT') @@ -2201,18 +1617,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 0) # mct self.assertEqual(c.segment[2].spcod[4], 1) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(c.segment[2].precinct_size, [(1, 1), (2, 2)]) @@ -2221,18 +1627,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[3].ccoc, 1) self.assertEqual(c.segment[3].spcoc[0], 1) # level self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcoc[3] & 0x0020) + self.verify_codeblock_style(c.segment[3].spcoc[3], + [False, False, False, False, False, False]) self.assertEqual(c.segment[3].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(c.segment[3].precinct_size, [(2, 2), (4, 4)]) @@ -2247,11 +1643,7 @@ class TestSuite(TestSuiteBase): pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[5], CMEsegment(*pargs)) - # SOT: start of tile part - self.assertEqual(c.segment[6].isot, 0) - self.assertEqual(c.segment[6].psot, 434) - self.assertEqual(c.segment[6].tpsot, 0) - self.assertEqual(c.segment[6].tnsot, 1) + self.verifySOTsegment(c.segment[6], SOTsegment(0, 434, 0, 1)) # scads of SOP, EPH segments @@ -2279,18 +1671,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 1) # mct self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(c.segment[2].precinct_size[0], (128, 128)) @@ -2311,18 +1693,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[4].ccoc, 1) self.assertEqual(c.segment[4].spcoc[0], 5) # level self.assertEqual(tuple(c.segment[4].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[4].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[4].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[4].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[4].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[4].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[4].spcoc[3] & 0x0020) + self.verify_codeblock_style(c.segment[4].spcoc[3], + [False, False, False, False, False, False]) self.assertEqual(c.segment[4].spcoc[4], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) @@ -2343,18 +1715,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[6].ccoc, 2) self.assertEqual(c.segment[6].spcoc[0], 5) # level self.assertEqual(tuple(c.segment[6].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[6].spcoc[3] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[6].spcoc[3] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[6].spcoc[3] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[6].spcoc[3] & 0x08) - # Predictable termination - self.assertFalse(c.segment[6].spcoc[3] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[6].spcoc[3] & 0x0020) + self.verify_codeblock_style(c.segment[6].spcoc[3], + [False, False, False, False, False, False]) self.assertEqual(c.segment[6].spcoc[4], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) @@ -2412,18 +1774,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 1) # mct self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(c.segment[2].precinct_size, @@ -2455,18 +1807,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 0) # mct self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2494,18 +1836,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 0) # mct self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2541,18 +1873,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 0) # mct self.assertEqual(c.segment[2].spcod[4], 11) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2588,18 +1910,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 0) # mct self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2641,18 +1953,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 0) # mct self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2692,18 +1994,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 1) # mct self.assertEqual(c.segment[2].spcod[4], 11) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2738,18 +2030,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 1) # mct self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2785,18 +2067,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 0) # mct self.assertEqual(c.segment[2].spcod[4], 8) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2849,18 +2121,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 0) # mct self.assertEqual(c.segment[2].spcod[4], 11) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2893,18 +2155,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 1) # mct self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2935,18 +2187,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 1) # mct self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2977,18 +2219,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 1) # mct self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -3023,18 +2255,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 0) # mct self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -3069,18 +2291,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 0) # mct self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -3115,18 +2327,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 0) # mct self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -3165,18 +2367,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[3], 0) # mct self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -3216,18 +2408,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 11) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -3263,18 +2445,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 11) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -3310,18 +2482,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(c.segment[2].precinct_size, @@ -3349,13 +2511,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(ids, ['ihdr', 'colr', 'pclr', 'cmap']) self.verifySignatureBox(jp2.box[0]) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jpxb') - self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') + self.verify_filetype_box(jp2.box[1], + FileTypeBox(compatibility_list=['jp2 ', 'jpxb', 'jpx '])) # Reader requirements talk. # unrestricted jpeg 2000 part 1 @@ -3400,18 +2557,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -3433,13 +2580,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(ids, ['ihdr', 'colr']) self.verifySignatureBox(jp2.box[0]) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jpxb') - self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') + self.verify_filetype_box(jp2.box[1], + FileTypeBox(compatibility_list=['jp2 ', 'jpxb', 'jpx '])) # Reader requirements talk. # unrestricted jpeg 2000 part 1 @@ -3475,18 +2617,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -3511,11 +2643,7 @@ class TestSuite(TestSuiteBase): self.assertEqual(ids, ['resd']) self.verifySignatureBox(jp2.box[0]) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + self.verify_filetype_box(jp2.box[1], FileTypeBox()) ihdr = glymur.jp2box.ImageHeaderBox(135, 135, num_components=2, colorspace_unknown=True) @@ -3552,18 +2680,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -3591,13 +2709,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(ids, ['ihdr', 'colr', 'pclr', 'cmap']) self.verifySignatureBox(jp2.box[0]) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jpxb') - self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') + self.verify_filetype_box(jp2.box[1], + FileTypeBox(compatibility_list=['jp2 ', 'jpxb', 'jpx '])) # Reader requirements talk. # unrestricted jpeg 2000 part 1 @@ -3645,18 +2758,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -3678,11 +2781,7 @@ class TestSuite(TestSuiteBase): self.assertEqual(ids, ['ihdr', 'colr']) self.verifySignatureBox(jp2.box[0]) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + self.verify_filetype_box(jp2.box[1], FileTypeBox()) ihdr = glymur.jp2box.ImageHeaderBox(576, 766, num_components=3) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) @@ -3711,18 +2810,8 @@ class TestSuite(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (32, 128)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -3747,7 +2836,7 @@ class TestSuite(TestSuiteBase): @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") -class TestSuiteWarns(TestSuiteBase): +class TestSuiteWarns(MetadataBase): @unittest.skipIf(re.match("1.5|2.0.0", glymur.version.openjpeg_version), "Test not passing on 1.5, 2.0: not introduced until 2.x") @@ -3790,7 +2879,7 @@ class TestSuiteWarns(TestSuiteBase): self.assertEqual(ids, ['ihdr', 'colr']) self.verifySignatureBox(jp2.box[0]) - self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) + self.verify_filetype_box(jp2.box[1], FileTypeBox()) ihdr = glymur.jp2box.ImageHeaderBox(152, 203, num_components=3) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) @@ -3822,18 +2911,8 @@ class TestSuiteWarns(TestSuiteBase): self.assertEqual(c.segment[3].spcod[4], 5) # level self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[3].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[3].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[3].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[3].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[3].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[3].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[3].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[3].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[3].spcod), 9) @@ -3891,7 +2970,7 @@ class TestSuiteWarns(TestSuiteBase): self.assertEqual(ids, ['ihdr', 'colr']) self.verifySignatureBox(jp2.box[0]) - self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) + self.verify_filetype_box(jp2.box[1], FileTypeBox()) # XML box tags = [x.tag for x in jp2.box[2].xml.getroot()] @@ -3924,7 +3003,7 @@ class TestSuiteWarns(TestSuiteBase): self.assertEqual(ids, ['ihdr', 'colr', 'cdef']) self.verifySignatureBox(jp2.box[0]) - self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) + self.verify_filetype_box(jp2.box[1], FileTypeBox()) ihdr = glymur.jp2box.ImageHeaderBox(640, 480, num_components=3) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) @@ -3955,7 +3034,7 @@ class TestSuiteWarns(TestSuiteBase): self.assertEqual(ids, ['ihdr', 'colr']) self.verifySignatureBox(jp2.box[0]) - self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) + self.verify_filetype_box(jp2.box[1], FileTypeBox()) ihdr = glymur.jp2box.ImageHeaderBox(640, 480, num_components=3) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) @@ -3987,7 +3066,7 @@ class TestSuiteWarns(TestSuiteBase): self.assertEqual(ids, ['ihdr', 'colr']) self.verifySignatureBox(jp2.box[0]) - self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) + self.verify_filetype_box(jp2.box[1], FileTypeBox()) ihdr = glymur.jp2box.ImageHeaderBox(512, 768) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) @@ -4015,7 +3094,9 @@ class TestSuiteWarns(TestSuiteBase): self.assertEqual(ids, ['ihdr', 'colr', 'colr']) self.verifySignatureBox(jp2.box[0]) - self.verifyFileTypeBox(jp2.box[1], 'jpx ', ['jp2 ', 'jpx ', 'jpxb']) + expected = FileTypeBox( + brand='jpx ', compatibility_list=['jp2 ', 'jpx ', 'jpxb']) + self.verify_filetype_box(jp2.box[1], expected) ihdr = glymur.jp2box.ImageHeaderBox(512, 768, num_components=3) self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) @@ -4038,7 +3119,7 @@ class TestSuiteWarns(TestSuiteBase): self.assertEqual(ids, ['ihdr', 'colr']) self.verifySignatureBox(jp2.box[0]) - self.verifyFileTypeBox(jp2.box[1], 'jp2 ', ['jp2 ']) + self.verify_filetype_box(jp2.box[1], FileTypeBox()) ihdr = glymur.jp2box.ImageHeaderBox(512, 768, bits_per_component=12) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) @@ -4096,11 +3177,7 @@ class TestSuiteWarns(TestSuiteBase): self.assertEqual(ids, ['ihdr', 'colr']) self.verifySignatureBox(jp2.box[0]) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) + self.verify_filetype_box(jp2.box[1], FileTypeBox()) ihdr = glymur.jp2box.ImageHeaderBox(400, 700) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) @@ -4138,11 +3215,7 @@ class TestSuiteWarns(TestSuiteBase): self.assertEqual(ids, ['ihdr', 'pclr', 'cmap', 'colr']) self.verifySignatureBox(jp2.box[0]) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) + self.verify_filetype_box(jp2.box[1], FileTypeBox()) ihdr = glymur.jp2box.ImageHeaderBox(512, 768) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) @@ -4183,11 +3256,7 @@ class TestSuiteWarns(TestSuiteBase): self.assertEqual(ids, ['ihdr', 'colr']) self.verifySignatureBox(jp2.box[0]) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + self.verify_filetype_box(jp2.box[1], FileTypeBox()) ihdr = glymur.jp2box.ImageHeaderBox(200, 200, num_components=3, colorspace_unknown=True) @@ -4219,18 +3288,8 @@ class TestSuiteWarns(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -4252,11 +3311,7 @@ class TestSuiteWarns(TestSuiteBase): self.assertEqual(ids, ['ihdr', 'colr']) self.verifySignatureBox(jp2.box[0]) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + self.verify_filetype_box(jp2.box[1], FileTypeBox()) ihdr = glymur.jp2box.ImageHeaderBox(117, 117, num_components=4) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) @@ -4291,18 +3346,8 @@ class TestSuiteWarns(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -4327,11 +3372,7 @@ class TestSuiteWarns(TestSuiteBase): self.assertEqual(ids, ['ihdr', 'colr']) self.verifySignatureBox(jp2.box[0]) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jp2 ') - self.assertEqual(jp2.box[1].minor_version, 0) - self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + self.verify_filetype_box(jp2.box[1], FileTypeBox()) ihdr = glymur.jp2box.ImageHeaderBox(117, 117, num_components=4) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) @@ -4366,18 +3407,8 @@ class TestSuiteWarns(TestSuiteBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk - # Selective arithmetic coding bypass - self.assertFalse(c.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(c.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(c.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(c.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(c.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(c.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 53b71cb..fa719fc 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -27,90 +27,37 @@ from . import fixtures from glymur import Jp2k import glymur -class CinemaBase(unittest.TestCase): +class CinemaBase(fixtures.MetadataBase): + + def verify_cinema_cod(self, cod_segment): + + self.assertFalse(cod_segment.scod & 2) # no sop + self.assertFalse(cod_segment.scod & 4) # no eph + self.assertEqual(cod_segment.spcod[0], glymur.core.CPRL) + self.assertEqual(cod_segment.layers, 1) + self.assertEqual(cod_segment.spcod[3], 1) # mct + self.assertEqual(cod_segment.spcod[4], 5) # levels + self.assertEqual(tuple(cod_segment.code_block_size), (32, 32)) # cblksz def check_cinema4k_codestream(self, codestream, image_size): - """Common out for cinema2k tests.""" - # SIZ: Image and tile size - # Profile: "3" means cinema2K - self.assertEqual(codestream.segment[1].rsiz, 4) - # Reference grid size - self.assertEqual((codestream.segment[1].xsiz, - codestream.segment[1].ysiz), - image_size) - # Reference grid offset - self.assertEqual((codestream.segment[1].xosiz, - codestream.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((codestream.segment[1].xtsiz, - codestream.segment[1].ytsiz), - image_size) - # Tile offset - self.assertEqual((codestream.segment[1].xtosiz, - codestream.segment[1].ytosiz), - (0, 0)) - # bitdepth - self.assertEqual(codestream.segment[1].bitdepth, (12, 12, 12)) - # signed - self.assertEqual(codestream.segment[1].signed, - (False, False, False)) - # subsampling - self.assertEqual(list(zip(codestream.segment[1].xrsiz, - codestream.segment[1].yrsiz)), - [(1, 1)] * 3) - - # COD: Coding style default - self.assertFalse(codestream.segment[2].scod & 2) # no sop - self.assertFalse(codestream.segment[2].scod & 4) # no eph - self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL) - self.assertEqual(codestream.segment[2].layers, 1) - self.assertEqual(codestream.segment[2].spcod[3], 1) # mct - self.assertEqual(codestream.segment[2].spcod[4], 5) # levels - self.assertEqual(tuple(codestream.segment[2].code_block_size), - (32, 32)) # cblksz + kwargs = {'rsiz': 4, 'xysiz': image_size, 'xyosiz': (0, 0), + 'xytsiz': image_size, 'xytosiz': (0, 0), + 'bitdepth': (12, 12, 12), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + self.verify_cinema_cod(codestream.segment[2]) def check_cinema2k_codestream(self, codestream, image_size): - """Common out for cinema2k tests.""" - # SIZ: Image and tile size - # Profile: "3" means cinema2K - self.assertEqual(codestream.segment[1].rsiz, 3) - # Reference grid size - self.assertEqual((codestream.segment[1].xsiz, - codestream.segment[1].ysiz), - image_size) - # Reference grid offset - self.assertEqual((codestream.segment[1].xosiz, - codestream.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((codestream.segment[1].xtsiz, - codestream.segment[1].ytsiz), - image_size) - # Tile offset - self.assertEqual((codestream.segment[1].xtosiz, - codestream.segment[1].ytosiz), - (0, 0)) - # bitdepth - self.assertEqual(codestream.segment[1].bitdepth, (12, 12, 12)) - # signed - self.assertEqual(codestream.segment[1].signed, - (False, False, False)) - # subsampling - self.assertEqual(list(zip(codestream.segment[1].xrsiz, - codestream.segment[1].yrsiz)), - [(1, 1)] * 3) - # COD: Coding style default - self.assertFalse(codestream.segment[2].scod & 2) # no sop - self.assertFalse(codestream.segment[2].scod & 4) # no eph - self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL) - self.assertEqual(codestream.segment[2].layers, 1) - self.assertEqual(codestream.segment[2].spcod[3], 1) # mct - self.assertEqual(codestream.segment[2].spcod[4], 5) # levels - self.assertEqual(tuple(codestream.segment[2].code_block_size), - (32, 32)) # cblksz + kwargs = {'rsiz': 3, 'xysiz': image_size, 'xyosiz': (0, 0), + 'xytsiz': image_size, 'xytosiz': (0, 0), + 'bitdepth': (12, 12, 12), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + self.verify_cinema_cod(codestream.segment[2]) @unittest.skipIf(NO_SKIMAGE_FREEIMAGE_SUPPORT, @@ -274,7 +221,7 @@ class TestSuiteNegative2pointzero(unittest.TestCase): @unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG) @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") -class TestSuiteWrite(unittest.TestCase): +class TestSuiteWrite(fixtures.MetadataBase): """Tests for writing with openjp2 backend. These tests either roughly correspond with those tests with similar names @@ -308,60 +255,28 @@ class TestSuiteWrite(unittest.TestCase): j.write(data, cratios=[200, 100, 50]) # Should be three layers. - codestream = j.get_codestream() + c = j.get_codestream() - # SIZ: Image and tile size - # Profile: "0" means profile 2 - self.assertEqual(codestream.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual((codestream.segment[1].xsiz, - codestream.segment[1].ysiz), - (640, 480)) - # Reference grid offset - self.assertEqual((codestream.segment[1].xosiz, - codestream.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((codestream.segment[1].xtsiz, - codestream.segment[1].ytsiz), - (640, 480)) - # Tile offset - self.assertEqual((codestream.segment[1].xtosiz, - codestream.segment[1].ytosiz), - (0, 0)) - # bitdepth - self.assertEqual(codestream.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(codestream.segment[1].signed, - (False, False, False)) - # subsampling - self.assertEqual(list(zip(codestream.segment[1].xrsiz, - codestream.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (640, 480), 'xyosiz': (0, 0), + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) - # COD: Coding style default - self.assertFalse(codestream.segment[2].scod & 2) # no sop - self.assertFalse(codestream.segment[2].scod & 4) # no eph - self.assertEqual(codestream.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(codestream.segment[2].layers, 3) # layers = 3 - self.assertEqual(codestream.segment[2].spcod[3], 1) # mct - self.assertEqual(codestream.segment[2].spcod[4], 5) # levels - self.assertEqual(tuple(codestream.segment[2].code_block_size), - (64, 64)) # cblksz - # Selective arithmetic coding bypass - self.assertFalse(codestream.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(codestream.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(codestream.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(codestream.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) - self.assertEqual(codestream.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(codestream.segment[2].spcod), 9) + # COD: Coding style default + self.assertFalse(c.segment[2].scod & 2) # no sop + self.assertFalse(c.segment[2].scod & 4) # no eph + self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(c.segment[2].layers, 3) # layers = 3 + self.assertEqual(c.segment[2].spcod[3], 1) # mct + self.assertEqual(c.segment[2].spcod[4], 5) # levels + self.assertEqual(tuple(c.segment[2].code_block_size), + (64, 64)) # cblksz + self.verify_codeblock_style(c.segment[2].spcod[7], + [False, False, False, False, False, False]) + self.assertEqual(c.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(c.segment[2].spcod), 9) def test_NR_ENC_Bretagne1_ppm_2_encode(self): """NR-ENC-Bretagne1.ppm-2-encode""" @@ -374,34 +289,11 @@ class TestSuiteWrite(unittest.TestCase): # Should be three layers. codestream = j.get_codestream() - # SIZ: Image and tile size - # Profile: "0" means profile 2 - self.assertEqual(codestream.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual((codestream.segment[1].xsiz, - codestream.segment[1].ysiz), - (640, 480)) - # Reference grid offset - self.assertEqual((codestream.segment[1].xosiz, - codestream.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((codestream.segment[1].xtsiz, - codestream.segment[1].ytsiz), - (640, 480)) - # Tile offset - self.assertEqual((codestream.segment[1].xtosiz, - codestream.segment[1].ytosiz), - (0, 0)) - # bitdepth - self.assertEqual(codestream.segment[1].bitdepth, - (8, 8, 8)) - # signed - self.assertEqual(codestream.segment[1].signed, - (False, False, False)) - # subsampling - self.assertEqual(list(zip(codestream.segment[1].xrsiz, - codestream.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (640, 480), 'xyosiz': (0, 0), + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -412,18 +304,8 @@ class TestSuiteWrite(unittest.TestCase): self.assertEqual(codestream.segment[2].spcod[4], 1) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz - # Selective arithmetic coding bypass - self.assertFalse(codestream.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(codestream.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(codestream.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(codestream.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(codestream.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -440,33 +322,11 @@ class TestSuiteWrite(unittest.TestCase): # Should be three layers. codestream = j.get_codestream() - # SIZ: Image and tile size - # Profile: "0" means profile 2 - self.assertEqual(codestream.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual((codestream.segment[1].xsiz, - codestream.segment[1].ysiz), - (640, 480)) - # Reference grid offset - self.assertEqual((codestream.segment[1].xosiz, - codestream.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((codestream.segment[1].xtsiz, - codestream.segment[1].ytsiz), - (640, 480)) - # Tile offset - self.assertEqual((codestream.segment[1].xtosiz, - codestream.segment[1].ytosiz), - (0, 0)) - # bitdepth - self.assertEqual(codestream.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(codestream.segment[1].signed, - (False, False, False)) - # subsampling - self.assertEqual(list(zip(codestream.segment[1].xrsiz, - codestream.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (640, 480), 'xyosiz': (0, 0), + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -477,18 +337,8 @@ class TestSuiteWrite(unittest.TestCase): self.assertEqual(codestream.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), (16, 16)) # cblksz - # Selective arithmetic coding bypass - self.assertFalse(codestream.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(codestream.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(codestream.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(codestream.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(codestream.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(codestream.segment[2].precinct_size, @@ -510,33 +360,11 @@ class TestSuiteWrite(unittest.TestCase): # Should be three layers. codestream = j.get_codestream() - # SIZ: Image and tile size - # Profile: "0" means profile 2 - self.assertEqual(codestream.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual((codestream.segment[1].xsiz, - codestream.segment[1].ysiz), - (data.shape[1], data.shape[0])) - # Reference grid offset - self.assertEqual((codestream.segment[1].xosiz, - codestream.segment[1].yosiz), (0, 0)) - # Tile size. Reported as XY, not RC. - self.assertEqual((codestream.segment[1].xtsiz, - codestream.segment[1].ytsiz), - (640, 480)) - # Tile offset - self.assertEqual((codestream.segment[1].xtosiz, - codestream.segment[1].ytosiz), - (0, 0)) - # bitdepth - self.assertEqual(codestream.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(codestream.segment[1].signed, - (False, False, False)) - # subsampling - self.assertEqual(list(zip(codestream.segment[1].xrsiz, - codestream.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (2592, 1944), 'xyosiz': (0, 0), + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -547,18 +375,8 @@ class TestSuiteWrite(unittest.TestCase): self.assertEqual(codestream.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), (32, 32)) # cblksz - # Selective arithmetic coding bypass - self.assertFalse(codestream.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(codestream.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(codestream.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(codestream.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(codestream.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(codestream.segment[2].precinct_size, @@ -574,33 +392,11 @@ class TestSuiteWrite(unittest.TestCase): codestream = j.get_codestream() - # SIZ: Image and tile size - # Profile: "0" means profile 2 - self.assertEqual(codestream.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual((codestream.segment[1].xsiz, - codestream.segment[1].ysiz), - (data.shape[1], data.shape[0])) - # Reference grid offset - self.assertEqual((codestream.segment[1].xosiz, - codestream.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((codestream.segment[1].xtsiz, - codestream.segment[1].ytsiz), - (127, 127)) - # Tile offset - self.assertEqual((codestream.segment[1].xtosiz, - codestream.segment[1].ytosiz), - (0, 0)) - # bitdepth - self.assertEqual(codestream.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(codestream.segment[1].signed, - (False, False, False)) - # subsampling - self.assertEqual(list(zip(codestream.segment[1].xrsiz, - codestream.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (2592, 1944), 'xyosiz': (0, 0), + 'xytsiz': (127, 127), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -611,18 +407,8 @@ class TestSuiteWrite(unittest.TestCase): self.assertEqual(codestream.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz - # Selective arithmetic coding bypass - self.assertFalse(codestream.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(codestream.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(codestream.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(codestream.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(codestream.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -637,33 +423,11 @@ class TestSuiteWrite(unittest.TestCase): codestream = j.get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "0" means profile 2 - self.assertEqual(codestream.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual((codestream.segment[1].xsiz, - codestream.segment[1].ysiz), - (5183, 3887)) - # Reference grid offset - self.assertEqual((codestream.segment[1].xosiz, - codestream.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((codestream.segment[1].xtsiz, - codestream.segment[1].ytsiz), - (5183, 3887)) - # Tile offset - self.assertEqual((codestream.segment[1].xtosiz, - codestream.segment[1].ytosiz), - (0, 0)) - # bitdepth - self.assertEqual(codestream.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(codestream.segment[1].signed, - (False, False, False)) - # subsampling - self.assertEqual(list(zip(codestream.segment[1].xrsiz, - codestream.segment[1].yrsiz)), - [(2, 2)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (5183, 3887), 'xyosiz': (0, 0), + 'xytsiz': (5183, 3887), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(2, 2, 2), (2, 2, 2)]} + self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(codestream.segment[2].scod & 2) # sop @@ -674,18 +438,8 @@ class TestSuiteWrite(unittest.TestCase): self.assertEqual(codestream.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz - # Selective arithmetic coding bypass - self.assertFalse(codestream.segment[2].spcod[7] & 0x01) - # Reset context probabilities - self.assertFalse(codestream.segment[2].spcod[7] & 0x02) - # Termination on each coding pass - self.assertFalse(codestream.segment[2].spcod[7] & 0x04) - # Vertically causal context - self.assertFalse(codestream.segment[2].spcod[7] & 0x08) - # Predictable termination - self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) - # Segmentation symbols - self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(codestream.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -705,33 +459,11 @@ class TestSuiteWrite(unittest.TestCase): codestream = j.get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "0" means profile 2 - self.assertEqual(codestream.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual((codestream.segment[1].xsiz, - codestream.segment[1].ysiz), - (2592, 1944)) - # Reference grid offset - self.assertEqual((codestream.segment[1].xosiz, - codestream.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((codestream.segment[1].xtsiz, - codestream.segment[1].ytsiz), - (2592, 1944)) - # Tile offset - self.assertEqual((codestream.segment[1].xtosiz, - codestream.segment[1].ytosiz), - (0, 0)) - # bitdepth - self.assertEqual(codestream.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(codestream.segment[1].signed, - (False, False, False)) - # subsampling - self.assertEqual(list(zip(codestream.segment[1].xrsiz, - codestream.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (2592, 1944), 'xyosiz': (0, 0), + 'xytsiz': (2592, 1944), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -742,18 +474,8 @@ class TestSuiteWrite(unittest.TestCase): self.assertEqual(codestream.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz - # Selective arithmetic coding BYPASS - self.assertFalse(codestream.segment[2].spcod[7] & 0x01) - # RESET context probabilities (RESET) - self.assertTrue(codestream.segment[2].spcod[7] & 0x02) - # Termination on each coding pass, RESTART(TERMALL) - self.assertTrue(codestream.segment[2].spcod[7] & 0x04) - # Vertically causal context (VSC) - self.assertFalse(codestream.segment[2].spcod[7] & 0x08) - # Predictable termination, ERTERM(SEGTERM) - self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) - # Segmentation symbols, SEGMARK(SEGSYSM) - self.assertTrue(codestream.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(codestream.segment[2].spcod[7], + [False, True, True, False, False, True]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -772,34 +494,11 @@ class TestSuiteWrite(unittest.TestCase): codestream = j.get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "0" means profile 2 - self.assertEqual(codestream.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual((codestream.segment[1].xsiz, - codestream.segment[1].ysiz), - (2742, 2244)) - # Reference grid offset - self.assertEqual((codestream.segment[1].xosiz, - codestream.segment[1].yosiz), - (150, 300)) - # Tile size - self.assertEqual((codestream.segment[1].xtsiz, - codestream.segment[1].ytsiz), - (2742, 2244)) - # Tile offset - self.assertEqual((codestream.segment[1].xtosiz, - codestream.segment[1].ytosiz), - (0, 0)) - # bitdepth - self.assertEqual(codestream.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(codestream.segment[1].signed, - (False, False, False)) - # subsampling - self.assertEqual(list(zip(codestream.segment[1].xrsiz, - codestream.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (2742, 2244), 'xyosiz': (150, 300), + 'xytsiz': (2742, 2244), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -810,18 +509,8 @@ class TestSuiteWrite(unittest.TestCase): self.assertEqual(codestream.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz - # Selective arithmetic coding BYPASS - self.assertFalse(codestream.segment[2].spcod[7] & 0x01) - # RESET context probabilities (RESET) - self.assertFalse(codestream.segment[2].spcod[7] & 0x02) - # Termination on each coding pass, RESTART(TERMALL) - self.assertFalse(codestream.segment[2].spcod[7] & 0x04) - # Vertically causal context (VSC) - self.assertFalse(codestream.segment[2].spcod[7] & 0x08) - # Predictable termination, ERTERM(SEGTERM) - self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) - # Segmentation symbols, SEGMARK(SEGSYSM) - self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(codestream.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -836,33 +525,11 @@ class TestSuiteWrite(unittest.TestCase): codestream = j.get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "0" means profile 2 - self.assertEqual(codestream.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual((codestream.segment[1].xsiz, - codestream.segment[1].ysiz), - (2592, 1944)) - # Reference grid offset - self.assertEqual((codestream.segment[1].xosiz, - codestream.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((codestream.segment[1].xtsiz, - codestream.segment[1].ytsiz), - (2592, 1944)) - # Tile offset - self.assertEqual((codestream.segment[1].xtosiz, - codestream.segment[1].ytosiz), - (0, 0)) - # bitdepth - self.assertEqual(codestream.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(codestream.segment[1].signed, - (False, False, False)) - # subsampling - self.assertEqual(list(zip(codestream.segment[1].xrsiz, - codestream.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (2592, 1944), 'xyosiz': (0, 0), + 'xytsiz': (2592, 1944), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -873,18 +540,8 @@ class TestSuiteWrite(unittest.TestCase): self.assertEqual(codestream.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz - # Selective arithmetic coding BYPASS - self.assertFalse(codestream.segment[2].spcod[7] & 0x01) - # RESET context probabilities (RESET) - self.assertFalse(codestream.segment[2].spcod[7] & 0x02) - # Termination on each coding pass, RESTART(TERMALL) - self.assertFalse(codestream.segment[2].spcod[7] & 0x04) - # Vertically causal context (VSC) - self.assertFalse(codestream.segment[2].spcod[7] & 0x08) - # Predictable termination, ERTERM(SEGTERM) - self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) - # Segmentation symbols, SEGMARK(SEGSYSM) - self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(codestream.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -899,33 +556,11 @@ class TestSuiteWrite(unittest.TestCase): codestream = j.get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "0" means profile 2 - self.assertEqual(codestream.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual((codestream.segment[1].xsiz, - codestream.segment[1].ysiz), - (640, 480)) - # Reference grid offset - self.assertEqual((codestream.segment[1].xosiz, - codestream.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((codestream.segment[1].xtsiz, - codestream.segment[1].ytsiz), - (640, 480)) - # Tile offset - self.assertEqual((codestream.segment[1].xtosiz, - codestream.segment[1].ytosiz), - (0, 0)) - # bitdepth - self.assertEqual(codestream.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(codestream.segment[1].signed, - (False, False, False)) - # subsampling - self.assertEqual(list(zip(codestream.segment[1].xrsiz, - codestream.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (640, 480), 'xyosiz': (0, 0), + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -936,18 +571,8 @@ class TestSuiteWrite(unittest.TestCase): self.assertEqual(codestream.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz - # Selective arithmetic coding BYPASS - self.assertFalse(codestream.segment[2].spcod[7] & 0x01) - # RESET context probabilities (RESET) - self.assertFalse(codestream.segment[2].spcod[7] & 0x02) - # Termination on each coding pass, RESTART(TERMALL) - self.assertFalse(codestream.segment[2].spcod[7] & 0x04) - # Vertically causal context (VSC) - self.assertFalse(codestream.segment[2].spcod[7] & 0x08) - # Predictable termination, ERTERM(SEGTERM) - self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) - # Segmentation symbols, SEGMARK(SEGSYSM) - self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(codestream.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -994,34 +619,11 @@ class TestSuiteWrite(unittest.TestCase): codestream = jp2.box[3].main_header - # SIZ: Image and tile size - # Profile: "0" means profile 2 - self.assertEqual(codestream.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual((codestream.segment[1].xsiz, - codestream.segment[1].ysiz), - (640, 480)) - # Reference grid offset - self.assertEqual((codestream.segment[1].xosiz, - codestream.segment[1].yosiz), - (0, 0)) - # Tile size - self.assertEqual((codestream.segment[1].xtsiz, - codestream.segment[1].ytsiz), - (640, 480)) - # Tile offset - self.assertEqual((codestream.segment[1].xtosiz, - codestream.segment[1].ytosiz), - (0, 0)) - # bitdepth - self.assertEqual(codestream.segment[1].bitdepth, (8, 8, 8)) - # signed - self.assertEqual(codestream.segment[1].signed, - (False, False, False)) - # subsampling - self.assertEqual(list(zip(codestream.segment[1].xrsiz, - codestream.segment[1].yrsiz)), - [(1, 1)] * 3) + kwargs = {'rsiz': 0, 'xysiz': (640, 480), 'xyosiz': (0, 0), + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -1032,18 +634,8 @@ class TestSuiteWrite(unittest.TestCase): self.assertEqual(codestream.segment[2].spcod[4], 2) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz - # Selective arithmetic coding BYPASS - self.assertFalse(codestream.segment[2].spcod[7] & 0x01) - # RESET context probabilities (RESET) - self.assertFalse(codestream.segment[2].spcod[7] & 0x02) - # Termination on each coding pass, RESTART(TERMALL) - self.assertFalse(codestream.segment[2].spcod[7] & 0x04) - # Vertically causal context (VSC) - self.assertFalse(codestream.segment[2].spcod[7] & 0x08) - # Predictable termination, ERTERM(SEGTERM) - self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) - # Segmentation symbols, SEGMARK(SEGSYSM) - self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(codestream.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -1060,32 +652,11 @@ class TestSuiteWrite(unittest.TestCase): codestream = j.get_codestream(header_only=False) - # SIZ: Image and tile size - # Profile: "0" means profile 2 - self.assertEqual(codestream.segment[1].rsiz, 0) - # Reference grid size - self.assertEqual((codestream.segment[1].xsiz, - codestream.segment[1].ysiz), - (1024, 1024)) - # Reference grid offset - self.assertEqual((codestream.segment[1].xosiz, - codestream.segment[1].yosiz), (0, 0)) - # Tile size - self.assertEqual((codestream.segment[1].xtsiz, - codestream.segment[1].ytsiz), - (1024, 1024)) - # Tile offset - self.assertEqual((codestream.segment[1].xtosiz, - codestream.segment[1].ytosiz), - (0, 0)) - # bitdepth - self.assertEqual(codestream.segment[1].bitdepth, (16,)) - # signed - self.assertEqual(codestream.segment[1].signed, (False,)) - # subsampling - self.assertEqual(list(zip(codestream.segment[1].xrsiz, - codestream.segment[1].yrsiz)), - [(1, 1)]) + kwargs = {'rsiz': 0, 'xysiz': (1024, 1024), 'xyosiz': (0, 0), + 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), + 'bitdepth': (16,), 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -1096,18 +667,8 @@ class TestSuiteWrite(unittest.TestCase): self.assertEqual(codestream.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz - # Selective arithmetic coding BYPASS - self.assertFalse(codestream.segment[2].spcod[7] & 0x01) - # RESET context probabilities (RESET) - self.assertFalse(codestream.segment[2].spcod[7] & 0x02) - # Termination on each coding pass, RESTART(TERMALL) - self.assertFalse(codestream.segment[2].spcod[7] & 0x04) - # Vertically causal context (VSC) - self.assertFalse(codestream.segment[2].spcod[7] & 0x08) - # Predictable termination, ERTERM(SEGTERM) - self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) - # Segmentation symbols, SEGMARK(SEGSYSM) - self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) + self.verify_codeblock_style(codestream.segment[2].spcod[7], + [False, False, False, False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) From 4ac5b575d20c2ccdd8b0901d8cb1d2a0442a70e6 Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 22 Sep 2014 21:15:12 -0400 Subject: [PATCH 253/326] config module refactor --- glymur/lib/config.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/glymur/lib/config.py b/glymur/lib/config.py index fdb5a92..04aece2 100644 --- a/glymur/lib/config.py +++ b/glymur/lib/config.py @@ -73,7 +73,6 @@ def load_openjpeg(path): return load_library_handle(path) - def load_openjp2(path): """Load the openjp2 library, falling back on defaults if necessary. """ @@ -100,7 +99,7 @@ def load_openjp2(path): def load_library_handle(path): """Load the library, return the ctypes handle.""" - if path is None: + if path is None or path in ['None', 'none']: # Either could not find a library via ctypes or user-configuration-file, # or we could not find it in any of the default locations. # This is probably a very old linux. @@ -130,14 +129,11 @@ def read_config_file(): # Read the configuration file for the library location. parser = ConfigParser() parser.read(filename) - try: - lib['openjp2'] = parser.get('library', 'openjp2') - except NoOptionError: - pass - try: - lib['openjpeg'] = parser.get('library', 'openjpeg') - except NoOptionError: - pass + for name in ['openjp2', 'openjpeg']: + try: + lib[name] = parser.get('library', name) + except NoOptionError: + pass return lib @@ -150,8 +146,7 @@ def glymur_config(): libopenjpeg_handle = load_openjpeg(libs['openjpeg']) if libopenjp2_handle is None and libopenjpeg_handle is None: msg = "Neither the openjp2 nor the openjpeg library could be loaded. " - msg += "Operating in severely degraded mode." - warnings.warn(msg, UserWarning) + raise IOError(msg) return libopenjp2_handle, libopenjpeg_handle From d2dc37bd5e7b5c56048f80ce3ef33c6fda34087f Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 22 Sep 2014 21:15:49 -0400 Subject: [PATCH 254/326] fixed two bugs unknowingly introduced into cinema2k/4k testing --- glymur/test/test_opj_suite_write.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index fa719fc..ebdce2e 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -24,8 +24,9 @@ from .fixtures import opj_data_file from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG from . import fixtures -from glymur import Jp2k import glymur +from glymur import Jp2k +from glymur.codestream import SIZsegment class CinemaBase(fixtures.MetadataBase): @@ -45,7 +46,7 @@ class CinemaBase(fixtures.MetadataBase): 'xytsiz': image_size, 'xytosiz': (0, 0), 'bitdepth': (12, 12, 12), 'signed': (False, False, False), 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + self.verifySizSegment(codestream.segment[1], SIZsegment(**kwargs)) self.verify_cinema_cod(codestream.segment[2]) @@ -55,7 +56,7 @@ class CinemaBase(fixtures.MetadataBase): 'xytsiz': image_size, 'xytosiz': (0, 0), 'bitdepth': (12, 12, 12), 'signed': (False, False, False), 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + self.verifySizSegment(codestream.segment[1], SIZsegment(**kwargs)) self.verify_cinema_cod(codestream.segment[2]) From 9bebd6438cf208f994131ae5bb81cecd3e632f6c Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 23 Sep 2014 20:00:47 -0400 Subject: [PATCH 255/326] box_id and longname are class attributes now instead of instance attributes Some simplification of the individual box constructors, and a palette box error message became a bit more clear because of this. --- glymur/jp2box.py | 153 +++++++++++++++++++++------- glymur/test/test_glymur_warnings.py | 6 +- glymur/test/test_jp2box.py | 10 -- 3 files changed, 119 insertions(+), 50 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index b2986ae..c3bd8e3 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -62,17 +62,13 @@ class Jp2kBox(object): length of the box in bytes. offset : int offset of the box from the start of the file. - longname : str - more verbose description of the box. box : list List of JPEG 2000 boxes. """ - def __init__(self, box_id='', offset=0, length=0, longname=''): - self.box_id = box_id + def __init__(self, offset=0, length=0): self.length = length self.offset = offset - self.longname = longname self.box = [] def __repr__(self): @@ -197,7 +193,7 @@ class Jp2kBox(object): msg = "Encountered an unrecoverable ValueError while parsing a {0} " msg += "box at byte offset {1}. The original error message was " msg += "\"{2}\"" - msg = msg.format(box_id.decode('utf-8'), start, str(err)) + msg = msg.format(_BOX_WITH_ID[box_id].longname, start, str(err)) warnings.warn(msg, UserWarning) box = UnknownBox(box_id.decode('utf-8'), length=num_bytes, offset=start, longname='Unknown') @@ -300,11 +296,12 @@ class ColourSpecificationBox(Jp2kBox): ICC profile header according to ICC profile specification. If colorspace is not None, then icc_profile must be empty. """ - + longname = 'Colour Specification' + box_id = 'colr' def __init__(self, method=ENUMERATED_COLORSPACE, precedence=0, approximation=0, colorspace=None, icc_profile=None, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='colr', longname='Colour Specification') + Jp2kBox.__init__(self) self.method = method self.precedence = precedence @@ -588,8 +585,11 @@ class ChannelDefinitionBox(Jp2kBox): association : list index of the associated color """ + box_id = 'cdef' + longname = 'Channel Definition' + def __init__(self, channel_type, association, index=None, **kwargs): - Jp2kBox.__init__(self, box_id='cdef', longname='Channel Definition') + Jp2kBox.__init__(self) if index is None: self.index = tuple(range(len(channel_type))) @@ -704,8 +704,11 @@ class CodestreamHeaderBox(Jp2kBox): box : list List of boxes contained in this superbox. """ + box_id = 'jpch' + longname = 'Codestream Header' + def __init__(self, box=None, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='jpch', longname='Codestream Header') + Jp2kBox.__init__(self) self.length = length self.offset = offset self.box = box if box is not None else [] @@ -765,8 +768,11 @@ class ColourGroupBox(Jp2kBox): box : list List of boxes contained in this superbox. """ + box_id = 'cgrp' + longname = 'Colour Group' + def __init__(self, box=None, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='cgrp', longname='Colour Group') + Jp2kBox.__init__(self) self.length = length self.offset = offset self.box = box if box is not None else [] @@ -834,9 +840,11 @@ class CompositingLayerHeaderBox(Jp2kBox): box : list List of boxes contained in this superbox. """ + box_id = 'jplh' + longname='Compositing Layer Header' + def __init__(self, box=None, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='jplh', - longname='Compositing Layer Header') + Jp2kBox.__init__(self) self.length = length self.offset = offset self.box = box if box is not None else [] @@ -900,9 +908,12 @@ class ComponentMappingBox(Jp2kBox): palette_index : tuple Index component from palette """ + box_id = 'cmap' + longname = 'Component Mapping' + def __init__(self, component_index, mapping_type, palette_index, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='cmap', longname='Component Mapping') + Jp2kBox.__init__(self) self.component_index = component_index self.mapping_type = mapping_type self.palette_index = palette_index @@ -995,9 +1006,12 @@ class ContiguousCodestreamBox(Jp2kBox): main_header_offset : int offset of main header from start of file """ + box_id = 'jp2c' + longname = 'Contiguous Codestream' + def __init__(self, main_header=None, main_header_offset=None, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='jp2c', longname='Contiguous Codestream') + Jp2kBox.__init__(self) self._main_header = main_header self.length = length self.offset = offset @@ -1078,8 +1092,11 @@ class DataReferenceBox(Jp2kBox): DR : list Data Entry URL boxes. """ + box_id = 'dtbl' + longname = 'Data Reference' + def __init__(self, data_entry_url_boxes=None, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='dtbl', longname='Data Reference') + Jp2kBox.__init__(self) if data_entry_url_boxes is None: self.DR = [] else: @@ -1205,9 +1222,12 @@ class FileTypeBox(Jp2kBox): compatibility_list: list List of file conformance profiles. """ + box_id = 'ftyp' + longname = 'File Type' + def __init__(self, brand='jp2 ', minor_version=0, compatibility_list=None, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='ftyp', longname='File Type') + Jp2kBox.__init__(self) self.brand = brand self.minor_version = minor_version if compatibility_list is None: @@ -1317,9 +1337,12 @@ class FragmentListBox(Jp2kBox): longname : str more verbose description of the box. """ + box_id = 'flst' + longname = 'Fragment List' + def __init__(self, fragment_offset, fragment_length, data_reference, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='flst', longname='Fragment List') + Jp2kBox.__init__(self) self.fragment_offset = fragment_offset self.fragment_length = fragment_length self.data_reference = data_reference @@ -1423,8 +1446,11 @@ class FragmentTableBox(Jp2kBox): longname : str more verbose description of the box. """ + box_id = 'ftbl' + longname = 'Fragment Table' + def __init__(self, box=None, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='ftbl', longname='Fragment Table') + Jp2kBox.__init__(self) self.length = length self.offset = offset self.box = box if box is not None else [] @@ -1493,8 +1519,11 @@ class FreeBox(Jp2kBox): longname : str more verbose description of the box. """ + box_id = 'free' + longname = 'Free' + def __init__(self, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='free', longname='Free') + Jp2kBox.__init__(self) self.length = length self.offset = offset @@ -1558,6 +1587,9 @@ class ImageHeaderBox(Jp2kBox): False if the file does not contain intellectual propery rights information. """ + box_id = 'ihdr' + longname = 'Image Header' + def __init__(self, height, width, num_components=1, signed=False, bits_per_component=8, compression=7, colorspace_unknown=False, ip_provided=False, length=0, offset=-1): @@ -1567,7 +1599,7 @@ class ImageHeaderBox(Jp2kBox): >>> import glymur >>> box = glymur.jp2box.ImageHeaderBox(height=512, width=256) """ - Jp2kBox.__init__(self, box_id='ihdr', longname='Image Header') + Jp2kBox.__init__(self) self.height = height self.width = width self.num_components = num_components @@ -1686,8 +1718,11 @@ class AssociationBox(Jp2kBox): box : list List of boxes contained in this superbox. """ + box_id = 'asoc' + longname = 'Association' + def __init__(self, box=None, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='asoc', longname='Association') + Jp2kBox.__init__(self) self.length = length self.offset = offset self.box = box if box is not None else [] @@ -1747,8 +1782,11 @@ class JP2HeaderBox(Jp2kBox): box : list List of boxes contained in this superbox. """ + box_id = 'jp2h' + longname = 'JP2 Header' + def __init__(self, box=None, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='jp2h', longname='JP2 Header') + Jp2kBox.__init__(self) self.length = length self.offset = offset self.box = box if box is not None else [] @@ -1808,8 +1846,11 @@ class JPEG2000SignatureBox(Jp2kBox): signature : tuple Four-byte tuple identifying the file as JPEG 2000. """ + box_id = 'jP ' + longname = 'JPEG 2000 Signature' + def __init__(self, signature=(13, 10, 135, 10), length=0, offset=-1): - Jp2kBox.__init__(self, box_id='jP ', longname='JPEG 2000 Signature') + Jp2kBox.__init__(self) self.signature = signature self.length = length self.offset = offset @@ -1872,9 +1913,12 @@ class PaletteBox(Jp2kBox): palette : ndarray Colormap array. """ + longname = 'Palette' + box_id = 'pclr' + def __init__(self, palette, bits_per_component, signed, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='pclr', longname='Palette') + Jp2kBox.__init__(self) self.palette = palette self.bits_per_component = bits_per_component self.signed = signed @@ -2129,9 +2173,12 @@ class ReaderRequirementsBox(Jp2kBox): Specifies the compatibility mask for each corresponding vendor feature. """ + box_id = 'rreq' + longname = 'Reader Requirements' + def __init__(self, fuam, dcm, standard_flag, standard_mask, vendor_feature, vendor_mask, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='rreq', longname='Reader Requirements') + Jp2kBox.__init__(self) self.fuam = fuam self.dcm = dcm self.standard_flag = tuple(standard_flag) @@ -2368,8 +2415,11 @@ class ResolutionBox(Jp2kBox): box : list List of boxes contained in this superbox. """ + box_id = 'res ' + longname = 'Resolution' + def __init__(self, box=None, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='res ', longname='Resolution') + Jp2kBox.__init__(self) self.length = length self.offset = offset self.box = box if box is not None else [] @@ -2425,9 +2475,12 @@ class CaptureResolutionBox(Jp2kBox): vertical_resolution, horizontal_resolution : float Vertical, horizontal resolution. """ + box_id = 'resc' + longname = 'Capture Resolution' + def __init__(self, vertical_resolution, horizontal_resolution, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='resc', longname='Capture Resolution') + Jp2kBox.__init__(self) self.vertical_resolution = vertical_resolution self.horizontal_resolution = horizontal_resolution self.length = length @@ -2488,9 +2541,12 @@ class DisplayResolutionBox(Jp2kBox): vertical_resolution, horizontal_resolution : float Vertical, horizontal resolution. """ + box_id = 'resd' + longname = 'Display Resolution' + def __init__(self, vertical_resolution, horizontal_resolution, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='resd', longname='Display Resolution') + Jp2kBox.__init__(self) self.vertical_resolution = vertical_resolution self.horizontal_resolution = horizontal_resolution self.length = length @@ -2552,8 +2608,11 @@ class LabelBox(Jp2kBox): label : str Textual label. """ + box_id = 'lbl ' + longname = 'Label' + def __init__(self, label, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='lbl ', longname='Label') + Jp2kBox.__init__(self) self.label = label self.length = length self.offset = offset @@ -2617,8 +2676,11 @@ class NumberListBox(Jp2kBox): Descriptors of an entity with which the data contained within the same Association box is associated. """ + box_id = 'nlst' + longname = 'Number List' + def __init__(self, associations, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='nlst', longname='Number List') + Jp2kBox.__init__(self) self.associations = associations self.length = length self.offset = offset @@ -2698,6 +2760,9 @@ class XMLBox(Jp2kBox): xml : ElementTree object XML section. """ + box_id = 'xml ' + longname = 'XML' + def __init__(self, xml=None, filename=None, length=0, offset=-1): """ Parameters @@ -2708,7 +2773,7 @@ class XMLBox(Jp2kBox): File from which to read XML. If filename is not None, then the xml keyword argument must be None. """ - Jp2kBox.__init__(self, box_id='xml ', longname='XML') + Jp2kBox.__init__(self) if filename is not None and xml is not None: msg = "Only one of either filename or xml should be provided." raise IOError(msg) @@ -2830,8 +2895,11 @@ class UUIDListBox(Jp2kBox): ulst : list List of UUIDs. """ + box_id = 'ulst' + longname = 'UUID List' + def __init__(self, ulst, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='ulst', longname='UUID List') + Jp2kBox.__init__(self) self.ulst = ulst self.length = length self.offset = offset @@ -2895,8 +2963,11 @@ class UUIDInfoBox(Jp2kBox): box : list List of boxes contained in this superbox. """ + box_id = 'uinf' + longname = 'UUIDInfo' + def __init__(self, box=None, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='uinf', longname='UUIDInfo') + Jp2kBox.__init__(self) self.length = length self.offset = offset self.box = box if box is not None else [] @@ -2956,8 +3027,11 @@ class DataEntryURLBox(Jp2kBox): URL : str Associated URL. """ + box_id = 'url ' + longname = 'Data Entry URL' + def __init__(self, version, flag, url, length=0, offset=-1): - Jp2kBox.__init__(self, box_id='url ', longname='Data Entry URL') + Jp2kBox.__init__(self) self.version = version self.flag = flag self.url = url @@ -3045,7 +3119,9 @@ class UnknownBox(Jp2kBox): more verbose description of the box. """ def __init__(self, box_id, length=0, offset=-1, longname=''): - Jp2kBox.__init__(self, box_id=box_id, longname=longname) + Jp2kBox.__init__(self) + self.longname = longname + self.box_id = box_id self.length = length self.offset = offset @@ -3089,6 +3165,9 @@ class UUIDBox(Jp2kBox): 16684-1:2012 - Graphic technology -- Extensible metadata platform (XMP) specification -- Part 1: Data model, serialization and core properties """ + box_id = 'uuid' + longname = 'UUID' + def __init__(self, the_uuid, raw_data, length=0, offset=-1): """ Parameters @@ -3102,7 +3181,7 @@ class UUIDBox(Jp2kBox): offset : int offset of the box from the start of the file. """ - Jp2kBox.__init__(self, box_id='uuid', longname='UUID') + Jp2kBox.__init__(self) self.uuid = the_uuid self.raw_data = raw_data self.length = length diff --git a/glymur/test/test_glymur_warnings.py b/glymur/test/test_glymur_warnings.py index ceb8319..ebe445c 100644 --- a/glymur/test/test_glymur_warnings.py +++ b/glymur/test/test_glymur_warnings.py @@ -40,9 +40,9 @@ class TestWarnings(unittest.TestCase): infile = os.path.join(OPJ_DATA_ROOT, 'input/nonregression/mem-b2ace68c-1381.jp2') regex = re.compile(r'''Encountered\san\sunrecoverable\sValueError\s - while\sparsing\sa\spclr\sbox\sat\sbyte\soffset\s - \d+\.\s+The\soriginal\serror\smessage\swas\s - "total\ssize\sof\snew\sarray\smust\sbe\s + while\sparsing\sa\sPalette\sbox\sat\sbyte\s + offset\s\d+\.\s+The\soriginal\serror\smessage\s + was\s"total\ssize\sof\snew\sarray\smust\sbe\s unchanged"''', re.VERBOSE) with self.assertWarnsRegex(UserWarning, regex): diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 378855c..061ad23 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -1246,16 +1246,6 @@ class TestRepr(MetadataBase): self.assertEqual(newbox.ulst[0], uuid1) self.assertEqual(newbox.ulst[1], uuid2) - def test_jp2k_box(self): - """Verify Superclass repr.""" - box = glymur.jp2box.Jp2kBox(box_id='one', offset=2, length=3, - longname='four') - newbox = eval(repr(box)) - self.assertEqual(newbox.box_id, 'one') - self.assertEqual(newbox.offset, 2) - self.assertEqual(newbox.length, 3) - self.assertEqual(newbox.longname, 'four') - def test_palette_box(self): """Verify Palette box repr.""" palette = np.array([[255, 0, 1000], [0, 255, 0]], dtype=np.int32) From 2e55a0a8a8951dc83aa7d01786e63bb29966f599 Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 24 Sep 2014 20:59:39 -0400 Subject: [PATCH 256/326] pinpointed test_main_header as culprit for 3 unexplained failures --- glymur/test/test_jp2k.py | 1 + glymur/test/test_opj_suite_dump.py | 2 -- glymur/test/test_printing.py | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 98f4607..89a9e62 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -1139,6 +1139,7 @@ class TestParsing(unittest.TestCase): with self.assertWarnsRegex(UserWarning, 'Invalid profile'): jp2 = Jp2k(filename) + @unittest.skip('trouble is a brewing...') def test_main_header(self): """Verify that the main header is not loaded when parsing turned off.""" # The hidden _main_header attribute should show up after accessing it. diff --git a/glymur/test/test_opj_suite_dump.py b/glymur/test/test_opj_suite_dump.py index cab7732..76dc444 100644 --- a/glymur/test/test_opj_suite_dump.py +++ b/glymur/test/test_opj_suite_dump.py @@ -2851,7 +2851,6 @@ class TestSuiteWarns(MetadataBase): d = j.read() self.assertTrue(True) - @unittest.skip("unexplained failure") def test_NR_broken4_jp2_dump(self): jfile = opj_data_file('input/nonregression/broken4.jp2') with self.assertWarns(UserWarning): @@ -2944,7 +2943,6 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(c.segment[6].exponent, [8] + [9, 9, 10] * 5) - @unittest.skip("unexplained failure") def test_NR_broken2_jp2_dump(self): """ Invalid marker ID in the codestream. diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 66d82d0..49bbe6d 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -854,7 +854,6 @@ class TestPrintingOpjDataRootWarns(unittest.TestCase): with patch('sys.stdout', new=StringIO()) as fake_out: print(jp2) - @unittest.skip("unexplained failure") def test_bad_rsiz(self): """Should still be able to print if rsiz is bad, issue196""" filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') From f22a6531109556d796a20d4410d376c1995f13f9 Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 24 Sep 2014 21:47:09 -0400 Subject: [PATCH 257/326] TestParsing tearDown was not being done correctly By just passing, the correct test state was not being restored. --- glymur/test/test_jp2k.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 89a9e62..87c307e 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -19,6 +19,7 @@ import sys import tempfile import unittest import uuid +import warnings from xml.etree import cElementTree as ET import numpy as np @@ -1104,7 +1105,8 @@ class TestJp2k_2_1(unittest.TestCase): tfile.write(data[offset+59:]) #tfile.write(data[3186:]) tfile.flush() - with self.assertWarns(UserWarning): + with warnings.catch_warnings(): + warnings.simplefilter('ignore') j = Jp2k(tfile.name) regexp = re.compile(r'''OpenJPEG\slibrary\serror:\s+ Invalid\svalues\sfor\scomp\s=\s0\s+ @@ -1126,7 +1128,7 @@ class TestParsing(unittest.TestCase): glymur.set_parseoptions(codestream=True) def tearDown(self): - pass + glymur.set_parseoptions(codestream=True) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_bad_rsiz(self): @@ -1139,7 +1141,7 @@ class TestParsing(unittest.TestCase): with self.assertWarnsRegex(UserWarning, 'Invalid profile'): jp2 = Jp2k(filename) - @unittest.skip('trouble is a brewing...') + #@unittest.skip('trouble is a brewing...') def test_main_header(self): """Verify that the main header is not loaded when parsing turned off.""" # The hidden _main_header attribute should show up after accessing it. From 873bb0c900f9f2695abc4d1c16f8e2b304a53c47 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 25 Sep 2014 09:12:46 -0400 Subject: [PATCH 258/326] housecleaning for 0.7.0 release --- CHANGES.txt | 2 ++ docs/source/conf.py | 4 ++-- docs/source/how_do_i.rst | 18 ++++++++++-------- docs/source/index.rst | 2 +- docs/source/introduction.rst | 5 +++-- docs/source/roadmap.rst | 7 +++++++ docs/source/whatsnew/0.7.rst | 11 +++++++++++ docs/source/whatsnew/index.rst | 1 + glymur/test/fixtures.py | 3 ++- glymur/version.py | 2 +- 10 files changed, 40 insertions(+), 15 deletions(-) create mode 100644 docs/source/whatsnew/0.7.rst diff --git a/CHANGES.txt b/CHANGES.txt index 44d6832..071453c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,5 @@ +Sep 25, 2014 - -v0.7.0 Added __getitem__, __setitem__ support. + Mar 06, 2014 - Added Cinema2K, Cinema4K write support. Changed constructor for ChannelDefinition box. Removed support for Python 2.6. Added write support for JP2 UUID, DataEntryURL, diff --git a/docs/source/conf.py b/docs/source/conf.py index 3e6b26e..5c44e90 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -76,9 +76,9 @@ copyright = u'2013, John Evans' # built documents. # # The short X.Y version. -version = '0.6' +version = '0.7' # The full version, including alpha/beta/rc tags. -release = '0.6.0' +release = '0.7.0rc1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index a9da7c1..3e0c3ee 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -3,11 +3,12 @@ How do I...? ------------ -... read the lower resolution images? -===================================== -Jp2k implements slicing via the :py:meth:`__getitem__` method so -any lower resolution images in a JPEG 2000 file can easily be -accessed, for example here's how to retrieve the first sub-image :: +... read images? +================ +Jp2k implements slicing via the :py:meth:`__getitem__` method, meaning that +multiple resolution imagery in a JPEG 2000 file can +easily be accessed. For example here's how to retrieve a full resolution and +first lower-resolution image :: >>> import glymur >>> jp2file = glymur.data.nemo() @@ -24,15 +25,16 @@ such as quality layers. ... display metadata? ===================== -There are two ways. From the command line, the script **jp2dump** is +There are two ways. From the command line, the console script **jp2dump** is available. :: $ jp2dump /path/to/glymur/installation/data/nemo.jp2 -From within Python, it is as simple as printing the Jp2k object, i.e. :: +From within Python, the same result is obtained simply by printing the Jp2k +object, i.e. :: >>> import glymur - >>> jp2file = glymur.data.nemo() + >>> jp2file = glymur.data.nemo() # just a path to a JP2 file >>> jp2 = glymur.Jp2k(jp2file) >>> print(jp2) File: nemo.jp2 diff --git a/docs/source/index.rst b/docs/source/index.rst index 0675eef..8c1fb03 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,9 +15,9 @@ Contents: introduction detailed_installation how_do_i - api whatsnew/index roadmap + api ------------------ Indices and tables diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index bfe7174..11059fd 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -27,5 +27,6 @@ but you should also be able to install Glymur via pip :: $ pip install glymur -In addition to the package, this also gives you a script **jp2dump** that can -be used from the command line line to print JPEG 2000 metadata. +In addition to the package, this also gives you a command line script +**jp2dump** that can be used from the command line line to print JPEG 2000 +metadata. diff --git a/docs/source/roadmap.rst b/docs/source/roadmap.rst index f0d7017..284c139 100644 --- a/docs/source/roadmap.rst +++ b/docs/source/roadmap.rst @@ -1,3 +1,10 @@ +------------------------------------- +Platforms Tested (0.7.0 release only) +------------------------------------- + * Linux Mint 17 / Python 3.4.0 + * Linux Mint 17 / Python 2.7.6 + * MacOS 10.6.8 / MacPorts / Python 3.4.1 + ------------ Known Issues ------------ diff --git a/docs/source/whatsnew/0.7.rst b/docs/source/whatsnew/0.7.rst new file mode 100644 index 0000000..0a21d9b --- /dev/null +++ b/docs/source/whatsnew/0.7.rst @@ -0,0 +1,11 @@ +===================== +Changes in glymur 0.7 +===================== + +Changes in 0.7.0 +================= + + * implemented :py:meth:`__getitem__`, :py:meth:`__setitem__` support + * added back windows support + * box_id and longname are class attributes now instead of instance + attributes (see issue 248) diff --git a/docs/source/whatsnew/index.rst b/docs/source/whatsnew/index.rst index da7e319..2d69949 100644 --- a/docs/source/whatsnew/index.rst +++ b/docs/source/whatsnew/index.rst @@ -10,3 +10,4 @@ These document the changes between minor (or major) versions of glymur. 0.5 0.6 + 0.7 diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 98554d6..838756a 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -23,7 +23,8 @@ if sys.hexversion < 0x03000000: WARNING_INFRASTRUCTURE_MSG = "3.x warning infrastructure only" elif re.match('1.[0-6]', six.__version__) is not None: WARNING_INFRASTRUCTURE_ISSUE = True - WARNING_INFRASTRUCTURE_MSG = "Cannot use with this version of six" + msg = "Cannot run test with version {0} of python-six" + WARNING_INFRASTRUCTURE_MSG = msg.format(six.__version__) class MetadataBase(unittest.TestCase): """ diff --git a/glymur/version.py b/glymur/version.py index 8ffd80f..43ae3f5 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -19,7 +19,7 @@ from .lib import openjp2 as opj2 # Do not change the format of this next line! Doing so risks breaking # setup.py -version = "0.6.0" +version = "0.7.0rc1" _sv = LooseVersion(version) version_tuple = _sv.version From 7feb71e55bcf85e1a084d438b66fa8fc68f4a4d9 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 25 Sep 2014 14:50:25 -0400 Subject: [PATCH 259/326] skip one test for openjpeg versions < 1.5 --- glymur/test/test_jp2k.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 87c307e..a9e22c7 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -27,6 +27,7 @@ import pkg_resources import glymur from glymur import Jp2k +from glymur.version import openjpeg_version from .fixtures import HAS_PYTHON_XMP_TOOLKIT from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG @@ -441,8 +442,9 @@ class TestJp2k(unittest.TestCase): actdata = j2.read() self.assertTrue(fixtures.mse(actdata[0], expdata[0]) < 0.38) - @unittest.skipIf(re.match('1.5.(1|2)', - glymur.version.openjpeg_version) is not None, + @unittest.skipIf(re.match('1.[0-4]', openjpeg_version) is not None, + "Not supported with OpenJPEG {0}".format(openjpeg_version)) + @unittest.skipIf(re.match('1.5.(1|2)', openjpeg_version) is not None, "Mysteriously fails in 1.5.1 and 1.5.2") def test_no_cxform_pclr_jpx(self): """Indices for pclr jpxfile if no color transform""" From 55ef875275c73dabf4cafb05277a0ebb517f1fe6 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 25 Sep 2014 14:51:23 -0400 Subject: [PATCH 260/326] clarification of wording --- glymur/jp2k.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 66b79c9..734ed60 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -873,7 +873,7 @@ class Jp2k(Jp2kBox): rlevel : int, optional Factor by which to rlevel output resolution. Use -1 to get the lowest resolution thumbnail. This is the only keyword option - available to use when only the OpenJPEG version 1.5.1 is present. + available to use when the OpenJPEG version is 1.5 or earlier. layer : int, optional Number of quality layer to decode. area : tuple, optional From 4284adb387b555e3517cab3440d18a0a66846dbf Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 25 Sep 2014 14:51:39 -0400 Subject: [PATCH 261/326] add testing mention of CentOS --- docs/source/roadmap.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/source/roadmap.rst b/docs/source/roadmap.rst index 284c139..d0f9015 100644 --- a/docs/source/roadmap.rst +++ b/docs/source/roadmap.rst @@ -1,9 +1,10 @@ ------------------------------------- Platforms Tested (0.7.0 release only) ------------------------------------- - * Linux Mint 17 / Python 3.4.0 - * Linux Mint 17 / Python 2.7.6 + * Linux Mint 17 / Python 3.4.0 and 2.7.6 / OpenJPEG 2.1.0 * MacOS 10.6.8 / MacPorts / Python 3.4.1 + * CentOS 6.5 / Anaconda Python 3.4.1 / OpenJPEG 1.3 + (please use OpenJPEG 2.1.0 instead, though) ------------ Known Issues From 653db644b5c8b01cc6a50c8116a24f9f67fcf3b5 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 25 Sep 2014 15:26:19 -0400 Subject: [PATCH 262/326] skipping some tests on versions 1.3 and 1.4 --- glymur/test/test_jp2k.py | 2 ++ glymur/test/test_opj_suite_write.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index a9e22c7..2868e84 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -774,6 +774,8 @@ class TestJp2k(unittest.TestCase): self.assertEqual(data.shape, (1024, 1024, 3)) +@unittest.skipIf(re.match('1.[0-4]', openjpeg_version) is not None, + "Not supported with OpenJPEG {0}".format(openjpeg_version)) @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") class TestJp2k_write(unittest.TestCase): """Write tests, can be run by versions 1.5+""" diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index ebdce2e..7e6357d 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -27,6 +27,7 @@ from . import fixtures import glymur from glymur import Jp2k from glymur.codestream import SIZsegment +from glymur.version import openjpeg_version class CinemaBase(fixtures.MetadataBase): @@ -218,6 +219,8 @@ class TestSuiteNegative2pointzero(unittest.TestCase): j.write(data, cinema2k=48) +@unittest.skipIf(re.match(r'''1.[0-4]''', openjpeg_version) is not None, + "Writing not supported until OpenJPEG 1.5") @unittest.skipIf(os.name == "nt", "no write support on windows, period") @unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG) @unittest.skipIf(OPJ_DATA_ROOT is None, From a2a6b62316f6908a825c7291b18363e52f599847 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 25 Sep 2014 15:26:34 -0400 Subject: [PATCH 263/326] minor doc mods --- docs/source/introduction.rst | 6 +++++- docs/source/roadmap.rst | 5 ++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index 11059fd..54681f7 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -5,7 +5,11 @@ Glymur: a Python interface for JPEG 2000 **Glymur** is an interface to the OpenJPEG library which allows one to read and write JPEG 2000 files from Python. Glymur supports both reading and writing of JPEG 2000 images, but writing -JPEG 2000 images is currently limited to images that can fit in memory +JPEG 2000 images is currently limited to images that can fit in memory. +**Glymur** can read images using OpenJPEG library versions as far back as 1.3, +but it is strongly recommended to use version 2.1.0, which is the most recently +released version of OpenJPEG at this time. + In regards to metadata, most JP2 boxes are properly interpreted. Certain optional JP2 boxes can also be written, including XML boxes and diff --git a/docs/source/roadmap.rst b/docs/source/roadmap.rst index d0f9015..47ccfd1 100644 --- a/docs/source/roadmap.rst +++ b/docs/source/roadmap.rst @@ -1,10 +1,9 @@ ------------------------------------- Platforms Tested (0.7.0 release only) ------------------------------------- - * Linux Mint 17 / Python 3.4.0 and 2.7.6 / OpenJPEG 2.1.0 + * Linux Mint 17 / Python 3.4.0 and 2.7.6 / OpenJPEG 2.1.0 and 1.3.0 * MacOS 10.6.8 / MacPorts / Python 3.4.1 - * CentOS 6.5 / Anaconda Python 3.4.1 / OpenJPEG 1.3 - (please use OpenJPEG 2.1.0 instead, though) + * CentOS 6.5 / Anaconda Python 3.4.1 / OpenJPEG 1.3.0 ------------ Known Issues From 83df9533899944c1b52285a5ebe5f2acd0b62e73 Mon Sep 17 00:00:00 2001 From: jevans Date: Thu, 25 Sep 2014 18:52:19 -0400 Subject: [PATCH 264/326] more doc updates --- docs/source/detailed_installation.rst | 8 +++--- docs/source/how_do_i.rst | 37 +++++++++++++++++++-------- docs/source/introduction.rst | 3 +-- docs/source/roadmap.rst | 2 +- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index ecf0632..1a9281c 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -24,11 +24,11 @@ configuration format is the same as used by Python’s configparser module, i.e. :: [library] - openjp2: /opt/openjp2-svn/lib/libopenjp2.so + openjp2: /somewhere/lib/libopenjp2.so This assumes, of course, that you've installed OpenJPEG into -/opt/openjp2-svn on a linux system. The location of the configuration file -can vary as well (of course). If you use either linux or mac, the path +/opt/openjpeg on a linux system. The location of the configuration file +can vary as well. If you use either linux or mac, the path to the configuration file would normally be :: $HOME/.config/glymur/glymurrc @@ -48,7 +48,7 @@ You may also include a line for the version 1.x openjpeg library if you have it installed in a non-standard place, i.e. :: [library] - openjpeg: /not/the/usual/location/lib/libopenjpeg.so + openjpeg: /somewhere/lib/libopenjpeg.so ''''''' Testing diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index 3e0c3ee..5d11d67 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -7,11 +7,11 @@ How do I...? ================ Jp2k implements slicing via the :py:meth:`__getitem__` method, meaning that multiple resolution imagery in a JPEG 2000 file can -easily be accessed. For example here's how to retrieve a full resolution and -first lower-resolution image :: +easily be accessed via array-style slicing. For example here's how to +retrieve a full resolution and first lower-resolution image :: >>> import glymur - >>> jp2file = glymur.data.nemo() + >>> jp2file = glymur.data.nemo() # just a path to a JPEG2000 file >>> jp2 = glymur.Jp2k(jp2file) >>> fullres = jp2[:] >>> print(fullres.shape) @@ -20,8 +20,21 @@ first lower-resolution image :: >>> print(thumbnail.shape) (728, 1296, 3) -The :py:meth:`read` method gives many more options for other JPEG 2000 features -such as quality layers. +The :py:meth:`read` method exposes many more options for other JPEG 2000 +features such as quality layers. + +... write images? +================= +So long as the image data can fit entirely into memory, array-style slicing may +also be used to write JPEG 2000 files. + + >>> import glymur, numpy as np + >>> jp2 = glymur.Jp2k('zeros.jp2', mode='wb') + >>> jp2[:] = np.zeros((640, 480), dtype=np.uint8) + +The :py:meth:`write` method exposes many more options for other JPEG 2000 +features. You should have OpenJPEG version 1.5 or more recent before writing +JPEG 2000 images. ... display metadata? ===================== @@ -328,10 +341,10 @@ is currently limited to XML and UUID boxes. ... create an image with an alpha layer? ======================================== -OpenJPEG can create JP2 files with more than 3 components (requires -the development version of OpenJPEG), but by default, any extra components are -not described as such. In order to do so, we need to rewrap such -an image in a set of boxes that includes a channel definition box. +OpenJPEG can create JP2 files with more than 3 components (use version 2.1.0+ +for this), but by default, any extra components are not described +as such. In order to do so, we need to rewrap such an image in a +set of boxes that includes a channel definition box. This example is based on SciPy example code found at http://scipy-lectures.github.io/advanced/image_processing/#basic-manipulations . @@ -389,7 +402,11 @@ Here's how the Preview application on the mac shows the RGBA image. ... work with XMP UUIDs? ======================== -XMP is metadata on steroids. +`Wikipedia `_ states +that "The Extensible Metadata Platform (XMP) is an ISO standard, +originally created by Adobe Systems Inc., for the creation, processing +and interchange of standardized and custom metadata for all kinds +of resources." The example JP2 file shipped with glymur has an XMP UUID. :: diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index 54681f7..ae6d6ab 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -10,12 +10,11 @@ JPEG 2000 images is currently limited to images that can fit in memory. but it is strongly recommended to use version 2.1.0, which is the most recently released version of OpenJPEG at this time. - In regards to metadata, most JP2 boxes are properly interpreted. Certain optional JP2 boxes can also be written, including XML boxes and XMP UUIDs. There is incomplete support for reading JPX metadata. -Glymur 0.6 works on Python versions 2.7, 3.3 and 3.4. If you have Python 2.6, +Glymur works on Python versions 2.7, 3.3 and 3.4. If you have Python 2.6, you should use the 0.5 series of Glymur. For more information about OpenJPEG, please consult http://www.openjpeg.org. diff --git a/docs/source/roadmap.rst b/docs/source/roadmap.rst index 47ccfd1..df629ac 100644 --- a/docs/source/roadmap.rst +++ b/docs/source/roadmap.rst @@ -2,7 +2,7 @@ Platforms Tested (0.7.0 release only) ------------------------------------- * Linux Mint 17 / Python 3.4.0 and 2.7.6 / OpenJPEG 2.1.0 and 1.3.0 - * MacOS 10.6.8 / MacPorts / Python 3.4.1 + * MacOS 10.6.8 / MacPorts / Python 3.4.1, 3.3.5,and 2.7.8 * CentOS 6.5 / Anaconda Python 3.4.1 / OpenJPEG 1.3.0 ------------ From 0e2aa4492cbad94c090c4792e49dd25cf5586db3 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 25 Sep 2014 09:12:46 -0400 Subject: [PATCH 265/326] housecleaning for 0.7.0 release Add testing mention of CentOS, windows. Skipping some tests on versions 1.3 and 1.4 Minor doc mods. For whatever stupid reason, np.log2(x) cannot be relied upon to be an integer when x is a power of 2 ON WIN32!!! All hail win32! Better error message if array-style slice other than [:] --- CHANGES.txt | 2 ++ docs/source/conf.py | 4 +-- docs/source/detailed_installation.rst | 8 ++--- docs/source/how_do_i.rst | 51 ++++++++++++++++++--------- docs/source/index.rst | 2 +- docs/source/introduction.rst | 12 ++++--- docs/source/roadmap.rst | 9 +++++ docs/source/whatsnew/0.7.rst | 11 ++++++ docs/source/whatsnew/index.rst | 1 + glymur/jp2k.py | 10 +++--- glymur/test/fixtures.py | 3 +- glymur/test/test_config.py | 1 + glymur/test/test_jp2k.py | 8 +++-- glymur/test/test_opj_suite_write.py | 3 ++ glymur/version.py | 2 +- 15 files changed, 92 insertions(+), 35 deletions(-) create mode 100644 docs/source/whatsnew/0.7.rst diff --git a/CHANGES.txt b/CHANGES.txt index 44d6832..071453c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,5 @@ +Sep 25, 2014 - -v0.7.0 Added __getitem__, __setitem__ support. + Mar 06, 2014 - Added Cinema2K, Cinema4K write support. Changed constructor for ChannelDefinition box. Removed support for Python 2.6. Added write support for JP2 UUID, DataEntryURL, diff --git a/docs/source/conf.py b/docs/source/conf.py index 3e6b26e..5c44e90 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -76,9 +76,9 @@ copyright = u'2013, John Evans' # built documents. # # The short X.Y version. -version = '0.6' +version = '0.7' # The full version, including alpha/beta/rc tags. -release = '0.6.0' +release = '0.7.0rc1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index ecf0632..1a9281c 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -24,11 +24,11 @@ configuration format is the same as used by Python’s configparser module, i.e. :: [library] - openjp2: /opt/openjp2-svn/lib/libopenjp2.so + openjp2: /somewhere/lib/libopenjp2.so This assumes, of course, that you've installed OpenJPEG into -/opt/openjp2-svn on a linux system. The location of the configuration file -can vary as well (of course). If you use either linux or mac, the path +/opt/openjpeg on a linux system. The location of the configuration file +can vary as well. If you use either linux or mac, the path to the configuration file would normally be :: $HOME/.config/glymur/glymurrc @@ -48,7 +48,7 @@ You may also include a line for the version 1.x openjpeg library if you have it installed in a non-standard place, i.e. :: [library] - openjpeg: /not/the/usual/location/lib/libopenjpeg.so + openjpeg: /somewhere/lib/libopenjpeg.so ''''''' Testing diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index a9da7c1..5d11d67 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -3,14 +3,15 @@ How do I...? ------------ -... read the lower resolution images? -===================================== -Jp2k implements slicing via the :py:meth:`__getitem__` method so -any lower resolution images in a JPEG 2000 file can easily be -accessed, for example here's how to retrieve the first sub-image :: +... read images? +================ +Jp2k implements slicing via the :py:meth:`__getitem__` method, meaning that +multiple resolution imagery in a JPEG 2000 file can +easily be accessed via array-style slicing. For example here's how to +retrieve a full resolution and first lower-resolution image :: >>> import glymur - >>> jp2file = glymur.data.nemo() + >>> jp2file = glymur.data.nemo() # just a path to a JPEG2000 file >>> jp2 = glymur.Jp2k(jp2file) >>> fullres = jp2[:] >>> print(fullres.shape) @@ -19,20 +20,34 @@ accessed, for example here's how to retrieve the first sub-image :: >>> print(thumbnail.shape) (728, 1296, 3) -The :py:meth:`read` method gives many more options for other JPEG 2000 features -such as quality layers. +The :py:meth:`read` method exposes many more options for other JPEG 2000 +features such as quality layers. + +... write images? +================= +So long as the image data can fit entirely into memory, array-style slicing may +also be used to write JPEG 2000 files. + + >>> import glymur, numpy as np + >>> jp2 = glymur.Jp2k('zeros.jp2', mode='wb') + >>> jp2[:] = np.zeros((640, 480), dtype=np.uint8) + +The :py:meth:`write` method exposes many more options for other JPEG 2000 +features. You should have OpenJPEG version 1.5 or more recent before writing +JPEG 2000 images. ... display metadata? ===================== -There are two ways. From the command line, the script **jp2dump** is +There are two ways. From the command line, the console script **jp2dump** is available. :: $ jp2dump /path/to/glymur/installation/data/nemo.jp2 -From within Python, it is as simple as printing the Jp2k object, i.e. :: +From within Python, the same result is obtained simply by printing the Jp2k +object, i.e. :: >>> import glymur - >>> jp2file = glymur.data.nemo() + >>> jp2file = glymur.data.nemo() # just a path to a JP2 file >>> jp2 = glymur.Jp2k(jp2file) >>> print(jp2) File: nemo.jp2 @@ -326,10 +341,10 @@ is currently limited to XML and UUID boxes. ... create an image with an alpha layer? ======================================== -OpenJPEG can create JP2 files with more than 3 components (requires -the development version of OpenJPEG), but by default, any extra components are -not described as such. In order to do so, we need to rewrap such -an image in a set of boxes that includes a channel definition box. +OpenJPEG can create JP2 files with more than 3 components (use version 2.1.0+ +for this), but by default, any extra components are not described +as such. In order to do so, we need to rewrap such an image in a +set of boxes that includes a channel definition box. This example is based on SciPy example code found at http://scipy-lectures.github.io/advanced/image_processing/#basic-manipulations . @@ -387,7 +402,11 @@ Here's how the Preview application on the mac shows the RGBA image. ... work with XMP UUIDs? ======================== -XMP is metadata on steroids. +`Wikipedia `_ states +that "The Extensible Metadata Platform (XMP) is an ISO standard, +originally created by Adobe Systems Inc., for the creation, processing +and interchange of standardized and custom metadata for all kinds +of resources." The example JP2 file shipped with glymur has an XMP UUID. :: diff --git a/docs/source/index.rst b/docs/source/index.rst index 0675eef..8c1fb03 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,9 +15,9 @@ Contents: introduction detailed_installation how_do_i - api whatsnew/index roadmap + api ------------------ Indices and tables diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index bfe7174..ae6d6ab 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -5,13 +5,16 @@ Glymur: a Python interface for JPEG 2000 **Glymur** is an interface to the OpenJPEG library which allows one to read and write JPEG 2000 files from Python. Glymur supports both reading and writing of JPEG 2000 images, but writing -JPEG 2000 images is currently limited to images that can fit in memory +JPEG 2000 images is currently limited to images that can fit in memory. +**Glymur** can read images using OpenJPEG library versions as far back as 1.3, +but it is strongly recommended to use version 2.1.0, which is the most recently +released version of OpenJPEG at this time. In regards to metadata, most JP2 boxes are properly interpreted. Certain optional JP2 boxes can also be written, including XML boxes and XMP UUIDs. There is incomplete support for reading JPX metadata. -Glymur 0.6 works on Python versions 2.7, 3.3 and 3.4. If you have Python 2.6, +Glymur works on Python versions 2.7, 3.3 and 3.4. If you have Python 2.6, you should use the 0.5 series of Glymur. For more information about OpenJPEG, please consult http://www.openjpeg.org. @@ -27,5 +30,6 @@ but you should also be able to install Glymur via pip :: $ pip install glymur -In addition to the package, this also gives you a script **jp2dump** that can -be used from the command line line to print JPEG 2000 metadata. +In addition to the package, this also gives you a command line script +**jp2dump** that can be used from the command line line to print JPEG 2000 +metadata. diff --git a/docs/source/roadmap.rst b/docs/source/roadmap.rst index f0d7017..503fbc4 100644 --- a/docs/source/roadmap.rst +++ b/docs/source/roadmap.rst @@ -1,3 +1,12 @@ +------------------------------------- +Platforms Tested (0.7.0 release only) +------------------------------------- + * Linux Mint 17 / Python 3.4.0 and 2.7.6 / OpenJPEG 2.1.0 and 1.3.0 + * MacOS 10.6.8 / MacPorts Python 3.4.1, 3.3.5,and 2.7.8 / OpenJPEG 2.1.0 + * CentOS 6.5 / Anaconda Python 3.4.1 / OpenJPEG 1.3.0 + * Fedora 20 i386 / Python 2.7.5 and 3.3.2 / OpenJPEG 1.5.1 + * Windows 7 32bit / Anaconda Python 2.7.6 and 3.4.1 / OpenJPEG 2.1.0 + ------------ Known Issues ------------ diff --git a/docs/source/whatsnew/0.7.rst b/docs/source/whatsnew/0.7.rst new file mode 100644 index 0000000..0a21d9b --- /dev/null +++ b/docs/source/whatsnew/0.7.rst @@ -0,0 +1,11 @@ +===================== +Changes in glymur 0.7 +===================== + +Changes in 0.7.0 +================= + + * implemented :py:meth:`__getitem__`, :py:meth:`__setitem__` support + * added back windows support + * box_id and longname are class attributes now instead of instance + attributes (see issue 248) diff --git a/docs/source/whatsnew/index.rst b/docs/source/whatsnew/index.rst index da7e319..2d69949 100644 --- a/docs/source/whatsnew/index.rst +++ b/docs/source/whatsnew/index.rst @@ -10,3 +10,4 @@ These document the changes between minor (or major) versions of glymur. 0.5 0.6 + 0.7 diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 66b79c9..87028e1 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -772,7 +772,7 @@ class Jp2k(Jp2kBox): # Should have a slice object where start = stop = step = None self.write(data) else: - msg = "Images currently must be written entirely at once." + msg = "Partial write operations are currently not allowed." raise TypeError(msg) def __getitem__(self, pargs): @@ -832,9 +832,11 @@ class Jp2k(Jp2kBox): # one of them. step = rows_step - if np.log2(step) != np.floor(np.log2(step)): + # Check if the step size is a power of 2. + if np.abs(np.log2(step) - np.round(np.log2(step))) > 1e-6: msg = "Row and column strides must be powers of 2." raise IndexError(msg) + rlevel = np.int(np.round(np.log2(step))) if rows.start is None: rows_start = 0 @@ -857,7 +859,7 @@ class Jp2k(Jp2kBox): cols_stop = cols.stop area = (rows_start, cols_start, rows_stop, cols_stop) - data = self.read(area=area, rlevel=np.int(np.log2(step))) + data = self.read(area=area, rlevel=rlevel) if len(pargs) == 2: return data @@ -873,7 +875,7 @@ class Jp2k(Jp2kBox): rlevel : int, optional Factor by which to rlevel output resolution. Use -1 to get the lowest resolution thumbnail. This is the only keyword option - available to use when only the OpenJPEG version 1.5.1 is present. + available to use when the OpenJPEG version is 1.5 or earlier. layer : int, optional Number of quality layer to decode. area : tuple, optional diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 98554d6..838756a 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -23,7 +23,8 @@ if sys.hexversion < 0x03000000: WARNING_INFRASTRUCTURE_MSG = "3.x warning infrastructure only" elif re.match('1.[0-6]', six.__version__) is not None: WARNING_INFRASTRUCTURE_ISSUE = True - WARNING_INFRASTRUCTURE_MSG = "Cannot use with this version of six" + msg = "Cannot run test with version {0} of python-six" + WARNING_INFRASTRUCTURE_MSG = msg.format(six.__version__) class MetadataBase(unittest.TestCase): """ diff --git a/glymur/test/test_config.py b/glymur/test/test_config.py index 7196d35..f908272 100644 --- a/glymur/test/test_config.py +++ b/glymur/test/test_config.py @@ -71,6 +71,7 @@ class TestSuite(unittest.TestCase): Jp2k(self.jp2file) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) + @unittest.skipIf(os.name == "nt", 'named temporary file issue on windows') def test_xdg_env_config_file_is_bad(self): """A non-existant library location should be rejected.""" with tempfile.TemporaryDirectory() as tdir: diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 87c307e..2868e84 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -27,6 +27,7 @@ import pkg_resources import glymur from glymur import Jp2k +from glymur.version import openjpeg_version from .fixtures import HAS_PYTHON_XMP_TOOLKIT from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG @@ -441,8 +442,9 @@ class TestJp2k(unittest.TestCase): actdata = j2.read() self.assertTrue(fixtures.mse(actdata[0], expdata[0]) < 0.38) - @unittest.skipIf(re.match('1.5.(1|2)', - glymur.version.openjpeg_version) is not None, + @unittest.skipIf(re.match('1.[0-4]', openjpeg_version) is not None, + "Not supported with OpenJPEG {0}".format(openjpeg_version)) + @unittest.skipIf(re.match('1.5.(1|2)', openjpeg_version) is not None, "Mysteriously fails in 1.5.1 and 1.5.2") def test_no_cxform_pclr_jpx(self): """Indices for pclr jpxfile if no color transform""" @@ -772,6 +774,8 @@ class TestJp2k(unittest.TestCase): self.assertEqual(data.shape, (1024, 1024, 3)) +@unittest.skipIf(re.match('1.[0-4]', openjpeg_version) is not None, + "Not supported with OpenJPEG {0}".format(openjpeg_version)) @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") class TestJp2k_write(unittest.TestCase): """Write tests, can be run by versions 1.5+""" diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index ebdce2e..7e6357d 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -27,6 +27,7 @@ from . import fixtures import glymur from glymur import Jp2k from glymur.codestream import SIZsegment +from glymur.version import openjpeg_version class CinemaBase(fixtures.MetadataBase): @@ -218,6 +219,8 @@ class TestSuiteNegative2pointzero(unittest.TestCase): j.write(data, cinema2k=48) +@unittest.skipIf(re.match(r'''1.[0-4]''', openjpeg_version) is not None, + "Writing not supported until OpenJPEG 1.5") @unittest.skipIf(os.name == "nt", "no write support on windows, period") @unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG) @unittest.skipIf(OPJ_DATA_ROOT is None, diff --git a/glymur/version.py b/glymur/version.py index 8ffd80f..43ae3f5 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -19,7 +19,7 @@ from .lib import openjp2 as opj2 # Do not change the format of this next line! Doing so risks breaking # setup.py -version = "0.6.0" +version = "0.7.0rc1" _sv = LooseVersion(version) version_tuple = _sv.version From fc56012dce6cab3cd57e340b655fdd27fd1454f4 Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 1 Oct 2014 18:53:46 -0400 Subject: [PATCH 266/326] release 0.7.0 --- CHANGES.txt | 4 ++-- docs/source/conf.py | 2 +- glymur/version.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 071453c..2abf6d0 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,6 @@ -Sep 25, 2014 - -v0.7.0 Added __getitem__, __setitem__ support. +Oct 01, 2014 - v0.7.0 Added array-style slicing. -Mar 06, 2014 - Added Cinema2K, Cinema4K write support. +August 03, 2014 - v0.6.0 Added Cinema2K, Cinema4K write support. Changed constructor for ChannelDefinition box. Removed support for Python 2.6. Added write support for JP2 UUID, DataEntryURL, Palette and Component Mapping boxes, JPX Association, NumberList diff --git a/docs/source/conf.py b/docs/source/conf.py index 5c44e90..ace116b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -78,7 +78,7 @@ copyright = u'2013, John Evans' # The short X.Y version. version = '0.7' # The full version, including alpha/beta/rc tags. -release = '0.7.0rc1' +release = '0.7.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/glymur/version.py b/glymur/version.py index 43ae3f5..2092d4b 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -19,7 +19,7 @@ from .lib import openjp2 as opj2 # Do not change the format of this next line! Doing so risks breaking # setup.py -version = "0.7.0rc1" +version = "0.7.0" _sv = LooseVersion(version) version_tuple = _sv.version From 6af4b7f0a70df0fbf004531abde647b8542628e7 Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 1 Oct 2014 18:55:19 -0400 Subject: [PATCH 267/326] fix merge conflict --- glymur/version.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/glymur/version.py b/glymur/version.py index 1e6e8fe..2092d4b 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -19,11 +19,7 @@ from .lib import openjp2 as opj2 # Do not change the format of this next line! Doing so risks breaking # setup.py -<<<<<<< HEAD -version = "0.6.1" -======= version = "0.7.0" ->>>>>>> release-0.7.0rc1 _sv = LooseVersion(version) version_tuple = _sv.version From af699fedfe5caf6a70bf7ed520420a205a87c019 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 2 Oct 2014 08:17:48 -0400 Subject: [PATCH 268/326] updated README to mention Python 3.4 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 22338f1..bdc84d4 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,6 @@ glymur: a Python interface for JPEG 2000 **glymur** contains a Python interface to the OpenJPEG library which allows one to read and write JPEG 2000 files. **glymur** works on -Python 2.7 and 3.3. Python 3.3 is strongly recommended. +Python 2.7, 3.3, and 3.4. Please read the docs, https://glymur.readthedocs.org/en/latest/ From 454afec67bc86b87a83c8db0d71749decaf17612 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 2 Oct 2014 08:21:04 -0400 Subject: [PATCH 269/326] bumping to 0.7.1 --- CHANGES.txt | 2 ++ docs/source/conf.py | 2 +- glymur/version.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2abf6d0..b61c616 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,5 @@ +Oct 02, 2014 - v0.7.1 Fixed README to mention Python 3.4 + Oct 01, 2014 - v0.7.0 Added array-style slicing. August 03, 2014 - v0.6.0 Added Cinema2K, Cinema4K write support. diff --git a/docs/source/conf.py b/docs/source/conf.py index ace116b..56a0b17 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -78,7 +78,7 @@ copyright = u'2013, John Evans' # The short X.Y version. version = '0.7' # The full version, including alpha/beta/rc tags. -release = '0.7.0' +release = '0.7.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/glymur/version.py b/glymur/version.py index 2092d4b..93171aa 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -19,7 +19,7 @@ from .lib import openjp2 as opj2 # Do not change the format of this next line! Doing so risks breaking # setup.py -version = "0.7.0" +version = "0.7.1" _sv = LooseVersion(version) version_tuple = _sv.version From 4d652895bdfcc865903481ea65776663e1173857 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 4 Oct 2014 19:43:40 -0400 Subject: [PATCH 270/326] added ellipsis support, fixed an array slicing corner case Implemented most common ellipsis use cases. Some refactoring. Corner case where a single non-degenerate slice object is passed. --- glymur/jp2k.py | 137 ++++++++++++++++++++++----------------- glymur/test/test_jp2k.py | 46 +++++++++++++ 2 files changed, 122 insertions(+), 61 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 87028e1..d460ea6 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -13,8 +13,10 @@ import sys # pylint: disable=E0611 if sys.hexversion >= 0x03030000: from contextlib import ExitStack + from itertools import compress, filterfalse else: from contextlib2 import ExitStack + from itertools import compress, ifilterfalse as filterfalse from collections import Counter import ctypes @@ -28,9 +30,6 @@ import warnings import numpy as np from .codestream import Codestream -from .core import SRGB, GREYSCALE -from .core import PROGRESSION_ORDER -from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE from . import core from .jp2box import Jp2kBox from .jp2box import JPEG2000SignatureBox, FileTypeBox, JP2HeaderBox @@ -148,8 +147,8 @@ class Jp2k(Jp2kBox): jp2h = [box for box in self.box if box.box_id == 'jp2h'][0] colrs = [box for box in jp2h.box if box.box_id == 'colr'] for colr in colrs: - if colr.method not in (ENUMERATED_COLORSPACE, - RESTRICTED_ICC_PROFILE): + if colr.method not in (core.ENUMERATED_COLORSPACE, + core.RESTRICTED_ICC_PROFILE): msg = "Color Specification box method must specify either " msg += "an enumerated colorspace or a restricted ICC " msg += "profile if the file type box brand is 'jp2 '." @@ -310,7 +309,7 @@ class Jp2k(Jp2kBox): if 'prog' in kwargs: prog = kwargs['prog'].upper() - cparams.prog_order = PROGRESSION_ORDER[prog] + cparams.prog_order = core.PROGRESSION_ORDER[prog] if 'psnr' in kwargs: cparams.tcp_numlayers = len(kwargs['psnr']) @@ -742,11 +741,11 @@ class Jp2k(Jp2kBox): width = codestream.segment[1].xsiz num_components = len(codestream.segment[1].xrsiz) if num_components < 3: - colorspace = GREYSCALE + colorspace = core.GREYSCALE else: if len(self.box) == 0: # Best guess is SRGB - colorspace = SRGB + colorspace = core.SRGB else: # Take whatever the first jp2 header / color specification # says. @@ -763,10 +762,10 @@ class Jp2k(Jp2kBox): """ Slicing protocol. """ - if isinstance(index, slice) and ( - index.start == None and + if ((isinstance(index, slice) and + (index.start == None and index.stop == None and - index.step == None): + index.step == None)) or (index is Ellipsis)): # Case of jp2[:] = data, i.e. write the entire image. # # Should have a slice object where start = stop = step = None @@ -780,31 +779,70 @@ class Jp2k(Jp2kBox): Slicing protocol. """ codestream = self.get_codestream(header_only=True) + numrows = codestream.segment[1].ysiz + numcols = codestream.segment[1].xsiz + numbands = codestream.segment[1].Csiz + if isinstance(pargs, int): # Not a very good use of this protocol, but technically legal. # This retrieves a single row. row = pargs - area = (row, 0, row + 1, codestream.segment[1].xsiz) + area = (row, 0, row + 1, numcols) return self.read(area=area).squeeze() - if isinstance(pargs, slice): - # Case of jp2[:], i.e. retrieve the entire image. - # - # Should have a slice object where start = stop = step = None + if pargs is Ellipsis: + # Case of jp2[...] return self.read() - if isinstance(pargs, tuple) and all(isinstance(x, int) for x in pargs): - # Retrieve a single pixel. - # Something like jp2[r, c] - row = pargs[0] - col = pargs[1] - area = (row, col, row + 1, col + 1) - pixel = self.read(area=area).squeeze() - - if len(pargs) == 2: - return pixel - elif len(pargs) == 3: - return pixel[pargs[2]] + if isinstance(pargs, slice): + if pargs.start is None and pargs.stop is None and pargs.step is None: + # Case of jp2[:] + return self.read() + + # Corner case of jp2[x] where x is a slice object with non-null + # members. Just augment it with an ellipsis and let the code + # below handle it. + pargs = (pargs, Ellipsis) + + if isinstance(pargs, tuple) and any(x is Ellipsis for x in pargs): + # Remove the first ellipsis we find. + rows = slice(0, numrows) + cols = slice(0, numcols) + bands = slice(0, numbands) + if pargs[0] is Ellipsis: + if len(pargs) == 2: + newindex = (rows, cols, pargs[1]) + else: + newindex = (rows, pargs[1], pargs[2]) + elif pargs[1] is Ellipsis: + if len(pargs) == 2: + newindex = (pargs[0], cols, bands) + else: + newindex = (pargs[0], cols, pargs[2]) + else: + # Assume that we don't have 4D imagery, of course. + newindex = (pargs[0], pargs[1], bands) + + # Run once again because it is possible that there's another + # Ellipsis object in the 2nd or 3rd position. + return self.__getitem__(newindex) + + if isinstance(pargs, tuple) and any(isinstance(x, int) for x in pargs): + # Replace the first such integer argument, replace it with a slice. + lst = list(pargs) + predicate = lambda x: not isinstance(x[1], int) + g = filterfalse(predicate, enumerate(pargs)) + idx = next(g)[0] + lst[idx] = slice(pargs[idx], pargs[idx] + 1) + newindex = tuple(lst) + + # Invoke array-based slicing again, as there may be additional + # integer argument remaining. + data = self.__getitem__(newindex) + + # Reduce dimensionality in the scalar dimension. + return np.squeeze(data, axis=idx) + # Assuming pargs is a tuple of slices from now on. rows = pargs[0] @@ -814,16 +852,8 @@ class Jp2k(Jp2kBox): else: bands = pargs[2] - if rows.step is None: - rows_step = 1 - else: - rows_step = rows.step - - if cols.step is None: - cols_step = 1 - else: - cols_step = cols.step - + rows_step = 1 if rows.step is None else rows.step + cols_step = 1 if cols.step is None else cols.step if rows_step != cols_step: msg = "Row and column strides must be the same." raise IndexError(msg) @@ -838,27 +868,12 @@ class Jp2k(Jp2kBox): raise IndexError(msg) rlevel = np.int(np.round(np.log2(step))) - if rows.start is None: - rows_start = 0 - else: - rows_start = rows.start - - if rows.stop is None: - rows_stop = codestream.segment[1].ysiz - else: - rows_stop = rows.stop - - if cols.start is None: - cols_start = 0 - else: - cols_start = cols.start - - if cols.stop is None: - cols_stop = codestream.segment[1].xsiz - else: - cols_stop = cols.stop - - area = (rows_start, cols_start, rows_stop, cols_stop) + area = ( + 0 if rows.start is None else rows.start, + 0 if cols.start is None else cols.start, + numrows if rows.stop is None else rows.stop, + numcols if cols.stop is None else cols.stop + ) data = self.read(area=area, rlevel=rlevel) if len(pargs) == 2: return data @@ -1475,14 +1490,14 @@ def _validate_channel_definition(jp2h, colr): raise IOError(msg) elif len(cdef_lst) == 1: cdef = jp2h.box[cdef_lst[0]] - if colr.colorspace == SRGB: + if colr.colorspace == core.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: + elif colr.colorspace == core.GREYSCALE: if 0 not in cdef.channel_type: msg = "All color channels must be defined in the " msg += "channel definition box." diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 2868e84..042a681 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -71,6 +71,16 @@ class SliceProtocolBase(unittest.TestCase): @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") class TestSliceProtocolBaseWrite(SliceProtocolBase): + def test_write_ellipsis(self): + expected = self.j2k_data + + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j[...] = self.j2k_data + actual = j.read() + + np.testing.assert_array_equal(actual, expected) + def test_basic_write(self): expected = self.j2k_data @@ -236,6 +246,42 @@ class TestSliceProtocolRead(SliceProtocolBase): expected = self.jp2.read(area=(0, 0, 202, 202), rlevel=1) np.testing.assert_array_equal(actual, expected) + def test_ellipsis_full_read(self): + actual = self.j2k[...] + expected = self.j2k_data + np.testing.assert_array_equal(actual, expected) + + def test_ellipsis_band_select(self): + actual = self.j2k[..., 0] + expected = self.j2k_data[..., 0] + np.testing.assert_array_equal(actual, expected) + + def test_ellipsis_row_select(self): + actual = self.j2k[0, ...] + expected = self.j2k_data[0, ...] + np.testing.assert_array_equal(actual, expected) + + def test_two_ellipsis_band_select(self): + actual = self.j2k[..., ..., 1] + expected = self.j2k_data[:, :, 1] + np.testing.assert_array_equal(actual, expected) + + def test_two_ellipsis_row_select(self): + actual = self.j2k[1, ..., ...] + expected = self.j2k_data[1, :, :] + np.testing.assert_array_equal(actual, expected) + + def test_two_ellipsis_and_full_slice(self): + actual = self.j2k[..., ..., :] + expected = self.j2k_data[:] + np.testing.assert_array_equal(actual, expected) + + def test_single_slice(self): + rows = slice(3, 8) + actual = self.j2k[rows] + expected = self.j2k_data[3:8, :,:] + np.testing.assert_array_equal(actual, expected) + def test_slice_protocol_2d_reduce_resolution(self): d = self.j2k[:] self.assertEqual(d.shape, (800, 480, 3)) From 617b9c4f00d7dc4743cec61f4c1ddbf04877e76e Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 6 Oct 2014 21:16:11 -0400 Subject: [PATCH 271/326] bumping to 0.7.2 --- CHANGES.txt | 2 ++ docs/source/conf.py | 2 +- glymur/version.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index b61c616..758941b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,5 @@ +Oct 06, 2014 - v0.7.2 Added ellipsis support in array-style slicing. + Oct 02, 2014 - v0.7.1 Fixed README to mention Python 3.4 Oct 01, 2014 - v0.7.0 Added array-style slicing. diff --git a/docs/source/conf.py b/docs/source/conf.py index 56a0b17..8a87a64 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -78,7 +78,7 @@ copyright = u'2013, John Evans' # The short X.Y version. version = '0.7' # The full version, including alpha/beta/rc tags. -release = '0.7.1' +release = '0.7.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/glymur/version.py b/glymur/version.py index 93171aa..8509980 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -19,7 +19,7 @@ from .lib import openjp2 as opj2 # Do not change the format of this next line! Doing so risks breaking # setup.py -version = "0.7.1" +version = "0.7.2" _sv = LooseVersion(version) version_tuple = _sv.version From edde7d36b13d90a7fd399ee02cde0f1e7fd45a90 Mon Sep 17 00:00:00 2001 From: jevans Date: Fri, 17 Oct 2014 22:51:14 -0400 Subject: [PATCH 272/326] refactored private methods used by Jp2k write method, closes #269 --- glymur/jp2k.py | 357 ++++++++++++++++++++----------------------------- 1 file changed, 148 insertions(+), 209 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index d460ea6..94a37ee 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -45,6 +45,11 @@ JP2_IDS = ['colr', 'cdef', 'cmap', 'jp2c', 'ftyp', 'ihdr', 'jp2h', 'jP ', 'uuid'] JPX_IDS = ['asoc', 'nlst'] +_COLORSPACE_MAP = {'rgb': opj2.CLRSPC_SRGB, + 'gray': opj2.CLRSPC_GRAY, + 'grey': opj2.CLRSPC_GRAY, + 'ycc': opj2.CLRSPC_YCC} + class Jp2k(Jp2kBox): """JPEG 2000 file. @@ -74,6 +79,7 @@ class Jp2k(Jp2kBox): self.mode = mode self.box = [] self._codec_format = None + self._colorspace = None # Parse the file for JP2/JPX contents only if we are reading it. if mode == 'rb': @@ -167,11 +173,14 @@ class Jp2k(Jp2kBox): fps : int Frames per second, should be either 24 or 48. """ - if re.match("(1.5|2.0.0)", version.openjpeg_version) is not None: + if re.match("1.5|2.0.0", version.openjpeg_version) is not None: msg = "Writing Cinema2K or Cinema4K files is not supported with " msg += 'openjpeg library versions less than 2.0.1.' raise IOError(msg) + # Cinema modes imply MCT. + cparams.tcp_mct = 1 + if cinema_mode == 'cinema2k': if fps not in [24, 48]: raise IOError('Cinema2K frame rate must be either 24 or 48.') @@ -204,52 +213,30 @@ class Jp2k(Jp2kBox): return - def _populate_cparams(self, **kwargs): - """Populate compression parameters structure from input arguments. + def _populate_cparams(self, img_array, **kwargs): + """Directs processing of write method 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). + img_array : ndarray + image data to be written to file + kwargs : dictionary + non-image keyword inputs provided to write method Returns ------- cparams : CompressionParametersType(ctypes.Structure) - Corresponds to cparameters_t type in openjp2 headers. + corresponds to cparameters_t openjpeg datatype """ + if (('cinema2k' in kwargs or 'cinema4k' in kwargs) and + (len(set(kwargs)) > 1)): + msg = "Cannot specify cinema2k/cinema4k along with other options." + raise IOError(msg) + + if 'cratios' in kwargs and 'psnr' in kwargs: + msg = "Cannot specify cratios and psnr together." + raise IOError(msg) + if version.openjpeg_version_tuple[0] == 1: cparams = opj.set_default_encoder_parameters() else: @@ -336,49 +323,9 @@ class Jp2k(Jp2kBox): 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. - colorspace : str, optional - Either 'rgb' or 'gray'. - - Returns - ------- - cparams : CompressionParametersType(ctypes.Structure) - Corresponds to cparameters_t type in openjp2 headers. - colorspace : int - Either CLRSPC_SRGB or CLRSPC_GRAY - """ - if (('cinema2k' in kwargs or 'cinema4k' in kwargs) and - (len(set(kwargs)) > 1)): - msg = "Cannot specify cinema2k/cinema4k along with other options." - raise IOError(msg) - - if 'cratios' in kwargs and 'psnr' in kwargs: - msg = "Cannot specify cratios and psnr together." - raise IOError(msg) - - cparams = self._populate_cparams(**kwargs) - _validate_compression_params(img_array, cparams) - - colorspace = _unpack_colorspace(colorspace, img_array, cparams) - 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. + if mct and self._colorspace == opj2.CLRSPC_GRAY: msg = "Cannot specify usage of the multi component transform " msg += "if the colorspace is gray." raise IOError(msg) @@ -386,12 +333,14 @@ class Jp2k(Jp2kBox): 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: + if self._colorspace == opj2.CLRSPC_SRGB: cparams.tcp_mct = 1 else: cparams.tcp_mct = 0 - return cparams, colorspace + self._validate_compression_params(img_array, cparams, **kwargs) + + return cparams def write(self, img_array, verbose=False, **kwargs): """Write image data to a JP2/JPX/J2k file. Intended usage of the @@ -466,21 +415,23 @@ class Jp2k(Jp2kBox): glymur.LibraryNotFoundError If glymur is unable to load the openjp2 library. """ - if opj2.OPENJP2 is not None: - self._write_openjp2(img_array, verbose=verbose, **kwargs) - elif opj.OPENJPEG is not None: - self._write_openjpeg(img_array, verbose=verbose, **kwargs) - else: + if opj2.OPENJP2 is None and opj.OPENJPEG is None: raise LibraryNotFoundError("You must have at least version 1.5 of " "OpenJPEG before using this " "functionality.") - def _write_openjpeg(self, img_array, verbose=False, **kwargs): + self._determine_colorspace(img_array, **kwargs) + cparams = self._populate_cparams(img_array, **kwargs) + + if opj2.OPENJP2 is not None: + self._write_openjp2(img_array, cparams, verbose=verbose) + else: + self._write_openjpeg(img_array, cparams, verbose=verbose) + + def _write_openjpeg(self, img_array, cparams, verbose=False): """ Write JPEG 2000 file using OpenJPEG 1.5 interface. """ - cparams, colorspace = self._process_write_inputs(img_array, **kwargs) - if img_array.ndim == 2: # Force the image to be 3D. Just makes things easier later on. img_array = img_array.reshape(img_array.shape[0], @@ -490,7 +441,7 @@ class Jp2k(Jp2kBox): comptparms = _populate_comptparms(img_array, cparams) with ExitStack() as stack: - image = opj.image_create(comptparms, colorspace) + image = opj.image_create(comptparms, self._colorspace) stack.callback(opj.image_destroy, image) numrows, numcols, numlayers = img_array.shape @@ -542,13 +493,114 @@ class Jp2k(Jp2kBox): self.parse() + def _validate_compression_params(self, img_array, cparams, **kwargs): + """Check that the compression parameters are valid. - def _write_openjp2(self, img_array, verbose=False, **kwargs): + Parameters + ---------- + img_array : ndarray + Image data to be written to file. + cparams : CompressionParametersType(ctypes.Structure) + Corresponds to cparameters_t type in openjp2 headers. """ - Write JPEG 2000 file using OpenJPEG 2.0 interface. - """ - cparams, colorspace = self._process_write_inputs(img_array, **kwargs) + # Cannot specify a colorspace with J2K. + if cparams.codec_fmt == opj2.CODEC_J2K and 'colorspace' in kwargs: + msg = 'Do not specify a colorspace when writing a raw ' + msg += 'codestream.' + raise IOError(msg) + # 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 re.match("2.0.0", version.openjpeg_version) is not None: + if (((img_array.ndim != 2) and + (img_array.shape[2] != 1 and img_array.shape[2] != 3))): + msg = "Writing images is restricted to single-channel " + 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) + + def _determine_colorspace(self, img_array, colorspace=None, **kwargs): + """Determine the colorspace from the supplied inputs. + + Parameters + ---------- + img_array : ndarray + Image data to be written to file. + colorspace : str, optional + Either 'rgb' or 'gray'. + """ + if colorspace is None: + # Must infer the colorspace from the image dimensions. + if img_array.ndim < 3: + # A single channel image is grayscale. + self._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. + self._colorspace = opj2.CLRSPC_GRAY + else: + # Anything else must be RGB, right? + self._colorspace = opj2.CLRSPC_SRGB + else: + 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. + self._colorspace = _COLORSPACE_MAP[colorspace.lower()] + + + def _write_openjp2(self, img_array, cparams, verbose=False): + """ + Write JPEG 2000 file using OpenJPEG 2.x interface. + """ if img_array.ndim == 2: # Force the image to be 3D. Just makes things easier later on. numrows, numcols = img_array.shape @@ -557,7 +609,7 @@ class Jp2k(Jp2kBox): comptparms = _populate_comptparms(img_array, cparams) with ExitStack() as stack: - image = opj2.image_create(comptparms, colorspace) + image = opj2.image_create(comptparms, self._colorspace) stack.callback(opj2.image_destroy, image) _populate_image_struct(cparams, image, img_array) @@ -1674,50 +1726,6 @@ def extract_image_bands(image): 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. @@ -1805,75 +1813,6 @@ def _populate_image_struct(cparams, image, imgdata): 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 re.match("2.0.0", version.openjpeg_version) is not None: - if (((img_array.ndim != 2) and - (img_array.shape[2] != 1 and img_array.shape[2] != 3))): - msg = "Writing images is restricted to single-channel " - 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) - -_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. From b500287700e4e30df3184f7adcc0973d05abb5a0 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 18 Oct 2014 10:45:07 -0400 Subject: [PATCH 273/326] improved LibraryNotFoundError messages, docstring information, closes #270, #272 --- glymur/jp2k.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 94a37ee..fc877f4 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -412,8 +412,8 @@ class Jp2k(Jp2kBox): Raises ------ - glymur.LibraryNotFoundError - If glymur is unable to load the openjp2 library. + glymur.jp2k.LibraryNotFoundError + if glymur is unable to load an openjpeg library suitable for writing """ if opj2.OPENJP2 is None and opj.OPENJPEG is None: raise LibraryNotFoundError("You must have at least version 1.5 of " @@ -963,9 +963,8 @@ class Jp2k(Jp2kBox): Raises ------ - glymur.LibraryNotFoundError - If glymur is unable to load either the openjpeg or openjp2 - libraries. + glymur.jp2k.LibraryNotFoundError + if glymur is unable to load an openjpeg library suitable for reading IOError If the image has differing subsample factors. @@ -984,15 +983,13 @@ class Jp2k(Jp2kBox): >>> thumbnail.shape (728, 1296, 3) """ + if opj2.OPENJP2 is None and opj.OPENJPEG is None: + raise LibraryNotFoundError("Cannot load the OpenJPEG library.") + if opj2.OPENJP2 is not None: img = self._read_openjp2(**kwargs) - elif opj.OPENJPEG is not None: - img = self._read_openjpeg(**kwargs) else: - raise LibraryNotFoundError("You must have either a recent version " - "of OpenJPEG or the development " - "version of OpenJP2 installed before " - "using this functionality.") + img = self._read_openjpeg(**kwargs) return img def _subsampling_sanity_check(self): @@ -1282,12 +1279,12 @@ class Jp2k(Jp2kBox): Raises ------ - glymur.LibraryNotFoundError - If glymur is unable to load the openjp2 library. + glymur.jp2k.LibraryNotFoundError + if glymur is unable to load an openjpeg library suitable for reading """ if version.openjpeg_version_tuple[0] < 2: raise LibraryNotFoundError("You must have at least version 2.0.0 " - "of OpenJP2 installed before using " + "of OpenJPEG installed before using " "this functionality.") dparam = self._populate_dparam(rlevel, ignore_pclr_cmap_cdef, From f36c169f52eb4ea2d42eb785303602135e3bb99c Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 18 Oct 2014 19:52:22 -0400 Subject: [PATCH 274/326] raise RuntimeError if writing with library version is too early, closes #273 We still raise a LibraryNotFoundError if there's no library at all --- glymur/jp2k.py | 8 +++++--- glymur/test/test_jp2k.py | 34 +++++++++++++++++++++++++--------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index fc877f4..6b95cc1 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -415,10 +415,12 @@ class Jp2k(Jp2kBox): glymur.jp2k.LibraryNotFoundError if glymur is unable to load an openjpeg library suitable for writing """ + if re.match("1.[0-4]", version.openjpeg_version) is not None: + raise RuntimeError("You must have at least version 1.5 of OpenJPEG " + "in order to write images.") + if opj2.OPENJP2 is None and opj.OPENJPEG is None: - raise LibraryNotFoundError("You must have at least version 1.5 of " - "OpenJPEG before using this " - "functionality.") + raise LibraryNotFoundError("Cannot load the OpenJPEG library.") self._determine_colorspace(img_array, **kwargs) cparams = self._populate_cparams(img_array, **kwargs) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 042a681..6c92ff9 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -22,6 +22,11 @@ import uuid import warnings from xml.etree import cElementTree as ET +if sys.hexversion <= 0x03030000: + from mock import patch +else: + from unittest.mock import patch + import numpy as np import pkg_resources @@ -833,6 +838,17 @@ class TestJp2k_write(unittest.TestCase): def tearDown(self): pass + def test_write_with_version_too_early(self): + """Should raise a runtime error if trying to write with version 1.3""" + data = np.zeros((128, 128), dtype=np.uint8) + versions = ["1.0.0", "1.1.0", "1.2.0", "1.3.0"] + for version in versions: + with patch('glymur.version.openjpeg_version', new=version): + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + with self.assertRaises(RuntimeError): + j = Jp2k(tfile.name, 'wb') + j.write(data) + def test_cblkh_different_than_width(self): """Verify that we can set a code block size where height does not equal width. @@ -1158,15 +1174,15 @@ class TestJp2k_2_1(unittest.TestCase): with warnings.catch_warnings(): warnings.simplefilter('ignore') j = Jp2k(tfile.name) - regexp = re.compile(r'''OpenJPEG\slibrary\serror:\s+ - Invalid\svalues\sfor\scomp\s=\s0\s+ - :\sdx=1\sdy=0''', re.VERBOSE) - if sys.hexversion < 0x03020000: - with self.assertRaisesRegexp((IOError, OSError), regexp): - j.read(rlevel=1) - else: - with self.assertRaisesRegex((IOError, OSError), regexp): - j.read(rlevel=1) + regexp = re.compile(r'''OpenJPEG\slibrary\serror:\s+ + Invalid\svalues\sfor\scomp\s=\s0\s+ + :\sdx=1\sdy=0''', re.VERBOSE) + if sys.hexversion < 0x03020000: + with self.assertRaisesRegexp((IOError, OSError), regexp): + j.read(rlevel=1) + else: + with self.assertRaisesRegex((IOError, OSError), regexp): + j.read(rlevel=1) @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") From 378cab43504ce3f21752dece3bb34308f043b681 Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 18 Oct 2014 21:41:44 -0400 Subject: [PATCH 275/326] replaced windows temp file skip messages with a constant, closes #276 --- glymur/test/fixtures.py | 3 +++ glymur/test/test_config.py | 10 +++++++--- glymur/test/test_jp2box.py | 20 +++++++++----------- glymur/test/test_jp2box_xml.py | 5 +++-- glymur/test/test_jp2k.py | 26 +++++++++++++------------- glymur/test/test_opj_suite_write.py | 8 ++++---- 6 files changed, 39 insertions(+), 33 deletions(-) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 838756a..4b3972c 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -26,6 +26,9 @@ elif re.match('1.[0-6]', six.__version__) is not None: msg = "Cannot run test with version {0} of python-six" WARNING_INFRASTRUCTURE_MSG = msg.format(six.__version__) +# Cannot reopen a named temporary file in windows. +WINDOWS_TMP_FILE_MSG = "cannot use NamedTemporaryFile like this in windows" + class MetadataBase(unittest.TestCase): """ Base class for testing metadata. diff --git a/glymur/test/test_config.py b/glymur/test/test_config.py index f908272..477e2e3 100644 --- a/glymur/test/test_config.py +++ b/glymur/test/test_config.py @@ -25,7 +25,11 @@ else: import glymur from glymur import Jp2k -from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG +from .fixtures import ( + WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG, + WINDOWS_TMP_FILE_MSG +) + @unittest.skipIf(sys.hexversion < 0x03020000, "TemporaryDirectory introduced in 3.2.") @@ -71,7 +75,7 @@ class TestSuite(unittest.TestCase): Jp2k(self.jp2file) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - @unittest.skipIf(os.name == "nt", 'named temporary file issue on windows') + @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) def test_xdg_env_config_file_is_bad(self): """A non-existant library location should be rejected.""" with tempfile.TemporaryDirectory() as tdir: @@ -122,7 +126,7 @@ class TestConfig(unittest.TestCase): with self.assertRaises(glymur.jp2k.LibraryNotFoundError): glymur.Jp2k(self.jp2file).read_bands() - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) def test_write_without_library(self): """Don't have openjpeg libraries? Must error out. """ diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 061ad23..7246349 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -37,7 +37,7 @@ from glymur.core import RED, GREEN, BLUE, GREY, WHOLE_IMAGE from .fixtures import ( WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG, - MetadataBase + WINDOWS_TMP_FILE_MSG, MetadataBase ) try: @@ -54,7 +54,7 @@ def load_tests(loader, tests, ignore): tests.addTests(doctest.DocTestSuite('glymur.jp2box')) return tests -@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") +@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) class TestDataEntryURL(unittest.TestCase): """Test suite for DataEntryURL boxes.""" def setUp(self): @@ -122,7 +122,7 @@ class TestDataEntryURL(unittest.TestCase): @unittest.skipIf(re.match(r'''(1|2.0.0)''', glymur.version.openjpeg_version) is not None, "Not supported until 2.1") -@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") +@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) class TestChannelDefinition(unittest.TestCase): """Test suite for channel definition boxes.""" @@ -434,8 +434,7 @@ class TestColourSpecificationBox(unittest.TestCase): def tearDown(self): pass - @unittest.skipIf(os.name == "nt", - "Problems using NamedTemporaryFile on windows.") + @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) def test_colr_with_out_enum_cspace(self): """must supply an enumerated colorspace when writing""" j2k = Jp2k(self.j2kfile) @@ -446,7 +445,7 @@ class TestColourSpecificationBox(unittest.TestCase): with self.assertRaises(IOError): j2k.wrap(tfile.name, boxes=boxes) - @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") + @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) def test_missing_colr_box(self): """jp2h must have a colr box""" j2k = Jp2k(self.j2kfile) @@ -456,7 +455,7 @@ class TestColourSpecificationBox(unittest.TestCase): with self.assertRaises(IOError): j2k.wrap(tfile.name, boxes=boxes) - @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") + @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) def test_bad_approx_jp2_field(self): """JP2 has requirements for approx field""" j2k = Jp2k(self.j2kfile) @@ -517,8 +516,7 @@ class TestColourSpecificationBox(unittest.TestCase): colr.write(tfile) -@unittest.skipIf(os.name == "nt", - "Problems using NamedTemporaryFile on windows.") +@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) class TestPaletteBox(unittest.TestCase): """Test suite for pclr box instantiation.""" @@ -560,7 +558,7 @@ class TestPaletteBox(unittest.TestCase): pclr.write(tfile) -@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") +@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) class TestAppend(unittest.TestCase): """Tests for append method.""" @@ -653,7 +651,7 @@ class TestAppend(unittest.TestCase): jp2.append(uuidbox) -@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") +@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) class TestWrap(unittest.TestCase): """Tests for wrap method.""" diff --git a/glymur/test/test_jp2box_xml.py b/glymur/test/test_jp2box_xml.py index eeb1fa8..45d02f1 100644 --- a/glymur/test/test_jp2box_xml.py +++ b/glymur/test/test_jp2box_xml.py @@ -41,8 +41,9 @@ from glymur.jp2box import JPEG2000SignatureBox from .fixtures import OPJ_DATA_ROOT, opj_data_file from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG +from . import fixtures -@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") +@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) class TestXML(unittest.TestCase): """Test suite for XML boxes.""" @@ -219,7 +220,7 @@ class TestJp2kBadXmlFile(unittest.TestCase): self.assertIsNone(jp2k.box[3].xml) -@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") +@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) class TestBadButRecoverableXmlFile(unittest.TestCase): """Test suite for XML box that is bad, but we can still recover the XML.""" diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 6c92ff9..48c0fdb 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -73,7 +73,7 @@ class SliceProtocolBase(unittest.TestCase): self.j2k_data = self.j2k.read() -@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") +@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) class TestSliceProtocolBaseWrite(SliceProtocolBase): def test_write_ellipsis(self): @@ -614,7 +614,7 @@ class TestJp2k(unittest.TestCase): jp2k = Jp2k(self.j2kfile) self.assertEqual(len(jp2k.box), 0) - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) def test_64bit_xl_field(self): """XL field should be supported""" # Verify that boxes with the XL field are properly read. @@ -648,7 +648,7 @@ class TestJp2k(unittest.TestCase): self.assertEqual(jp2k.box[4].offset, 3223) self.assertEqual(jp2k.box[4].length, 1133427 + 8) - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) 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. @@ -705,7 +705,7 @@ class TestJp2k(unittest.TestCase): j = Jp2k(self.j2kfile) self.assertEqual(j.box, []) - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) 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 @@ -764,7 +764,7 @@ class TestJp2k(unittest.TestCase): self.assertEqual(jp2k.box[3].box[1].flag, (0, 0, 0)) self.assertEqual(jp2k.box[3].box[1].url, 'abcd') - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) 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: @@ -827,7 +827,7 @@ class TestJp2k(unittest.TestCase): @unittest.skipIf(re.match('1.[0-4]', openjpeg_version) is not None, "Not supported with OpenJPEG {0}".format(openjpeg_version)) -@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") +@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) class TestJp2k_write(unittest.TestCase): """Write tests, can be run by versions 1.5+""" @@ -1015,7 +1015,7 @@ class TestJp2k_1_x(unittest.TestCase): class TestJp2k_2_0_official(unittest.TestCase): """Test suite to only be run on v2.0 official.""" - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) def test_extra_components_on_v2(self): """Can only write 4 components on 2.0+, should error out otherwise.""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: @@ -1050,7 +1050,7 @@ class TestJp2k_2_0(unittest.TestCase): # End corner must be >= start corner j.read(area=(10, 10, 8, 8)) - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) def test_unrecognized_jp2_clrspace(self): """We only allow RGB and GRAYSCALE. Should error out with others""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: @@ -1059,7 +1059,7 @@ class TestJp2k_2_0(unittest.TestCase): data = np.zeros((128, 128, 3), dtype=np.uint8) j.write(data, colorspace='cmyk') - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) def test_asoc_label_box(self): """Test asoc and label box""" # Construct a fake file with an asoc and a label box, as @@ -1121,7 +1121,7 @@ class TestJp2k_2_1(unittest.TestCase): def tearDown(self): pass - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) def test_grey_with_extra_component(self): """version 2.0 cannot write gray + extra""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: @@ -1134,7 +1134,7 @@ class TestJp2k_2_1(unittest.TestCase): self.assertEqual(j.box[2].box[1].colorspace, glymur.core.GREYSCALE) - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) def test_rgb_with_extra_component(self): """v2.0+ should be able to write extra components""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: @@ -1147,7 +1147,7 @@ class TestJp2k_2_1(unittest.TestCase): self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) def test_openjpeg_library_message(self): """Verify the error message produced by the openjpeg library""" # This will confirm that the error callback mechanism is working. @@ -1269,7 +1269,7 @@ class TestJp2kOpjDataRootWarnings(unittest.TestCase): class TestJp2kOpjDataRoot(unittest.TestCase): """These tests should be run by just about all configuration.""" - @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) def test_irreversible(self): """Irreversible""" filename = opj_data_file('input/nonregression/issue141.rawl') diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 7e6357d..e0f947c 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -64,7 +64,7 @@ class CinemaBase(fixtures.MetadataBase): @unittest.skipIf(NO_SKIMAGE_FREEIMAGE_SUPPORT, "Cannot read input image without scikit-image/freeimage") -@unittest.skipIf(os.name == "nt", "no write support on windows, period") +@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) @unittest.skipIf(re.match(r'''(1|2.0.0)''', glymur.version.openjpeg_version) is not None, "Uses features not supported until 2.0.1") @@ -100,7 +100,7 @@ class WriteCinema(CinemaBase): @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @unittest.skipIf(NO_SKIMAGE_FREEIMAGE_SUPPORT, "Cannot read input image without scikit-image/freeimage") -@unittest.skipIf(os.name == "nt", "no write support on windows, period") +@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) @unittest.skipIf(re.match(r'''(1|2.0.0)''', glymur.version.openjpeg_version) is not None, "Uses features not supported until 2.0.1") @@ -194,7 +194,7 @@ class WriteCinemaWarns(CinemaBase): @unittest.skipIf(NO_SKIMAGE_FREEIMAGE_SUPPORT, "Cannot read input image without scikit-image/freeimage") -@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") +@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) @unittest.skipIf(not re.match("(1.5|2.0.0)", glymur.version.openjpeg_version), "Functionality implemented for 2.0.1") @unittest.skipIf(OPJ_DATA_ROOT is None, @@ -221,7 +221,7 @@ class TestSuiteNegative2pointzero(unittest.TestCase): @unittest.skipIf(re.match(r'''1.[0-4]''', openjpeg_version) is not None, "Writing not supported until OpenJPEG 1.5") -@unittest.skipIf(os.name == "nt", "no write support on windows, period") +@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) @unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG) @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") From 41252f54de5c7df05425cf5304b121988fe3255e Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 18 Oct 2014 23:09:15 -0400 Subject: [PATCH 276/326] tidied up top-level imports, closes #277 --- glymur/__init__.py | 6 ++++-- glymur/codestream.py | 9 +++++---- glymur/jp2box.py | 13 +++++++------ glymur/jp2k.py | 15 ++++++--------- glymur/lib/openjp2.py | 1 + glymur/lib/openjpeg.py | 1 + glymur/version.py | 3 +-- 7 files changed, 25 insertions(+), 23 deletions(-) diff --git a/glymur/__init__.py b/glymur/__init__.py index eb0139c..9a4d8b1 100644 --- a/glymur/__init__.py +++ b/glymur/__init__.py @@ -7,8 +7,10 @@ from glymur import version __version__ = version.version from .jp2k import Jp2k -from .jp2box import get_printoptions, set_printoptions -from .jp2box import get_parseoptions, set_parseoptions +from .jp2box import ( + get_printoptions, set_printoptions, + get_parseoptions, set_parseoptions +) from . import data diff --git a/glymur/codestream.py b/glymur/codestream.py index c87c2d2..c8ab00f 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -24,10 +24,11 @@ import warnings import numpy as np -from .core import LRCP, RLCP, RPCL, PCRL, CPRL -from .core import WAVELET_XFORM_9X7_IRREVERSIBLE -from .core import WAVELET_XFORM_5X3_REVERSIBLE -from .core import _Keydefaultdict +from .core import ( + LRCP, RLCP, RPCL, PCRL, CPRL, + WAVELET_XFORM_9X7_IRREVERSIBLE, WAVELET_XFORM_5X3_REVERSIBLE, + _Keydefaultdict +) from .lib import openjp2 as opj2 _factory = lambda x: '{0} (invalid)'.format(x) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index c3bd8e3..a2148ae 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -29,12 +29,13 @@ import lxml.etree as ET import numpy as np from .codestream import Codestream -from .core import _COLORSPACE_MAP_DISPLAY -from .core import _COLOR_TYPE_MAP_DISPLAY -from .core import SRGB, GREYSCALE, YCC -from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE -from .core import ANY_ICC_PROFILE, VENDOR_COLOR_METHOD -from .core import _Keydefaultdict +from .core import ( + _COLORSPACE_MAP_DISPLAY, _COLOR_TYPE_MAP_DISPLAY, + SRGB, GREYSCALE, YCC, + ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE, + ANY_ICC_PROFILE, VENDOR_COLOR_METHOD, + _Keydefaultdict +) from . import _uuid_io diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 6b95cc1..b7b699c 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -30,15 +30,12 @@ import warnings import numpy as np from .codestream import Codestream -from . import core -from .jp2box import Jp2kBox -from .jp2box import JPEG2000SignatureBox, FileTypeBox, JP2HeaderBox -from .jp2box import ColourSpecificationBox, ContiguousCodestreamBox -from .jp2box import ImageHeaderBox -from .lib import openjpeg as opj -from .lib import openjp2 as opj2 -from . import version -from .lib import c as libc +from . import core, version +from .jp2box import ( + Jp2kBox, JPEG2000SignatureBox, FileTypeBox, JP2HeaderBox, + ColourSpecificationBox, ContiguousCodestreamBox, ImageHeaderBox +) +from .lib import openjpeg as opj, openjp2 as opj2, c as libc JP2_IDS = ['colr', 'cdef', 'cmap', 'jp2c', 'ftyp', 'ihdr', 'jp2h', 'jP ', 'pclr', 'res ', 'resc', 'resd', 'xml ', 'ulst', 'uinf', 'url ', diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py index 5b5f3c4..f3f5b5c 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -9,6 +9,7 @@ import re import sys from .config import glymur_config + OPENJP2, OPENJPEG = glymur_config() def version(): diff --git a/glymur/lib/openjpeg.py b/glymur/lib/openjpeg.py index 418e9df..d1918ad 100644 --- a/glymur/lib/openjpeg.py +++ b/glymur/lib/openjpeg.py @@ -9,6 +9,7 @@ import sys import numpy as np from .config import glymur_config + _, OPENJPEG = glymur_config() # Maximum number of tile parts expected by JPWL: increase at your will diff --git a/glymur/version.py b/glymur/version.py index 8509980..55e4b88 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -14,8 +14,7 @@ from distutils.version import LooseVersion import lxml.etree import numpy as np -from .lib import openjpeg as opj -from .lib import openjp2 as opj2 +from .lib import openjpeg as opj, openjp2 as opj2 # Do not change the format of this next line! Doing so risks breaking # setup.py From 60dcdfe9c54fe8b43ece8e9d05a97058efa48e5c Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 18 Oct 2014 23:59:32 -0400 Subject: [PATCH 277/326] refactored configuration file handling, closes #265 --- glymur/lib/config.py | 109 +++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 57 deletions(-) diff --git a/glymur/lib/config.py b/glymur/lib/config.py index 04aece2..a475233 100644 --- a/glymur/lib/config.py +++ b/glymur/lib/config.py @@ -18,6 +18,19 @@ else: from configparser import ConfigParser from configparser import NoOptionError +# default library locations for MacPorts +_macports_default_location = { + 'openjp2': '/opt/local/lib/libopenjp2.dylib', + 'openjpeg': '/opt/local/lib/libopenjpeg.dylib' +} + +# default library locations on Windows +_windows_default_location = { + 'openjp2': os.path.join('C:\\', 'Program files', 'OpenJPEG 2.0', + 'bin', 'openjp2.dll'), + 'openjpeg': os.path.join('C:\\', 'Program files', 'OpenJPEG 1.5', + 'bin', 'openjpeg.dll') +} def glymurrc_fname(): """Return the path to the configuration file. @@ -42,30 +55,23 @@ def glymurrc_fname(): # didn't find a configuration file. return None +def load_openjpeg_library(libname): + + path = read_config_file(libname) + if path is not None: + return load_library_handle(path) -def load_openjpeg(path): - """Load the openjpeg library, falling back on defaults if necessary. + # No location specified by the configuration file, must look for it + # elsewhere. + path = find_library(libname) - Parameters - ---------- - path : str - Path to openjpeg 1.5 library as specified by configuration file. Will - be None if no configuration file specified. - """ - if path is None: - # Let ctypes try to find it. - path = find_library('openjpeg') - - # If we could not find it, then look in some likely locations on mac - # and win. if path is None: # Could not find a library via ctypes if platform.system() == 'Darwin': # MacPorts - path = '/opt/local/lib/libopenjpeg.dylib' + path = _macports_default_location[libname] elif os.name == 'nt': - path = os.path.join('C:\\', 'Program files', 'OpenJPEG 1.5', - 'bin', 'openjpeg.dll') + path = _windows_default_location[libname] if path is not None and not os.path.exists(path): # the mac/win default location does not exist. @@ -73,29 +79,6 @@ def load_openjpeg(path): return load_library_handle(path) -def load_openjp2(path): - """Load the openjp2 library, falling back on defaults if necessary. - """ - if path is None: - # No help from the config file, try to find it via ctypes. - path = find_library('openjp2') - - if path is None: - # Could not find a library via ctypes - if platform.system() == 'Darwin': - # MacPorts - path = '/opt/local/lib/libopenjp2.dylib' - elif os.name == 'nt': - path = os.path.join('C:\\', 'Program files', 'OpenJPEG 2.0', - 'bin', 'openjp2.dll') - - if path is not None and not os.path.exists(path): - # the mac/win default location does not exist. - return None - - return load_library_handle(path) - - def load_library_handle(path): """Load the library, return the ctypes handle.""" @@ -119,36 +102,48 @@ def load_library_handle(path): return opj_lib -def read_config_file(): +def read_config_file(libname): """ - We must use a configuration file that the user must write. + Extract library locations from a configuration file. + + Parameters + ---------- + libname : str + One of either 'openjp2' or 'openjpeg' + + Returns + ------- + path : None or str + None if no location is specified, otherwise a path to the library """ - lib = {'openjp2': None, 'openjpeg': None} filename = glymurrc_fname() if filename is not None: # Read the configuration file for the library location. parser = ConfigParser() parser.read(filename) - for name in ['openjp2', 'openjpeg']: - try: - lib[name] = parser.get('library', name) - except NoOptionError: - pass - - return lib + try: + path = parser.get('library', libname) + except NoOptionError: + path = None + return path def glymur_config(): - """Try to ascertain locations of openjp2, openjpeg libraries. """ - libs = read_config_file() - libopenjp2_handle = load_openjp2(libs['openjp2']) - libopenjpeg_handle = load_openjpeg(libs['openjpeg']) - if libopenjp2_handle is None and libopenjpeg_handle is None: + Try to ascertain locations of openjp2, openjpeg libraries. + + Returns + ------- + tpl : tuple + tuple of library handles + """ + lst = [] + for libname in ['openjp2', 'openjpeg']: + lst.append(load_openjpeg_library(libname)) + if all(handle is None for handle in lst): msg = "Neither the openjp2 nor the openjpeg library could be loaded. " raise IOError(msg) - return libopenjp2_handle, libopenjpeg_handle - + return tuple(lst) def get_configdir(): """Return string representing the configuration directory. From 5de1b8b240a95fbddb31495af4dddc9a2116ed34 Mon Sep 17 00:00:00 2001 From: jevans Date: Sun, 19 Oct 2014 11:24:23 -0400 Subject: [PATCH 278/326] remove LibraryNotFoundError, use RuntimeError instead, closes #274, #278 LibraryNotFoundError isn't needed because we already error out during the import of glymur if neither openjpeg nor openjp2 can be found. When read_bands is used and the library version is not at least 2.0.0, use RuntimeError instead of LibraryNotFoundError. --- glymur/jp2k.py | 31 +++---------------------------- glymur/test/test_config.py | 33 --------------------------------- glymur/test/test_jp2k.py | 5 +++++ 3 files changed, 8 insertions(+), 61 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index b7b699c..0bda856 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -406,19 +406,11 @@ class Jp2k(Jp2kBox): >>> tfile = NamedTemporaryFile(suffix='.jp2', delete=False) >>> j = Jp2k(tfile.name, mode='wb') >>> j.write(data.astype(np.uint8)) - - Raises - ------ - glymur.jp2k.LibraryNotFoundError - if glymur is unable to load an openjpeg library suitable for writing """ if re.match("1.[0-4]", version.openjpeg_version) is not None: raise RuntimeError("You must have at least version 1.5 of OpenJPEG " "in order to write images.") - if opj2.OPENJP2 is None and opj.OPENJPEG is None: - raise LibraryNotFoundError("Cannot load the OpenJPEG library.") - self._determine_colorspace(img_array, **kwargs) cparams = self._populate_cparams(img_array, **kwargs) @@ -962,8 +954,6 @@ class Jp2k(Jp2kBox): Raises ------ - glymur.jp2k.LibraryNotFoundError - if glymur is unable to load an openjpeg library suitable for reading IOError If the image has differing subsample factors. @@ -982,9 +972,6 @@ class Jp2k(Jp2kBox): >>> thumbnail.shape (728, 1296, 3) """ - if opj2.OPENJP2 is None and opj.OPENJPEG is None: - raise LibraryNotFoundError("Cannot load the OpenJPEG library.") - if opj2.OPENJP2 is not None: img = self._read_openjp2(**kwargs) else: @@ -1275,16 +1262,11 @@ class Jp2k(Jp2kBox): >>> jfile = glymur.data.nemo() >>> jp = glymur.Jp2k(jfile) >>> components_lst = jp.read_bands(rlevel=1) - - Raises - ------ - glymur.jp2k.LibraryNotFoundError - if glymur is unable to load an openjpeg library suitable for reading """ if version.openjpeg_version_tuple[0] < 2: - raise LibraryNotFoundError("You must have at least version 2.0.0 " - "of OpenJPEG installed before using " - "this functionality.") + raise RuntimeError("You must have at least version 2.0.0 of " + "OpenJPEG installed before using this " + "functionality.") dparam = self._populate_dparam(rlevel, ignore_pclr_cmap_cdef, layer=layer, tile=tile, area=area) @@ -1835,10 +1817,3 @@ def _default_warning_handler(library_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/test/test_config.py b/glymur/test/test_config.py index 477e2e3..37db88d 100644 --- a/glymur/test/test_config.py +++ b/glymur/test/test_config.py @@ -108,36 +108,3 @@ class TestConfig(unittest.TestCase): def tearDown(self): pass - 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): - glymur.Jp2k(self.jp2file).read() - - 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 patch('glymur.version.openjpeg_version_tuple', - new=(0, 0, 0)): - with self.assertRaises(glymur.jp2k.LibraryNotFoundError): - glymur.Jp2k(self.jp2file).read_bands() - - @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) - def test_write_without_library(self): - """Don't have openjpeg libraries? Must error out. - """ - data = glymur.Jp2k(self.j2kfile).read() - with patch('glymur.lib.openjp2.OPENJP2', new=None): - with patch('glymur.lib.openjpeg.OPENJPEG', new=None): - with self.assertRaises(glymur.jp2k.LibraryNotFoundError): - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - ofile = Jp2k(tfile.name, 'wb') - ofile.write(data) - - -if __name__ == "__main__": - unittest.main() diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 48c0fdb..3800f44 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -824,6 +824,11 @@ class TestJp2k(unittest.TestCase): data = jpx.read() self.assertEqual(data.shape, (1024, 1024, 3)) + def test_read_bands_without_openjp2(self): + """Don't have openjp2 library? Must error out.""" + with patch('glymur.version.openjpeg_version_tuple', new=(1, 5, 0)): + with self.assertRaises(RuntimeError): + glymur.Jp2k(self.jp2file).read_bands() @unittest.skipIf(re.match('1.[0-4]', openjpeg_version) is not None, "Not supported with OpenJPEG {0}".format(openjpeg_version)) From 185f4da07347914a94726962408f414d5b08c5c1 Mon Sep 17 00:00:00 2001 From: jevans Date: Sun, 19 Oct 2014 13:13:35 -0400 Subject: [PATCH 279/326] fix library path issue when no configuration file exists, #265 --- glymur/lib/config.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/glymur/lib/config.py b/glymur/lib/config.py index a475233..431a00c 100644 --- a/glymur/lib/config.py +++ b/glymur/lib/config.py @@ -117,15 +117,17 @@ def read_config_file(libname): None if no location is specified, otherwise a path to the library """ filename = glymurrc_fname() - if filename is not None: - # Read the configuration file for the library location. - parser = ConfigParser() - parser.read(filename) - try: - path = parser.get('library', libname) - except NoOptionError: - path = None + if filename is None: + # There's no library file path to return in this case. + return None + # Read the configuration file for the library location. + parser = ConfigParser() + parser.read(filename) + try: + path = parser.get('library', libname) + except NoOptionError: + path = None return path def glymur_config(): From ff849df520d3e8f5d5d7c51875d18cd56d3e6087 Mon Sep 17 00:00:00 2001 From: jevans Date: Sun, 19 Oct 2014 13:00:45 -0400 Subject: [PATCH 280/326] add __str__ methods for openjp2 ctypes structures, refactored their use The openjp2 structures are now largely maintained as weak internal-use-only attributes where possible. This allows us to inspect (print) them from the command line after the fact. --- glymur/jp2k.py | 290 +++++++++++++++---------------- glymur/lib/openjp2.py | 92 ++++++++++ glymur/lib/test/fixtures.py | 144 +++++++++++++++ glymur/lib/test/test_printing.py | 77 ++++++++ glymur/test/fixtures.py | 1 + glymur/test/test_printing.py | 12 +- 6 files changed, 456 insertions(+), 160 deletions(-) create mode 100644 glymur/lib/test/fixtures.py create mode 100644 glymur/lib/test/test_printing.py diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 0bda856..aadb5ed 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -157,7 +157,7 @@ class Jp2k(Jp2kBox): msg += "profile if the file type box brand is 'jp2 '." warnings.warn(msg) - def _set_cinema_params(self, cparams, cinema_mode, fps): + def _set_cinema_params(self, cinema_mode, fps): """Populate compression parameters structure for cinema2K. Parameters @@ -176,7 +176,7 @@ class Jp2k(Jp2kBox): raise IOError(msg) # Cinema modes imply MCT. - cparams.tcp_mct = 1 + self._cparams.tcp_mct = 1 if cinema_mode == 'cinema2k': if fps not in [24, 48]: @@ -185,30 +185,28 @@ class Jp2k(Jp2kBox): if re.match("2.0", version.openjpeg_version) is not None: # 2.0 API if fps == 24: - cparams.cp_cinema = core.OPJ_CINEMA2K_24 + self._cparams.cp_cinema = core.OPJ_CINEMA2K_24 else: - cparams.cp_cinema = core.OPJ_CINEMA2K_48 + self._cparams.cp_cinema = core.OPJ_CINEMA2K_48 else: # 2.1 API if fps == 24: - cparams.rsiz = core.OPJ_PROFILE_CINEMA_2K - cparams.max_comp_size = core.OPJ_CINEMA_24_COMP - cparams.max_cs_size = core.OPJ_CINEMA_24_CS + self._cparams.rsiz = core.OPJ_PROFILE_CINEMA_2K + self._cparams.max_comp_size = core.OPJ_CINEMA_24_COMP + self._cparams.max_cs_size = core.OPJ_CINEMA_24_CS else: - cparams.rsiz = core.OPJ_PROFILE_CINEMA_2K - cparams.max_comp_size = core.OPJ_CINEMA_48_COMP - cparams.max_cs_size = core.OPJ_CINEMA_48_CS + self._cparams.rsiz = core.OPJ_PROFILE_CINEMA_2K + self._cparams.max_comp_size = core.OPJ_CINEMA_48_COMP + self._cparams.max_cs_size = core.OPJ_CINEMA_48_CS else: # cinema4k if re.match("2.0", version.openjpeg_version) is not None: # 2.0 API - cparams.cp_cinema = core.OPJ_CINEMA4K_24 + self._cparams.cp_cinema = core.OPJ_CINEMA4K_24 else: # 2.1 API - cparams.rsiz = core.OPJ_PROFILE_CINEMA_4K - - return + self._cparams.rsiz = core.OPJ_PROFILE_CINEMA_4K def _populate_cparams(self, img_array, **kwargs): """Directs processing of write method arguments. @@ -219,11 +217,6 @@ class Jp2k(Jp2kBox): image data to be written to file kwargs : dictionary non-image keyword inputs provided to write method - - Returns - ------- - cparams : CompressionParametersType(ctypes.Structure) - corresponds to cparameters_t openjpeg datatype """ if (('cinema2k' in kwargs or 'cinema4k' in kwargs) and (len(set(kwargs)) > 1)): @@ -258,12 +251,14 @@ class Jp2k(Jp2kBox): cparams.irreversible = 1 if 'cinema2k' in kwargs: - self._set_cinema_params(cparams, 'cinema2k', kwargs['cinema2k']) - return cparams + self._cparams = cparams + self._set_cinema_params('cinema2k', kwargs['cinema2k']) + return if 'cinema4k' in kwargs: - self._set_cinema_params(cparams, 'cinema4k', kwargs['cinema4k']) - return cparams + self._cparams = cparams + self._set_cinema_params('cinema4k', kwargs['cinema4k']) + return if 'cbsize' in kwargs: cparams.cblockw_init = kwargs['cbsize'][1] @@ -337,7 +332,7 @@ class Jp2k(Jp2kBox): self._validate_compression_params(img_array, cparams, **kwargs) - return cparams + self._cparams = cparams def write(self, img_array, verbose=False, **kwargs): """Write image data to a JP2/JPX/J2k file. Intended usage of the @@ -412,14 +407,14 @@ class Jp2k(Jp2kBox): "in order to write images.") self._determine_colorspace(img_array, **kwargs) - cparams = self._populate_cparams(img_array, **kwargs) + self._populate_cparams(img_array, **kwargs) if opj2.OPENJP2 is not None: - self._write_openjp2(img_array, cparams, verbose=verbose) + self._write_openjp2(img_array, verbose=verbose) else: - self._write_openjpeg(img_array, cparams, verbose=verbose) + self._write_openjpeg(img_array, verbose=verbose) - def _write_openjpeg(self, img_array, cparams, verbose=False): + def _write_openjpeg(self, img_array, verbose=False): """ Write JPEG 2000 file using OpenJPEG 1.5 interface. """ @@ -429,21 +424,21 @@ class Jp2k(Jp2kBox): img_array.shape[1], 1) - comptparms = _populate_comptparms(img_array, cparams) + self._populate_comptparms(img_array) with ExitStack() as stack: - image = opj.image_create(comptparms, self._colorspace) + image = opj.image_create(self._comptparms, self._colorspace) stack.callback(opj.image_destroy, image) numrows, numcols, numlayers = img_array.shape # set image offset and reference grid - image.contents.x0 = cparams.image_offset_x0 - image.contents.y0 = cparams.image_offset_y0 + image.contents.x0 = self._cparams.image_offset_x0 + image.contents.y0 = self._cparams.image_offset_y0 image.contents.x1 = image.contents.x0 \ - + (numcols - 1) * cparams.subsampling_dx + 1 + + (numcols - 1) * self._cparams.subsampling_dx + 1 image.contents.y1 = image.contents.y0 \ - + (numrows - 1) * cparams.subsampling_dy + 1 + + (numrows - 1) * self._cparams.subsampling_dy + 1 # Stage the image data to the openjpeg data structure. for k in range(0, numlayers): @@ -453,7 +448,7 @@ class Jp2k(Jp2kBox): src = layer.ctypes.data ctypes.memmove(dest, src, layer.nbytes) - cinfo = opj.create_compress(cparams.codec_fmt) + cinfo = opj.create_compress(self._cparams.codec_fmt) stack.callback(opj.destroy_compress, cinfo) # Setup the info, warning, and error handlers. @@ -467,7 +462,7 @@ class Jp2k(Jp2kBox): event_mgr.error_handler = ctypes.cast(_ERROR_CALLBACK, ctypes.c_void_p) - opj.setup_encoder(cinfo, ctypes.byref(cparams), image) + opj.setup_encoder(cinfo, ctypes.byref(self._cparams), image) cio = opj.cio_open(cinfo) stack.callback(opj.cio_close, cio) @@ -588,7 +583,7 @@ class Jp2k(Jp2kBox): self._colorspace = _COLORSPACE_MAP[colorspace.lower()] - def _write_openjp2(self, img_array, cparams, verbose=False): + def _write_openjp2(self, img_array, verbose=False): """ Write JPEG 2000 file using OpenJPEG 2.x interface. """ @@ -597,15 +592,15 @@ class Jp2k(Jp2kBox): numrows, numcols = img_array.shape img_array = img_array.reshape(numrows, numcols, 1) - comptparms = _populate_comptparms(img_array, cparams) + self._populate_comptparms(img_array) with ExitStack() as stack: - image = opj2.image_create(comptparms, self._colorspace) + image = opj2.image_create(self._comptparms, self._colorspace) stack.callback(opj2.image_destroy, image) - _populate_image_struct(cparams, image, img_array) + self._populate_image_struct(image, img_array) - codec = opj2.create_compress(cparams.codec_fmt) + codec = opj2.create_compress(self._cparams.codec_fmt) stack.callback(opj2.destroy_codec, codec) info_handler = _INFO_CALLBACK if verbose else None @@ -613,7 +608,7 @@ class Jp2k(Jp2kBox): opj2.set_warning_handler(codec, _WARNING_CALLBACK) opj2.set_error_handler(codec, _ERROR_CALLBACK) - opj2.setup_encoder(codec, cparams, image) + opj2.setup_encoder(codec, self._cparams, image) if re.match("2.0", version.openjpeg_version) is not None: fptr = libc.fopen(self.filename, 'wb') @@ -1020,13 +1015,13 @@ class Jp2k(Jp2kBox): """ self._subsampling_sanity_check() - dparameters = self._populate_dparam(rlevel, ignore_pclr_cmap_cdef) + self._populate_dparams(rlevel, ignore_pclr_cmap_cdef) with ExitStack() as stack: try: - dparameters.decod_format = self._codec_format + self._dparams.decod_format = self._codec_format - dinfo = opj.create_decompress(dparameters.decod_format) + dinfo = opj.create_decompress(self._dparams.decod_format) event_mgr = opj.EventMgrType() info_handler = ctypes.cast(_INFO_CALLBACK, ctypes.c_void_p) @@ -1037,7 +1032,7 @@ class Jp2k(Jp2kBox): ctypes.c_void_p) opj.set_event_mgr(dinfo, ctypes.byref(event_mgr)) - opj.setup_decoder(dinfo, dparameters) + opj.setup_decoder(dinfo, self._dparams) with open(self.filename, 'rb') as fptr: src = fptr.read() @@ -1104,8 +1099,8 @@ class Jp2k(Jp2kBox): """ self._subsampling_sanity_check() - dparam = self._populate_dparam(rlevel, ignore_pclr_cmap_cdef, - layer=layer, tile=tile, area=area) + self._populate_dparams(rlevel, ignore_pclr_cmap_cdef, + layer=layer, tile=tile, area=area) with ExitStack() as stack: if re.match("2.1", version.openjpeg_version): @@ -1127,16 +1122,17 @@ class Jp2k(Jp2kBox): else: opj2.set_info_handler(codec, None) - opj2.setup_decoder(codec, dparam) + opj2.setup_decoder(codec, self._dparams) 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) + if self._dparams.nb_tile_to_decode: + opj2.get_decoded_tile(codec, stream, image, + self._dparams.tile_index) else: opj2.set_decode_area(codec, image, - dparam.DA_x0, dparam.DA_y0, - dparam.DA_x1, dparam.DA_y1) + self._dparams.DA_x0, self._dparams.DA_y0, + self._dparams.DA_x1, self._dparams.DA_y1) opj2.decode(codec, stream, image) opj2.end_decompress(codec, stream) @@ -1147,8 +1143,8 @@ class Jp2k(Jp2kBox): return img_array - def _populate_dparam(self, rlevel, ignore_pclr_cmap_cdef, tile=None, - layer=None, area=None): + def _populate_dparams(self, rlevel, ignore_pclr_cmap_cdef, tile=None, + layer=None, area=None): """Populate decompression structure with appropriate input parameters. Parameters @@ -1165,11 +1161,6 @@ class Jp2k(Jp2kBox): ignore_pclr_cmap_cdef : bool Whether or not to ignore the pclr, cmap, or cdef boxes during any color transformation. Defaults to False. - - Returns - ------- - dparam : DecompressionParametersType (ctypes) - Corresponds to openjp2 decompression parameters structure. """ if opj2.OPENJP2 is not None: dparam = opj2.set_default_decoder_parameters() @@ -1220,7 +1211,7 @@ class Jp2k(Jp2kBox): # Return raw codestream components. dparam.flags |= 1 - return dparam + self._dparams = dparam def read_bands(self, rlevel=0, layer=0, area=None, tile=None, verbose=False, ignore_pclr_cmap_cdef=False): @@ -1268,8 +1259,8 @@ class Jp2k(Jp2kBox): "OpenJPEG installed before using this " "functionality.") - dparam = self._populate_dparam(rlevel, ignore_pclr_cmap_cdef, - layer=layer, tile=tile, area=area) + self._populate_dparams(rlevel, ignore_pclr_cmap_cdef, + layer=layer, tile=tile, area=area) with ExitStack() as stack: if re.match("2.1", version.openjpeg_version): @@ -1292,16 +1283,17 @@ class Jp2k(Jp2kBox): else: opj2.set_info_handler(codec, None) - opj2.setup_decoder(codec, dparam) + opj2.setup_decoder(codec, self._dparams) 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) + if self._dparams.nb_tile_to_decode: + opj2.get_decoded_tile(codec, stream, image, + self._dparams.tile_index) else: opj2.set_decode_area(codec, image, - dparam.DA_x0, dparam.DA_y0, - dparam.DA_x1, dparam.DA_y1) + self._dparams.DA_x0, self._dparams.DA_y0, + self._dparams.DA_x1, self._dparams.DA_y1) opj2.decode(codec, stream, image) opj2.end_decompress(codec, stream) @@ -1362,7 +1354,83 @@ class Jp2k(Jp2kBox): return codestream + def _populate_image_struct(self, image, imgdata): + """Populates image struct needed for compression. + + Parameters + ---------- + image : ImageType(ctypes.Structure) + Corresponds to image_t type in openjp2 headers. + img_array : ndarray + Image data to be written to file. + """ + + numrows, numcols, num_comps = imgdata.shape + + # set image offset and reference grid + image.contents.x0 = self._cparams.image_offset_x0 + image.contents.y0 = self._cparams.image_offset_y0 + image.contents.x1 = (image.contents.x0 + + (numcols - 1) * self._cparams.subsampling_dx + 1) + image.contents.y1 = (image.contents.y0 + + (numrows - 1) * self._cparams.subsampling_dy + 1) + + # Stage the image data to the openjpeg data structure. + for k in range(0, num_comps): + if re.match("2.0", version.openjpeg_version) is not None: + # 2.0 API + if self._cparams.cp_cinema: + image.contents.comps[k].prec = 12 + image.contents.comps[k].bpp = 12 + else: + # 2.1 API + if self._cparams.rsiz in (core.OPJ_PROFILE_CINEMA_2K, + core.OPJ_PROFILE_CINEMA_4K): + image.contents.comps[k].prec = 12 + image.contents.comps[k].bpp = 12 + + layer = np.ascontiguousarray(imgdata[:, :, k], dtype=np.int32) + dest = image.contents.comps[k].data + src = layer.ctypes.data + ctypes.memmove(dest, src, layer.nbytes) + + return image + + def _populate_comptparms(self, img_array): + """Instantiate and populate comptparms structure. + + This structure defines the image components. + + Parameters + ---------- + img_array : ndarray + Image data to be written to file. + """ + # 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 + if version.openjpeg_version_tuple[0] == 1: + comptparms = (opj.ImageComptParmType * num_comps)() + else: + comptparms = (opj2.ImageComptParmType * num_comps)() + for j in range(num_comps): + comptparms[j].dx = self._cparams.subsampling_dx + comptparms[j].dy = self._cparams.subsampling_dy + comptparms[j].w = numcols + comptparms[j].h = numrows + comptparms[j].x0 = self._cparams.image_offset_x0 + comptparms[j].y0 = self._cparams.image_offset_y0 + comptparms[j].prec = comp_prec + comptparms[j].bpp = comp_prec + comptparms[j].sgnd = 0 + + self._comptparms = comptparms + + def _component2dtype(component): """Take an OpenJPEG component structure and determine the numpy datatype. @@ -1704,92 +1772,6 @@ def extract_image_bands(image): return data -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 - if version.openjpeg_version_tuple[0] == 1: - comptparms = (opj.ImageComptParmType * num_comps)() - else: - 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): - if re.match("2.0", version.openjpeg_version) is not None: - # 2.0 API - if cparams.cp_cinema: - image.contents.comps[k].prec = 12 - image.contents.comps[k].bpp = 12 - else: - # 2.1 API - if cparams.rsiz in (core.OPJ_PROFILE_CINEMA_2K, - core.OPJ_PROFILE_CINEMA_4K): - image.contents.comps[k].prec = 12 - image.contents.comps[k].bpp = 12 - - layer = np.ascontiguousarray(imgdata[:, :, k], dtype=np.int32) - dest = image.contents.comps[k].data - src = layer.ctypes.data - ctypes.memmove(dest, src, layer.nbytes) - - return image - # Setup the default callback handlers. See the callback functions subsection # in the ctypes section of the Python documentation for a solid explanation of diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py index f3f5b5c..f0a5f09 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -7,6 +7,7 @@ Wraps individual functions in openjp2 library. import ctypes import re import sys +import textwrap from .config import glymur_config @@ -138,6 +139,13 @@ class PocType(ctypes.Structure): ("tx0_t", ctypes.c_uint32), ("ty0_t", ctypes.c_uint32)] + def __str__(self): + msg = "{0}:\n".format(self.__class__) + for field_name, _ in self._fields_: + msg += " {0}: {1}\n".format( + field_name, getattr(self, field_name)) + return msg + class DecompressionParametersType(ctypes.Structure): """Decompression parameters. @@ -201,6 +209,13 @@ class DecompressionParametersType(ctypes.Structure): # maximum number of tiles ("flags", ctypes.c_uint32)] + def __str__(self): + msg = "{0}:\n".format(self.__class__) + for field_name, _ in self._fields_: + msg += " {0}: {1}\n".format( + field_name, getattr(self, field_name)) + return msg + class CompressionParametersType(ctypes.Structure): """Compression parameters. @@ -392,6 +407,47 @@ class CompressionParametersType(ctypes.Structure): # values. _fields_.append(("rsiz", ctypes.c_uint16)) + def __str__(self): + msg = "{0}:\n".format(self.__class__) + for field_name, _ in self._fields_: + + if field_name == 'poc': + msg += " numpocs: {0}\n".format(self.numpocs) + for j in range(self.numpocs): + msg += " [#{0}]:".format(j) + msg += " {0}".format(str(self.poc[j])) + msg += textwrap.indent(textstr, ' ' * 12) + + elif field_name in ['tcp_rates', 'tcp_distoratio']: + lst = [] + arr = getattr(self, field_name) + lst = [arr[j] for j in range(self.tcp_numlayers)] + msg += " {0}: {1}\n".format(field_name, lst) + + elif field_name in ['prcw_init', 'prch_init']: + pass + + elif field_name == 'res_spec': + prcw_init = [self.prcw_init[j] for j in range(self.res_spec)] + prch_init = [self.prch_init[j] for j in range(self.res_spec)] + msg += " res_spec: {0}\n".format(self.res_spec) + msg += " prch_init: {0}\n".format(prch_init) + msg += " prcw_init: {0}\n".format(prcw_init) + + elif field_name in [ + 'jpwl_hprot_tph_tileno', 'jpwl_hprot_tph', + 'jpwl_pprot_tileno', 'jpwl_pprot_packno', 'jpwl_pprot', + 'jpwl_sens_tph_tileno', 'jpwl_sens_tph']: + arr = getattr(self, field_name) + lst = [arr[j] for j in range(JPWL_MAX_NO_TILESPECS)] + msg += " {0}: {1}\n".format(field_name, lst) + + else: + msg += " {0}: {1}\n".format( + field_name, getattr(self, field_name)) + return msg + + class ImageCompType(ctypes.Structure): """Defines a single image component. @@ -433,6 +489,14 @@ class ImageCompType(ctypes.Structure): if _MINOR == '1': _fields_.append(("alpha", ctypes.c_uint16)) + def __str__(self): + msg = "{0}:\n".format(self.__class__) + for field_name, _ in self._fields_: + msg += " {0}: {1}\n".format( + field_name, getattr(self, field_name)) + return msg + + class ImageType(ctypes.Structure): """Defines image data and characteristics. @@ -463,6 +527,27 @@ class ImageType(ctypes.Structure): # restricted ICC profile buffer length ("icc_profile_len", ctypes.c_uint32)] + def __str__(self): + msg = "{0}:\n".format(self.__class__) + for field_name, _ in self._fields_: + + if field_name == "numcomps": + msg += " numcomps: {0}\n".format(self.numcomps) + for j in range(self.numcomps): + msg += " comps[#{0}]:\n".format(j) + msg += textwrap.indent(str(self.comps[j]), ' ' * 12) + + elif field_name == "comps": + # handled above + pass + + else: + msg += " {0}: {1}\n".format( + field_name, getattr(self, field_name)) + + return msg + + class ImageComptParmType(ctypes.Structure): """Component parameters structure used by image_create function. @@ -492,6 +577,13 @@ class ImageComptParmType(ctypes.Structure): # signed (1) / unsigned (0) ("sgnd", ctypes.c_uint32)] + def __str__(self): + msg = "{0}:\n".format(self.__class__) + for field_name, _ in self._fields_: + msg += " {0}: {1}\n".format( + field_name, getattr(self, field_name)) + return msg + class TccpInfo(ctypes.Structure): """Tile-component coding parameters information. diff --git a/glymur/lib/test/fixtures.py b/glymur/lib/test/fixtures.py new file mode 100644 index 0000000..b5b9648 --- /dev/null +++ b/glymur/lib/test/fixtures.py @@ -0,0 +1,144 @@ +decompression_parameters_type = """: + cp_reduce: 0 + cp_layer: 0 + infile: b'' + outfile: b'' + decod_format: -1 + cod_format: -1 + DA_x0: 0 + DA_x1: 0 + DA_y0: 0 + DA_y1: 0 + m_verbose: 0 + tile_index: 0 + nb_tile_to_decode: 0 + jpwl_correct: 0 + jpwl_exp_comps: 0 + jpwl_max_tiles: 0 + flags: 0""" + +default_progression_order_changes_type = """: + resno0: 0 + compno0: 0 + layno1: 0 + resno1: 0 + compno1: 0 + layno0: 0 + precno0: 0 + precno1: 0 + prg1: 0 + prg: 0 + progorder: b'' + tile: 0 + tx0: 0 + tx1: 0 + ty0: 0 + ty1: 0 + layS: 0 + resS: 0 + compS: 0 + prcS: 0 + layE: 0 + resE: 0 + compE: 0 + prcE: 0 + txS: 0 + txE: 0 + tyS: 0 + tyE: 0 + dx: 0 + dy: 0 + lay_t: 0 + res_t: 0 + comp_t: 0 + prec_t: 0 + tx0_t: 0 + ty0_t: 0""" + +default_compression_parameters_type = """: + tile_size_on: 0 + cp_tx0: 0 + cp_ty0: 0 + cp_tdx: 0 + cp_tdy: 0 + cp_disto_alloc: 0 + cp_fixed_alloc: 0 + cp_fixed_quality: 0 + cp_matrice: None + cp_comment: None + csty: 0 + prog_order: 0 + numpocs: 0 + numpocs: 0 + tcp_numlayers: 0 + tcp_rates: [] + tcp_distoratio: [] + numresolution: 6 + cblockw_init: 64 + cblockh_init: 64 + mode: 0 + irreversible: 0 + roi_compno: -1 + roi_shift: 0 + res_spec: 0 + prch_init: [] + prcw_init: [] + infile: b'' + outfile: b'' + index_on: 0 + index: b'' + image_offset_x0: 0 + image_offset_y0: 0 + subsampling_dx: 1 + subsampling_dy: 1 + decod_format: -1 + cod_format: -1 + jpwl_epc_on: 0 + jpwl_hprot_mh: 0 + jpwl_hprot_tph_tileno: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + jpwl_hprot_tph: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + jpwl_pprot_tileno: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + jpwl_pprot_packno: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + jpwl_pprot: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + jpwl_sens_size: 0 + jpwl_sens_addr: 0 + jpwl_sens_range: 0 + jpwl_sens_mh: 0 + jpwl_sens_tph_tileno: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + jpwl_sens_tph: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + cp_cinema: 0 + max_comp_size: 0 + cp_rsiz: 0 + tp_on: 0 + tp_flag: 0 + tcp_mct: 0 + jpip_on: 0 + mct_data: None + max_cs_size: 0 + rsiz: 0""" + +default_image_component_parameters = """: + dx: 0 + dy: 0 + w: 0 + h: 0 + x0: 0 + y0: 0 + prec: 0 + bpp: 0 + sgnd: 0""" + +# The "icc_profile_buf" field is problematic as it is a pointer value, i.e. +# +# icc_profile_buf: +# +# Have to treat it as a regular expression. +default_image_type = """: + x0: 0 + y0: 0 + x1: 0 + y1: 0 + numcomps: 0 + color_space: 0 + icc_profile_buf: + icc_profile_len: 0""" diff --git a/glymur/lib/test/test_printing.py b/glymur/lib/test/test_printing.py new file mode 100644 index 0000000..3d0e2b6 --- /dev/null +++ b/glymur/lib/test/test_printing.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +"""Test suite for printing. +""" + +import re +import sys +import unittest + +if sys.hexversion < 0x03000000: + from mock import patch + from StringIO import StringIO +else: + from unittest.mock import patch + from io import StringIO + +import glymur +from . import fixtures + +@unittest.skipIf(sys.hexversion < 0x03000000, "do not care about 2.7 here") +@unittest.skipIf(re.match('1|2.0', glymur.version.openjpeg_version), + "Requires openjpeg 2.1.0 or higher") +class TestPrintingOpenjp2(unittest.TestCase): + """Tests for verifying how printing works on openjp2 library structures.""" + def setUp(self): + self.jp2file = glymur.data.nemo() + + def tearDown(self): + pass + + def test_decompression_parameters(self): + """printing DecompressionParametersType""" + dparams = glymur.lib.openjp2.set_default_decoder_parameters() + with patch('sys.stdout', new=StringIO()) as fake_out: + print(dparams) + actual = fake_out.getvalue().strip() + expected = fixtures.decompression_parameters_type + self.assertEqual(actual, expected) + + def test_progression_order_changes(self): + """printing PocType""" + ptype = glymur.lib.openjp2.PocType() + with patch('sys.stdout', new=StringIO()) as fake_out: + print(ptype) + actual = fake_out.getvalue().strip() + expected = fixtures.default_progression_order_changes_type + self.assertEqual(actual, expected) + + def test_default_compression_parameters(self): + """printing default compression parameters""" + cparams = glymur.lib.openjp2.set_default_encoder_parameters() + with patch('sys.stdout', new=StringIO()) as fake_out: + print(cparams) + actual = fake_out.getvalue().strip() + expected = fixtures.default_compression_parameters_type + self.assertEqual(actual, expected) + + def test_default_component_parameters(self): + """printing default image component parameters""" + icpt = glymur.lib.openjp2.ImageComptParmType() + with patch('sys.stdout', new=StringIO()) as fake_out: + print(icpt) + actual = fake_out.getvalue().strip() + expected = fixtures.default_image_component_parameters + self.assertEqual(actual, expected) + + def test_default_image_type(self): + """printing default image type""" + it = glymur.lib.openjp2.ImageType() + with patch('sys.stdout', new=StringIO()) as fake_out: + print(it) + actual = fake_out.getvalue().strip() + + expected = fixtures.default_image_type + self.assertRegex(actual, expected) + + + diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 4b3972c..481da2d 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -900,3 +900,4 @@ goodstuff_with_full_header = r"""Codestream: Step size: [(0, 8), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10)] SOD marker segment @ (164, 0) EOC marker segment @ (115218, 0)""" + diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 49bbe6d..b6eca2f 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -32,12 +32,13 @@ import lxml.etree as ET import glymur from glymur import Jp2k, command_line from . import fixtures -from .fixtures import OPJ_DATA_ROOT, opj_data_file -from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG -from .fixtures import text_gbr_27, text_gbr_33, text_gbr_34 +from .fixtures import ( + OPJ_DATA_ROOT, opj_data_file, + WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG, + WINDOWS_TMP_FILE_MSG, text_gbr_27, text_gbr_33, text_gbr_34 +) - -@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") +@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) class TestPrinting(unittest.TestCase): """Tests for verifying how printing works.""" def setUp(self): @@ -617,7 +618,6 @@ class TestPrinting(unittest.TestCase): self.assertEqual(actual, expected) - @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") From 3636939c12b604c4bc70909a13bf2f389217169d Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 21 Oct 2014 17:49:32 -0400 Subject: [PATCH 281/326] use end_decompress when decoding a tile, closes #285 had mistakenly only been using end_decompress when decoding by area --- glymur/jp2k.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index aadb5ed..499fb29 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -1134,7 +1134,8 @@ class Jp2k(Jp2kBox): self._dparams.DA_x0, self._dparams.DA_y0, self._dparams.DA_x1, self._dparams.DA_y1) opj2.decode(codec, stream, image) - opj2.end_decompress(codec, stream) + + opj2.end_decompress(codec, stream) img_array = extract_image_cube(image) From ad423635860c93021d8dcf8ba33cc86ddd006132 Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 21 Oct 2014 18:02:16 -0400 Subject: [PATCH 282/326] fix error message when no OpenJPEG library can be found, closes #284 --- glymur/lib/config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/glymur/lib/config.py b/glymur/lib/config.py index 431a00c..2254387 100644 --- a/glymur/lib/config.py +++ b/glymur/lib/config.py @@ -94,9 +94,9 @@ def load_library_handle(path): else: opj_lib = ctypes.CDLL(path) except (TypeError, OSError): - msg = '"Library {0}" could not be loaded. Operating in degraded mode.' - msg = msg.format(path) - warnings.warn(msg, UserWarning) + msg = 'The library specified by configuration file at {0} could not be ' + msg += 'loaded.' + warnings.warn(msg.format(path), UserWarning) opj_lib = None return opj_lib From da25953de30ab2864fc413cb26dc569a195c3eb8 Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 21 Oct 2014 19:43:26 -0400 Subject: [PATCH 283/326] updated tests to run on fully loaded platform, closes #287 --- glymur/jp2k.py | 6 ++-- glymur/test/test_callbacks.py | 56 +++++++---------------------- glymur/test/test_jp2k.py | 36 +++++++++---------- glymur/test/test_opj_suite_write.py | 23 +++++++----- 4 files changed, 48 insertions(+), 73 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 499fb29..6817dbc 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -967,10 +967,10 @@ class Jp2k(Jp2kBox): >>> thumbnail.shape (728, 1296, 3) """ - if opj2.OPENJP2 is not None: - img = self._read_openjp2(**kwargs) - else: + if version.openjpeg_version_tuple[0] < 2: img = self._read_openjpeg(**kwargs) + else: + img = self._read_openjp2(**kwargs) return img def _subsampling_sanity_check(self): diff --git a/glymur/test/test_callbacks.py b/glymur/test/test_callbacks.py index e87b3af..ce3bb8c 100644 --- a/glymur/test/test_callbacks.py +++ b/glymur/test/test_callbacks.py @@ -25,8 +25,6 @@ import glymur from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG -@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None, - "Missing openjp2 library.") class TestCallbacks(unittest.TestCase): """Test suite for callbacks.""" @@ -37,6 +35,8 @@ class TestCallbacks(unittest.TestCase): def tearDown(self): pass + @unittest.skipIf(glymur.version.openjpeg_version[0] != '2', + "Missing openjp2 library.") @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_info_callback_on_write(self): @@ -59,46 +59,20 @@ class TestCallbacks(unittest.TestCase): # callback handler is enabled. j = glymur.Jp2k(self.j2kfile) with patch('sys.stdout', new=StringIO()) as fake_out: - j.read(rlevel=1, verbose=True, area=(0, 0, 200, 150)) + j.read(rlevel=1, verbose=True) actual = fake_out.getvalue().strip() - lines = ['[INFO] Start to read j2k main header (0).', - '[INFO] Main header has been correctly decoded.', - '[INFO] Setting decoding area to 0,0,150,200', - '[INFO] Header of tile 0 / 0 has been read.', - '[INFO] Tile 1/1 has been decoded.', - '[INFO] Image data has been updated with tile 1.'] - - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - - -@unittest.skipIf(glymur.lib.openjpeg.OPENJPEG is None, - "Missing openjpeg library.") -class TestCallbacks15(unittest.TestCase): - """This test suite is for OpenJPEG 1.5.1 properties. - """ - - def setUp(self): - self.jp2file = glymur.data.nemo() - self.j2kfile = glymur.data.goodstuff() - - def tearDown(self): - pass - - def test_info_callbacks_on_read(self): - """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: - j.read(rlevel=1, verbose=True) - actual = fake_out.getvalue().strip() + if glymur.version.openjpeg_version[0] == '2': + lines = ['[INFO] Start to read j2k main header (0).', + '[INFO] Main header has been correctly decoded.', + '[INFO] No decoded area parameters, set the decoded area to the whole image', + '[INFO] Header of tile 0 / 0 has been read.', + '[INFO] Tile 1/1 has been decoded.', + '[INFO] Image data has been updated with tile 1.'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + else: regex = re.compile(r"""\[INFO\]\stile\s1\sof\s1\s+ \[INFO\]\s-\stiers-1\stook\s [0-9]+\.[0-9]+\ss\s+ @@ -114,7 +88,3 @@ class TestCallbacks15(unittest.TestCase): self.assertRegexpMatches(actual, regex) else: self.assertRegex(actual, regex) - - -if __name__ == "__main__": - unittest.main() diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 3800f44..61ebdce 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -987,8 +987,6 @@ class TestJp2k_write(unittest.TestCase): self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL) -@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] >= 2, - "Negative tests only for version 1.x") class TestJp2k_1_x(unittest.TestCase): """Test suite for openjpeg 1.x, not appropriate for 2.x""" @@ -1002,32 +1000,32 @@ class TestJp2k_1_x(unittest.TestCase): def test_tile(self): """tile option not allowed for 1.x. """ - j2k = Jp2k(self.j2kfile) - with self.assertRaises(TypeError): - j2k.read(tile=0) + with patch('glymur.version.openjpeg_version_tuple', new=(1, 5, 0)): + j2k = Jp2k(self.j2kfile) + with self.assertRaises(TypeError): + j2k.read(tile=0) def test_layer(self): """layer option not allowed for 1.x. """ - j2k = Jp2k(self.j2kfile) - with self.assertRaises(TypeError): - j2k.read(layer=1) + with patch('glymur.version.openjpeg_version_tuple', new=(1, 5, 0)): + j2k = Jp2k(self.j2kfile) + with self.assertRaises(TypeError): + j2k.read(layer=1) -@unittest.skipIf(re.match(r'''2.0.0''', - glymur.version.openjpeg_version) is None, - "Tests only to be run on 2.0 official.") -class TestJp2k_2_0_official(unittest.TestCase): - """Test suite to only be run on v2.0 official.""" +@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) +class Test_2p0_official(unittest.TestCase): + """Tests specific to v2.0.0""" - @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) def test_extra_components_on_v2(self): """Can only write 4 components on 2.0+, should error out otherwise.""" - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = Jp2k(tfile.name, 'wb') - data = np.zeros((128, 128, 4), dtype=np.uint8) - with self.assertRaises(IOError): - j.write(data) + with patch('glymur.version.openjpeg_version', new="2.0.0"): + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') + data = np.zeros((128, 128, 4), dtype=np.uint8) + with self.assertRaises(IOError): + j.write(data) @unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2, diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index e0f947c..8be1cef 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -12,6 +12,11 @@ import sys import tempfile import unittest +if sys.hexversion <= 0x03030000: + from mock import patch +else: + from unittest.mock import patch + import numpy as np try: import skimage.io @@ -195,12 +200,10 @@ class WriteCinemaWarns(CinemaBase): @unittest.skipIf(NO_SKIMAGE_FREEIMAGE_SUPPORT, "Cannot read input image without scikit-image/freeimage") @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) -@unittest.skipIf(not re.match("(1.5|2.0.0)", glymur.version.openjpeg_version), - "Functionality implemented for 2.0.1") @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_OPJ_DATA_ROOT environment variable not set") -class TestSuiteNegative2pointzero(unittest.TestCase): - """Feature set not supported for versions less than 2.0""" +class TestNegative2pointzero(unittest.TestCase): + """Feature set not supported for versions less than 2.0.1""" def setUp(self): self.jp2file = glymur.data.nemo() @@ -210,13 +213,17 @@ class TestSuiteNegative2pointzero(unittest.TestCase): pass def test_cinema_mode(self): + """Cinema mode not allowed for anything less than 2.0.1""" relfile = 'input/nonregression/X_4_2K_24_185_CBR_WB_000.tif' infile = opj_data_file(relfile) data = skimage.io.imread(infile) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - with self.assertRaises(IOError): - j.write(data, cinema2k=48) + versions = ["1.5.0", "2.0.0"] + for version in versions: + with patch('glymur.version.openjpeg_version', new=version): + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError): + j.write(data, cinema2k=48) @unittest.skipIf(re.match(r'''1.[0-4]''', openjpeg_version) is not None, From 1c87d982f8ca4c35a86cbb385b2764d7e74351df Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 22 Oct 2014 09:44:51 -0400 Subject: [PATCH 284/326] add negative test for writing non-uint8-uint16 images, closes #281 Improved the error message. --- glymur/jp2k.py | 3 ++- glymur/test/test_jp2k.py | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 6817dbc..264bd43 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -545,7 +545,8 @@ class Jp2k(Jp2kBox): raise IOError(msg) if img_array.dtype != np.uint8 and img_array.dtype != np.uint16: - msg = "Only uint8 and uint16 images are currently supported." + msg = "Only uint8 and uint16 datatypes are currently supported " + msg += "when writing." raise RuntimeError(msg) def _determine_colorspace(self, img_array, colorspace=None, **kwargs): diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 61ebdce..9df575f 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -843,6 +843,14 @@ class TestJp2k_write(unittest.TestCase): def tearDown(self): pass + def test_unsupported_datatype(self): + """Should raise a runtime error if trying to write uint32""" + data = np.zeros((128, 128), dtype=np.uint32) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + with self.assertRaises(RuntimeError): + j = Jp2k(tfile.name, 'wb') + j.write(data) + def test_write_with_version_too_early(self): """Should raise a runtime error if trying to write with version 1.3""" data = np.zeros((128, 128), dtype=np.uint8) From fc4f5cec80c8a99217bd827797d68b69b1212e6f Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 22 Oct 2014 15:34:13 -0400 Subject: [PATCH 285/326] add negative tests for bad precinct sizes, closes #280 --- glymur/test/test_jp2k.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 9df575f..3f3fa55 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -843,6 +843,22 @@ class TestJp2k_write(unittest.TestCase): def tearDown(self): pass + def test_precinct_size_too_small(self): + """first precinct size must be >= 2x that of the code block size""" + data = np.zeros((640, 480), dtype=np.uint8) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + with self.assertRaises(IOError): + j = Jp2k(tfile.name, 'wb') + j.write(data, cbsize=(16, 16), psizes=[(16, 16)]) + + def test_precinct_size_not_power_of_two(self): + """must be power of two""" + data = np.zeros((640, 480), dtype=np.uint8) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + with self.assertRaises(IOError): + j = Jp2k(tfile.name, 'wb') + j.write(data, cbsize=(16, 16), psizes=[(48, 48)]) + def test_unsupported_datatype(self): """Should raise a runtime error if trying to write uint32""" data = np.zeros((128, 128), dtype=np.uint32) From b9e44a4f8a09ed12f2805e05ea3975b9de0d4651 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 22 Oct 2014 18:58:11 -0400 Subject: [PATCH 286/326] re-enabled test_glymur_warning tests on linux/python3.3, closes #288 The origin issue was probably fixed during the whole #253, #255, #260, --- glymur/test/test_glymur_warnings.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/glymur/test/test_glymur_warnings.py b/glymur/test/test_glymur_warnings.py index ebe445c..b8e5c8b 100644 --- a/glymur/test/test_glymur_warnings.py +++ b/glymur/test/test_glymur_warnings.py @@ -21,8 +21,6 @@ import glymur from .fixtures import opj_data_file, OPJ_DATA_ROOT from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG -@unittest.skipIf(sys.hexversion < 0x03040000 and platform.system() == 'Linux', - "inexplicable failures on 3.3 and linux") @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) From 61cafc00b807fcbb7f58feeb3a814230738a63a6 Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 22 Oct 2014 20:02:55 -0400 Subject: [PATCH 287/326] remove FORMAT_CORPUS tests, closes #289 We don't lose any code coverage, so it's just one less thing to worry about. --- glymur/test/test_jp2box.py | 45 -------------------------------------- 1 file changed, 45 deletions(-) diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 7246349..72daf17 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -40,12 +40,6 @@ from .fixtures import ( WINDOWS_TMP_FILE_MSG, MetadataBase ) -try: - FORMAT_CORPUS_DATA_ROOT = os.environ['FORMAT_CORPUS_DATA_ROOT'] -except KeyError: - FORMAT_CORPUS_DATA_ROOT = None - - def load_tests(loader, tests, ignore): """Run doc tests as well.""" if os.name == "nt": @@ -1338,42 +1332,3 @@ class TestRepr(MetadataBase): self.assertRegexpMatches(repr(box), regexp) else: self.assertRegex(repr(box), regexp) - - - -class TestJpxBoxes(unittest.TestCase): - """Tests for JPX boxes.""" - - def setUp(self): - pass - - def tearDown(self): - pass - - @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, - 'jp2k-formats/balloon.jpf') - jpx = Jp2k(jfile) - - # This superbox just happens to be empty. - self.assertEqual(jpx.box[4].box_id, 'jpch') - self.assertEqual(len(jpx.box[4].box), 0) - - @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, - 'jp2k-formats/balloon.jpf') - jpx = Jp2k(jfile) - - # This superbox just happens to be empty. - self.assertEqual(jpx.box[5].box_id, 'jplh') - self.assertEqual(len(jpx.box[5].box), 0) - - -if __name__ == "__main__": - unittest.main() From 954e86a2abed68c43a9f97dc4df84ec2e0f492a0 Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 22 Oct 2014 20:23:45 -0400 Subject: [PATCH 288/326] added negative test for int32 imagery, closes #291 --- glymur/test/test_jp2k.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 3f3fa55..7c4e2a3 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -859,7 +859,15 @@ class TestJp2k_write(unittest.TestCase): j = Jp2k(tfile.name, 'wb') j.write(data, cbsize=(16, 16), psizes=[(48, 48)]) - def test_unsupported_datatype(self): + def test_unsupported_int32(self): + """Should raise a runtime error if trying to write int32""" + data = np.zeros((128, 128), dtype=np.int32) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + with self.assertRaises(RuntimeError): + j = Jp2k(tfile.name, 'wb') + j.write(data) + + def test_unsupported_uint32(self): """Should raise a runtime error if trying to write uint32""" data = np.zeros((128, 128), dtype=np.uint32) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: From 5aac62598130631d3a6b81ea75a7fe88f11115a4 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 23 Oct 2014 12:21:37 -0400 Subject: [PATCH 289/326] added test for None usage in config file, closes #293 --- glymur/lib/config.py | 4 ++-- glymur/test/test_config.py | 34 ++++++++++++++++++++-------------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/glymur/lib/config.py b/glymur/lib/config.py index 2254387..4b92b9d 100644 --- a/glymur/lib/config.py +++ b/glymur/lib/config.py @@ -84,8 +84,8 @@ def load_library_handle(path): if path is None or path in ['None', 'none']: # Either could not find a library via ctypes or user-configuration-file, - # or we could not find it in any of the default locations. - # This is probably a very old linux. + # or we could not find it in any of the default locations, or possibly + # the user intentionally does not want one of the libraries to load. return None try: diff --git a/glymur/test/test_config.py b/glymur/test/test_config.py index 37db88d..95170e7 100644 --- a/glymur/test/test_config.py +++ b/glymur/test/test_config.py @@ -94,17 +94,23 @@ class TestSuite(unittest.TestCase): with self.assertWarnsRegex(UserWarning, regex): imp.reload(glymur.lib.openjp2) - -@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None and - 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() - self.j2kfile = glymur.data.goodstuff() - - def tearDown(self): - pass - + @unittest.skipIf(glymur.lib.openjp2.OPENJPEG is None, + "Needs openjp2 and openjpeg before this test make sense.") + @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) + def test_library_specified_as_None(self): + """Verify that we can stop a library from being loaded by using None.""" + 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 fptr: + # Essentially comment out openjp2 and preferentially load + # openjpeg instead. + fptr.write('[library]\n') + fptr.write('openjp2: None\n') + fptr.write('openjpeg: {0}\n'.format(glymur.lib.openjp2.OPENJPEG._name)) + fptr.flush() + with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}): + imp.reload(glymur.lib.openjp2) + self.assertIsNone(glymur.lib.openjp2.OPENJP2) + self.assertIsNotNone(glymur.lib.openjp2.OPENJPEG) From d36f521b6106d628fdaa6eebe4d69b36d1147180 Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 24 Oct 2014 09:48:47 -0400 Subject: [PATCH 290/326] added test for case when config dir is empty, closes #290 --- glymur/test/test_config.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/glymur/test/test_config.py b/glymur/test/test_config.py index 95170e7..5246f1e 100644 --- a/glymur/test/test_config.py +++ b/glymur/test/test_config.py @@ -10,7 +10,7 @@ OPENJP2 may be present in some form or other. # unittest.mock only in Python 3.3 (python2.7/pylint import issue) # pylint: disable=E0611,F0401 - +import ctypes import imp import os import sys @@ -30,6 +30,16 @@ from .fixtures import ( WINDOWS_TMP_FILE_MSG ) +def openjpeg_not_found_by_ctypes(): + """ + Need to know if openjpeg library can be picked right up by ctypes for one + of the tests. + """ + if ctypes.util.find_library('openjpeg') is None: + return True + else: + return False + @unittest.skipIf(sys.hexversion < 0x03020000, "TemporaryDirectory introduced in 3.2.") @@ -114,3 +124,20 @@ class TestSuite(unittest.TestCase): imp.reload(glymur.lib.openjp2) self.assertIsNone(glymur.lib.openjp2.OPENJP2) self.assertIsNotNone(glymur.lib.openjp2.OPENJPEG) + + @unittest.skipIf(glymur.lib.openjp2.OPENJPEG is None, + "Needs openjpeg before this test make sense.") + @unittest.skipIf(openjpeg_not_found_by_ctypes(), + "OpenJPEG must be easily found before this test can work.") + @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) + def test_config_dir_but_no_config_file(self): + + with tempfile.TemporaryDirectory() as tdir: + configdir = os.path.join(tdir, 'glymur') + os.mkdir(configdir) + fname = os.path.join(configdir, 'glymurrc') + with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}): + # Should still be able to load openjpeg, despite the + # configuration file being empty. + imp.reload(glymur.lib.openjpeg) + self.assertIsNotNone(glymur.lib.openjp2.OPENJPEG) From 5c262025b316ab4834e9854e658d189b43523f98 Mon Sep 17 00:00:00 2001 From: jevans Date: Fri, 24 Oct 2014 19:32:21 -0400 Subject: [PATCH 291/326] fix test to pick up library on macports, closes #290 The environment DYLD_FALLBACK_LIBRARY_PATH helps to find macports libraries in /opt/local/lib --- glymur/test/test_config.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/glymur/test/test_config.py b/glymur/test/test_config.py index 5246f1e..ac24b89 100644 --- a/glymur/test/test_config.py +++ b/glymur/test/test_config.py @@ -35,10 +35,11 @@ def openjpeg_not_found_by_ctypes(): Need to know if openjpeg library can be picked right up by ctypes for one of the tests. """ - if ctypes.util.find_library('openjpeg') is None: - return True - else: - return False + with patch.dict('os.environ', {'DYLD_FALLBACK_LIBRARY_PATH': '/opt/local/lib'}): + if ctypes.util.find_library('openjpeg') is None: + return True + else: + return False @unittest.skipIf(sys.hexversion < 0x03020000, From 351ae294d6b0fdb0c02e70ae989c14eedab9ec30 Mon Sep 17 00:00:00 2001 From: jevans Date: Fri, 24 Oct 2014 20:33:01 -0400 Subject: [PATCH 292/326] add test for config file in current directory, closes #292 --- glymur/test/test_config.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/glymur/test/test_config.py b/glymur/test/test_config.py index ac24b89..feb3235 100644 --- a/glymur/test/test_config.py +++ b/glymur/test/test_config.py @@ -10,6 +10,7 @@ OPENJP2 may be present in some form or other. # unittest.mock only in Python 3.3 (python2.7/pylint import issue) # pylint: disable=E0611,F0401 +import contextlib import ctypes import imp import os @@ -42,6 +43,26 @@ def openjpeg_not_found_by_ctypes(): return False +@contextlib.contextmanager +def chdir(dirname=None): + """ + This context manager restores the value of the current working directory + (cwd) after the enclosed code block completes or raises an exception. If a + directory name is supplied to the context manager then the cwd is changed + prior to running the code block. + + Shamelessly lifted from + http://www.astropython.org/snippet/2009/10/chdir-context-manager + """ + curdir = os.getcwd() + try: + if dirname is not None: + os.chdir(dirname) + yield + finally: + os.chdir(curdir) + + @unittest.skipIf(sys.hexversion < 0x03020000, "TemporaryDirectory introduced in 3.2.") @unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None, @@ -142,3 +163,19 @@ class TestSuite(unittest.TestCase): # configuration file being empty. imp.reload(glymur.lib.openjpeg) self.assertIsNotNone(glymur.lib.openjp2.OPENJPEG) + + @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) + def test_config_file_in_current_directory(self): + """A configuration file in the current directory should be honored.""" + libloc = glymur.lib.openjp2.OPENJP2._name + with tempfile.TemporaryDirectory() as tdir1: + fname = os.path.join(tdir1, 'glymurrc') + with open(fname, 'w') as fptr: + fptr.write('[library]\n') + fptr.write('openjp2: {0}\n'.format(libloc)) + fptr.flush() + with chdir(tdir1): + # Should be able to load openjp2 as before. + imp.reload(glymur.lib.openjp2) + self.assertEqual(glymur.lib.openjp2.OPENJP2._name, libloc) + From 4ffb46833fc563da9e4d32714fbffac962df039c Mon Sep 17 00:00:00 2001 From: jevans Date: Fri, 24 Oct 2014 21:21:54 -0400 Subject: [PATCH 293/326] fixed printing of j2k files with "-c 0", closes #294 It's kind of meaningless to supply that option with a raw codestream, but if it's what the user wanted to do, then it's what the user wanted to do. --- glymur/command_line.py | 14 +++++++++----- glymur/test/test_printing.py | 9 +++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/glymur/command_line.py b/glymur/command_line.py index 62b9057..bf96293 100644 --- a/glymur/command_line.py +++ b/glymur/command_line.py @@ -2,9 +2,11 @@ Entry point for console script jp2dump. """ import argparse +import os import sys import warnings -from . import Jp2k, set_printoptions + +from . import Jp2k, set_printoptions, lib def main(): """ @@ -55,11 +57,13 @@ def main(): # JP2 metadata can be extensive, so don't print any warnings until we # are done with the metadata. - j = Jp2k(filename) - if print_full_codestream: - print(j.get_codestream(header_only=False)) + jp2 = Jp2k(filename) + if jp2._codec_format == lib.openjp2.CODEC_J2K and codestream_level == 0: + print('File: {0}'.format(os.path.basename(filename))) + elif print_full_codestream: + print(jp2.get_codestream(header_only=False)) else: - print(j) + print(jp2) # Re-emit any warnings that may have been suppressed. if len(wctx) > 0: diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index b6eca2f..73ded9c 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -1102,3 +1102,12 @@ class TestJp2dump(unittest.TestCase): actual = self.run_jp2dump(['', '-x', self.jp2file]) self.assertEqual(actual, fixtures.nemo_dump_no_codestream_no_xml) + + def test_codestream_0_with_j2k_file(self): + """-c 0 should print just a single line when used on a codestream.""" + sys.argv = ['', '-c', '0', self.j2kfile] + with patch('sys.stdout', new=StringIO()) as fake_out: + command_line.main() + actual = fake_out.getvalue().strip() + self.assertRegex(actual, "File: .*") + From aef47ed318ff77903ba914a8df804b58e5e4fbe2 Mon Sep 17 00:00:00 2001 From: jevans Date: Fri, 24 Oct 2014 21:58:08 -0400 Subject: [PATCH 294/326] skip test on 2.7, closes #294 assertRegex not available --- glymur/test/test_printing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 73ded9c..7a5f9b9 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -1103,6 +1103,7 @@ class TestJp2dump(unittest.TestCase): self.assertEqual(actual, fixtures.nemo_dump_no_codestream_no_xml) + @unittest.skipIf(sys.hexversion < 0x03000000, "assertRegex not in 2.7") def test_codestream_0_with_j2k_file(self): """-c 0 should print just a single line when used on a codestream.""" sys.argv = ['', '-c', '0', self.j2kfile] From a713d44238a68c553eb08f35fd7b5a160e7894cd Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 25 Oct 2014 11:22:19 -0400 Subject: [PATCH 295/326] added shape attribute, closes #295 --- glymur/jp2k.py | 37 +++++++++++++++++++++++++++++ glymur/test/test_jp2k.py | 38 ++++++++++++++++++++++++++++++ glymur/test/test_opj_suite_dump.py | 2 +- 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 264bd43..b7c82ee 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -77,11 +77,48 @@ class Jp2k(Jp2kBox): self.box = [] self._codec_format = None self._colorspace = None + self._shape = None # Parse the file for JP2/JPX contents only if we are reading it. if mode == 'rb': self.parse() + @property + def shape(self): + if self._shape is not None: + return self._shape + + cstr = self.get_codestream(header_only=True) + height = cstr.segment[1].ysiz + width = cstr.segment[1].xsiz + num_components = len(cstr.segment[1].xrsiz) + + # If JP2 and a palette box is present, then determine the shape from + # that. + if num_components == 1: + if self._codec_format == opj2.CODEC_J2K: + # There's no palette box or component mapping in a J2K file. + # The 3rd component in the shape would then be 1, but we'll + # ignore that. + self.shape = (height, width) + else: + jp2h = [box for box in self.box if box.box_id == 'jp2h'][0] + pclr = [box for box in jp2h.box if box.box_id == 'pclr'] + if len(pclr) == 0: + # No palette box, so just one component, which we will + # ignore. + self.shape = (height, width) + else: + self.shape = (height, width, len(pclr[0].signed)) + else: + self.shape = (height, width, num_components) + + return self._shape + + @shape.setter + def shape(self, shape): + self._shape = shape + def __repr__(self): msg = "glymur.Jp2k('{0}')".format(self.filename) return msg diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 7c4e2a3..9a9fb72 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -476,6 +476,44 @@ class TestJp2k(unittest.TestCase): def tearDown(self): pass + def test_shape_jp2(self): + """verify shape attribute for JP2 file + """ + jp2 = Jp2k(self.jp2file) + self.assertEqual(jp2.shape, (1456, 2592, 3)) + + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_shape_greyscale_jp2(self): + """verify shape attribute for greyscale JP2 file + """ + with warnings.catch_warnings(): + # Suppress a warning due to bad compatibility list entry. + warnings.simplefilter("ignore") + jfile = opj_data_file('input/conformance/file4.jp2') + jp2 = Jp2k(jfile) + self.assertEqual(jp2.shape, (512, 768)) + + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_shape_single_channel_j2k(self): + """verify shape attribute for single channel J2K file + """ + jfile = opj_data_file('input/conformance/p0_01.j2k') + jp2 = Jp2k(jfile) + self.assertEqual(jp2.shape, (128, 128)) + + def test_shape_j2k(self): + """verify shape attribute for J2K file + """ + j2k = Jp2k(self.j2kfile) + self.assertEqual(j2k.shape, (800, 480, 3)) + + def test_shape_jpx_jp2(self): + """verify shape attribute for JPX file with JP2 compatibility + """ + jpx = Jp2k(self.jpxfile) + self.assertEqual(jpx.shape, (1024, 1024, 3)) @unittest.skipIf(os.name == "nt", "Unexplained failure on windows") def test_irreversible(self): diff --git a/glymur/test/test_opj_suite_dump.py b/glymur/test/test_opj_suite_dump.py index 76dc444..8201c9f 100644 --- a/glymur/test/test_opj_suite_dump.py +++ b/glymur/test/test_opj_suite_dump.py @@ -3052,7 +3052,7 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(codestream.segment[1].yrsiz[2], 2) def test_NR_file4_dump(self): - # One 8-bit component in the sRGB-grey colourspace. + # One 8-bit component in the grey colourspace. jfile = opj_data_file('input/conformance/file4.jp2') with self.assertWarns(UserWarning): jp2 = Jp2k(jfile) From 1782cb957fa948bccccaccc6c1f98e2b172f0c3b Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 30 Oct 2014 11:48:20 -0400 Subject: [PATCH 296/326] removed img_array from private _determine_colorspace method, closes #297 --- glymur/jp2k.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index b7c82ee..f36b75e 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -443,7 +443,8 @@ class Jp2k(Jp2kBox): raise RuntimeError("You must have at least version 1.5 of OpenJPEG " "in order to write images.") - self._determine_colorspace(img_array, **kwargs) + self._shape = img_array.shape + self._determine_colorspace(**kwargs) self._populate_cparams(img_array, **kwargs) if opj2.OPENJP2 is not None: @@ -586,22 +587,20 @@ class Jp2k(Jp2kBox): msg += "when writing." raise RuntimeError(msg) - def _determine_colorspace(self, img_array, colorspace=None, **kwargs): + def _determine_colorspace(self, colorspace=None, **kwargs): """Determine the colorspace from the supplied inputs. Parameters ---------- - img_array : ndarray - Image data to be written to file. colorspace : str, optional Either 'rgb' or 'gray'. """ if colorspace is None: # Must infer the colorspace from the image dimensions. - if img_array.ndim < 3: + if len(self.shape) < 3: # A single channel image is grayscale. self._colorspace = opj2.CLRSPC_GRAY - elif img_array.shape[2] == 1 or img_array.shape[2] == 2: + elif self.shape[2] == 1 or self.shape[2] == 2: # A single channel image or an image with two channels is going # to be greyscale. self._colorspace = opj2.CLRSPC_GRAY @@ -612,7 +611,7 @@ class Jp2k(Jp2kBox): 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: + elif colorspace.lower() == 'rgb' and self.shape[2] < 3: msg = 'RGB colorspace requires at least 3 components.' raise IOError(msg) From 1f0d0c37865b91f908c2a338f57f6729616dee83 Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 5 Nov 2014 20:33:10 -0500 Subject: [PATCH 297/326] deleted TccpInfo, TileInfoV2, CodestreamInvoV2 classes They are used in tests, but not in top-level glymur classes. --- glymur/lib/openjp2.py | 101 -------------------------------- glymur/lib/test/test_openjp2.py | 94 ----------------------------- 2 files changed, 195 deletions(-) diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py index f0a5f09..5e58cf4 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -585,107 +585,6 @@ class ImageComptParmType(ctypes.Structure): return msg -class TccpInfo(ctypes.Structure): - """Tile-component coding parameters information. - - Corresponds to tccp_info_t type in openjp2 header file. - """ - _fields_ = [ - # component index - ("compno", ctypes.c_uint32), - - # coding style - ("csty", ctypes.c_uint32), - - # number of resolutions - ("numresolutions", ctypes.c_uint32), - - # code-blocks width - ("cblkw", ctypes.c_uint32), - - # code-blocks height - ("cblkh", ctypes.c_uint32), - - # code-block coding style - ("cblksty", ctypes.c_uint32), - - # discrete wavelet transform identifier - ("qmfbid", ctypes.c_uint32), - - # quantization style - ("qntsty", ctypes.c_uint32), - - # stepsizes used for quantization - ("stepsizes_mant", ctypes.c_uint32 * J2K_MAXBANDS), - ("stepsizes_expn", ctypes.c_uint32 * J2K_MAXBANDS), - - # stepsizes used for quantization - ("numgbits", ctypes.c_uint32), - - # region of interest shift - ("roishift", ctypes.c_int32), - - # precinct width - ("prcw", ctypes.c_uint32 * J2K_MAXRLVLS), - - # precinct width - ("prch", ctypes.c_uint32 * J2K_MAXRLVLS)] - - -class TileInfoV2(ctypes.Structure): - """Tile coding parameters information - - Corresponds to tile_info_v2_t type in openjp2 headers. - """ - _fields_ = [ - # number (index) of tile - ("tileno", ctypes.c_int32), - - # coding style - ("csty", ctypes.c_uint32), - - # progression order - ("prg", PROG_ORDER_TYPE), - - # number of layers - ("numlayers", ctypes.c_uint32), - - # multi-component transform identifier - ("mct", ctypes.c_uint32), - - # information concerning tile component parameters - ("tccp_info", ctypes.POINTER(TccpInfo))] - - -class CodestreamInfoV2(ctypes.Structure): - """information about the codestream. - - Corresponds to codestream_info_v2_t type in openjp2 header files. - """ - _fields_ = [ - # tile info - # tile origin in x, y (XTOsiz, YTOsiz) - ("tx0", ctypes.c_uint32), - ("ty0", ctypes.c_uint32), - - # tile size in x, y = XTsiz, YTsiz - ("tdx", ctypes.c_uint32), - ("tdy", ctypes.c_uint32), - - # number of tiles in X, Y - ("tw", ctypes.c_uint32), - ("th", ctypes.c_uint32), - - # number of components - ("nbcomps", ctypes.c_uint32), - - # default information regarding tiles inside of image - ("m_default_tile_info", TileInfoV2), - - # information regarding tiles inside of image - ("tile_info", ctypes.POINTER(TileInfoV2))] - - def check_error(status): """Set a generic function as the restype attribute of all OpenJPEG functions that return a BOOL_TYPE value. This way we do not have to check diff --git a/glymur/lib/test/test_openjp2.py b/glymur/lib/test/test_openjp2.py index 70ef9b6..988ace1 100644 --- a/glymur/lib/test/test_openjp2.py +++ b/glymur/lib/test/test_openjp2.py @@ -56,53 +56,6 @@ class TestOpenJP2(unittest.TestCase): self.assertEqual(dparams.DA_x1, 0) self.assertEqual(dparams.DA_y1, 0) - def tile_macro(self, 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): - """fixture called by the test_rtaX methods""" - dparam = openjp2.set_default_decoder_parameters() - - infile = filename.encode() - nelts = openjp2.PATH_LEN - len(infile) - infile += b'0' * nelts - dparam.infile = infile - - dparam.decod_format = codec_format - - codec = openjp2.create_decompress(codec_format) - - openjp2.set_info_handler(codec, None) - openjp2.set_warning_handler(codec, None) - openjp2.set_error_handler(codec, None) - - stream = openjp2.stream_create_default_file_stream(filename, True) - - openjp2.setup_decoder(codec, dparam) - image = openjp2.read_header(stream, codec) - - cstr_info = openjp2.get_cstr_info(codec) - - tile_ul = 0 - tile_ur = cstr_info.contents.tw - 1 - tile_lr = cstr_info.contents.tw * cstr_info.contents.th - 1 - tile_ll = tile_lr - cstr_info.contents.tw - - self.tile_macro(codec, stream, image, tile_ul) - self.tile_macro(codec, stream, image, tile_ur) - self.tile_macro(codec, stream, image, tile_lr) - self.tile_macro(codec, stream, image, tile_ll) - - openjp2.destroy_cstr_info(cstr_info) - - openjp2.end_decompress(codec, stream) - openjp2.destroy_codec(codec) - openjp2.stream_destroy(stream) - openjp2.image_destroy(image) - def test_tte0(self): """Runs test designated tte0 in OpenJPEG test suite.""" with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: @@ -160,15 +113,6 @@ class TestOpenJP2(unittest.TestCase): 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: - self.xtx1_setup(tfile.name) - - 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.""" with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: @@ -190,62 +134,24 @@ class TestOpenJP2(unittest.TestCase): 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: - xtx2_setup(tfile.name) - - 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: xtx3_setup(tfile.name) self.assertTrue(True) - def test_rta3(self): - """Runs test designated rta3 in OpenJPEG test suite.""" - with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: - xtx3_setup(tfile.name) - - 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.""" with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: xtx4_setup(tfile.name) self.assertTrue(True) - def test_rta4(self): - """Runs test designated rta4 in OpenJPEG test suite.""" - with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: - xtx4_setup(tfile.name) - - 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.""" with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: xtx5_setup(tfile.name) self.assertTrue(True) - def test_rta5(self): - """Runs test designated rta5 in OpenJPEG test suite.""" - with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: - xtx5_setup(tfile.name) - - 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']) * From eeee8ae9dc304276cfffd403da5f399c9ff1d161 Mon Sep 17 00:00:00 2001 From: jevans Date: Wed, 5 Nov 2014 20:47:53 -0500 Subject: [PATCH 298/326] modified travis config for coveralls support --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index 406e470..822c717 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ before_install: - sudo apt-get install -qq python-numpy - wget http://openjpeg.googlecode.com/files/openjpeg-1.5.0-Linux-x86_64.tar.gz - sudo tar -xvf openjpeg-1.5.0-Linux-x86_64.tar.gz --strip-components=1 -C / + - pip install coveralls # command to install dependencies install: @@ -19,6 +20,11 @@ install: # command to run tests script: - python -m unittest discover +after_success: + - if [[ $ENV == pythone=3.4* ]]; then + coveralls; + fi + notifications: email: "john.g.evans.ne@gmail.com" From 03cb19d22b7d1ad165fbda158f617a199dd194a8 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 11 Nov 2014 10:46:59 -0500 Subject: [PATCH 299/326] deprecated read method, removed write method, closes #299 use array-style slicing instead. Many former read methods are now properties. Many former write method parameters are now moved into the constructor. --- docs/source/how_do_i.rst | 27 +- docs/source/roadmap.rst | 9 - docs/source/whatsnew/0.7.rst | 18 +- docs/source/whatsnew/0.8.rst | 10 + docs/source/whatsnew/index.rst | 5 +- glymur/jp2k.py | 359 ++++++++------ glymur/test/test_callbacks.py | 26 +- glymur/test/test_codestream.py | 149 ------ glymur/test/test_jp2box.py | 18 +- glymur/test/test_jp2k.py | 744 +++++++++++++++------------- glymur/test/test_opj_suite.py | 427 ++++++++++++++-- glymur/test/test_opj_suite_2p1.py | 342 ------------- glymur/test/test_opj_suite_dump.py | 3 +- glymur/test/test_opj_suite_neg.py | 102 ++-- glymur/test/test_opj_suite_write.py | 74 ++- glymur/test/test_printing.py | 7 +- 16 files changed, 1132 insertions(+), 1188 deletions(-) create mode 100644 docs/source/whatsnew/0.8.rst delete mode 100644 glymur/test/test_codestream.py delete mode 100644 glymur/test/test_opj_suite_2p1.py diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index 5d11d67..8a62b32 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -14,27 +14,22 @@ retrieve a full resolution and first lower-resolution image :: >>> jp2file = glymur.data.nemo() # just a path to a JPEG2000 file >>> jp2 = glymur.Jp2k(jp2file) >>> fullres = jp2[:] - >>> print(fullres.shape) + >>> fullres.shape (1456, 2592, 3) >>> thumbnail = jp2[::2, ::2] - >>> print(thumbnail.shape) + >>> thumbnail.shape (728, 1296, 3) -The :py:meth:`read` method exposes many more options for other JPEG 2000 -features such as quality layers. - ... write images? ================= -So long as the image data can fit entirely into memory, array-style slicing may -also be used to write JPEG 2000 files. +It's pretty simple, just supply the image data as the 2nd argument to the Jp2k +constructor. >>> import glymur, numpy as np - >>> jp2 = glymur.Jp2k('zeros.jp2', mode='wb') - >>> jp2[:] = np.zeros((640, 480), dtype=np.uint8) + >>> data = np.zeros((640, 480), dtype=np.uint8) + >>> jp2 = glymur.Jp2k('zeros.jp2', data=data) -The :py:meth:`write` method exposes many more options for other JPEG 2000 -features. You should have OpenJPEG version 1.5 or more recent before writing -JPEG 2000 images. +You should have OpenJPEG version 1.5 or more recent before writing JPEG 2000 images. ... display metadata? ===================== @@ -354,15 +349,14 @@ image isn't square. :: >>> import numpy as np >>> import glymur >>> from glymur import Jp2k - >>> rgb = Jp2k(glymur.data.goodstuff()).read() + >>> rgb = Jp2k(glymur.data.goodstuff())[:] >>> lx, ly = rgb.shape[0:2] >>> X, Y = np.ogrid[0:lx, 0:ly] >>> mask = ly**2*(X - lx / 2) ** 2 + lx**2*(Y - ly / 2) ** 2 > (lx * ly / 2)**2 >>> alpha = 255 * np.ones((lx, ly, 1), dtype=np.uint8) >>> alpha[mask] = 0 >>> rgba = np.concatenate((rgb, alpha), axis=2) - >>> jp2 = Jp2k('tmp.jp2', 'wb') - >>> jp2[:] = rgba + >>> jp2 = Jp2k('tmp.jp2', data=rgba) Next we need to specify what types of channels we have. The first three channels are color channels, but we identify the fourth as @@ -465,8 +459,7 @@ http://photojournal.jpl.nasa.gov/tiff/PIA17145.tif info JPEG 2000:: >>> import skimage.io >>> image = skimage.io.imread('PIA17145.tif') >>> from glymur import Jp2k - >>> jp2 = Jp2k('PIA17145.jp2', 'wb') - >>> jp2[:] = image + >>> jp2 = Jp2k('PIA17145.jp2', data=image) Next you can extract the XMP metadata. diff --git a/docs/source/roadmap.rst b/docs/source/roadmap.rst index 503fbc4..f0d7017 100644 --- a/docs/source/roadmap.rst +++ b/docs/source/roadmap.rst @@ -1,12 +1,3 @@ -------------------------------------- -Platforms Tested (0.7.0 release only) -------------------------------------- - * Linux Mint 17 / Python 3.4.0 and 2.7.6 / OpenJPEG 2.1.0 and 1.3.0 - * MacOS 10.6.8 / MacPorts Python 3.4.1, 3.3.5,and 2.7.8 / OpenJPEG 2.1.0 - * CentOS 6.5 / Anaconda Python 3.4.1 / OpenJPEG 1.3.0 - * Fedora 20 i386 / Python 2.7.5 and 3.3.2 / OpenJPEG 1.5.1 - * Windows 7 32bit / Anaconda Python 2.7.6 and 3.4.1 / OpenJPEG 2.1.0 - ------------ Known Issues ------------ diff --git a/docs/source/whatsnew/0.7.rst b/docs/source/whatsnew/0.7.rst index 0a21d9b..3ed7715 100644 --- a/docs/source/whatsnew/0.7.rst +++ b/docs/source/whatsnew/0.7.rst @@ -2,10 +2,26 @@ Changes in glymur 0.7 ===================== +Changes in 0.7.3 +================= + + * added read support back for metadata only when the OpenJPEG library is + not installed + +Changes in 0.7.2 +================= + + * added ellipsis support in array-style slicing + +Changes in 0.7.1 +================= + + * fixed release notes regarding Python 3.4 + Changes in 0.7.0 ================= * implemented :py:meth:`__getitem__`, :py:meth:`__setitem__` support * added back windows support * box_id and longname are class attributes now instead of instance - attributes (see issue 248) + attributes diff --git a/docs/source/whatsnew/0.8.rst b/docs/source/whatsnew/0.8.rst new file mode 100644 index 0000000..f36b593 --- /dev/null +++ b/docs/source/whatsnew/0.8.rst @@ -0,0 +1,10 @@ +===================== +Changes in glymur 0.8 +===================== + +Changes in 0.8.0 +================= + + * Simplified writing images by moving data and options into the + constructor. This is backwards-incompatible with 0.7.x. + * Deprecated :py:meth:`read` method in favor of array-style slicing. diff --git a/docs/source/whatsnew/index.rst b/docs/source/whatsnew/index.rst index 2d69949..4569abd 100644 --- a/docs/source/whatsnew/index.rst +++ b/docs/source/whatsnew/index.rst @@ -8,6 +8,7 @@ These document the changes between minor (or major) versions of glymur. .. toctree:: - 0.5 - 0.6 + 0.8 0.7 + 0.6 + 0.5 diff --git a/glymur/jp2k.py b/glymur/jp2k.py index f36b75e..74834a9 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -37,16 +37,6 @@ from .jp2box import ( ) from .lib import openjpeg as opj, openjp2 as opj2, c as libc -JP2_IDS = ['colr', 'cdef', 'cmap', 'jp2c', 'ftyp', 'ihdr', 'jp2h', 'jP ', - 'pclr', 'res ', 'resc', 'resd', 'xml ', 'ulst', 'uinf', 'url ', - 'uuid'] -JPX_IDS = ['asoc', 'nlst'] - -_COLORSPACE_MAP = {'rgb': opj2.CLRSPC_SRGB, - 'gray': opj2.CLRSPC_GRAY, - 'grey': opj2.CLRSPC_GRAY, - 'ycc': opj2.CLRSPC_YCC} - class Jp2k(Jp2kBox): """JPEG 2000 file. @@ -54,34 +44,150 @@ class Jp2k(Jp2kBox): ---------- filename : str The path to the JPEG 2000 file. - mode : str - The mode used to open the file. box : sequence List of top-level boxes in the file. Each box may in turn contain its own list of boxes. Will be empty if the file consists only of a raw codestream. + shape : tuple + Size of the image. + + Properties + ---------- + ignore_pclr_cmap_cdef : bool + whether or not to ignore the pclr, cmap, or cdef boxes during any + color transformation, defaults to False. + layer : int + zero-based number of quality layer to decode + verbose : bool + whether or not to print informational messages produced by the + OpenJPEG library, defaults to false + + Examples + -------- + >>> import glymur + >>> jfile = glymur.data.nemo() + >>> jp2 = glymur.Jp2k(jfile) + >>> jp2.shape + (1456, 2592, 3) + >>> image = jp2[:] + >>> image.shape + (1456, 2592, 3) + + Read a lower resolution thumbnail. + + >>> thumbnail = jp2[::2, ::2] + >>> thumbnail.shape + (728, 1296, 3) """ - def __init__(self, filename, mode='rb'): + def __init__(self, filename, data=None, shape=None, **kwargs): """ + Only the filename parameter is required in order to read a JPEG 2000 + file. + Parameters ---------- filename : str or file - The path to JPEG 2000 file. - mode : str, optional - The mode used to open the file. + the path to JPEG 2000 file + image_data : ndarray, optional + image data to be written + shape : tuple + size of image data, only required when image_data is not provided + cbsize : tuple, optional + code block size (DY, DX) + cinema2k : int, optional + frames per second, either 24 or 48 + cinema4k : bool, optional + set to True to specify Cinema4K mode, defaults to false + colorspace : str, optional + either 'rgb' or 'gray' + cratios : iterable + 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 + irreversible : bool, optional + if true, use the irreversible DWT 9-7 transform + 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) + verbose : bool, optional + print informational messages produced by the OpenJPEG library + """ Jp2kBox.__init__(self) self.filename = filename - self.mode = mode + self.box = [] self._codec_format = None self._colorspace = None - self._shape = None + self._layer = 0 + if data is not None: + self._shape = data.shape + else: + self._shape = shape + + self._ignore_pclr_cmap_cdef = False + self._verbose = False # Parse the file for JP2/JPX contents only if we are reading it. - if mode == 'rb': + if data is None and shape is None: self.parse() + elif data is not None: + self._write(data, **kwargs) + + @property + def ignore_pclr_cmap_cdef(self): + return self._ignore_pclr_cmap_cdef + + @ignore_pclr_cmap_cdef.setter + def ignore_pclr_cmap_cdef(self, ignore_pclr_cmap_cdef): + self._ignore_pclr_cmap_cdef = ignore_pclr_cmap_cdef + + @property + def layer(self): + return self._layer + + @layer.setter + def layer(self, layer): + if version.openjpeg_version_tuple[0] < 2: + msg = "Layer property not supported unless the version of OpenJPEG " + msg += "is 2.0 or higher." + raise RuntimeError(msg) + self._layer = layer + + @property + def verbose(self): + return self._verbose + + @verbose.setter + def verbose(self, verbose): + self._verbose = verbose @property def shape(self): @@ -371,79 +477,17 @@ class Jp2k(Jp2kBox): self._cparams = cparams - def write(self, img_array, verbose=False, **kwargs): + 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. This method can only be used to create JPEG 2000 images that can fit in memory. - - Parameters - ---------- - img_array : ndarray - Image data to be written to file. - cbsize : tuple, optional - Code block size (DY, DX). - cinema2k : int, optional - frames per second, either 24 or 48 - cinema4k : bool, optional - Set to True to specify Cinema4K mode, defaults to false. - colorspace : str, optional - Either 'rgb' or 'gray'. - cratios : iterable - 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. - irreversible : bool, optional - If true, use the irreversible DWT 9-7 transform. - 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). - verbose : bool, optional - Print informational messages produced by the OpenJPEG library. - - Examples - -------- - >>> import glymur - >>> jfile = glymur.data.nemo() - >>> jp2 = glymur.Jp2k(jfile) - >>> data = jp2.read(rlevel=1) - >>> from tempfile import NamedTemporaryFile - >>> tfile = NamedTemporaryFile(suffix='.jp2', delete=False) - >>> j = Jp2k(tfile.name, mode='wb') - >>> j.write(data.astype(np.uint8)) """ if re.match("1.[0-4]", version.openjpeg_version) is not None: raise RuntimeError("You must have at least version 1.5 of OpenJPEG " "in order to write images.") - self._shape = img_array.shape self._determine_colorspace(**kwargs) self._populate_cparams(img_array, **kwargs) @@ -617,7 +661,12 @@ class Jp2k(Jp2kBox): # Turn the colorspace from a string to the enumerated value that # the library expects. - self._colorspace = _COLORSPACE_MAP[colorspace.lower()] + COLORSPACE_MAP = {'rgb': opj2.CLRSPC_SRGB, + 'gray': opj2.CLRSPC_GRAY, + 'grey': opj2.CLRSPC_GRAY, + 'ycc': opj2.CLRSPC_YCC} + + self._colorspace = COLORSPACE_MAP[colorspace.lower()] def _write_openjp2(self, img_array, verbose=False): @@ -640,7 +689,11 @@ class Jp2k(Jp2kBox): codec = opj2.create_compress(self._cparams.codec_fmt) stack.callback(opj2.destroy_codec, codec) - info_handler = _INFO_CALLBACK if verbose else None + if self._verbose or verbose: + info_handler = _INFO_CALLBACK + else: + info_handler = None + opj2.set_info_handler(codec, info_handler) opj2.set_warning_handler(codec, _WARNING_CALLBACK) opj2.set_error_handler(codec, _ERROR_CALLBACK) @@ -844,7 +897,7 @@ class Jp2k(Jp2kBox): # Case of jp2[:] = data, i.e. write the entire image. # # Should have a slice object where start = stop = step = None - self.write(data) + self._write(data) else: msg = "Partial write operations are currently not allowed." raise TypeError(msg) @@ -863,16 +916,16 @@ class Jp2k(Jp2kBox): # This retrieves a single row. row = pargs area = (row, 0, row + 1, numcols) - return self.read(area=area).squeeze() + return self._read(area=area).squeeze() if pargs is Ellipsis: # Case of jp2[...] - return self.read() + return self._read() if isinstance(pargs, slice): if pargs.start is None and pargs.stop is None and pargs.step is None: # Case of jp2[:] - return self.read() + return self._read() # Corner case of jp2[x] where x is a slice object with non-null # members. Just augment it with an ellipsis and let the code @@ -949,7 +1002,7 @@ class Jp2k(Jp2kBox): numrows if rows.stop is None else rows.stop, numcols if cols.stop is None else cols.stop ) - data = self.read(area=area, rlevel=rlevel) + data = self._read(area=area, rlevel=rlevel) if len(pargs) == 2: return data @@ -957,28 +1010,9 @@ class Jp2k(Jp2kBox): return data[:, :, bands] - def read(self, **kwargs): + def _read(self, **kwargs): """Read a JPEG 2000 image. - Parameters - ---------- - rlevel : int, optional - Factor by which to rlevel output resolution. Use -1 to get the - lowest resolution thumbnail. This is the only keyword option - available to use when the OpenJPEG version is 1.5 or earlier. - layer : int, optional - Number of quality layer to decode. - area : tuple, optional - Specifies decoding image area, - (first_row, first_col, last_row, last_col) - tile : int, optional - Number of tile to decode. - ignore_pclr_cmap_cdef : bool - Whether or not to ignore the pclr, cmap, or cdef boxes during any - color transformation. Defaults to False. - verbose : bool, optional - Print informational messages produced by the OpenJPEG library. - Returns ------- img_array : ndarray @@ -988,21 +1022,6 @@ class Jp2k(Jp2kBox): ------ IOError If the image has differing subsample factors. - - Examples - -------- - >>> import glymur - >>> jfile = glymur.data.nemo() - >>> jp = glymur.Jp2k(jfile) - >>> image = jp.read() - >>> image.shape - (1456, 2592, 3) - - Read the lowest resolution thumbnail. - - >>> thumbnail = jp.read(rlevel=-1) - >>> thumbnail.shape - (728, 1296, 3) """ if version.openjpeg_version_tuple[0] < 2: img = self._read_openjpeg(**kwargs) @@ -1010,6 +1029,46 @@ class Jp2k(Jp2kBox): img = self._read_openjp2(**kwargs) return img + def read(self, **kwargs): + """ + """ + #Read a JPEG 2000 image. + # + #Parameters + #---------- + #rlevel : int, optional + # Factor by which to rlevel output resolution. Use -1 to get the + # lowest resolution thumbnail. This is the only keyword option + # available to use when the OpenJPEG version is 1.5 or earlier. + #layer : int, optional + # Number of quality layer to decode. + #area : tuple, optional + # Specifies decoding image area, + # (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. + # + #Returns + #------- + #img_array : ndarray + # The image data. + # + #Raises + #------ + #IOError + # If the image has differing subsample factors. + + if 'ignore_pclr_cmap_cdef' in kwargs: + self.ignore_pclr_cmap_cdef = kwargs['ignore_pclr_cmap_cdef'] + warnings.warn("Use array-style slicing instead.", DeprecationWarning) + if version.openjpeg_version_tuple[0] < 2: + img = self._read_openjpeg(**kwargs) + else: + img = self._read_openjp2(**kwargs) + return img + def _subsampling_sanity_check(self): """Check for differing subsample factors. """ @@ -1022,8 +1081,7 @@ class Jp2k(Jp2kBox): msg += "the read_bands method instead." raise RuntimeError(msg) - def _read_openjpeg(self, rlevel=0, ignore_pclr_cmap_cdef=False, - verbose=False, area=None): + def _read_openjpeg(self, rlevel=0, verbose=False, area=None): """Read a JPEG 2000 image using libopenjpeg. Parameters @@ -1031,9 +1089,6 @@ class Jp2k(Jp2kBox): rlevel : int, optional Factor by which to rlevel output resolution. Use -1 to get the lowest resolution thumbnail. - ignore_pclr_cmap_cdef : bool - Whether or not to ignore the pclr, cmap, or cdef boxes during any - color transformation. Defaults to False. verbose : bool, optional Print informational messages produced by the OpenJPEG library. area : tuple, optional @@ -1052,7 +1107,7 @@ class Jp2k(Jp2kBox): """ self._subsampling_sanity_check() - self._populate_dparams(rlevel, ignore_pclr_cmap_cdef) + self._populate_dparams(rlevel) with ExitStack() as stack: try: @@ -1062,7 +1117,10 @@ class Jp2k(Jp2kBox): event_mgr = opj.EventMgrType() info_handler = ctypes.cast(_INFO_CALLBACK, ctypes.c_void_p) - event_mgr.info_handler = info_handler if verbose else None + if verbose or self._verbose: + event_mgr.info_handler = info_handler + else: + event_mgr.info_handler = None event_mgr.warning_handler = ctypes.cast(_WARNING_CALLBACK, ctypes.c_void_p) event_mgr.error_handler = ctypes.cast(_ERROR_CALLBACK, @@ -1105,8 +1163,7 @@ class Jp2k(Jp2kBox): return data - def _read_openjp2(self, rlevel=0, layer=0, area=None, tile=None, - verbose=False, ignore_pclr_cmap_cdef=False): + def _read_openjp2(self, rlevel=0, layer=None, area=None, tile=None, verbose=False): """Read a JPEG 2000 image using libopenjp2. Parameters @@ -1134,10 +1191,12 @@ class Jp2k(Jp2kBox): RuntimeError If the image has differing subsample factors. """ + if layer is not None: + self._layer = layer + self._subsampling_sanity_check() - self._populate_dparams(rlevel, ignore_pclr_cmap_cdef, - layer=layer, tile=tile, area=area) + self._populate_dparams(rlevel, tile=tile, area=area) with ExitStack() as stack: if re.match("2.1", version.openjpeg_version): @@ -1154,7 +1213,8 @@ class Jp2k(Jp2kBox): opj2.set_error_handler(codec, _ERROR_CALLBACK) opj2.set_warning_handler(codec, _WARNING_CALLBACK) - if verbose: + + if self._verbose or verbose: opj2.set_info_handler(codec, _INFO_CALLBACK) else: opj2.set_info_handler(codec, None) @@ -1181,14 +1241,11 @@ class Jp2k(Jp2kBox): return img_array - def _populate_dparams(self, rlevel, ignore_pclr_cmap_cdef, tile=None, - layer=None, area=None): + def _populate_dparams(self, rlevel, tile=None, area=None): """Populate decompression structure with appropriate input parameters. Parameters ---------- - layer : int - Number of quality layer to decode. rlevel : int Factor by which to rlevel output resolution. area : tuple @@ -1196,9 +1253,6 @@ class Jp2k(Jp2kBox): (first_row, first_col, last_row, last_col) tile : int Number of tile to decode. - ignore_pclr_cmap_cdef : bool - Whether or not to ignore the pclr, cmap, or cdef boxes during any - color transformation. Defaults to False. """ if opj2.OPENJP2 is not None: dparam = opj2.set_default_decoder_parameters() @@ -1211,10 +1265,13 @@ class Jp2k(Jp2kBox): infile += b'0' * nelts dparam.infile = infile + if self.ignore_pclr_cmap_cdef: + # Return raw codestream components. + dparam.flags |= 1 + dparam.decod_format = self._codec_format - if layer is not None: - dparam.cp_layer = layer + dparam.cp_layer = self._layer # Must check the specified rlevel against the maximum. if rlevel != 0: @@ -1245,13 +1302,13 @@ class Jp2k(Jp2kBox): dparam.tile_index = tile dparam.nb_tile_to_decode = 1 - if ignore_pclr_cmap_cdef is True: + if self.ignore_pclr_cmap_cdef: # Return raw codestream components. dparam.flags |= 1 self._dparams = dparam - def read_bands(self, rlevel=0, layer=0, area=None, tile=None, + def read_bands(self, rlevel=0, layer=None, area=None, tile=None, verbose=False, ignore_pclr_cmap_cdef=False): """Read a JPEG 2000 image. @@ -1297,8 +1354,10 @@ class Jp2k(Jp2kBox): "OpenJPEG installed before using this " "functionality.") - self._populate_dparams(rlevel, ignore_pclr_cmap_cdef, - layer=layer, tile=tile, area=area) + self.ignore_pclr_cmap_cdef = ignore_pclr_cmap_cdef + if layer is not None: + self._layer = layer + self._populate_dparams(rlevel, tile=tile, area=area) with ExitStack() as stack: if re.match("2.1", version.openjpeg_version): @@ -1710,6 +1769,8 @@ def _validate_singletons(boxes): if 'dtbl' in multiples: raise IOError('There can only be one dtbl box in a file.') +JPX_IDS = ['asoc', 'nlst'] + def _validate_jpx_brand(boxes, brand): """ If there is a JPX box then the brand must be 'jpx '. diff --git a/glymur/test/test_callbacks.py b/glymur/test/test_callbacks.py index ce3bb8c..c29f816 100644 --- a/glymur/test/test_callbacks.py +++ b/glymur/test/test_callbacks.py @@ -39,15 +39,28 @@ class TestCallbacks(unittest.TestCase): "Missing openjp2 library.") @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") - def test_info_callback_on_write(self): + def test_info_callback_on_write_backwards_compatibility(self): """Verify messages printed when writing an image in verbose mode.""" j = glymur.Jp2k(self.jp2file) with self.assertWarns(UserWarning): tiledata = j.read(tile=0) with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = glymur.Jp2k(tfile.name, 'wb') with patch('sys.stdout', new=StringIO()) as fake_out: - j.write(tiledata, verbose=True) + j = glymur.Jp2k(tfile.name, data=tiledata, verbose=True) + actual = fake_out.getvalue().strip() + expected = '[INFO] tile number 1 / 1' + self.assertEqual(actual, expected) + + @unittest.skipIf(glymur.version.openjpeg_version[0] != '2', + "Missing openjp2 library.") + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") + def test_info_callback_on_write(self): + """Verify messages printed when writing an image in verbose mode.""" + j = glymur.Jp2k(self.jp2file) + tiledata = j[:] + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + with patch('sys.stdout', new=StringIO()) as fake_out: + jp2 = glymur.Jp2k(tfile.name, data=tiledata, verbose=True) actual = fake_out.getvalue().strip() expected = '[INFO] tile number 1 / 1' self.assertEqual(actual, expected) @@ -57,15 +70,16 @@ class TestCallbacks(unittest.TestCase): # Verify that we get the expected stdio output when our internal info # callback handler is enabled. - j = glymur.Jp2k(self.j2kfile) + jp2 = glymur.Jp2k(self.j2kfile) with patch('sys.stdout', new=StringIO()) as fake_out: - j.read(rlevel=1, verbose=True) + jp2.verbose = True + jp2[::2, ::2] actual = fake_out.getvalue().strip() if glymur.version.openjpeg_version[0] == '2': lines = ['[INFO] Start to read j2k main header (0).', '[INFO] Main header has been correctly decoded.', - '[INFO] No decoded area parameters, set the decoded area to the whole image', + '[INFO] Setting decoding area to 0,0,480,800', '[INFO] Header of tile 0 / 0 has been read.', '[INFO] Tile 1/1 has been decoded.', '[INFO] Image data has been updated with tile 1.'] diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py deleted file mode 100644 index e520713..0000000 --- a/glymur/test/test_codestream.py +++ /dev/null @@ -1,149 +0,0 @@ -""" -Test suite for codestream parsing. -""" - -# unittest doesn't work well with R0904. -# pylint: disable=R0904 - -import os -import struct -import sys -import tempfile -import unittest - -from glymur import Jp2k -import glymur - -from .fixtures import opj_data_file, OPJ_DATA_ROOT - -class TestCodestream(unittest.TestCase): - """Test suite for unusual codestream cases.""" - - def setUp(self): - self.jp2file = glymur.data.nemo() - - def tearDown(self): - pass - - def test_siz_segment_ssiz_unsigned(self): - """ssiz attribute to be removed in future release""" - j = Jp2k(self.jp2file) - codestream = j.get_codestream() - - # The ssiz attribute was simply a tuple of raw bytes. - # The first 7 bits are interpreted as the bitdepth, the MSB determines - # whether or not it is signed. - self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7)) - - -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -class TestCodestreamOpjData(unittest.TestCase): - """Test suite for unusual codestream cases. Uses OPJ_DATA_ROOT""" - - def setUp(self): - self.jp2file = glymur.data.nemo() - - def tearDown(self): - pass - - @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(OPJ_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. - read_buffer = ifile.read(45) - tfile.write(read_buffer) - - # Write the new marker segment, 0xff6f = 65391 - read_buffer = struct.pack('>HHB', int(65391), int(3), int(0)) - tfile.write(read_buffer) - - # Get the rest of the input file. - read_buffer = ifile.read() - tfile.write(read_buffer) - tfile.flush() - - codestream = Jp2k(tfile.name).get_codestream() - - self.assertEqual(codestream.segment[2].marker_id, '0xff6f') - 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(OPJ_DATA_ROOT, - 'input/nonregression/123.j2c') - j = Jp2k(filename) - 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(codestream.segment[-1].marker_id, 'EOC') - - - def test_siz_segment_ssiz_signed(self): - """ssiz attribute to be removed in future release""" - filename = os.path.join(OPJ_DATA_ROOT, 'input/conformance/p0_03.j2k') - j = Jp2k(filename) - codestream = j.get_codestream() - - # The ssiz attribute was simply a tuple of raw bytes. - # The first 7 bits are interpreted as the bitdepth, the MSB determines - # whether or not it is signed. - self.assertEqual(codestream.segment[1].ssiz, (131,)) - - -class TestCodestreamRepr(unittest.TestCase): - - def setUp(self): - self.jp2file = glymur.data.nemo() - - def tearDown(self): - pass - - def test_soc(self): - """Test SOC segment repr""" - segment = glymur.codestream.SOCsegment() - newseg = eval(repr(segment)) - self.assertEqual(newseg.marker_id, 'SOC') - - def test_siz(self): - """Test SIZ segment repr""" - kwargs = {'rsiz': 0, - 'xysiz': (2592, 1456), - 'xyosiz': (0, 0), - 'xytsiz': (2592, 1456), - 'xytosiz': (0, 0), - 'Csiz': 3, - 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': ((1, 1, 1), (1, 1, 1))} - segment = glymur.codestream.SIZsegment(**kwargs) - newseg = eval(repr(segment)) - self.assertEqual(newseg.marker_id, 'SIZ') - self.assertEqual(newseg.xsiz, 2592) - self.assertEqual(newseg.ysiz, 1456) - self.assertEqual(newseg.xosiz, 0) - self.assertEqual(newseg.yosiz, 0) - self.assertEqual(newseg.xtsiz, 2592) - self.assertEqual(newseg.ytsiz, 1456) - self.assertEqual(newseg.xtosiz, 0) - self.assertEqual(newseg.ytosiz, 0) - - self.assertEqual(newseg.xrsiz, (1, 1, 1)) - self.assertEqual(newseg.yrsiz, (1, 1, 1)) - self.assertEqual(newseg.bitdepth, (8, 8, 8)) - self.assertEqual(newseg.signed, (False, False, False)) - - -if __name__ == "__main__": - unittest.main() diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 72daf17..9d1f9cf 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -54,16 +54,17 @@ class TestDataEntryURL(unittest.TestCase): def setUp(self): self.jp2file = glymur.data.nemo() + @unittest.skipIf(re.match("1.5|2", glymur.version.openjpeg_version) is None, + "Must have openjpeg 1.5 or higher to run") def test_wrap_greyscale(self): """A single component should be wrapped as GREYSCALE.""" j = Jp2k(self.jp2file) - data = j.read() + data = j[:] red = data[:, :, 0] # Write it back out as a raw codestream. with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile1: - j2k = glymur.Jp2k(tfile1.name, 'wb') - j2k.write(data[:, :, 0]) + j2k = glymur.Jp2k(tfile1.name, data=red) # Ok, now rewrap it as JP2. The colorspace should be GREYSCALE. with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile2: @@ -124,24 +125,21 @@ class TestChannelDefinition(unittest.TestCase): def setUpClass(cls): """Need a one_plane plane image for greyscale testing.""" j2k = Jp2k(glymur.data.goodstuff()) - data = j2k.read() + data = j2k[:] # Write the first component back out to file. with tempfile.NamedTemporaryFile(suffix=".j2k", delete=False) as tfile: - grey_j2k = Jp2k(tfile.name, 'wb') - grey_j2k.write(data[:, :, 0]) + grey_j2k = Jp2k(tfile.name, data=data[:, :, 0]) cls.one_plane = tfile.name # Write the first two components back out to file. with tempfile.NamedTemporaryFile(suffix=".j2k", delete=False) as tfile: - grey_j2k = Jp2k(tfile.name, 'wb') - grey_j2k.write(data[:, :, 0:1]) + grey_j2k = Jp2k(tfile.name, data=data[:, :, 0:1]) cls.two_planes = tfile.name # Write four components back out to file. with tempfile.NamedTemporaryFile(suffix=".j2k", delete=False) as tfile: - rgba_jp2 = Jp2k(tfile.name, 'wb') shape = (data.shape[0], data.shape[1], 1) alpha = np.zeros((shape), dtype=data.dtype) data4 = np.concatenate((data, alpha), axis=2) - rgba_jp2.write(data4) + rgba_jp2 = Jp2k(tfile.name, data=data4) cls.four_planes = tfile.name @classmethod diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 9a9fb72..b903dda 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -67,12 +67,17 @@ class SliceProtocolBase(unittest.TestCase): def setUpClass(self): self.jp2 = Jp2k(glymur.data.nemo()) - self.jp2_data = self.jp2.read() + self.jp2_data = self.jp2[:] + self.jp2_data_r1 = self.jp2[::2, ::2] self.j2k = Jp2k(glymur.data.goodstuff()) - self.j2k_data = self.j2k.read() + self.j2k_data = self.j2k[:] + self.j2k_data_r1 = self.j2k[::2, ::2] + self.j2k_data_r5 = self.j2k[::32, ::32] +@unittest.skipIf(re.match("1.5|2", glymur.version.openjpeg_version) is None, + "Must have openjpeg 1.5 or higher to run") @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) class TestSliceProtocolBaseWrite(SliceProtocolBase): @@ -80,9 +85,9 @@ class TestSliceProtocolBaseWrite(SliceProtocolBase): expected = self.j2k_data with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - j[...] = self.j2k_data - actual = j.read() + j = Jp2k(tfile.name, shape=expected.shape) + j[...] = expected + actual = j[:] np.testing.assert_array_equal(actual, expected) @@ -90,15 +95,14 @@ class TestSliceProtocolBaseWrite(SliceProtocolBase): expected = self.j2k_data with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - j[:] = self.j2k_data - actual = j.read() + j = Jp2k(tfile.name, data=self.j2k_data) + actual = j[:] np.testing.assert_array_equal(actual, expected) def test_cannot_write_with_non_default_single_slice(self): with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') + j = Jp2k(tfile.name, shape=self.j2k_data.shape) with self.assertRaises(TypeError): j[slice(None, 0)] = self.j2k_data with self.assertRaises(TypeError): @@ -110,31 +114,31 @@ class TestSliceProtocolBaseWrite(SliceProtocolBase): def test_cannot_write_a_row(self): with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') + j = Jp2k(tfile.name, shape=self.j2k_data.shape) with self.assertRaises(TypeError): j[5] = self.j2k_data def test_cannot_write_a_pixel(self): with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') + j = Jp2k(tfile.name, shape=self.j2k_data.shape) with self.assertRaises(TypeError): j[25, 35] = self.j2k_data[25, 35] def test_cannot_write_a_column(self): with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') + j = Jp2k(tfile.name, shape=self.j2k_data.shape) with self.assertRaises(TypeError): j[:, 25, :] = self.j2k_data[:, :25, :] def test_cannot_write_a_band(self): with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') + j = Jp2k(tfile.name, shape=self.j2k_data.shape) with self.assertRaises(TypeError): j[:, :, 0] = self.j2k_data[:, :, 0] def test_cannot_write_a_subarray(self): with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') + j = Jp2k(tfile.name, shape=self.j2k_data.shape) with self.assertRaises(TypeError): j[:25, :45, :] = self.j2k_data[:25, :25, :] @@ -167,9 +171,9 @@ class TestSliceProtocolRead(SliceProtocolBase): np.testing.assert_array_equal(actual, expected) def test_reduce_resolution_and_slice_in_third_dimension(self): - d = self.j2k[::2, ::2, 1:3] - all = self.j2k.read(rlevel=1) - np.testing.assert_array_equal(all[:,:,1:3], d) + actual = self.j2k[::2, ::2, 1:3] + expected = self.j2k_data_r1[:, :, 1:3] + np.testing.assert_array_equal(actual, expected) def test_retrieve_single_row(self): actual = self.jp2[0] @@ -186,71 +190,6 @@ class TestSliceProtocolRead(SliceProtocolBase): expected = self.jp2_data[20, 20, 2] np.testing.assert_array_equal(actual, expected) - def test_full_resolution_slicing_by_quarters_upper_left(self): - actual = self.jp2[:728, :1296] - expected = self.jp2_data[:728, :1296] - np.testing.assert_array_equal(actual, expected) - - def test_full_resolution_slicing_by_quarters_lower_left(self): - actual = self.jp2[728:, :1296] - expected = self.jp2_data[728:, :1296] - np.testing.assert_array_equal(actual, expected) - - def test_full_resolution_slicing_by_quarters_upper_right(self): - actual = self.jp2[:728, 1296:] - expected = self.jp2_data[:728, 1296:] - np.testing.assert_array_equal(actual, expected) - - def test_full_resolution_slicing_by_quarters_lower_right(self): - actual = self.jp2[728:, 1296:] - expected = self.jp2_data[728:, 1296:] - np.testing.assert_array_equal(actual, expected) - - def test_full_resolution_slicing_by_quarters_center(self): - actual = self.jp2[364:1092, 648:1942] - expected = self.jp2_data[364:1092, 648:1942] - np.testing.assert_array_equal(actual, expected) - - def test_full_resolution_slicing_by_halves_left(self): - actual = self.jp2[:, :1296] - expected = self.jp2_data[:, :1296] - np.testing.assert_array_equal(actual, expected) - - def test_full_resolution_slicing_by_right_half(self): - actual = self.jp2[:, 1296:] - expected = self.jp2_data[:, 1296:] - np.testing.assert_array_equal(actual, expected) - - def test_full_resolution_slicing_by_top_half(self): - actual = self.jp2[:728, :] - expected = self.jp2_data[:728, :] - np.testing.assert_array_equal(actual, expected) - - def test_full_resolution_slicing_by_bottom_half(self): - actual = self.jp2[728:, :] - expected = self.jp2_data[728:, :] - np.testing.assert_array_equal(actual, expected) - - def test_region_rlevel1(self): - actual = self.jp2[0:201:2, 0:201:2] - expected = self.jp2.read(area=(0, 0, 201, 201), rlevel=1) - np.testing.assert_array_equal(actual, expected) - - def test_region_rlevel1_slice_start_is_none(self): - actual = self.jp2[:201:2, :201:2] - expected = self.jp2.read(area=(0, 0, 201, 201), rlevel=1) - np.testing.assert_array_equal(actual, expected) - - def test_region_rlevel1_slice_stop_is_none(self): - actual = self.jp2[201::2, 201::2] - expected = self.jp2.read(area=(201, 201, 1456, 2592), rlevel=1) - np.testing.assert_array_equal(actual, expected) - - def test_region_rlevel1(self): - actual = self.jp2[0:202:2, 0:202:2] - expected = self.jp2.read(area=(0, 0, 202, 202), rlevel=1) - np.testing.assert_array_equal(actual, expected) - def test_ellipsis_full_read(self): actual = self.j2k[...] expected = self.j2k_data @@ -287,182 +226,18 @@ class TestSliceProtocolRead(SliceProtocolBase): expected = self.j2k_data[3:8, :,:] np.testing.assert_array_equal(actual, expected) - def test_slice_protocol_2d_reduce_resolution(self): - d = self.j2k[:] - self.assertEqual(d.shape, (800, 480, 3)) - - d = self.j2k[::1, ::1] - self.assertEqual(d.shape, (800, 480, 3)) - - d = self.j2k[::2, ::2] - self.assertEqual(d.shape, (400, 240, 3)) - - d = self.j2k[::4, ::4] - self.assertEqual(d.shape, (200, 120, 3)) - - d = self.j2k[::8, ::8] - self.assertEqual(d.shape, (100, 60, 3)) - - d = self.j2k[::16, ::16] - self.assertEqual(d.shape, (50, 30, 3)) - - d = self.j2k[::32, ::32] - self.assertEqual(d.shape, (25, 15, 3)) - + @unittest.skipIf(re.match("0|1", glymur.version.openjpeg_version), + "Must have openjpeg 2 or higher to run") def test_region_rlevel5(self): + """ + maximim rlevel + + There seems to be a difference between version of openjpeg, as + openjp2 produces an image of size (16, 13, 3) and openjpeg produced + (17, 12, 3). + """ actual = self.j2k[5:533:32, 27:423:32] - expected = self.j2k.read(area=(5, 27, 533, 423), rlevel=5) - np.testing.assert_array_equal(actual, expected) - -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -class TestSliceProtocolOpjData(unittest.TestCase): - """ - Test slice protocol, i.e. when using [ ] to read image data. - These correspond to tests for the read method with the area parameter. - """ - @classmethod - def setUpClass(self): - - jfile = opj_data_file('input/conformance/p1_04.j2k') - self.j2k = Jp2k(jfile) - self.j2k_data = self.j2k.read() - self.j2k_half_data = self.j2k.read(rlevel=1) - self.j2k_quarter_data = self.j2k.read(rlevel=2) - - def test_NR_DEC_p1_04_j2k_43_decode(self): - actual = self.j2k[:1024, :1024] - expected = self.j2k_data - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_44_decode(self): - actual = self.j2k[640:768, 512:640] - expected = self.j2k_data[640:768, 512:640] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_45_decode(self): - actual = self.j2k[896:1024, 896:1024] - expected = self.j2k_data[896:1024, 896:1024] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_46_decode(self): - actual = self.j2k[500:800, 100:300] - expected = self.j2k_data[500:800, 100:300] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_47_decode(self): - actual = self.j2k[520:600, 260:360] - expected = self.j2k_data[520:600, 260:360] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_48_decode(self): - actual = self.j2k[520:660, 260:360] - expected = self.j2k_data[520:660, 260:360] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_49_decode(self): - actual = self.j2k[520:600, 360:400] - expected = self.j2k_data[520:600, 360:400] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_50_decode(self): - actual = self.j2k[:1024:4, :1024:4] - expected = self.j2k_quarter_data[:256, :256] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_51_decode(self): - actual = self.j2k[640:768:4, 512:640:4] - expected = self.j2k_quarter_data[160:192, 128:160] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_52_decode(self): - actual = self.j2k[896:1024:4, 896:1024:4] - expected = self.j2k_quarter_data[224:352, 224:352] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_53_decode(self): - actual = self.j2k[500:800:4, 100:300:4] - expected = self.j2k_quarter_data[125:200, 25:75] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_54_decode(self): - actual = self.j2k[520:600:4, 260:360:4] - expected = self.j2k_quarter_data[130:150, 65:90] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_55_decode(self): - actual = self.j2k[520:660:4, 260:360:4] - expected = self.j2k_quarter_data[130:165, 65:90] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_56_decode(self): - actual = self.j2k[520:600:4, 360:400:4] - expected = self.j2k_quarter_data[130:150, 90:100] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_06_j2k_75_decode(self): - # Image size would be 0 x 0. - with self.assertRaises((IOError, OSError)): - self.j2k[9:12:4, 9:12:4] - - def test_NR_DEC_p0_04_j2k_85_decode(self): - actual = self.j2k[:256, :256] - expected = self.j2k_data[:256, :256] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_86_decode(self): - actual = self.j2k[:128, 128:256] - expected = self.j2k_data[:128, 128:256] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_87_decode(self): - actual = self.j2k[10:200, 50:120] - expected = self.j2k_data[10:200, 50:120] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_88_decode(self): - actual = self.j2k[150:210, 10:190] - expected = self.j2k_data[150:210, 10:190] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_89_decode(self): - actual = self.j2k[80:150, 100:200] - expected = self.j2k_data[80:150, 100:200] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_90_decode(self): - actual = self.j2k[20:50, 150:200] - expected = self.j2k_data[20:50, 150:200] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_91_decode(self): - actual = self.j2k[:256:4, :256:4] - expected = self.j2k_quarter_data[0:64, 0:64] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_92_decode(self): - actual = self.j2k[:128:4, 128:256:4] - expected = self.j2k_quarter_data[:32, 32:64] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_93_decode(self): - actual = self.j2k[10:200:4, 50:120:4] - expected = self.j2k_quarter_data[3:50, 13:30] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_94_decode(self): - actual = self.j2k[150:210:4, 10:190:4] - expected = self.j2k_quarter_data[38:53, 3:48] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_95_decode(self): - actual = self.j2k[80:150:4, 100:200:4] - expected = self.j2k_quarter_data[20:38, 25:50] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_96_decode(self): - actual = self.j2k[20:50:4, 150:200:4] - expected = self.j2k_quarter_data[5:13, 38:50] + expected = self.j2k_data_r5[1:17, 1:14] np.testing.assert_array_equal(actual, expected) class TestJp2k(unittest.TestCase): @@ -476,6 +251,12 @@ class TestJp2k(unittest.TestCase): def tearDown(self): pass + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) + def test_warn_if_using_read_method(self): + """Should warn if deprecated read method is called""" + with self.assertWarns(DeprecationWarning): + Jp2k(self.jp2file).read() + def test_shape_jp2(self): """verify shape attribute for JP2 file """ @@ -515,20 +296,21 @@ class TestJp2k(unittest.TestCase): jpx = Jp2k(self.jpxfile) self.assertEqual(jpx.shape, (1024, 1024, 3)) + @unittest.skipIf(re.match("0|1.[0-4]", glymur.version.openjpeg_version), + "Must have openjpeg 1.5 or higher to run") @unittest.skipIf(os.name == "nt", "Unexplained failure on windows") def test_irreversible(self): """Irreversible""" j = Jp2k(self.jp2file) - expdata = j.read() + expdata = j[:] with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j2 = Jp2k(tfile.name, 'wb') - j2.write(expdata, irreversible=True) + j2 = Jp2k(tfile.name, data=expdata, irreversible=True) codestream = j2.get_codestream() self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - actdata = j2.read() + actdata = j2[:] self.assertTrue(fixtures.mse(actdata[0], expdata[0]) < 0.38) @unittest.skipIf(re.match('1.[0-4]', openjpeg_version) is not None, @@ -538,8 +320,9 @@ class TestJp2k(unittest.TestCase): def test_no_cxform_pclr_jpx(self): """Indices for pclr jpxfile if no color transform""" j = Jp2k(self.jpxfile) - rgb = j.read() - idx = j.read(ignore_pclr_cmap_cdef=True) + rgb = j[:] + j.ignore_pclr_cmap_cdef = True + idx = j[:] nr, nc = 1024, 1024 self.assertEqual(rgb.shape, (nr, nc, 3)) self.assertEqual(idx.shape, (nr, nc)) @@ -560,14 +343,27 @@ class TestJp2k(unittest.TestCase): newjp2 = eval(repr(j)) self.assertEqual(newjp2.filename, self.j2kfile) - self.assertEqual(newjp2.mode, 'rb') self.assertEqual(len(newjp2.box), 0) - def test_rlevel_max(self): - """Verify that rlevel=-1 gets us the lowest resolution image""" + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) + def test_rlevel_max_backwards_compatibility(self): + """ + Verify that rlevel=-1 gets us the lowest resolution image + + This is an old option only available via the read method, not via + array-style slicing. + """ j = Jp2k(self.j2kfile) - thumbnail1 = j.read(rlevel=-1) - thumbnail2 = j.read(rlevel=5) + if sys.hexversion < 0x03000000: + with warnings.catch_warnings(): + # Suppress a warning due to deprecated syntax + # Not as easy to verify the warning under python2. + warnings.simplefilter("ignore") + thumbnail1 = j.read(rlevel=-1) + else: + with self.assertWarns(DeprecationWarning): + thumbnail1 = j.read(rlevel=-1) + thumbnail2 = j[::32, ::32] np.testing.assert_array_equal(thumbnail1, thumbnail2) self.assertEqual(thumbnail1.shape, (25, 15, 3)) @@ -575,7 +371,7 @@ class TestJp2k(unittest.TestCase): """Should error out appropriately if reduce level too high""" j = Jp2k(self.jp2file) with self.assertRaises(IOError): - j.read(rlevel=6) + j[::64, ::64] def test_not_jpeg2000(self): """Should error out appropriately if not given a JPEG 2000 file.""" @@ -726,7 +522,7 @@ class TestJp2k(unittest.TestCase): """Just a very basic test that reading a JP2 file does not error out. """ j2k = Jp2k(self.jp2file) - j2k.read(rlevel=1) + j2k[::2, ::2] def test_basic_j2k(self): """This test is only useful when openjp2 is not available @@ -734,7 +530,7 @@ class TestJp2k(unittest.TestCase): working J2K test. """ j2k = Jp2k(self.j2kfile) - j2k.read() + j2k[:] def test_empty_box_with_j2k(self): """Verify that the list of boxes in a J2C/J2K file is present, but @@ -859,7 +655,7 @@ class TestJp2k(unittest.TestCase): """Read JPX codestream when jp2-compatible.""" # The file in question has multiple codestreams. jpx = Jp2k(self.jpxfile) - data = jpx.read() + data = jpx[:] self.assertEqual(data.shape, (1024, 1024, 3)) def test_read_bands_without_openjp2(self): @@ -886,32 +682,30 @@ class TestJp2k_write(unittest.TestCase): data = np.zeros((640, 480), dtype=np.uint8) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, 'wb') - j.write(data, cbsize=(16, 16), psizes=[(16, 16)]) + j = Jp2k(tfile.name, data=data, + cbsize=(16, 16), psizes=[(16, 16)]) def test_precinct_size_not_power_of_two(self): """must be power of two""" data = np.zeros((640, 480), dtype=np.uint8) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, 'wb') - j.write(data, cbsize=(16, 16), psizes=[(48, 48)]) + j = Jp2k(tfile.name, data=data, + cbsize=(16, 16), psizes=[(48, 48)]) def test_unsupported_int32(self): """Should raise a runtime error if trying to write int32""" data = np.zeros((128, 128), dtype=np.int32) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(RuntimeError): - j = Jp2k(tfile.name, 'wb') - j.write(data) + j = Jp2k(tfile.name, data=data) def test_unsupported_uint32(self): """Should raise a runtime error if trying to write uint32""" data = np.zeros((128, 128), dtype=np.uint32) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(RuntimeError): - j = Jp2k(tfile.name, 'wb') - j.write(data) + j = Jp2k(tfile.name, data=data) def test_write_with_version_too_early(self): """Should raise a runtime error if trying to write with version 1.3""" @@ -921,8 +715,7 @@ class TestJp2k_write(unittest.TestCase): with patch('glymur.version.openjpeg_version', new=version): with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(RuntimeError): - j = Jp2k(tfile.name, 'wb') - j.write(data) + j = Jp2k(tfile.name, data=data) def test_cblkh_different_than_width(self): """Verify that we can set a code block size where height does not equal @@ -930,11 +723,8 @@ class TestJp2k_write(unittest.TestCase): """ data = np.zeros((128, 128), dtype=np.uint8) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - # The code block dimensions are given as rows x columns. - j.write(data, cbsize=(16, 32)) - + j = Jp2k(tfile.name, data=data, cbsize=(16, 32)) codestream = j.get_codestream() # Code block size is reported as XY in the codestream. @@ -943,59 +733,55 @@ class TestJp2k_write(unittest.TestCase): 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): - data = np.zeros((128, 128, 2, 2), dtype=np.uint8) - j.write(data) + j = Jp2k(tfile.name, + data=np.zeros((128, 128, 2, 2), dtype=np.uint8)) 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): - data = np.zeros((128, 128, 2), dtype=np.uint8) - j.write(data, colorspace='rgb') + j = Jp2k(tfile.name, + data=np.zeros((128, 128, 2), dtype=np.uint8), + colorspace='rgb') def test_colorspace_with_j2k(self): """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): - data = np.zeros((128, 128, 3), dtype=np.uint8) - j.write(data, colorspace='rgb') + j = Jp2k(tfile.name, + data=np.zeros((128, 128, 3), dtype=np.uint8), + colorspace='rgb') 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) - j.write(data, colorspace='rgb') + j = Jp2k(tfile.name, + data=np.zeros((128, 128, 3), dtype=np.uint8), + colorspace='rgb') self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB) 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) - j.write(data, colorspace='gray') + j = Jp2k(tfile.name, data=data, colorspace='gray') self.assertEqual(j.box[2].box[1].colorspace, glymur.core.GREYSCALE) 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) - j.write(data, colorspace='grey') + j = Jp2k(tfile.name, data=data, colorspace='grey') self.assertEqual(j.box[2].box[1].colorspace, glymur.core.GREYSCALE) 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) - j.write(data, colorspace='gray') + j = Jp2k(tfile.name, data=data, colorspace='gray') self.assertEqual(j.box[2].box[0].height, 128) self.assertEqual(j.box[2].box[0].width, 128) self.assertEqual(j.box[2].box[0].num_components, 3) @@ -1005,29 +791,26 @@ class TestJp2k_write(unittest.TestCase): def test_specify_ycc(self): """Should reject YCC""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = Jp2k(tfile.name, 'wb') with self.assertRaises(IOError): data = np.zeros((128, 128, 3), dtype=np.uint8) - j.write(data, colorspace='ycc') + j = Jp2k(tfile.name, data=data, colorspace='ycc') def test_write_with_jp2_in_caps(self): """should be able to write with JP2 suffix.""" j2k = Jp2k(self.j2kfile) - expdata = j2k.read() + expdata = j2k[:] with tempfile.NamedTemporaryFile(suffix='.JP2') as tfile: - ofile = Jp2k(tfile.name, 'wb') - ofile.write(expdata) - actdata = ofile.read() + ofile = Jp2k(tfile.name, data=expdata) + actdata = ofile[:] np.testing.assert_array_equal(actdata, expdata) def test_write_srgb_without_mct(self): """should be able to write RGB without specifying mct""" j2k = Jp2k(self.j2kfile) - expdata = j2k.read() + expdata = j2k[:] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - ofile = Jp2k(tfile.name, 'wb') - ofile.write(expdata, mct=False) - actdata = ofile.read() + ofile = Jp2k(tfile.name, data=expdata, mct=False) + actdata = ofile[:] np.testing.assert_array_equal(actdata, expdata) codestream = ofile.get_codestream() @@ -1036,21 +819,19 @@ class TestJp2k_write(unittest.TestCase): def test_write_grayscale_with_mct(self): """MCT usage makes no sense for grayscale images.""" j2k = Jp2k(self.j2kfile) - expdata = j2k.read() + expdata = j2k[:] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - ofile = Jp2k(tfile.name, 'wb') with self.assertRaises(IOError): - ofile.write(expdata[:, :, 0], mct=True) + ofile = Jp2k(tfile.name, data=expdata[:, :, 0], mct=True) 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) + expdata = j[::2, ::2] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - ofile = Jp2k(tfile.name, 'wb') - ofile.write(expdata, prog='CPRL') - actdata = ofile.read() + ofile = Jp2k(tfile.name, data=expdata, prog='CPRL') + actdata = ofile[:] np.testing.assert_array_equal(actdata, expdata) codestream = ofile.get_codestream() @@ -1072,16 +853,19 @@ class TestJp2k_1_x(unittest.TestCase): """ with patch('glymur.version.openjpeg_version_tuple', new=(1, 5, 0)): j2k = Jp2k(self.j2kfile) - with self.assertRaises(TypeError): - j2k.read(tile=0) + with warnings.catch_warnings(): + # The tile keyword is deprecated, so suppress the warning. + warnings.simplefilter('ignore') + with self.assertRaises(TypeError): + j2k.read(tile=0) def test_layer(self): """layer option not allowed for 1.x. """ with patch('glymur.version.openjpeg_version_tuple', new=(1, 5, 0)): j2k = Jp2k(self.j2kfile) - with self.assertRaises(TypeError): - j2k.read(layer=1) + with self.assertRaises(RuntimeError): + j2k.layer = 1 @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) @@ -1092,10 +876,9 @@ class Test_2p0_official(unittest.TestCase): """Can only write 4 components on 2.0+, should error out otherwise.""" with patch('glymur.version.openjpeg_version', new="2.0.0"): with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = Jp2k(tfile.name, 'wb') data = np.zeros((128, 128, 4), dtype=np.uint8) with self.assertRaises(IOError): - j.write(data) + Jp2k(tfile.name, data=data) @unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2, @@ -1115,32 +898,30 @@ class TestJp2k_2_0(unittest.TestCase): j = Jp2k(self.jp2file) with self.assertRaises(IOError): # Start corner must be >= 0 - j.read(area=(-1, -1, 1, 1)) + j[-1:1, -1:1] with self.assertRaises(IOError): # End corner must be > 0 - j.read(area=(10, 10, 0, 0)) + j[10:0, 10:0] with self.assertRaises(IOError): # End corner must be >= start corner - j.read(area=(10, 10, 8, 8)) + j[10:8, 10:8] @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) 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') + data = np.zeros((128, 128, 3), dtype=np.uint8) with self.assertRaises(IOError): - data = np.zeros((128, 128, 3), dtype=np.uint8) - j.write(data, colorspace='cmyk') + j = Jp2k(tfile.name, data=data, colorspace='cmyk') @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) 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) + data = Jp2k(self.jp2file)[::2, ::2] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = Jp2k(tfile.name, 'wb') - j.write(data) + j = Jp2k(tfile.name, data=data) with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2: @@ -1198,9 +979,8 @@ class TestJp2k_2_1(unittest.TestCase): 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) - j.write(data) + j = Jp2k(tfile.name, data=data) self.assertEqual(j.box[2].box[0].height, 128) self.assertEqual(j.box[2].box[0].width, 128) self.assertEqual(j.box[2].box[0].num_components, 2) @@ -1211,9 +991,8 @@ class TestJp2k_2_1(unittest.TestCase): 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) - j.write(data) + j = Jp2k(tfile.name, data=data) self.assertEqual(j.box[2].box[0].height, 128) self.assertEqual(j.box[2].box[0].width, 128) self.assertEqual(j.box[2].box[0].num_components, 4) @@ -1238,11 +1017,9 @@ class TestJp2k_2_1(unittest.TestCase): tfile.write(data[offset+53:offset+55]) tfile.write(b'\x00') tfile.write(data[offset+57:offset+59]) - #tfile.write(data[3184:3186]) tfile.write(b'\x00') tfile.write(data[offset+59:]) - #tfile.write(data[3186:]) tfile.flush() with warnings.catch_warnings(): warnings.simplefilter('ignore') @@ -1252,10 +1029,10 @@ class TestJp2k_2_1(unittest.TestCase): :\sdx=1\sdy=0''', re.VERBOSE) if sys.hexversion < 0x03020000: with self.assertRaisesRegexp((IOError, OSError), regexp): - j.read(rlevel=1) + j[::2, ::2] else: with self.assertRaisesRegex((IOError, OSError), regexp): - j.read(rlevel=1) + j[::2, ::2] @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @@ -1280,7 +1057,6 @@ class TestParsing(unittest.TestCase): with self.assertWarnsRegex(UserWarning, 'Invalid profile'): jp2 = Jp2k(filename) - #@unittest.skip('trouble is a brewing...') def test_main_header(self): """Verify that the main header is not loaded when parsing turned off.""" # The hidden _main_header attribute should show up after accessing it. @@ -1342,6 +1118,8 @@ class TestJp2kOpjDataRootWarnings(unittest.TestCase): class TestJp2kOpjDataRoot(unittest.TestCase): """These tests should be run by just about all configuration.""" + @unittest.skipIf(re.match("0|1.[0-4]", glymur.version.openjpeg_version), + "Must have openjpeg 1.5 or higher to run") @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) def test_irreversible(self): """Irreversible""" @@ -1349,14 +1127,13 @@ class TestJp2kOpjDataRoot(unittest.TestCase): expdata = np.fromfile(filename, dtype=np.uint16) expdata.resize((2816, 2048)) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - j.write(expdata, irreversible=True) + j = Jp2k(tfile.name, data=expdata, irreversible=True) codestream = j.get_codestream() self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - actdata = j.read() + actdata = j[:] self.assertTrue(fixtures.mse(actdata, expdata) < 250) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @@ -1364,15 +1141,16 @@ class TestJp2kOpjDataRoot(unittest.TestCase): """Indices for pclr jpxfile if no color transform""" filename = opj_data_file('input/conformance/file9.jp2') with self.assertWarns(UserWarning): - j = Jp2k(filename) - rgb = j.read() - idx = j.read(ignore_pclr_cmap_cdef=True) + jp2 = Jp2k(filename) + rgb = jp2[:] + jp2.ignore_pclr_cmap_cdef = True + idx = jp2[:] self.assertEqual(rgb.shape, (512, 768, 3)) self.assertEqual(idx.shape, (512, 768)) # Should be able to manually reconstruct the RGB image from the palette # and indices. - palette = j.box[2].box[1].palette + palette = jp2.box[2].box[1].palette rgb_from_idx = np.zeros(rgb.shape, dtype=np.uint8) for r in np.arange(rgb.shape[0]): for c in np.arange(rgb.shape[1]): @@ -1388,7 +1166,7 @@ class TestJp2kOpjDataRoot(unittest.TestCase): filename = opj_data_file('input/conformance/p0_05.j2k') j = Jp2k(filename) with self.assertRaises(RuntimeError): - j.read() + j[:] @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_no_cxform_cmap(self): @@ -1399,8 +1177,9 @@ class TestJp2kOpjDataRoot(unittest.TestCase): with self.assertWarns(UserWarning): # The file has a bad compatibility list entry. Not important here. j = Jp2k(filename) - ycbcr = j.read() - crcby = j.read(ignore_pclr_cmap_cdef=True) + ycbcr = j[:] + j.ignore_pclr_cmap_cdef = True + crcby = j[:] expected = np.zeros(ycbcr.shape, ycbcr.dtype) for k in range(crcby.shape[2]): @@ -1409,6 +1188,259 @@ class TestJp2kOpjDataRoot(unittest.TestCase): np.testing.assert_array_equal(ycbcr, expected) +class TestCodestream(unittest.TestCase): + """Test suite for unusual codestream cases.""" -if __name__ == "__main__": - unittest.main() + def setUp(self): + self.jp2file = glymur.data.nemo() + + def tearDown(self): + pass + + def test_siz_segment_ssiz_unsigned(self): + """ssiz attribute to be removed in future release""" + j = Jp2k(self.jp2file) + codestream = j.get_codestream() + + # The ssiz attribute was simply a tuple of raw bytes. + # The first 7 bits are interpreted as the bitdepth, the MSB determines + # whether or not it is signed. + self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7)) + + +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class TestCodestreamOpjData(unittest.TestCase): + """Test suite for unusual codestream cases. Uses OPJ_DATA_ROOT""" + + def setUp(self): + self.jp2file = glymur.data.nemo() + + def tearDown(self): + pass + + @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(OPJ_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. + read_buffer = ifile.read(45) + tfile.write(read_buffer) + + # Write the new marker segment, 0xff6f = 65391 + read_buffer = struct.pack('>HHB', int(65391), int(3), int(0)) + tfile.write(read_buffer) + + # Get the rest of the input file. + read_buffer = ifile.read() + tfile.write(read_buffer) + tfile.flush() + + codestream = Jp2k(tfile.name).get_codestream() + + self.assertEqual(codestream.segment[2].marker_id, '0xff6f') + 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(OPJ_DATA_ROOT, + 'input/nonregression/123.j2c') + j = Jp2k(filename) + 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(codestream.segment[-1].marker_id, 'EOC') + + + def test_siz_segment_ssiz_signed(self): + """ssiz attribute to be removed in future release""" + filename = os.path.join(OPJ_DATA_ROOT, 'input/conformance/p0_03.j2k') + j = Jp2k(filename) + codestream = j.get_codestream() + + # The ssiz attribute was simply a tuple of raw bytes. + # The first 7 bits are interpreted as the bitdepth, the MSB determines + # whether or not it is signed. + self.assertEqual(codestream.segment[1].ssiz, (131,)) + + +class TestCodestreamRepr(unittest.TestCase): + + def setUp(self): + self.jp2file = glymur.data.nemo() + + def tearDown(self): + pass + + def test_soc(self): + """Test SOC segment repr""" + segment = glymur.codestream.SOCsegment() + newseg = eval(repr(segment)) + self.assertEqual(newseg.marker_id, 'SOC') + + def test_siz(self): + """Test SIZ segment repr""" + kwargs = {'rsiz': 0, + 'xysiz': (2592, 1456), + 'xyosiz': (0, 0), + 'xytsiz': (2592, 1456), + 'xytosiz': (0, 0), + 'Csiz': 3, + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': ((1, 1, 1), (1, 1, 1))} + segment = glymur.codestream.SIZsegment(**kwargs) + newseg = eval(repr(segment)) + self.assertEqual(newseg.marker_id, 'SIZ') + self.assertEqual(newseg.xsiz, 2592) + self.assertEqual(newseg.ysiz, 1456) + self.assertEqual(newseg.xosiz, 0) + self.assertEqual(newseg.yosiz, 0) + self.assertEqual(newseg.xtsiz, 2592) + self.assertEqual(newseg.ytsiz, 1456) + self.assertEqual(newseg.xtosiz, 0) + self.assertEqual(newseg.ytosiz, 0) + + self.assertEqual(newseg.xrsiz, (1, 1, 1)) + self.assertEqual(newseg.yrsiz, (1, 1, 1)) + self.assertEqual(newseg.bitdepth, (8, 8, 8)) + self.assertEqual(newseg.signed, (False, False, False)) + + +class TestCodestream(unittest.TestCase): + """Test suite for unusual codestream cases.""" + + def setUp(self): + self.jp2file = glymur.data.nemo() + + def tearDown(self): + pass + + def test_siz_segment_ssiz_unsigned(self): + """ssiz attribute to be removed in future release""" + j = Jp2k(self.jp2file) + codestream = j.get_codestream() + + # The ssiz attribute was simply a tuple of raw bytes. + # The first 7 bits are interpreted as the bitdepth, the MSB determines + # whether or not it is signed. + self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7)) + + +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class TestCodestreamOpjData(unittest.TestCase): + """Test suite for unusual codestream cases. Uses OPJ_DATA_ROOT""" + + def setUp(self): + self.jp2file = glymur.data.nemo() + + def tearDown(self): + pass + + @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(OPJ_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. + read_buffer = ifile.read(45) + tfile.write(read_buffer) + + # Write the new marker segment, 0xff6f = 65391 + read_buffer = struct.pack('>HHB', int(65391), int(3), int(0)) + tfile.write(read_buffer) + + # Get the rest of the input file. + read_buffer = ifile.read() + tfile.write(read_buffer) + tfile.flush() + + codestream = Jp2k(tfile.name).get_codestream() + + self.assertEqual(codestream.segment[2].marker_id, '0xff6f') + 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(OPJ_DATA_ROOT, + 'input/nonregression/123.j2c') + j = Jp2k(filename) + 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(codestream.segment[-1].marker_id, 'EOC') + + + def test_siz_segment_ssiz_signed(self): + """ssiz attribute to be removed in future release""" + filename = os.path.join(OPJ_DATA_ROOT, 'input/conformance/p0_03.j2k') + j = Jp2k(filename) + codestream = j.get_codestream() + + # The ssiz attribute was simply a tuple of raw bytes. + # The first 7 bits are interpreted as the bitdepth, the MSB determines + # whether or not it is signed. + self.assertEqual(codestream.segment[1].ssiz, (131,)) + + +class TestCodestreamRepr(unittest.TestCase): + + def setUp(self): + self.jp2file = glymur.data.nemo() + + def tearDown(self): + pass + + def test_soc(self): + """Test SOC segment repr""" + segment = glymur.codestream.SOCsegment() + newseg = eval(repr(segment)) + self.assertEqual(newseg.marker_id, 'SOC') + + def test_siz(self): + """Test SIZ segment repr""" + kwargs = {'rsiz': 0, + 'xysiz': (2592, 1456), + 'xyosiz': (0, 0), + 'xytsiz': (2592, 1456), + 'xytosiz': (0, 0), + 'Csiz': 3, + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': ((1, 1, 1), (1, 1, 1))} + segment = glymur.codestream.SIZsegment(**kwargs) + newseg = eval(repr(segment)) + self.assertEqual(newseg.marker_id, 'SIZ') + self.assertEqual(newseg.xsiz, 2592) + self.assertEqual(newseg.ysiz, 1456) + self.assertEqual(newseg.xosiz, 0) + self.assertEqual(newseg.yosiz, 0) + self.assertEqual(newseg.xtsiz, 2592) + self.assertEqual(newseg.ytsiz, 1456) + self.assertEqual(newseg.xtosiz, 0) + self.assertEqual(newseg.ytosiz, 0) + + self.assertEqual(newseg.xrsiz, (1, 1, 1)) + self.assertEqual(newseg.yrsiz, (1, 1, 1)) + self.assertEqual(newseg.bitdepth, (8, 8, 8)) + self.assertEqual(newseg.signed, (False, False, False)) diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index a033aae..cf44eaa 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -30,6 +30,7 @@ suite. import re import sys import unittest +import warnings import numpy as np @@ -57,7 +58,7 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P0_p0_01_j2k(self): jfile = opj_data_file('input/conformance/p0_01.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) + jpdata = jp2k[:] pgxfile = opj_data_file('baseline/conformance/c1p0_01_0.pgx') pgxdata = read_pgx(pgxfile) @@ -67,7 +68,7 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P0_p0_03_j2k(self): jfile = opj_data_file('input/conformance/p0_03.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) + jpdata = jp2k[:] pgxfile = opj_data_file('baseline/conformance/c1p0_03_0.pgx') pgxdata = read_pgx(pgxfile) @@ -77,7 +78,7 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P0_p0_04_j2k(self): jfile = opj_data_file('input/conformance/p0_04.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) + jpdata = jp2k[:] pgxfile = opj_data_file('baseline/conformance/c1p0_04_0.pgx') pgxdata = read_pgx(pgxfile) @@ -97,7 +98,7 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P0_p0_08_j2k(self): jfile = opj_data_file('input/conformance/p0_08.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=1) + jpdata = jp2k[::2, ::2] pgxfile = opj_data_file('baseline/conformance/c1p0_08_0.pgx') pgxdata = read_pgx(pgxfile) @@ -114,7 +115,7 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P0_p0_09_j2k(self): jfile = opj_data_file('input/conformance/p0_09.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) + jpdata = jp2k[:] pgxfile = opj_data_file('baseline/conformance/c1p0_09_0.pgx') pgxdata = read_pgx(pgxfile) @@ -123,7 +124,7 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P0_p0_11_j2k(self): jfile = opj_data_file('input/conformance/p0_11.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) + jpdata = jp2k[:] pgxfile = opj_data_file('baseline/conformance/c1p0_11_0.pgx') pgxdata = read_pgx(pgxfile) @@ -132,7 +133,7 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P0_p0_14_j2k(self): jfile = opj_data_file('input/conformance/p0_14.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) + jpdata = jp2k[:] pgxfile = opj_data_file('baseline/conformance/c1p0_14_0.pgx') pgxdata = read_pgx(pgxfile) @@ -149,7 +150,7 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P0_p0_15_j2k(self): jfile = opj_data_file('input/conformance/p0_15.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) + jpdata = jp2k[:] pgxfile = opj_data_file('baseline/conformance/c1p0_15_0.pgx') pgxdata = read_pgx(pgxfile) @@ -158,7 +159,7 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P0_p0_16_j2k(self): jfile = opj_data_file('input/conformance/p0_16.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) + jpdata = jp2k[:] pgxfile = opj_data_file('baseline/conformance/c1p0_16_0.pgx') pgxdata = read_pgx(pgxfile) @@ -167,7 +168,7 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P1_p1_01_j2k(self): jfile = opj_data_file('input/conformance/p1_01.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) + jpdata = jp2k[:] pgxfile = opj_data_file('baseline/conformance/c1p1_01_0.pgx') pgxdata = read_pgx(pgxfile) @@ -176,7 +177,7 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P1_p1_02_j2k(self): jfile = opj_data_file('input/conformance/p1_02.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) + jpdata = jp2k[:] pgxfile = opj_data_file('baseline/conformance/c1p1_02_0.pgx') pgxdata = read_pgx(pgxfile) @@ -196,7 +197,7 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P1_p1_04_j2k(self): jfile = opj_data_file('input/conformance/p1_04.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k.read() + jpdata = jp2k[:] pgxfile = opj_data_file('baseline/conformance/c1p1_04_0.pgx') pgxdata = read_pgx(pgxfile) @@ -206,95 +207,95 @@ class TestSuite(unittest.TestCase): def test_NR_DEC_Bretagne2_j2k_1_decode(self): jfile = opj_data_file('input/nonregression/Bretagne2.j2k') jp2 = Jp2k(jfile) - jp2.read() + jp2[:] self.assertTrue(True) def test_NR_DEC__00042_j2k_2_decode(self): jfile = opj_data_file('input/nonregression/_00042.j2k') jp2 = Jp2k(jfile) - jp2.read() + jp2[:] self.assertTrue(True) def test_NR_DEC_buxI_j2k_9_decode(self): jfile = opj_data_file('input/nonregression/buxI.j2k') - Jp2k(jfile).read() + Jp2k(jfile)[:] self.assertTrue(True) def test_NR_DEC_buxR_j2k_10_decode(self): jfile = opj_data_file('input/nonregression/buxR.j2k') - Jp2k(jfile).read() + Jp2k(jfile)[:] self.assertTrue(True) def test_NR_DEC_Cannotreaddatawithnosizeknown_j2k_11_decode(self): relpath = 'input/nonregression/Cannotreaddatawithnosizeknown.j2k' jfile = opj_data_file(relpath) - Jp2k(jfile).read() + Jp2k(jfile)[:] self.assertTrue(True) def test_NR_DEC_cthead1_j2k_12_decode(self): jfile = opj_data_file('input/nonregression/cthead1.j2k') - Jp2k(jfile).read() + Jp2k(jfile)[:] self.assertTrue(True) def test_NR_DEC_CT_Phillips_JPEG2K_Decompr_Problem_j2k_13_decode(self): relpath = 'input/nonregression/CT_Phillips_JPEG2K_Decompr_Problem.j2k' jfile = opj_data_file(relpath) - Jp2k(jfile).read() + Jp2k(jfile)[:] self.assertTrue(True) def test_NR_DEC_j2k32_j2k_15_decode(self): jfile = opj_data_file('input/nonregression/j2k32.j2k') - Jp2k(jfile).read() + Jp2k(jfile)[:] self.assertTrue(True) def test_NR_DEC_MarkerIsNotCompliant_j2k_17_decode(self): jfile = opj_data_file('input/nonregression/MarkerIsNotCompliant.j2k') - Jp2k(jfile).read() + Jp2k(jfile)[:] self.assertTrue(True) def test_NR_DEC_Marrin_jp2_18_decode(self): jfile = opj_data_file('input/nonregression/Marrin.jp2') - Jp2k(jfile).read() + Jp2k(jfile)[:] self.assertTrue(True) def test_NR_DEC_movie_00000_j2k_20_decode(self): jfile = opj_data_file('input/nonregression/movie_00000.j2k') - Jp2k(jfile).read() + Jp2k(jfile)[:] self.assertTrue(True) def test_NR_DEC_movie_00001_j2k_21_decode(self): jfile = opj_data_file('input/nonregression/movie_00001.j2k') - Jp2k(jfile).read() + Jp2k(jfile)[:] self.assertTrue(True) def test_NR_DEC_movie_00002_j2k_22_decode(self): jfile = opj_data_file('input/nonregression/movie_00002.j2k') - Jp2k(jfile).read() + Jp2k(jfile)[:] self.assertTrue(True) def test_NR_DEC_orb_blue_lin_j2k_j2k_23_decode(self): jfile = opj_data_file('input/nonregression/orb-blue10-lin-j2k.j2k') - Jp2k(jfile).read() + Jp2k(jfile)[:] self.assertTrue(True) def test_NR_DEC_orb_blue_win_j2k_j2k_24_decode(self): jfile = opj_data_file('input/nonregression/orb-blue10-win-j2k.j2k') - Jp2k(jfile).read() + Jp2k(jfile)[:] self.assertTrue(True) def test_NR_DEC_relax_jp2_27_decode(self): jfile = opj_data_file('input/nonregression/relax.jp2') - Jp2k(jfile).read() + Jp2k(jfile)[:] self.assertTrue(True) def test_NR_DEC_test_lossless_j2k_28_decode(self): jfile = opj_data_file('input/nonregression/test_lossless.j2k') - Jp2k(jfile).read() + Jp2k(jfile)[:] self.assertTrue(True) def test_NR_DEC_pacs_ge_j2k_30_decode(self): jfile = opj_data_file('input/nonregression/pacs.ge.j2k') - Jp2k(jfile).read() + Jp2k(jfile)[:] self.assertTrue(True) @@ -317,14 +318,14 @@ class TestSuiteWarns(MetadataBase): with self.assertWarns(UserWarning): # Bad compatibility list item. jp2k = Jp2k(jfile) - jpdata = jp2k.read() + jpdata = jp2k[:] self.assertEqual(jpdata.shape, (512, 768, 3)) def test_ETS_JP2_file2(self): jfile = opj_data_file('input/conformance/file2.jp2') with self.assertWarns(UserWarning): jp2k = Jp2k(jfile) - jpdata = jp2k.read() + jpdata = jp2k[:] self.assertEqual(jpdata.shape, (640, 480, 3)) @unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2, @@ -342,7 +343,7 @@ class TestSuiteWarns(MetadataBase): jfile = opj_data_file('input/conformance/file4.jp2') with self.assertWarns(UserWarning): jp2k = Jp2k(jfile) - jpdata = jp2k.read() + jpdata = jp2k[:] self.assertEqual(jpdata.shape, (512, 768)) def test_ETS_JP2_file5(self): @@ -351,35 +352,35 @@ class TestSuiteWarns(MetadataBase): # There's a warning for an unknown compatibility entry. # Ignore it here. jp2k = Jp2k(jfile) - jpdata = jp2k.read() + jpdata = jp2k[:] self.assertEqual(jpdata.shape, (512, 768, 3)) def test_ETS_JP2_file6(self): jfile = opj_data_file('input/conformance/file6.jp2') with self.assertWarns(UserWarning): jp2k = Jp2k(jfile) - jpdata = jp2k.read() + jpdata = jp2k[:] self.assertEqual(jpdata.shape, (512, 768)) def test_ETS_JP2_file7(self): jfile = opj_data_file('input/conformance/file7.jp2') with self.assertWarns(UserWarning): jp2k = Jp2k(jfile) - jpdata = jp2k.read() + jpdata = jp2k[:] self.assertEqual(jpdata.shape, (640, 480, 3)) def test_ETS_JP2_file8(self): jfile = opj_data_file('input/conformance/file8.jp2') with self.assertWarns(UserWarning): jp2k = Jp2k(jfile) - jpdata = jp2k.read() + jpdata = jp2k[:] self.assertEqual(jpdata.shape, (400, 700)) def test_ETS_JP2_file9(self): jfile = opj_data_file('input/conformance/file9.jp2') with self.assertWarns(UserWarning): jp2k = Jp2k(jfile) - jpdata = jp2k.read() + jpdata = jp2k[:] self.assertEqual(jpdata.shape, (512, 768, 3)) def test_NR_broken_jp2_dump(self): @@ -469,13 +470,13 @@ class TestSuiteWarns(MetadataBase): jfile = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2') with self.assertWarns(UserWarning): # This file has an invalid ICC profile - Jp2k(jfile).read() + Jp2k(jfile)[:] self.assertTrue(True) def test_NR_DEC_orb_blue_win_jp2_26_decode(self): jfile = opj_data_file('input/nonregression/orb-blue10-win-jp2.jp2') with self.assertWarns(UserWarning): - Jp2k(jfile).read() + Jp2k(jfile)[:] self.assertTrue(True) @@ -590,7 +591,7 @@ class TestSuite2point0(unittest.TestCase): def test_ETS_C1P0_p0_10_j2k(self): jfile = opj_data_file('input/conformance/p0_10.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k.read(rlevel=0) + jpdata = jp2k[:] pgxfile = opj_data_file('baseline/conformance/c1p0_10_0.pgx') pgxdata = read_pgx(pgxfile) @@ -611,7 +612,7 @@ class TestSuite2point0(unittest.TestCase): with self.assertRaises(IOError): with self.assertWarns(UserWarning): # Invalid marker ID. - Jp2k(jfile).read() + Jp2k(jfile)[:] self.assertTrue(True) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @@ -620,7 +621,7 @@ class TestSuite2point0(unittest.TestCase): with self.assertRaises(IOError): with self.assertWarns(UserWarning): # invalid number of subbands, bad marker ID - Jp2k(jfile).read() + Jp2k(jfile)[:] self.assertTrue(True) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @@ -632,11 +633,343 @@ class TestSuite2point0(unittest.TestCase): if glymur.version.openjpeg_version_tuple[0] < 2: with self.assertWarns(UserWarning): # Incorrect warning issued about tile parts. - Jp2k(jfile).read() + Jp2k(jfile)[:] else: - Jp2k(jfile).read() + Jp2k(jfile)[:] self.assertTrue(True) -if __name__ == "__main__": - unittest.main() +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +@unittest.skipIf(re.match(r'''(1|2.0.0)''', + glymur.version.openjpeg_version) is not None, + "Only supported in 2.0.1 or higher") +class TestSuite2point1(unittest.TestCase): + """Runs tests introduced in version 2.0+ or that pass only in 2.0+""" + + def setUp(self): + pass + + def tearDown(self): + pass + + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) + def test_NR_DEC_text_GBR_jp2_29_decode(self): + jfile = opj_data_file('input/nonregression/text_GBR.jp2') + with self.assertWarns(UserWarning): + # brand is 'jp2 ', but has any icc profile. + jp2 = Jp2k(jfile) + jp2[:] + self.assertTrue(True) + + def test_NR_DEC_kodak_2layers_lrcp_j2c_31_decode(self): + jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c') + Jp2k(jfile)[:] + self.assertTrue(True) + + def test_NR_DEC_kodak_2layers_lrcp_j2c_32_decode(self): + jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c') + Jp2k(jfile)[::4, ::4] + self.assertTrue(True) + + def test_NR_DEC_issue104_jpxstream_jp2_33_decode(self): + jfile = opj_data_file('input/nonregression/issue104_jpxstream.jp2') + Jp2k(jfile)[:] + self.assertTrue(True) + + def test_NR_DEC_mem_b2b86b74_2753_jp2_35_decode(self): + jfile = opj_data_file('input/nonregression/mem-b2b86b74-2753.jp2') + Jp2k(jfile)[:] + self.assertTrue(True) + + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) + def test_NR_DEC_gdal_fuzzer_unchecked_num_resolutions_jp2_36_decode(self): + f = 'input/nonregression/gdal_fuzzer_unchecked_numresolutions.jp2' + jfile = opj_data_file(f) + with self.assertWarns(UserWarning): + # Invalid number of resolutions. + j = Jp2k(jfile) + with self.assertRaises(IOError): + j[:] + + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) + def test_NR_DEC_gdal_fuzzer_check_number_of_tiles_jp2_38_decode(self): + relpath = 'input/nonregression/gdal_fuzzer_check_number_of_tiles.jp2' + jfile = opj_data_file(relpath) + with self.assertWarns(UserWarning): + # Invalid number of tiles. + j = Jp2k(jfile) + with self.assertRaises(IOError): + j[:] + + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) + def test_NR_DEC_gdal_fuzzer_check_comp_dx_dy_jp2_39_decode(self): + relpath = 'input/nonregression/gdal_fuzzer_check_comp_dx_dy.jp2' + jfile = opj_data_file(relpath) + with self.assertWarns(UserWarning): + # Invalid subsampling value + with self.assertRaises(IOError): + Jp2k(jfile)[:] + + def test_NR_DEC_file_409752_jp2_40_decode(self): + jfile = opj_data_file('input/nonregression/file409752.jp2') + with self.assertRaises(RuntimeError): + Jp2k(jfile)[:] + + def test_NR_DEC_issue206_image_000_jp2_42_decode(self): + jfile = opj_data_file('input/nonregression/issue206_image-000.jp2') + Jp2k(jfile)[:] + self.assertTrue(True) + + def test_NR_DEC_p1_04_j2k_57_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + tdata = jp2k[896:1024, 896:1024] # last tile + odata = jp2k[:] + np.testing.assert_array_equal(tdata, odata[896:1024, 896:1024]) + + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) + def test_NR_DEC_p1_04_j2k_57_decode_0p7_backwards_compatibility(self): + """ + 0.7.x usage deprecated + """ + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + if sys.hexversion < 0x03000000: + with warnings.catch_warnings(): + # Suppress a warning due to deprecated syntax + warnings.simplefilter("ignore") + tdata = jp2k.read(tile=63) # last tile + else: + with self.assertWarns(DeprecationWarning): + tdata = jp2k.read(tile=63) # last tile + odata = jp2k[:] + np.testing.assert_array_equal(tdata, odata[896:1024, 896:1024]) + + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) + def test_NR_DEC_p1_04_j2k_58_decode_0p7_backwards_compatibility(self): + """ + 0.7.x usage deprecated + """ + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + if sys.hexversion < 0x03000000: + with warnings.catch_warnings(): + # Suppress a warning due to deprecated syntax + tdata = jp2k.read(tile=63, rlevel=2) # last tile + else: + with self.assertWarns(DeprecationWarning): + tdata = jp2k.read(tile=63, rlevel=2) # last tile + odata = jp2k[::4, ::4] + np.testing.assert_array_equal(tdata, odata[224:256, 224:256]) + + def test_NR_DEC_p1_04_j2k_58_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + tdata = jp2k[896:1024:4, 896:1024:4] # last tile + odata = jp2k[::4, ::4] + np.testing.assert_array_equal(tdata, odata[224:256, 224:256]) + + def test_NR_DEC_p1_04_j2k_59_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + tdata = jp2k[128:256, 512:640] # 2nd row, 5th column + odata = jp2k[:] + np.testing.assert_array_equal(tdata, odata[128:256, 512:640]) + + def test_NR_DEC_p1_04_j2k_60_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + tdata = jp2k[128:256:2, 512:640:2] # 2nd row, 5th column + odata = jp2k[::2, ::2] + np.testing.assert_array_equal(tdata, odata[64:128, 256:320]) + + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) + def test_NR_DEC_jp2_36_decode(self): + lst = ('input', + 'nonregression', + 'gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc.patch.jp2') + jfile = opj_data_file('/'.join(lst)) + with self.assertWarns(UserWarning): + # Invalid component number. + j = Jp2k(jfile) + with self.assertRaises(IOError): + j[:] + +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +@unittest.skipIf(re.match(r'''(1|2.0.0)''', + glymur.version.openjpeg_version) is not None, + "Only supported in 2.0.1 or higher") +class TestReadArea(unittest.TestCase): + """ + Runs tests introduced in version 2.0+ or that pass only in 2.0+ + + Specifically for read method with area parameter. + """ + @classmethod + def setUpClass(self): + + jfile = opj_data_file('input/conformance/p1_04.j2k') + self.j2k = Jp2k(jfile) + self.j2k_data = self.j2k[:] + self.j2k_half_data = self.j2k[::2, ::2] + self.j2k_quarter_data = self.j2k[::4, ::4] + + jfile = opj_data_file('input/conformance/p1_06.j2k') + self.j2k_p1_06 = Jp2k(jfile) + + def test_NR_DEC_p1_04_j2k_43_decode(self): + actual = self.j2k[:1024, :1024] + expected = self.j2k_data + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_44_decode(self): + actual = self.j2k[640:768, 512:640] + expected = self.j2k_data[640:768, 512:640] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_45_decode(self): + actual = self.j2k[896:1024, 896:1024] + expected = self.j2k_data[896:1024, 896:1024] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_46_decode(self): + actual = self.j2k[500:800, 100:300] + expected = self.j2k_data[500:800, 100:300] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_47_decode(self): + actual = self.j2k[520:600, 260:360] + expected = self.j2k_data[520:600, 260:360] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_48_decode(self): + actual = self.j2k[520:660, 260:360] + expected = self.j2k_data[520:660, 260:360] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_49_decode(self): + actual = self.j2k[520:600, 360:400] + expected = self.j2k_data[520:600, 360:400] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_50_decode(self): + actual = self.j2k[:1024:4, :1024:4] + expected = self.j2k_quarter_data + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_51_decode(self): + actual = self.j2k[640:768:4, 512:640:4] + expected = self.j2k_quarter_data[160:192, 128:160] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_52_decode(self): + actual = self.j2k[896:1024:4, 896:1024:4] + expected = self.j2k_quarter_data[224:352, 224:352] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_53_decode(self): + actual = self.j2k[500:800:4, 100:300:4] + expected = self.j2k_quarter_data[125:200, 25:75] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_54_decode(self): + actual = self.j2k[520:600:4, 260:360:4] + expected = self.j2k_quarter_data[130:150, 65:90] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_55_decode(self): + actual = self.j2k[520:660:4, 260:360:4] + expected = self.j2k_quarter_data[130:165, 65:90] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_04_j2k_56_decode(self): + actual = self.j2k[520:600:4, 360:400:4] + expected = self.j2k_quarter_data[130:150, 90:100] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p1_06_j2k_70_decode(self): + actual = self.j2k_p1_06[9:12:2, 9:12:2] + self.assertEqual(actual.shape, (1, 1, 3)) + + def test_NR_DEC_p1_06_j2k_71_decode(self): + actual = self.j2k_p1_06[10:12:2, 4:10:2] + self.assertEqual(actual.shape, (1, 3, 3)) + + def test_NR_DEC_p1_06_j2k_72_decode(self): + ssdata = self.j2k_p1_06[3:9:2, 3:9:2] + self.assertEqual(ssdata.shape, (3, 3, 3)) + + def test_NR_DEC_p1_06_j2k_73_decode(self): + ssdata = self.j2k_p1_06[4:7:2, 4:7:2] + self.assertEqual(ssdata.shape, (2, 2, 3)) + + def test_NR_DEC_p1_06_j2k_74_decode(self): + ssdata = self.j2k_p1_06[4:5:2, 4:5:2] + self.assertEqual(ssdata.shape, (1, 1, 3)) + + def test_NR_DEC_p1_06_j2k_75_decode(self): + # Image size would be 0 x 0. + with self.assertRaises((IOError, OSError)): + self.j2k_p1_06[9:12:4, 9:12:4] + + def test_NR_DEC_p0_04_j2k_85_decode(self): + actual = self.j2k[:256, :256] + expected = self.j2k_data[:256, :256] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_86_decode(self): + actual = self.j2k[:128, 128:256] + expected = self.j2k_data[:128, 128:256] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_87_decode(self): + actual = self.j2k[10:200, 50:120] + expected = self.j2k_data[10:200, 50:120] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_88_decode(self): + actual = self.j2k[150:210, 10:190] + expected = self.j2k_data[150:210, 10:190] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_89_decode(self): + actual = self.j2k[80:150, 100:200] + expected = self.j2k_data[80:150, 100:200] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_90_decode(self): + actual = self.j2k[20:50, 150:200] + expected = self.j2k_data[20:50, 150:200] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_91_decode(self): + actual = self.j2k[:256:4, :256:4] + expected = self.j2k_quarter_data[0:64, 0:64] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_92_decode(self): + actual = self.j2k[:128:4, 128:256:4] + expected = self.j2k_quarter_data[:32, 32:64] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_93_decode(self): + actual = self.j2k[10:200:4, 50:120:4] + expected = self.j2k_quarter_data[3:50, 13:30] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_94_decode(self): + actual = self.j2k[150:210:4, 10:190:4] + expected = self.j2k_quarter_data[38:53, 3:48] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_95_decode(self): + actual = self.j2k[80:150:4, 100:200:4] + expected = self.j2k_quarter_data[20:38, 25:50] + np.testing.assert_array_equal(actual, expected) + + def test_NR_DEC_p0_04_j2k_96_decode(self): + actual = self.j2k[20:50:4, 150:200:4] + expected = self.j2k_quarter_data[5:13, 38:50] + np.testing.assert_array_equal(actual, expected) diff --git a/glymur/test/test_opj_suite_2p1.py b/glymur/test/test_opj_suite_2p1.py deleted file mode 100644 index addc48b..0000000 --- a/glymur/test/test_opj_suite_2p1.py +++ /dev/null @@ -1,342 +0,0 @@ -""" -The tests defined here roughly correspond to what is in the OpenJPEG test -suite. -""" - -# Some test names correspond with openjpeg tests. Long names are ok in this -# case. -# pylint: disable=C0103 - -# All of these tests correspond to tests in openjpeg, so no docstring is really -# needed. -# pylint: disable=C0111 - -# This module is very long, cannot be helped. -# pylint: disable=C0302 - -# unittest fools pylint with "too many public methods" -# pylint: disable=R0904 - -# Some tests use numpy test infrastructure, which means the tests never -# reference "self", so pylint claims it should be a function. No, no, no. -# pylint: disable=R0201 - -# Many tests are pretty long and that can't be helped. -# pylint: disable=R0915 - -# asserWarns introduced in python 3.2 (python2.7/pylint issue) -# pylint: disable=E1101 - -import re -import sys -import unittest - -import numpy as np - -from glymur import Jp2k -import glymur - -from .fixtures import OPJ_DATA_ROOT -from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG -from .fixtures import mse, peak_tolerance, read_pgx, opj_data_file - - -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -@unittest.skipIf(re.match(r'''(1|2.0.0)''', - glymur.version.openjpeg_version) is not None, - "Only supported in 2.0.1 or higher") -class TestSuite2point1(unittest.TestCase): - """Runs tests introduced in version 2.0+ or that pass only in 2.0+""" - - def setUp(self): - pass - - def tearDown(self): - pass - - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_NR_DEC_text_GBR_jp2_29_decode(self): - jfile = opj_data_file('input/nonregression/text_GBR.jp2') - with self.assertWarns(UserWarning): - # brand is 'jp2 ', but has any icc profile. - jp2 = Jp2k(jfile) - jp2.read() - self.assertTrue(True) - - def test_NR_DEC_kodak_2layers_lrcp_j2c_31_decode(self): - jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_kodak_2layers_lrcp_j2c_32_decode(self): - jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c') - Jp2k(jfile).read(layer=2) - self.assertTrue(True) - - def test_NR_DEC_issue104_jpxstream_jp2_33_decode(self): - jfile = opj_data_file('input/nonregression/issue104_jpxstream.jp2') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_mem_b2b86b74_2753_jp2_35_decode(self): - jfile = opj_data_file('input/nonregression/mem-b2b86b74-2753.jp2') - Jp2k(jfile).read() - self.assertTrue(True) - - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_NR_DEC_gdal_fuzzer_unchecked_num_resolutions_jp2_36_decode(self): - f = 'input/nonregression/gdal_fuzzer_unchecked_numresolutions.jp2' - jfile = opj_data_file(f) - with self.assertWarns(UserWarning): - # Invalid number of resolutions. - j = Jp2k(jfile) - with self.assertRaises(IOError): - j.read() - - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_NR_DEC_gdal_fuzzer_check_number_of_tiles_jp2_38_decode(self): - relpath = 'input/nonregression/gdal_fuzzer_check_number_of_tiles.jp2' - jfile = opj_data_file(relpath) - with self.assertWarns(UserWarning): - # Invalid number of tiles. - j = Jp2k(jfile) - with self.assertRaises(IOError): - j.read() - - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_NR_DEC_gdal_fuzzer_check_comp_dx_dy_jp2_39_decode(self): - relpath = 'input/nonregression/gdal_fuzzer_check_comp_dx_dy.jp2' - jfile = opj_data_file(relpath) - with self.assertWarns(UserWarning): - # Invalid subsampling value - with self.assertRaises(IOError): - Jp2k(jfile).read() - - def test_NR_DEC_file_409752_jp2_40_decode(self): - jfile = opj_data_file('input/nonregression/file409752.jp2') - with self.assertRaises(RuntimeError): - Jp2k(jfile).read() - - def test_NR_DEC_issue206_image_000_jp2_42_decode(self): - jfile = opj_data_file('input/nonregression/issue206_image-000.jp2') - Jp2k(jfile).read() - self.assertTrue(True) - - def test_NR_DEC_p1_04_j2k_57_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - tdata = jp2k.read(tile=63) # last tile - odata = jp2k.read() - np.testing.assert_array_equal(tdata, odata[896:1024, 896:1024]) - - def test_NR_DEC_p1_04_j2k_58_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - tdata = jp2k.read(tile=63, rlevel=2) # last tile - odata = jp2k.read(rlevel=2) - np.testing.assert_array_equal(tdata, odata[224:256, 224:256]) - - def test_NR_DEC_p1_04_j2k_59_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - tdata = jp2k.read(tile=12) # 2nd row, 5th column - odata = jp2k.read() - np.testing.assert_array_equal(tdata, odata[128:256, 512:640]) - - def test_NR_DEC_p1_04_j2k_60_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - tdata = jp2k.read(tile=12, rlevel=1) # 2nd row, 5th column - odata = jp2k.read(rlevel=1) - np.testing.assert_array_equal(tdata, odata[64:128, 256:320]) - - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_NR_DEC_jp2_36_decode(self): - lst = ('input', - 'nonregression', - 'gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc.patch.jp2') - jfile = opj_data_file('/'.join(lst)) - with self.assertWarns(UserWarning): - # Invalid component number. - j = Jp2k(jfile) - with self.assertRaises(IOError): - j.read() - -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -@unittest.skipIf(re.match(r'''(1|2.0.0)''', - glymur.version.openjpeg_version) is not None, - "Only supported in 2.0.1 or higher") -class TestReadArea(unittest.TestCase): - """ - Runs tests introduced in version 2.0+ or that pass only in 2.0+ - - Specifically for read method with area parameter. - """ - @classmethod - def setUpClass(self): - - jfile = opj_data_file('input/conformance/p1_04.j2k') - self.j2k = Jp2k(jfile) - self.j2k_data = self.j2k.read() - self.j2k_half_data = self.j2k.read(rlevel=1) - self.j2k_quarter_data = self.j2k.read(rlevel=2) - - jfile = opj_data_file('input/conformance/p1_06.j2k') - self.j2k_p1_06 = Jp2k(jfile) - - def test_NR_DEC_p1_04_j2k_43_decode(self): - actual = self.j2k.read(area=(0, 0, 1024, 1024)) - expected = self.j2k_data - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_44_decode(self): - actual = self.j2k.read(area=(640, 512, 768, 640)) - expected = self.j2k_data[640:768, 512:640] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_45_decode(self): - actual = self.j2k.read(area=(896, 896, 1024, 1024)) - expected = self.j2k_data[896:1024, 896:1024] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_46_decode(self): - actual = self.j2k.read(area=(500, 100, 800, 300)) - expected = self.j2k_data[500:800, 100:300] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_47_decode(self): - actual = self.j2k.read(area=(520, 260, 600, 360)) - expected = self.j2k_data[520:600, 260:360] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_48_decode(self): - actual = self.j2k.read(area=(520, 260, 660, 360)) - expected = self.j2k_data[520:660, 260:360] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_49_decode(self): - actual = self.j2k.read(area=(520, 360, 600, 400)) - expected = self.j2k_data[520:600, 360:400] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_50_decode(self): - actual = self.j2k.read(area=(0, 0, 1024, 1024), rlevel=2) - expected = self.j2k_quarter_data - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_51_decode(self): - actual = self.j2k.read(area=(640, 512, 768, 640), rlevel=2) - expected = self.j2k_quarter_data[160:192, 128:160] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_52_decode(self): - actual = self.j2k.read(area=(896, 896, 1024, 1024), rlevel=2) - expected = self.j2k_quarter_data[224:352, 224:352] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_53_decode(self): - actual = self.j2k.read(area=(500, 100, 800, 300), rlevel=2) - expected = self.j2k_quarter_data[125:200, 25:75] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_54_decode(self): - actual = self.j2k.read(area=(520, 260, 600, 360), rlevel=2) - expected = self.j2k_quarter_data[130:150, 65:90] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_55_decode(self): - actual = self.j2k.read(area=(520, 260, 660, 360), rlevel=2) - expected = self.j2k_quarter_data[130:165, 65:90] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_56_decode(self): - actual = self.j2k.read(area=(520, 360, 600, 400), rlevel=2) - expected = self.j2k_quarter_data[130:150, 90:100] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_06_j2k_70_decode(self): - actual = self.j2k_p1_06.read(area=(9, 9, 12, 12), rlevel=1) - self.assertEqual(actual.shape, (1, 1, 3)) - - def test_NR_DEC_p1_06_j2k_71_decode(self): - actual = self.j2k_p1_06.read(area=(10, 4, 12, 10), rlevel=1) - self.assertEqual(actual.shape, (1, 3, 3)) - - def test_NR_DEC_p1_06_j2k_72_decode(self): - ssdata = self.j2k_p1_06.read(area=(3, 3, 9, 9), rlevel=1) - self.assertEqual(ssdata.shape, (3, 3, 3)) - - def test_NR_DEC_p1_06_j2k_73_decode(self): - ssdata = self.j2k_p1_06.read(area=(4, 4, 7, 7), rlevel=1) - self.assertEqual(ssdata.shape, (2, 2, 3)) - - def test_NR_DEC_p1_06_j2k_74_decode(self): - ssdata = self.j2k_p1_06.read(area=(4, 4, 5, 5), rlevel=1) - self.assertEqual(ssdata.shape, (1, 1, 3)) - - def test_NR_DEC_p1_06_j2k_75_decode(self): - # Image size would be 0 x 0. - with self.assertRaises((IOError, OSError)): - self.j2k_p1_06.read(area=(9, 9, 12, 12), rlevel=2) - - def test_NR_DEC_p0_04_j2k_85_decode(self): - actual = self.j2k.read(area=(0, 0, 256, 256)) - expected = self.j2k_data[:256, :256] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_86_decode(self): - actual = self.j2k.read(area=(0, 128, 128, 256)) - expected = self.j2k_data[:128, 128:256] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_87_decode(self): - actual = self.j2k.read(area=(10, 50, 200, 120)) - expected = self.j2k_data[10:200, 50:120] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_88_decode(self): - actual = self.j2k.read(area=(150, 10, 210, 190)) - expected = self.j2k_data[150:210, 10:190] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_89_decode(self): - actual = self.j2k.read(area=(80, 100, 150, 200)) - expected = self.j2k_data[80:150, 100:200] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_90_decode(self): - actual = self.j2k.read(area=(20, 150, 50, 200)) - expected = self.j2k_data[20:50, 150:200] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_91_decode(self): - actual = self.j2k.read(area=(0, 0, 256, 256), rlevel=2) - expected = self.j2k_quarter_data[0:64, 0:64] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_92_decode(self): - actual = self.j2k.read(area=(0, 128, 128, 256), rlevel=2) - expected = self.j2k_quarter_data[:32, 32:64] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_93_decode(self): - actual = self.j2k.read(area=(10, 50, 200, 120), rlevel=2) - expected = self.j2k_quarter_data[3:50, 13:30] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_94_decode(self): - actual = self.j2k.read(area=(150, 10, 210, 190), rlevel=2) - expected = self.j2k_quarter_data[38:53, 3:48] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_95_decode(self): - actual = self.j2k.read(area=(80, 100, 150, 200), rlevel=2) - expected = self.j2k_quarter_data[20:38, 25:50] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_96_decode(self): - actual = self.j2k.read(area=(20, 150, 50, 200), rlevel=2) - expected = self.j2k_quarter_data[5:13, 38:50] - np.testing.assert_array_equal(actual, expected) diff --git a/glymur/test/test_opj_suite_dump.py b/glymur/test/test_opj_suite_dump.py index 8201c9f..ee1838b 100644 --- a/glymur/test/test_opj_suite_dump.py +++ b/glymur/test/test_opj_suite_dump.py @@ -2847,8 +2847,7 @@ class TestSuiteWarns(MetadataBase): relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2' jfile = opj_data_file(relpath) with self.assertWarns(UserWarning): - j = Jp2k(jfile) - d = j.read() + d = Jp2k(jfile)[:] self.assertTrue(True) def test_NR_broken4_jp2_dump(self): diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index 058be42..c7e31e5 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -24,6 +24,7 @@ from .fixtures import OPJ_DATA_ROOT, opj_data_file, read_image from .fixtures import NO_READ_BACKEND, NO_READ_BACKEND_MSG from .fixtures import NO_SKIMAGE_FREEIMAGE_SUPPORT from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG +from . import fixtures from glymur import Jp2k import glymur @@ -31,7 +32,7 @@ import glymur @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_OPJ_DATA_ROOT environment variable not set") -class TestSuiteNegative(unittest.TestCase): +class TestSuiteNegativeRead(unittest.TestCase): """Test suite for certain negative tests from openjpeg suite.""" def setUp(self): @@ -41,33 +42,6 @@ class TestSuiteNegative(unittest.TestCase): def tearDown(self): pass - - @unittest.skipIf(NO_SKIMAGE_FREEIMAGE_SUPPORT, - "Cannot read input image without scikit-image/freeimage") - @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") - def test_cinema2K_bad_frame_rate(self): - """Cinema2k frame rate must be either 24 or 48.""" - relfile = 'input/nonregression/X_5_2K_24_235_CBR_STEM24_000.tif' - infile = opj_data_file(relfile) - data = skimage.io.imread(infile) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - with self.assertRaises(IOError): - j.write(data, cinema2k=36) - - - @unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG) - @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") - def test_psnr_with_cratios(self): - """Using psnr with cratios options is not allowed.""" - # Not an OpenJPEG test, but close. - infile = opj_data_file('input/nonregression/Bretagne1.ppm') - data = read_image(infile) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - with self.assertRaises(IOError): - j.write(data, psnr=[30, 35, 40], cratios=[2, 3, 4]) - def test_nr_marker_not_compliant(self): """non-compliant marker, should still be able to read""" relpath = 'input/nonregression/MarkerIsNotCompliant.j2k' @@ -98,56 +72,84 @@ class TestSuiteNegative(unittest.TestCase): jp2k.get_codestream(header_only=False) self.assertTrue(True) - @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") + +@unittest.skipIf(re.match("1.5|2", glymur.version.openjpeg_version) is None, + "Must have openjpeg 1.5 or higher to run") +@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_OPJ_DATA_ROOT environment variable not set") +class TestSuiteNegativeWrite(unittest.TestCase): + """Test suite for certain negative tests from openjpeg suite.""" + + def setUp(self): + self.jp2file = glymur.data.nemo() + self.j2kfile = glymur.data.goodstuff() + + def tearDown(self): + pass + + + @unittest.skipIf(NO_SKIMAGE_FREEIMAGE_SUPPORT, + "Cannot read input image without scikit-image/freeimage") + def test_cinema2K_bad_frame_rate(self): + """Cinema2k frame rate must be either 24 or 48.""" + relfile = 'input/nonregression/X_5_2K_24_235_CBR_STEM24_000.tif' + infile = opj_data_file(relfile) + data = skimage.io.imread(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + with self.assertRaises(IOError): + j = Jp2k(tfile.name, data=data, cinema2k=36) + + + @unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG) + def test_psnr_with_cratios(self): + """Using psnr with cratios options is not allowed.""" + # Not an OpenJPEG test, but close. + infile = opj_data_file('input/nonregression/Bretagne1.ppm') + data = read_image(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + with self.assertRaises(IOError): + j = Jp2k(tfile.name, + data=data, psnr=[30, 35, 40], cratios=[2, 3, 4]) + def test_code_block_dimensions(self): """don't allow extreme codeblock sizes""" # opj_compress doesn't allow the dimensions of a codeblock # to be too small or too big, so neither will we. data = np.zeros((256, 256), dtype=np.uint8) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - # opj_compress doesn't allow code block area to exceed 4096. with self.assertRaises(IOError): - j.write(data, cbsize=(256, 256)) + j = Jp2k(tfile.name, data=data, cbsize=(256, 256)) # opj_compress doesn't allow either dimension to be less than 4. with self.assertRaises(IOError): - j.write(data, cbsize=(2048, 2)) + j = Jp2k(tfile.name, data=data, cbsize=(2048, 2)) with self.assertRaises(IOError): - j.write(data, cbsize=(2, 2048)) + j = Jp2k(tfile.name, data=data, cbsize=(2, 2048)) - @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_precinct_size_not_p2(self): """precinct sizes should be powers of two.""" ifile = Jp2k(self.j2kfile) - data = ifile.read(rlevel=2) + data = ifile[::4, ::4] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - ofile = Jp2k(tfile.name, 'wb') with self.assertRaises(IOError): - ofile.write(data, psizes=[(13, 13)]) + ofile = Jp2k(tfile.name, data=data, psizes=[(13, 13)]) - @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_cblk_size_not_power_of_two(self): """code block sizes should be powers of two.""" ifile = Jp2k(self.j2kfile) - data = ifile.read(rlevel=2) + data = ifile[::4, ::4] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - ofile = Jp2k(tfile.name, 'wb') with self.assertRaises(IOError): - ofile.write(data, cbsize=(13, 12)) + ofile = Jp2k(tfile.name, data=data, cbsize=(13, 12)) - @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_cblk_size_precinct_size(self): """code block sizes should never exceed half that of precinct size.""" ifile = Jp2k(self.j2kfile) - data = ifile.read(rlevel=2) + data = ifile[::4, ::4] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - ofile = Jp2k(tfile.name, 'wb') with self.assertRaises(IOError): - ofile.write(data, - cbsize=(64, 64), - psizes=[(64, 64)]) + ofile = Jp2k(tfile.name, + data=data, cbsize=(64, 64), psizes=[(64, 64)]) -if __name__ == "__main__": - unittest.main() diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 8be1cef..ecb7a7d 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -87,9 +87,9 @@ class WriteCinema(CinemaBase): infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') with self.assertRaises(IOError): - j.write(data, cinema2k=48, cratios=[200, 100, 50]) + j = Jp2k(tfile.name, data=data, + cinema2k=48, cratios=[200, 100, 50]) def test_cinema4K_with_others(self): """Can't specify cinema4k with any other options.""" @@ -97,9 +97,9 @@ class WriteCinema(CinemaBase): infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') with self.assertRaises(IOError): - j.write(data, cinema4k=True, cratios=[200, 100, 50]) + j = Jp2k(tfile.name, data=data, + cinema4k=True, cratios=[200, 100, 50]) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @@ -123,10 +123,9 @@ class WriteCinemaWarns(CinemaBase): infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') regex = 'OpenJPEG library warning:.*' with self.assertWarnsRegex(UserWarning, re.compile(regex)): - j.write(data, cinema4k=True) + j = Jp2k(tfile.name, data=data, cinema4k=True) codestream = j.get_codestream() self.check_cinema4k_codestream(codestream, (4096, 2160)) @@ -136,9 +135,8 @@ class WriteCinemaWarns(CinemaBase): infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') with self.assertWarnsRegex(UserWarning, 'OpenJPEG library warning'): - j.write(data, cinema2k=48) + j = Jp2k(tfile.name, data=data, cinema2k=48) codestream = j.get_codestream() self.check_cinema2k_codestream(codestream, (2048, 857)) @@ -148,9 +146,8 @@ class WriteCinemaWarns(CinemaBase): infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') with self.assertWarnsRegex(UserWarning, 'OpenJPEG library warning'): - j.write(data, cinema2k=48) + j = Jp2k(tfile.name, data=data, cinema2k=48) codestream = j.get_codestream() self.check_cinema2k_codestream(codestream, (2048, 1080)) @@ -160,9 +157,8 @@ class WriteCinemaWarns(CinemaBase): infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') with self.assertWarnsRegex(UserWarning, 'OpenJPEG library warning'): - j.write(data, cinema2k=24) + j = Jp2k(tfile.name, data=data, cinema2k=24) codestream = j.get_codestream() self.check_cinema2k_codestream(codestream, (2048, 1080)) @@ -172,11 +168,10 @@ class WriteCinemaWarns(CinemaBase): infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') with self.assertWarnsRegex(UserWarning, 'OpenJPEG library warning'): # OpenJPEG library warning: The desired maximum codestream # size has limited at least one of the desired quality layers - j.write(data, cinema2k=24) + j = Jp2k(tfile.name, data=data, cinema2k=24) codestream = j.get_codestream() self.check_cinema2k_codestream(codestream, (2048, 857)) @@ -186,12 +181,11 @@ class WriteCinemaWarns(CinemaBase): infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') regex = 'OpenJPEG library warning' with self.assertWarnsRegex(UserWarning, regex): # OpenJPEG library warning: The desired maximum codestream # size has limited at least one of the desired quality layers - j.write(data, cinema2k=48) + j = Jp2k(tfile.name, data=data, cinema2k=48) codestream = j.get_codestream() self.check_cinema2k_codestream(codestream, (1998, 1080)) @@ -221,9 +215,8 @@ class TestNegative2pointzero(unittest.TestCase): for version in versions: with patch('glymur.version.openjpeg_version', new=version): with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') with self.assertRaises(IOError): - j.write(data, cinema2k=48) + j = Jp2k(tfile.name, data=data, cinema2k=48) @unittest.skipIf(re.match(r'''1.[0-4]''', openjpeg_version) is not None, @@ -249,8 +242,7 @@ class TestSuiteWrite(fixtures.MetadataBase): expdata = np.fromfile(filename, dtype=np.uint16) expdata.resize((2816, 2048)) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - j.write(expdata, irreversible=True) + j = Jp2k(tfile.name, data=expdata, irreversible=True) codestream = j.get_codestream() self.assertEqual(codestream.segment[2].spcod[8], @@ -262,8 +254,7 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/Bretagne1.ppm') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - j.write(data, cratios=[200, 100, 50]) + j = Jp2k(tfile.name, data=data, cratios=[200, 100, 50]) # Should be three layers. c = j.get_codestream() @@ -294,8 +285,7 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/Bretagne1.ppm') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - j.write(data, psnr=[30, 35, 40], numres=2) + j = Jp2k(tfile.name, data=data, psnr=[30, 35, 40], numres=2) # Should be three layers. codestream = j.get_codestream() @@ -326,9 +316,9 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/Bretagne1.ppm') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - j.write(data, psnr=[30, 35, 40], cbsize=(16, 16), - psizes=[(64, 64)]) + j = Jp2k(tfile.name, + data=data, + psnr=[30, 35, 40], cbsize=(16, 16), psizes=[(64, 64)]) # Should be three layers. codestream = j.get_codestream() @@ -361,8 +351,8 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/Bretagne2.ppm') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - j.write(data, + j = Jp2k(tfile.name, + data=data, psizes=[(128, 128)] * 3, cratios=[100, 20, 2], tilesize=(480, 640), @@ -398,8 +388,7 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/Bretagne2.ppm') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - j.write(data, tilesize=(127, 127), prog="PCRL") + j = Jp2k(tfile.name, data=data, tilesize=(127, 127), prog="PCRL") codestream = j.get_codestream() @@ -429,8 +418,7 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/Bretagne2.ppm') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - j.write(data, subsam=(2, 2), sop=True) + j = Jp2k(tfile.name, data=data, subsam=(2, 2), sop=True) codestream = j.get_codestream(header_only=False) @@ -465,8 +453,7 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/Bretagne2.ppm') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - j.write(data, modesw=38, eph=True) + j = Jp2k(tfile.name, data=data, modesw=38, eph=True) codestream = j.get_codestream(header_only=False) @@ -500,8 +487,8 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/Bretagne2.ppm') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - j.write(data, grid_offset=[300, 150], cratios=[800]) + j = Jp2k(tfile.name, + data=data, grid_offset=[300, 150], cratios=[800]) codestream = j.get_codestream(header_only=False) @@ -531,8 +518,7 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/Cevennes1.bmp') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - j.write(data, cratios=[800]) + j = Jp2k(tfile.name, data=data, cratios=[800]) codestream = j.get_codestream(header_only=False) @@ -562,8 +548,7 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/Cevennes2.ppm') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - j.write(data, cratios=[50]) + j = Jp2k(tfile.name, data=data, cratios=[50]) codestream = j.get_codestream(header_only=False) @@ -592,8 +577,8 @@ class TestSuiteWrite(fixtures.MetadataBase): """NR-ENC-Rome.bmp-11-encode""" data = read_image(opj_data_file('input/nonregression/Rome.bmp')) with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - jp2 = Jp2k(tfile.name, 'wb') - jp2.write(data, psnr=[30, 35, 50], prog='LRCP', numres=3) + jp2 = Jp2k(tfile.name, + data=data, psnr=[30, 35, 50], prog='LRCP', numres=3) ids = [box.box_id for box in jp2.box] self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) @@ -658,8 +643,7 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/random-issue-0005.tif') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, 'wb') - j.write(data) + j = Jp2k(tfile.name, data=data) codestream = j.get_codestream(header_only=False) diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 7a5f9b9..5eedfe5 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -107,14 +107,15 @@ class TestPrinting(unittest.TestCase): with self.assertRaises(TypeError): glymur.set_printoptions(hi='low') + @unittest.skipIf(re.match("1.5|2", glymur.version.openjpeg_version) is None, + "Must have openjpeg 1.5 or higher to run") def test_asoc_label_box(self): """verify printing of asoc, label boxes""" # Construct a fake file with an asoc and a label box, as # OpenJPEG doesn't have such a file. - data = glymur.Jp2k(self.jp2file).read(rlevel=1) + data = glymur.Jp2k(self.jp2file)[::2, ::2] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = glymur.Jp2k(tfile.name, 'wb') - j.write(data) + j = glymur.Jp2k(tfile.name, data=data) with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2: From 6a59e38aede9a3246d00c206d6d8d2ad6fafba93 Mon Sep 17 00:00:00 2001 From: John Evans Date: Fri, 21 Nov 2014 17:56:52 -0500 Subject: [PATCH 300/326] reading metadata still works if library not installed, closes #304 --- glymur/jp2k.py | 2 +- glymur/lib/config.py | 2 +- glymur/lib/test/test_printing.py | 2 +- glymur/test/fixtures.py | 8 ++++++++ glymur/test/test_callbacks.py | 2 ++ glymur/test/test_jp2box.py | 2 +- glymur/test/test_jp2k.py | 15 ++++++++++++++- glymur/test/test_opj_suite.py | 12 +++++++----- 8 files changed, 35 insertions(+), 10 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 74834a9..097bb07 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -484,7 +484,7 @@ class Jp2k(Jp2kBox): This method can only be used to create JPEG 2000 images that can fit in memory. """ - if re.match("1.[0-4]", version.openjpeg_version) is not None: + if re.match("0|1.[0-4]", version.openjpeg_version) is not None: raise RuntimeError("You must have at least version 1.5 of OpenJPEG " "in order to write images.") diff --git a/glymur/lib/config.py b/glymur/lib/config.py index 4b92b9d..3aca5aa 100644 --- a/glymur/lib/config.py +++ b/glymur/lib/config.py @@ -144,7 +144,7 @@ def glymur_config(): lst.append(load_openjpeg_library(libname)) if all(handle is None for handle in lst): msg = "Neither the openjp2 nor the openjpeg library could be loaded. " - raise IOError(msg) + warnings.warn(msg) return tuple(lst) def get_configdir(): diff --git a/glymur/lib/test/test_printing.py b/glymur/lib/test/test_printing.py index 3d0e2b6..fb78676 100644 --- a/glymur/lib/test/test_printing.py +++ b/glymur/lib/test/test_printing.py @@ -17,7 +17,7 @@ import glymur from . import fixtures @unittest.skipIf(sys.hexversion < 0x03000000, "do not care about 2.7 here") -@unittest.skipIf(re.match('1|2.0', glymur.version.openjpeg_version), +@unittest.skipIf(re.match('0|1|2.0', glymur.version.openjpeg_version), "Requires openjpeg 2.1.0 or higher") class TestPrintingOpenjp2(unittest.TestCase): """Tests for verifying how printing works on openjp2 library structures.""" diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 481da2d..113a47b 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -13,6 +13,14 @@ import six import glymur +# If openjpeg is not installed, many tests cannot be run. +if glymur.version.openjpeg_version == '0.0.0': + OPENJPEG_NOT_AVAILABLE = True + OPENJPEG_NOT_AVAILABLE_MSG = 'OpenJPEG library not installed' +else: + OPENJPEG_NOT_AVAILABLE = False + OPENJPEG_NOT_AVAILABLE_MSG = None + # Some versions of "six" on python3 cause problems when verifying warnings. # Only use when the version is 1.7 or higher. # And moreover, we only test using the 3.x infrastructure, never on 2.x. diff --git a/glymur/test/test_callbacks.py b/glymur/test/test_callbacks.py index c29f816..849d987 100644 --- a/glymur/test/test_callbacks.py +++ b/glymur/test/test_callbacks.py @@ -65,6 +65,8 @@ class TestCallbacks(unittest.TestCase): expected = '[INFO] tile number 1 / 1' self.assertEqual(actual, expected) + @unittest.skipIf(glymur.version.openjpeg_version[0] == '0', + "Missing openjpeg/openjp2 library.") def test_info_callbacks_on_read(self): """stdio output when info callback handler is enabled""" diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 9d1f9cf..28ae4fe 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -114,7 +114,7 @@ class TestDataEntryURL(unittest.TestCase): self.assertEqual(url + chr(0), read_url) -@unittest.skipIf(re.match(r'''(1|2.0.0)''', +@unittest.skipIf(re.match(r'''0|1|2.0.0''', glymur.version.openjpeg_version) is not None, "Not supported until 2.1") @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index b903dda..edb19a1 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -36,6 +36,7 @@ from glymur.version import openjpeg_version from .fixtures import HAS_PYTHON_XMP_TOOLKIT from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG +from .fixtures import OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG if HAS_PYTHON_XMP_TOOLKIT: import libxmp @@ -76,6 +77,7 @@ class SliceProtocolBase(unittest.TestCase): self.j2k_data_r1 = self.j2k[::2, ::2] self.j2k_data_r5 = self.j2k[::32, ::32] +@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(re.match("1.5|2", glymur.version.openjpeg_version) is None, "Must have openjpeg 1.5 or higher to run") @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) @@ -143,6 +145,7 @@ class TestSliceProtocolBaseWrite(SliceProtocolBase): j[:25, :45, :] = self.j2k_data[:25, :25, :] +@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) class TestSliceProtocolRead(SliceProtocolBase): def test_resolution_strides_cannot_differ(self): @@ -251,6 +254,7 @@ class TestJp2k(unittest.TestCase): def tearDown(self): pass + @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_warn_if_using_read_method(self): """Should warn if deprecated read method is called""" @@ -313,6 +317,7 @@ class TestJp2k(unittest.TestCase): actdata = j2[:] self.assertTrue(fixtures.mse(actdata[0], expdata[0]) < 0.38) + @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(re.match('1.[0-4]', openjpeg_version) is not None, "Not supported with OpenJPEG {0}".format(openjpeg_version)) @unittest.skipIf(re.match('1.5.(1|2)', openjpeg_version) is not None, @@ -345,6 +350,7 @@ class TestJp2k(unittest.TestCase): self.assertEqual(newjp2.filename, self.j2kfile) self.assertEqual(len(newjp2.box), 0) + @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_rlevel_max_backwards_compatibility(self): """ @@ -367,6 +373,7 @@ class TestJp2k(unittest.TestCase): np.testing.assert_array_equal(thumbnail1, thumbnail2) self.assertEqual(thumbnail1.shape, (25, 15, 3)) + @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) def test_rlevel_too_high(self): """Should error out appropriately if reduce level too high""" j = Jp2k(self.jp2file) @@ -518,12 +525,14 @@ class TestJp2k(unittest.TestCase): self.assertEqual(new_jp2.box[j].length, baseline_jp2.box[j].length) + @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) def test_basic_jp2(self): """Just a very basic test that reading a JP2 file does not error out. """ j2k = Jp2k(self.jp2file) j2k[::2, ::2] + @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) def test_basic_j2k(self): """This test is only useful when openjp2 is not available and OPJ_DATA_ROOT is not set. We need at least one @@ -648,6 +657,7 @@ class TestJp2k(unittest.TestCase): creator_tool = xmp.get_property(libxmp.consts.XMP_NS_XMP, 'CreatorTool') self.assertEqual(creator_tool, 'Google') + @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(re.match(r'''(1|2.0.0)''', glymur.version.openjpeg_version) is not None, "Not supported until 2.0.1") @@ -664,6 +674,7 @@ class TestJp2k(unittest.TestCase): with self.assertRaises(RuntimeError): glymur.Jp2k(self.jp2file).read_bands() +@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(re.match('1.[0-4]', openjpeg_version) is not None, "Not supported with OpenJPEG {0}".format(openjpeg_version)) @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) @@ -869,6 +880,7 @@ class TestJp2k_1_x(unittest.TestCase): @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) +@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) class Test_2p0_official(unittest.TestCase): """Tests specific to v2.0.0""" @@ -962,6 +974,7 @@ class TestJp2k_2_0(unittest.TestCase): self.assertEqual(jasoc.box[3].box[1].box_id, 'xml ') +@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(re.match(r'''(1|2.0.0)''', glymur.version.openjpeg_version) is not None, "Not to be run until unless 2.0.1 or higher is present") @@ -1135,7 +1148,7 @@ class TestJp2kOpjDataRoot(unittest.TestCase): actdata = j[:] self.assertTrue(fixtures.mse(actdata, expdata) < 250) - + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_no_cxform_pclr_jp2(self): """Indices for pclr jpxfile if no color transform""" diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index cf44eaa..028734d 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -41,10 +41,12 @@ from glymur.jp2box import FileTypeBox, ImageHeaderBox, ColourSpecificationBox from .fixtures import ( OPJ_DATA_ROOT, MetadataBase, WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG, - mse, peak_tolerance, read_pgx, opj_data_file + mse, peak_tolerance, read_pgx, opj_data_file, + OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG ) +@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") class TestSuite(unittest.TestCase): @@ -482,7 +484,7 @@ class TestSuiteWarns(MetadataBase): @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") -@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1, +@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] != 2, "Feature not supported in glymur until openjpeg 2.0") class TestSuiteBands(unittest.TestCase): """ @@ -577,7 +579,7 @@ class TestSuiteBands(unittest.TestCase): @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") -@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1, +@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2, "Tests not passing until 2.0") class TestSuite2point0(unittest.TestCase): """Runs tests introduced in version 2.0 or that pass only in 2.0""" @@ -641,7 +643,7 @@ class TestSuite2point0(unittest.TestCase): @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") -@unittest.skipIf(re.match(r'''(1|2.0.0)''', +@unittest.skipIf(re.match(r'''0|1|2.0.0''', glymur.version.openjpeg_version) is not None, "Only supported in 2.0.1 or higher") class TestSuite2point1(unittest.TestCase): @@ -798,7 +800,7 @@ class TestSuite2point1(unittest.TestCase): @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") -@unittest.skipIf(re.match(r'''(1|2.0.0)''', +@unittest.skipIf(re.match(r'''0|1|2.0.0''', glymur.version.openjpeg_version) is not None, "Only supported in 2.0.1 or higher") class TestReadArea(unittest.TestCase): From c39661b586626d9a819fbd7e25f6d89bd53eb48a Mon Sep 17 00:00:00 2001 From: jevans Date: Fri, 21 Nov 2014 21:53:39 -0500 Subject: [PATCH 301/326] pep8 cleanup --- glymur/_uuid_io.py | 9 +-- glymur/codestream.py | 13 ++- glymur/command_line.py | 28 ++++--- glymur/core.py | 24 +++--- glymur/jp2box.py | 119 ++++++++++++++-------------- glymur/jp2k.py | 152 +++++++++++++++++++---------------- glymur/lib/openjp2.py | 14 +--- glymur/lib/openjpeg.py | 176 +++++++++++++++++++---------------------- 8 files changed, 267 insertions(+), 268 deletions(-) diff --git a/glymur/_uuid_io.py b/glymur/_uuid_io.py index bf9960a..7bcf2cb 100644 --- a/glymur/_uuid_io.py +++ b/glymur/_uuid_io.py @@ -11,6 +11,7 @@ import warnings import lxml.etree as ET + def xml(raw_data): """ XMP data to be parsed as XML. @@ -23,6 +24,7 @@ def xml(raw_data): return ET.ElementTree(elt) + def tiff_header(read_buffer): """ Interpret the uuid raw data as a tiff header. @@ -37,8 +39,8 @@ def tiff_header(read_buffer): # big endian endian = '>' else: - msg = "The byte order indication in the TIFF header ({0}) is invalid. " - msg += "It should be either {1} or {2}." + msg = "The byte order indication in the TIFF header ({0}) is " + msg += "invalid. It should be either {1} or {2}." msg = msg.format(read_buffer[6:8], bytes([73, 73]), bytes([77, 77])) raise IOError(msg) @@ -503,6 +505,3 @@ class _ExifInteroperabilityIfd(_Ifd): def __init__(self, endian, read_buffer, offset): _Ifd.__init__(self, endian, read_buffer, offset) self.post_process(self.tagnum2name) - - - diff --git a/glymur/codestream.py b/glymur/codestream.py index c8ab00f..aa12bd7 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -24,11 +24,10 @@ import warnings import numpy as np -from .core import ( - LRCP, RLCP, RPCL, PCRL, CPRL, - WAVELET_XFORM_9X7_IRREVERSIBLE, WAVELET_XFORM_5X3_REVERSIBLE, - _Keydefaultdict -) +from .core import (LRCP, RLCP, RPCL, PCRL, CPRL, + WAVELET_XFORM_9X7_IRREVERSIBLE, + WAVELET_XFORM_5X3_REVERSIBLE, + _Keydefaultdict) from .lib import openjp2 as opj2 _factory = lambda x: '{0} (invalid)'.format(x) @@ -57,7 +56,7 @@ _CAPABILITIES_DISPLAY = _Keydefaultdict(_factory, _PROFILE_0: '0', _PROFILE_1: '1', _PROFILE_3: 'Cinema 2K', - _PROFILE_4: 'Cinema 4K'} ) + _PROFILE_4: 'Cinema 4K'}) # Need a catch-all list of valid markers. # See table A-1 in ISO/IEC FCD15444-1. @@ -703,7 +702,6 @@ class Codestream(object): msg = "Invalid number of tiles ({0}).".format(numtiles) warnings.warn(msg) - kwargs = {'rsiz': rsiz, 'xysiz': xysiz, 'xyosiz': xyosiz, @@ -1614,6 +1612,7 @@ class SOCsegment(Segment): msg = "glymur.codestream.SOCsegment()" return msg + class SODsegment(Segment): """Container for Start of Data (SOD) segment information. diff --git a/glymur/command_line.py b/glymur/command_line.py index bf96293..5f0d357 100644 --- a/glymur/command_line.py +++ b/glymur/command_line.py @@ -8,28 +8,29 @@ import warnings from . import Jp2k, set_printoptions, lib + def main(): """ Entry point for console script jp2dump. """ - description='Print JPEG2000 metadata.' + description = 'Print JPEG2000 metadata.' parser = argparse.ArgumentParser(description=description) parser.add_argument('-x', '--noxml', - help='Suppress XML.', - action='store_true') + help='Suppress XML.', + action='store_true') parser.add_argument('-s', '--short', - help='Only print box id, offset, and length.', - action='store_true') + help='Only print box id, offset, and length.', + action='store_true') chelp = 'Level of codestream information. 0 suppressed all details, ' chelp += '1 prints headers, 2 prints the full codestream' parser.add_argument('-c', '--codestream', - help=chelp, - nargs=1, - type=int, - default=[0]) + help=chelp, + nargs=1, + type=int, + default=[0]) parser.add_argument('filename') @@ -38,7 +39,7 @@ def main(): set_printoptions(xml=False) if args.short: set_printoptions(short=True) - + codestream_level = args.codestream[0] if codestream_level not in [0, 1, 2]: raise ValueError("Invalid level of codestream information specified.") @@ -50,15 +51,16 @@ def main(): print_full_codestream = False else: print_full_codestream = True - + filename = args.filename - + with warnings.catch_warnings(record=True) as wctx: # JP2 metadata can be extensive, so don't print any warnings until we # are done with the metadata. jp2 = Jp2k(filename) - if jp2._codec_format == lib.openjp2.CODEC_J2K and codestream_level == 0: + if (((jp2._codec_format == lib.openjp2.CODEC_J2K) and + (codestream_level == 0))): print('File: {0}'.format(os.path.basename(filename))) elif print_full_codestream: print(jp2.get_codestream(header_only=False)) diff --git a/glymur/core.py b/glymur/core.py index 4d9a3af..3327253 100644 --- a/glymur/core.py +++ b/glymur/core.py @@ -4,6 +4,7 @@ import collections import copy import lxml.etree as ET + class _Keydefaultdict(collections.defaultdict): """Unlisted keys help form their own error message. @@ -121,12 +122,12 @@ ROMM_RGB = 21 _factory = lambda x: '{0} (unrecognized)'.format(x) _COLORSPACE_MAP_DISPLAY = _Keydefaultdict(_factory, - { CMYK: 'CMYK', - SRGB: 'sRGB', - GREYSCALE: 'greyscale', - YCC: 'YCC', - E_SRGB: 'e-sRGB', - ROMM_RGB: 'ROMM-RGB'} ) + {CMYK: 'CMYK', + SRGB: 'sRGB', + GREYSCALE: 'greyscale', + YCC: 'YCC', + E_SRGB: 'e-sRGB', + ROMM_RGB: 'ROMM-RGB'}) # enumerated color channel types COLOR = 0 @@ -134,11 +135,11 @@ OPACITY = 1 PRE_MULTIPLIED_OPACITY = 2 _UNSPECIFIED = 65535 _factory = lambda x: '{0} (invalid)'.format(x) -_COLOR_TYPE_MAP_DISPLAY = _Keydefaultdict(_factory, - { COLOR: 'color', - OPACITY: 'opacity', - PRE_MULTIPLIED_OPACITY: 'pre-multiplied opacity', - _UNSPECIFIED: 'unspecified'}) +_dict = {COLOR: 'color', + OPACITY: 'opacity', + PRE_MULTIPLIED_OPACITY: 'pre-multiplied opacity', + _UNSPECIFIED: 'unspecified'} +_COLOR_TYPE_MAP_DISPLAY = _Keydefaultdict(_factory, _dict) # color channel definitions. RED = 1 @@ -153,4 +154,3 @@ _COLORSPACE = {SRGB: {"R": 1, "G": 2, "B": 3}, YCC: {"Y": 1, "Cb": 2, "Cr": 3}, E_SRGB: {"R": 1, "G": 2, "B": 3}, ROMM_RGB: {"R": 1, "G": 2, "B": 3}} - diff --git a/glymur/jp2box.py b/glymur/jp2box.py index a2148ae..39e1f6a 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -29,13 +29,11 @@ import lxml.etree as ET import numpy as np from .codestream import Codestream -from .core import ( - _COLORSPACE_MAP_DISPLAY, _COLOR_TYPE_MAP_DISPLAY, - SRGB, GREYSCALE, YCC, - ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE, - ANY_ICC_PROFILE, VENDOR_COLOR_METHOD, - _Keydefaultdict -) +from .core import (_COLORSPACE_MAP_DISPLAY, _COLOR_TYPE_MAP_DISPLAY, + SRGB, GREYSCALE, YCC, + ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE, + ANY_ICC_PROFILE, VENDOR_COLOR_METHOD, + _Keydefaultdict) from . import _uuid_io @@ -52,6 +50,7 @@ _APPROX_DISPLAY = _Keydefaultdict(_factory, 3: 'approximates correct colorspace definition, reasonable quality', 4: 'approximates correct colorspace definition, poor quality'}) + class Jp2kBox(object): """Superclass for JPEG 2000 boxes. @@ -109,7 +108,6 @@ class Jp2kBox(object): msg += '\n' + self._indent(boxstr) return msg - def _indent(self, textstr, indent_level=4): """ Indent a string. @@ -135,7 +133,6 @@ class Jp2kBox(object): lst = [(' ' * indent_level + x) for x in textstr.split('\n')] return '\n'.join(lst) - def _write_superbox(self, fptr, box_id): """Write a superbox. @@ -191,13 +188,14 @@ class Jp2kBox(object): try: box = parser(fptr, start, num_bytes) except ValueError as err: - msg = "Encountered an unrecoverable ValueError while parsing a {0} " - msg += "box at byte offset {1}. The original error message was " - msg += "\"{2}\"" + msg = "Encountered an unrecoverable ValueError while parsing a " + msg += "{0} box at byte offset {1}. The original error message " + msg += "was \"{2}\"" msg = msg.format(_BOX_WITH_ID[box_id].longname, start, str(err)) warnings.warn(msg, UserWarning) box = UnknownBox(box_id.decode('utf-8'), - length=num_bytes, offset=start, longname='Unknown') + length=num_bytes, + offset=start, longname='Unknown') return box @@ -299,6 +297,7 @@ class ColourSpecificationBox(Jp2kBox): """ longname = 'Colour Specification' box_id = 'colr' + def __init__(self, method=ENUMERATED_COLORSPACE, precedence=0, approximation=0, colorspace=None, icc_profile=None, length=0, offset=-1): @@ -337,16 +336,16 @@ class ColourSpecificationBox(Jp2kBox): if self.icc_profile is None: if self.colorspace not in [SRGB, GREYSCALE, YCC]: - msg = "Colorspace should correspond to one of SRGB, GREYSCALE, " - msg += "or YCC." + msg = "Colorspace should correspond to one of SRGB, " + msg += "GREYSCALE, or YCC." self._dispatch_validation_error(msg, writing=True) self._validate(writing=True) - def __repr__(self): msg = "glymur.jp2box.ColourSpecificationBox(" - msg += "method={0}, precedence={1}, approximation={2}, colorspace={3}, " + msg += "method={0}, precedence={1}, approximation={2}, " + msg += "colorspace={3}, " msg += "icc_profile={4})" msg = msg.format(self.method, self.precedence, @@ -357,7 +356,7 @@ class ColourSpecificationBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg msg += '\n Method: {0}'.format(_METHOD_DISPLAY[self.method]) @@ -619,10 +618,9 @@ class ChannelDefinitionBox(Jp2kBox): msg += " 65535 - unspecified" self._dispatch_validation_error(msg, writing=writing) - def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg for j in range(len(self.association)): @@ -842,7 +840,7 @@ class CompositingLayerHeaderBox(Jp2kBox): List of boxes contained in this superbox. """ box_id = 'jplh' - longname='Compositing Layer Header' + longname = 'Compositing Layer Header' def __init__(self, box=None, length=0, offset=-1): Jp2kBox.__init__(self) @@ -931,7 +929,7 @@ class ComponentMappingBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg for k in range(len(self.component_index)): @@ -1027,7 +1025,9 @@ class ContiguousCodestreamBox(Jp2kBox): if self._filename is not None: with open(self._filename, 'rb') as fptr: fptr.seek(self.main_header_offset) - main_header = Codestream(fptr, self._length, header_only=True) + main_header = Codestream(fptr, + self._length, + header_only=True) self._main_header = main_header return self._main_header @@ -1037,9 +1037,9 @@ class ContiguousCodestreamBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg - if _printoptions['codestream'] == False: + if _printoptions['codestream'] is False: return msg msg += '\n Main header:' @@ -1118,7 +1118,8 @@ class DataReferenceBox(Jp2kBox): """Verify that the box obeys the specifications for writing. """ if len(self.DR) == 0: - msg = "A data reference box cannot be empty when written to a file." + msg = "A data reference box cannot be empty when written to a " + msg += "file." self._dispatch_validation_error(msg, writing=True) self._validate(writing=True) @@ -1145,7 +1146,7 @@ class DataReferenceBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg for box in self.DR: @@ -1248,7 +1249,7 @@ class FileTypeBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg lst = [msg, @@ -1311,7 +1312,7 @@ class FileTypeBox(Jp2kBox): brand = brand.decode('utf-8') # Extract the compatibility list. Each entry has 4 bytes. - num_entries = int((length - 16)/ 4) + num_entries = int((length - 16) / 4) compatibility_list = [] for j in range(int(num_entries)): entry, = struct.unpack_from('>4s', read_buffer, 8 + j * 4) @@ -1374,7 +1375,7 @@ class FragmentListBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg for j in range(len(self.fragment_offset)): @@ -1458,7 +1459,10 @@ class FragmentTableBox(Jp2kBox): def __repr__(self): msg = "glymur.jp2box.FragmentTableBox(box={0})" - msg = msg.format(None) if (len(self.box) == 0) else msg.format(self.box) + if len(self.box) == 0: + msg = msg.format(None) + else: + msg = msg.format(self.box) return msg def __str__(self): @@ -1505,7 +1509,6 @@ class FragmentTableBox(Jp2kBox): self._write_superbox(fptr, b'ftbl') - class FreeBox(Jp2kBox): """Container for JPX free box information. @@ -1534,7 +1537,7 @@ class FreeBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg return msg @@ -1630,7 +1633,7 @@ class ImageHeaderBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg msg = "{0}" @@ -1861,7 +1864,7 @@ class JPEG2000SignatureBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg msg += '\n Signature: {0:02x}{1:02x}{2:02x}{3:02x}' @@ -1950,7 +1953,7 @@ class PaletteBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg msg += '\n Size: ({0} x {1})'.format(*self.palette.shape) @@ -2203,7 +2206,7 @@ class ReaderRequirementsBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg msg += '\n Fully Understands Aspect Mask: 0x{0:x}'.format(self.fuam) @@ -2262,7 +2265,8 @@ class ReaderRequirementsBox(Jp2kBox): standard_flag, standard_mask = data nflags = len(standard_flag) - vendor_offset = 1 + 2 * mask_length + 2 + (2 + mask_length) * nflags + vendor_offset = 1 + 2 * mask_length + 2 \ + + (2 + mask_length) * nflags data = _parse_vendor_features(read_buffer[vendor_offset:], mask_length) vendor_feature, vendor_mask = data @@ -2348,14 +2352,11 @@ def _parse_standard_flag(read_buffer, mask_length): # 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_from('>H', read_buffer, offset=0) # 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_from(fmt, read_buffer, offset=2) @@ -2386,7 +2387,6 @@ def _parse_vendor_features(read_buffer, mask_length): # 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): @@ -2494,7 +2494,7 @@ class CaptureResolutionBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg msg += '\n VCR: {0}'.format(self.vertical_resolution) @@ -2560,7 +2560,7 @@ class DisplayResolutionBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg msg += '\n VDR: {0}'.format(self.vertical_resolution) @@ -2620,7 +2620,7 @@ class LabelBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg msg += '\n Label: {0}'.format(self.label) @@ -2688,7 +2688,7 @@ class NumberListBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg for j, association in enumerate(self.associations): @@ -2738,7 +2738,8 @@ class NumberListBox(Jp2kBox): def write(self, fptr): """Write a NumberList box to file. """ - fptr.write(struct.pack('>I4s', len(self.associations) * 4 + 8, b'nlst')) + fptr.write(struct.pack('>I4s', + len(self.associations) * 4 + 8, b'nlst')) fmt = '>' + 'I' * len(self.associations) write_buffer = struct.pack(fmt, *self.associations) @@ -2790,9 +2791,9 @@ class XMLBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg - if _printoptions['xml'] == False: + if _printoptions['xml'] is False: return msg msg += '\n' @@ -2911,7 +2912,7 @@ class UUIDListBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg for j, uuid_item in enumerate(self.ulst): @@ -2942,7 +2943,7 @@ class UUIDListBox(Jp2kBox): ulst = [] for j in range(num_uuids): - uuid_buffer = read_buffer[2 + j * 16 : 2 + (j + 1) * 16] + uuid_buffer = read_buffer[2 + j * 16:2 + (j + 1) * 16] ulst.append(uuid.UUID(bytes=uuid_buffer)) return cls(ulst, length=length, offset=offset) @@ -3056,7 +3057,6 @@ class DataEntryURLBox(Jp2kBox): fptr.write(write_buffer) fptr.write(url) - def __repr__(self): msg = "glymur.jp2box.DataEntryURLBox({0}, {1}, '{2}')" msg = msg.format(self.version, self.flag, self.url) @@ -3064,7 +3064,7 @@ class DataEntryURLBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg msg += '\n ' @@ -3216,7 +3216,7 @@ class UUIDBox(Jp2kBox): def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] == True: + if _printoptions['short'] is True: return msg msg = '{0}\n UUID: {1}'.format(msg, self.uuid) @@ -3227,7 +3227,7 @@ class UUIDBox(Jp2kBox): else: msg += ' (unknown)' - if (((_printoptions['xml'] == False) and + if (((_printoptions['xml'] is False) and (self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac')))): # If it's an XMP UUID, don't print the XML contents. return msg @@ -3312,6 +3312,7 @@ _BOX_WITH_ID = { _parseoptions = {'codestream': True} + def set_parseoptions(codestream=True): """Set parsing options. @@ -3336,6 +3337,7 @@ def set_parseoptions(codestream=True): """ _parseoptions['codestream'] = codestream + def get_parseoptions(): """Return the current parsing options. @@ -3356,6 +3358,7 @@ def get_parseoptions(): _printoptions = {'short': False, 'xml': True, 'codestream': True} + def set_printoptions(**kwargs): """Set printing options. @@ -3365,7 +3368,8 @@ def set_printoptions(**kwargs): ---------- short : bool, optional When True, only the box ID, offset, and length are displayed. Useful - for displaying only the basic structure or skeleton of a JPEG 2000 file. + for displaying only the basic structure or skeleton of a JPEG 2000 + file. xml : bool, optional When False, printing of the XML contents of any XML boxes or UUID XMP boxes is suppressed. @@ -3388,6 +3392,7 @@ def set_printoptions(**kwargs): raise TypeError('"{0}" not a valid keyword parameter.'.format(key)) _printoptions[key] = value + def get_printoptions(): """Return the current print options. @@ -3407,5 +3412,3 @@ def get_printoptions(): set_printoptions """ return _printoptions - - diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 097bb07..b1e5c60 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -31,12 +31,12 @@ import numpy as np from .codestream import Codestream from . import core, version -from .jp2box import ( - Jp2kBox, JPEG2000SignatureBox, FileTypeBox, JP2HeaderBox, - ColourSpecificationBox, ContiguousCodestreamBox, ImageHeaderBox -) +from .jp2box import (Jp2kBox, JPEG2000SignatureBox, FileTypeBox, + JP2HeaderBox, ColourSpecificationBox, + ContiguousCodestreamBox, ImageHeaderBox) from .lib import openjpeg as opj, openjp2 as opj2, c as libc + class Jp2k(Jp2kBox): """JPEG 2000 file. @@ -138,7 +138,6 @@ class Jp2k(Jp2kBox): not (X, Y) verbose : bool, optional print informational messages produced by the OpenJPEG library - """ Jp2kBox.__init__(self) self.filename = filename @@ -176,8 +175,8 @@ class Jp2k(Jp2kBox): @layer.setter def layer(self, layer): if version.openjpeg_version_tuple[0] < 2: - msg = "Layer property not supported unless the version of OpenJPEG " - msg += "is 2.0 or higher." + msg = "Layer property not supported unless the version of " + msg += "OpenJPEG is 2.0 or higher." raise RuntimeError(msg) self._layer = layer @@ -361,8 +360,8 @@ class Jp2k(Jp2kBox): kwargs : dictionary non-image keyword inputs provided to write method """ - if (('cinema2k' in kwargs or 'cinema4k' in kwargs) and - (len(set(kwargs)) > 1)): + if ((('cinema2k' in kwargs or 'cinema4k' in kwargs) and + (len(set(kwargs)) > 1))): msg = "Cannot specify cinema2k/cinema4k along with other options." raise IOError(msg) @@ -485,8 +484,9 @@ class Jp2k(Jp2kBox): in memory. """ if re.match("0|1.[0-4]", version.openjpeg_version) is not None: - raise RuntimeError("You must have at least version 1.5 of OpenJPEG " - "in order to write images.") + msg = "You must have at least version 1.5 of OpenJPEG " + msg += "in order to write images." + raise RuntimeError(msg) self._determine_colorspace(**kwargs) self._populate_cparams(img_array, **kwargs) @@ -518,9 +518,11 @@ class Jp2k(Jp2kBox): image.contents.x0 = self._cparams.image_offset_x0 image.contents.y0 = self._cparams.image_offset_y0 image.contents.x1 = image.contents.x0 \ - + (numcols - 1) * self._cparams.subsampling_dx + 1 + + (numcols - 1) * self._cparams.subsampling_dx \ + + 1 image.contents.y1 = image.contents.y0 \ - + (numrows - 1) * self._cparams.subsampling_dy + 1 + + (numrows - 1) * self._cparams.subsampling_dy \ + + 1 # Stage the image data to the openjpeg data structure. for k in range(0, numlayers): @@ -633,7 +635,7 @@ class Jp2k(Jp2kBox): def _determine_colorspace(self, colorspace=None, **kwargs): """Determine the colorspace from the supplied inputs. - + Parameters ---------- colorspace : str, optional @@ -658,7 +660,7 @@ class Jp2k(Jp2kBox): elif colorspace.lower() == 'rgb' and self.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_MAP = {'rgb': opj2.CLRSPC_SRGB, @@ -667,8 +669,7 @@ class Jp2k(Jp2kBox): 'ycc': opj2.CLRSPC_YCC} self._colorspace = COLORSPACE_MAP[colorspace.lower()] - - + def _write_openjp2(self, img_array, verbose=False): """ Write JPEG 2000 file using OpenJPEG 2.x interface. @@ -734,7 +735,8 @@ class Jp2k(Jp2kBox): if not ((box.box_id == 'xml ') or (box.box_id == 'uuid' and box.uuid == UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'))): - msg = "Only XML boxes and XMP UUID boxes can currently be appended." + msg = "Only XML boxes and XMP UUID boxes can currently be " + msg += "appended." raise IOError(msg) # Check the last box. If the length field is zero, then rewrite @@ -891,9 +893,9 @@ class Jp2k(Jp2kBox): Slicing protocol. """ if ((isinstance(index, slice) and - (index.start == None and - index.stop == None and - index.step == None)) or (index is Ellipsis)): + (index.start is None and + index.stop is None and + index.step is None)) or (index is Ellipsis)): # Case of jp2[:] = data, i.e. write the entire image. # # Should have a slice object where start = stop = step = None @@ -923,12 +925,14 @@ class Jp2k(Jp2kBox): return self._read() if isinstance(pargs, slice): - if pargs.start is None and pargs.stop is None and pargs.step is None: + if (((pargs.start is None) and + (pargs.stop is None) and + (pargs.step is None))): # Case of jp2[:] return self._read() # Corner case of jp2[x] where x is a slice object with non-null - # members. Just augment it with an ellipsis and let the code + # members. Just augment it with an ellipsis and let the code # below handle it. pargs = (pargs, Ellipsis) @@ -971,8 +975,7 @@ class Jp2k(Jp2kBox): # Reduce dimensionality in the scalar dimension. return np.squeeze(data, axis=idx) - - # Assuming pargs is a tuple of slices from now on. + # Assuming pargs is a tuple of slices from now on. rows = pargs[0] cols = pargs[1] if len(pargs) == 2: @@ -989,15 +992,14 @@ class Jp2k(Jp2kBox): # Ok, reduce layer step is the same in both xy directions, so just take # one of them. step = rows_step - + # Check if the step size is a power of 2. if np.abs(np.log2(step) - np.round(np.log2(step))) > 1e-6: msg = "Row and column strides must be powers of 2." raise IndexError(msg) rlevel = np.int(np.round(np.log2(step))) - area = ( - 0 if rows.start is None else rows.start, + area = (0 if rows.start is None else rows.start, 0 if cols.start is None else cols.start, numrows if rows.stop is None else rows.stop, numcols if cols.stop is None else cols.stop @@ -1009,7 +1011,6 @@ class Jp2k(Jp2kBox): # Ok, 3 arguments in pargs. return data[:, :, bands] - def _read(self, **kwargs): """Read a JPEG 2000 image. @@ -1032,34 +1033,34 @@ class Jp2k(Jp2kBox): def read(self, **kwargs): """ """ - #Read a JPEG 2000 image. + # Read a JPEG 2000 image. # - #Parameters - #---------- - #rlevel : int, optional - # Factor by which to rlevel output resolution. Use -1 to get the - # lowest resolution thumbnail. This is the only keyword option - # available to use when the OpenJPEG version is 1.5 or earlier. - #layer : int, optional - # Number of quality layer to decode. - #area : tuple, optional - # Specifies decoding image area, - # (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. + # Parameters + # ---------- + # rlevel : int, optional + # Factor by which to rlevel output resolution. Use -1 to get the + # lowest resolution thumbnail. This is the only keyword option + # available to use when the OpenJPEG version is 1.5 or earlier. + # layer : int, optional + # Number of quality layer to decode. + # area : tuple, optional + # Specifies decoding image area, + # (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. # - #Returns - #------- - #img_array : ndarray - # The image data. + # Returns + # ------- + # img_array : ndarray + # The image data. # - #Raises - #------ - #IOError - # If the image has differing subsample factors. - + # Raises + # ------ + # IOError + # If the image has differing subsample factors. + if 'ignore_pclr_cmap_cdef' in kwargs: self.ignore_pclr_cmap_cdef = kwargs['ignore_pclr_cmap_cdef'] warnings.warn("Use array-style slicing instead.", DeprecationWarning) @@ -1163,7 +1164,8 @@ class Jp2k(Jp2kBox): return data - def _read_openjp2(self, rlevel=0, layer=None, area=None, tile=None, verbose=False): + def _read_openjp2(self, rlevel=0, layer=None, area=None, tile=None, + verbose=False): """Read a JPEG 2000 image using libopenjp2. Parameters @@ -1453,7 +1455,7 @@ class Jp2k(Jp2kBox): def _populate_image_struct(self, image, imgdata): """Populates image struct needed for compression. - + Parameters ---------- image : ImageType(ctypes.Structure) @@ -1461,9 +1463,9 @@ class Jp2k(Jp2kBox): img_array : ndarray Image data to be written to file. """ - + numrows, numcols, num_comps = imgdata.shape - + # set image offset and reference grid image.contents.x0 = self._cparams.image_offset_x0 image.contents.y0 = self._cparams.image_offset_y0 @@ -1471,7 +1473,7 @@ class Jp2k(Jp2kBox): (numcols - 1) * self._cparams.subsampling_dx + 1) image.contents.y1 = (image.contents.y0 + (numrows - 1) * self._cparams.subsampling_dy + 1) - + # Stage the image data to the openjpeg data structure. for k in range(0, num_comps): if re.match("2.0", version.openjpeg_version) is not None: @@ -1485,19 +1487,19 @@ class Jp2k(Jp2kBox): core.OPJ_PROFILE_CINEMA_4K): image.contents.comps[k].prec = 12 image.contents.comps[k].bpp = 12 - + layer = np.ascontiguousarray(imgdata[:, :, k], dtype=np.int32) dest = image.contents.comps[k].data src = layer.ctypes.data ctypes.memmove(dest, src, layer.nbytes) - + return image - + def _populate_comptparms(self, img_array): """Instantiate and populate comptparms structure. - + This structure defines the image components. - + Parameters ---------- img_array : ndarray @@ -1526,8 +1528,8 @@ class Jp2k(Jp2kBox): comptparms[j].sgnd = 0 self._comptparms = comptparms - - + + def _component2dtype(component): """Take an OpenJPEG component structure and determine the numpy datatype. @@ -1573,6 +1575,7 @@ JP2_IDS = ['colr', 'cdef', 'cmap', 'jp2c', 'ftyp', 'ihdr', 'jp2h', 'jP ', 'pclr', 'res ', 'resc', 'resd', 'xml ', 'ulst', 'uinf', 'url ', 'uuid'] + def _validate_jp2_box_sequence(boxes): """Run through series of tests for JP2 box legality. @@ -1588,12 +1591,13 @@ def _validate_jp2_box_sequence(boxes): count = _collect_box_count(boxes) for box_id in count.keys(): if box_id not in JP2_IDS: - msg = "The presence of a '{0}' box requires that the file type " - msg += "brand be set to 'jpx '." + msg = "The presence of a '{0}' box requires that the file " + msg += "type brand be set to 'jpx '." raise IOError(msg.format(box_id)) _validate_jp2_colr(boxes) + def _validate_jp2_colr(boxes): """ Validate JP2 requirements on colour specification boxes. @@ -1605,6 +1609,7 @@ def _validate_jp2_colr(boxes): msg = "A JP2 colr box cannot have a non-zero approximation field." raise IOError(msg) + def _validate_jpx_box_sequence(boxes): """Run through series of tests for JPX box legality.""" _validate_label(boxes) @@ -1613,6 +1618,7 @@ def _validate_jpx_box_sequence(boxes): _validate_singletons(boxes) _validate_top_level(boxes) + def _validate_signature_compatibility(boxes): """Validate the file signature and compatibility status.""" # Check for a bad sequence of boxes. @@ -1700,6 +1706,8 @@ def _validate_channel_definition(jp2h, colr): JP2H_CHILDREN = set(['bpcc', 'cdef', 'cmap', 'ihdr', 'pclr']) + + def _check_jp2h_child_boxes(boxes, parent_box_name): """Certain boxes can only reside in the JP2 header.""" box_ids = set([box.box_id for box in boxes]) @@ -1727,6 +1735,7 @@ def _collect_box_count(boxes): TOP_LEVEL_ONLY_BOXES = set(['dtbl']) + def _check_superbox_for_top_levels(boxes): """Several boxes can only occur at the top level.""" # We are only looking at the boxes contained in a superbox, so if any of @@ -1742,6 +1751,7 @@ def _check_superbox_for_top_levels(boxes): if hasattr(box, 'box'): _check_superbox_for_top_levels(box.box) + def _validate_top_level(boxes): """Several boxes can only occur at the top level.""" # Add the counts in the superboxes. @@ -1761,6 +1771,7 @@ def _validate_top_level(boxes): msg += 'a fragment table box as well.' raise IOError(msg) + def _validate_singletons(boxes): """Several boxes can only occur once.""" count = _collect_box_count(boxes) @@ -1771,6 +1782,7 @@ def _validate_singletons(boxes): JPX_IDS = ['asoc', 'nlst'] + def _validate_jpx_brand(boxes, brand): """ If there is a JPX box then the brand must be 'jpx '. @@ -1785,6 +1797,7 @@ def _validate_jpx_brand(boxes, brand): # Same set of checks on any child boxes. _validate_jpx_brand(box.box, brand) + def _validate_jpx_compatibility(boxes, compatibility_list): """ If there is a JPX box then the compatibility list must also contain 'jpx '. @@ -1800,6 +1813,7 @@ def _validate_jpx_compatibility(boxes, compatibility_list): # Same set of checks on any child boxes. _validate_jpx_compatibility(box.box, compatibility_list) + def _validate_label(boxes): """ Label boxes can only be inside association, codestream headers, or @@ -1816,6 +1830,7 @@ def _validate_label(boxes): # Same set of checks on any child boxes. _validate_label(box.box) + def extract_image_cube(image): """Extract 3D image from openjpeg data structure. """ @@ -1871,7 +1886,6 @@ def extract_image_bands(image): return data - # 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. diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py index 5e58cf4..24122b9 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -13,6 +13,7 @@ from .config import glymur_config OPENJP2, OPENJPEG = glymur_config() + def version(): """Wrapper for opj_version library routine.""" try: @@ -50,13 +51,6 @@ JPWL_MAX_NO_TILESPECS = 16 TRUE = 1 FALSE = 0 -#PROFILE = {'none': 0, # No profile -# 0: 1, # Profile 0 -# 1: 2, # Profile 1 -# 'part2': 0x8000, # At least one extension -# 'Cinema2K': 0x0003, # 2K cinema profile -# 'Cinema4K': 0x0004, # 4K cinema profile - # supported color spaces CLRSPC_UNKNOWN = -1 CLRSPC_UNSPECIFIED = 0 @@ -548,7 +542,6 @@ class ImageType(ctypes.Structure): return msg - class ImageComptParmType(ctypes.Structure): """Component parameters structure used by image_create function. @@ -958,7 +951,7 @@ def read_header(stream, codec): ARGTYPES = [STREAM_TYPE_P, CODEC_TYPE, ctypes.POINTER(ctypes.POINTER(ImageType))] OPENJP2.opj_read_header.argtypes = ARGTYPES - OPENJP2.opj_read_header.restype = check_error + OPENJP2.opj_read_header.restype = check_error imagep = ctypes.POINTER(ImageType)() OPENJP2.opj_read_header(stream, codec, ctypes.byref(imagep)) @@ -1317,6 +1310,7 @@ def _stream_create_default_file_stream_2p0(fptr, isa_read_stream): stream = OPENJP2.opj_stream_create_default_file_stream(fptr, read_stream) return stream + def _stream_create_default_file_stream_2p1(fname, isa_read_stream): """Wraps openjp2 library function opj_stream_create_default_vile_stream. @@ -1343,7 +1337,7 @@ def _stream_create_default_file_stream_2p1(fname, isa_read_stream): stream = OPENJP2.opj_stream_create_default_file_stream(file_argument, read_stream) return stream - + if re.match(r'''2.0''', version()): stream_create_default_file_stream = _stream_create_default_file_stream_2p0 else: diff --git a/glymur/lib/openjpeg.py b/glymur/lib/openjpeg.py index d1918ad..924cac5 100644 --- a/glymur/lib/openjpeg.py +++ b/glymur/lib/openjpeg.py @@ -59,8 +59,10 @@ class CommonStructType(ctypes.Structure): ("mj2_handle", ctypes.c_void_p)] -STREAM_READ = 0x0001 # The stream was opened for reading. -STREAM_WRITE = 0x0002 # The stream was opened for writing. +STREAM_READ = 0x0001 # The stream was opened for reading. +STREAM_WRITE = 0x0002 # The stream was opened for writing. + + class CioType(ctypes.Structure): """Byte input-output stream (CIO) @@ -91,70 +93,57 @@ class CompressionInfoType(CommonStructType): class PocType(ctypes.Structure): """Progression order changes.""" _fields_ = [("resno", ctypes.c_int), - # Resolution num start, Component num start, given by POC - ("compno0", ctypes.c_int), + # Resolution num start, Component num start, given by POC + ("compno0", ctypes.c_int), - # Layer num end,Resolution num end, Component num end, given by POC - ("layno1", ctypes.c_int), - ("resno1", ctypes.c_int), - ("compno1", ctypes.c_int), + # Layer num end,Resolution num end, Component num end, given + # by POC + ("layno1", ctypes.c_int), + ("resno1", ctypes.c_int), + ("compno1", ctypes.c_int), - # Layer num start,Precinct num start, Precinct num end - ("layno0", ctypes.c_int), - ("precno0", ctypes.c_int), - ("precno1", ctypes.c_int), + # Layer num start,Precinct num start, Precinct num end + ("layno0", ctypes.c_int), + ("precno0", ctypes.c_int), + ("precno1", ctypes.c_int), - # Progression order enum - # OPJ_PROG_ORDER prg1,prg; - ("prg1", ctypes.c_int), - ("prg", ctypes.c_int), + # Progression order enum + # OPJ_PROG_ORDER prg1,prg; + ("prg1", ctypes.c_int), + ("prg", ctypes.c_int), - # Progression order string - # char progorder[5]; - ("progorder", ctypes.c_char * 5), + # Progression order string + # char progorder[5]; + ("progorder", ctypes.c_char * 5), - # Tile number - # int tile; - ("tile", ctypes.c_int), + # Tile number + # int tile; + ("tile", ctypes.c_int), - # /** Start and end values for Tile width and height*/ - # int tx0,tx1,ty0,ty1; - ("tx0", ctypes.c_int), - ("tx1", ctypes.c_int), - ("ty0", ctypes.c_int), - ("ty1", ctypes.c_int), - - # /** Start value, initialised in pi_initialise_encode*/ - # int layS, resS, compS, prcS; - ("layS", ctypes.c_int), - ("resS", ctypes.c_int), - ("compS", ctypes.c_int), - ("prcS", ctypes.c_int), - - # /** End value, initialised in pi_initialise_encode */ - # int layE, resE, compE, prcE; - ("layE", ctypes.c_int), - ("resE", ctypes.c_int), - ("compE", ctypes.c_int), - ("prcE", ctypes.c_int), - - # Start and end values of Tile width and height, initialised in - # pi_initialise_encode int txS,txE,tyS,tyE,dx,dy; - ("txS", ctypes.c_int), - ("txE", ctypes.c_int), - ("tyS", ctypes.c_int), - ("tyE", ctypes.c_int), - ("dx", ctypes.c_int), - ("dy", ctypes.c_int), - - # Temporary values for Tile parts, initialised in pi_create_encode - # int lay_t, res_t, comp_t, prc_t,tx0_t,ty0_t; - ("lay_t", ctypes.c_int), - ("res_t", ctypes.c_int), - ("comp_t", ctypes.c_int), - ("prc_t", ctypes.c_int), - ("tx0_t", ctypes.c_int), - ("ty0_t", ctypes.c_int)] + ("tx0", ctypes.c_int), + ("tx1", ctypes.c_int), + ("ty0", ctypes.c_int), + ("ty1", ctypes.c_int), + ("layS", ctypes.c_int), + ("resS", ctypes.c_int), + ("compS", ctypes.c_int), + ("prcS", ctypes.c_int), + ("layE", ctypes.c_int), + ("resE", ctypes.c_int), + ("compE", ctypes.c_int), + ("prcE", ctypes.c_int), + ("txS", ctypes.c_int), + ("txE", ctypes.c_int), + ("tyS", ctypes.c_int), + ("tyE", ctypes.c_int), + ("dx", ctypes.c_int), + ("dy", ctypes.c_int), + ("lay_t", ctypes.c_int), + ("res_t", ctypes.c_int), + ("comp_t", ctypes.c_int), + ("prc_t", ctypes.c_int), + ("tx0_t", ctypes.c_int), + ("ty0_t", ctypes.c_int)] class CompressionParametersType(ctypes.Structure): @@ -375,48 +364,47 @@ class DecompressionParametersType(ctypes.Structure): class ImageComptParmType(ctypes.Structure): """Component parameters structure used by the opj_image_create function. """ - _fields_ = [ - # XRsiz: horizontal separation of a sample of ith component with - # respect to the reference grid - ("dx", ctypes.c_int), + _fields_ = [# XRsiz: horizontal separation of a sample of ith component + # with respect to the reference grid + ("dx", ctypes.c_int), - # YRsiz: vertical separation of a sample of ith component with - # respect to the reference grid */ - ("dy", ctypes.c_int), + # YRsiz: vertical separation of a sample of ith component with + # respect to the reference grid */ + ("dy", ctypes.c_int), - # data width, height - ("w", ctypes.c_int), - ("h", ctypes.c_int), + # data width, height + ("w", ctypes.c_int), + ("h", ctypes.c_int), - # x component offset compared to the whole image - # y component offset compared to the whole image - ("x0", ctypes.c_int), - ("y0", ctypes.c_int), + # x component offset compared to the whole image + # y component offset compared to the whole image + ("x0", ctypes.c_int), + ("y0", ctypes.c_int), - # precision - ('prec', ctypes.c_int), + # precision + ('prec', ctypes.c_int), - # image depth in bits - ('bpp', ctypes.c_int), + # image depth in bits + ('bpp', ctypes.c_int), - # signed (1) / unsigned (0) - ('sgnd', ctypes.c_int)] + # signed (1) / unsigned (0) + ('sgnd', ctypes.c_int)] class ImageCompType(ctypes.Structure): """Defines a single image component. """ _fields_ = [("dx", ctypes.c_int), - ("dy", ctypes.c_int), - ("w", ctypes.c_int), - ("h", ctypes.c_int), - ("x0", ctypes.c_int), - ("y0", ctypes.c_int), - ("prec", ctypes.c_int), - ("bpp", ctypes.c_int), - ("sgnd", ctypes.c_int), - ("resno_decoded", ctypes.c_int), - ("factor", ctypes.c_int), - ("data", ctypes.POINTER(ctypes.c_int))] + ("dy", ctypes.c_int), + ("w", ctypes.c_int), + ("h", ctypes.c_int), + ("x0", ctypes.c_int), + ("y0", ctypes.c_int), + ("prec", ctypes.c_int), + ("bpp", ctypes.c_int), + ("sgnd", ctypes.c_int), + ("resno_decoded", ctypes.c_int), + ("factor", ctypes.c_int), + ("data", ctypes.POINTER(ctypes.c_int))] class ImageType(ctypes.Structure): @@ -468,6 +456,7 @@ def cio_tell(cio): pos = OPENJPEG.cio_tell(cio) return pos + def create_compress(fmt): """Wrapper for openjpeg library function opj_create_compress. @@ -585,9 +574,8 @@ def image_cmptparm_t_from_np(np_image): def image_create(cmptparms, cspace): """Wrapper for openjpeg library function opj_image_create. """ - OPENJPEG.opj_image_create.argtypes = [ctypes.c_int, - ctypes.POINTER(ImageComptParmType), - ctypes.c_int] + lst = [ctypes.c_int, ctypes.POINTER(ImageComptParmType), ctypes.c_int] + OPENJPEG.opj_image_create.argtypes = lst OPENJPEG.opj_image_create.restype = ctypes.POINTER(ImageType) image = OPENJPEG.opj_image_create(len(cmptparms), cmptparms, cspace) From 4527c774fad978b0105209783df7eb0f034de5b6 Mon Sep 17 00:00:00 2001 From: John Evans Date: Sun, 23 Nov 2014 22:20:15 -0500 Subject: [PATCH 302/326] Failures on windows are due to named temp file issue, closes #306 --- glymur/test/test_jp2box_uuid.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/glymur/test/test_jp2box_uuid.py b/glymur/test/test_jp2box_uuid.py index 8ec35a2..0e6619a 100644 --- a/glymur/test/test_jp2box_uuid.py +++ b/glymur/test/test_jp2box_uuid.py @@ -35,8 +35,10 @@ else: import lxml.etree -from .fixtures import HAS_PYTHON_XMP_TOOLKIT, OPJ_DATA_ROOT -from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG +from .fixtures import (HAS_PYTHON_XMP_TOOLKIT, OPJ_DATA_ROOT, + WARNING_INFRASTRUCTURE_ISSUE, + WARNING_INFRASTRUCTURE_MSG, + WINDOWS_TMP_FILE_MSG) if HAS_PYTHON_XMP_TOOLKIT: from libxmp import XMPMeta @@ -46,7 +48,7 @@ from glymur import Jp2k from .fixtures import OPJ_DATA_ROOT, opj_data_file, SimpleRDF -@unittest.skipIf(os.name == "nt", "Unexplained failure on windows") +@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) class TestSuite(unittest.TestCase): """Tests for XMP, Exif UUIDs.""" @@ -102,7 +104,7 @@ class TestSuite(unittest.TestCase): self.assertEqual(jp2.box[-1].data['Make'], "HTC") @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) -@unittest.skipIf(os.name == "nt", "Unexplained failure on windows") +@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) class TestSuiteWarns(unittest.TestCase): """Tests for XMP, Exif UUIDs, issues warnings.""" @@ -191,6 +193,3 @@ class TestSuiteWarns(unittest.TestCase): jp2 = glymur.Jp2k(tfile.name) self.assertEqual(jp2.box[-1].box_id, 'uuid') - -if __name__ == "__main__": - unittest.main() From 02ff73078426b152a43e294c3b0a89f191618212 Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 22 Dec 2014 23:20:48 -0500 Subject: [PATCH 303/326] pep8 work --- glymur/lib/__init__.py | 2 + glymur/lib/config.py | 44 +- glymur/lib/openjp2.py | 40 -- glymur/lib/openjpeg.py | 50 +- glymur/test/fixtures.py | 28 +- glymur/test/test_callbacks.py | 5 +- glymur/test/test_config.py | 47 +- glymur/test/test_glymur_warnings.py | 21 +- glymur/test/test_icc.py | 2 - glymur/test/test_jp2box.py | 64 ++- glymur/test/test_jp2box_jpx.py | 11 +- glymur/test/test_jp2box_uuid.py | 24 +- glymur/test/test_jp2box_xml.py | 22 +- glymur/test/test_jp2k.py | 243 +++----- glymur/test/test_opj_suite.py | 31 +- glymur/test/test_opj_suite_dump.py | 838 ++++++++++++++++------------ glymur/test/test_opj_suite_neg.py | 23 +- glymur/test/test_opj_suite_write.py | 203 ++++--- glymur/test/test_printing.py | 52 +- 19 files changed, 823 insertions(+), 927 deletions(-) diff --git a/glymur/lib/__init__.py b/glymur/lib/__init__.py index a283f7f..ddce813 100644 --- a/glymur/lib/__init__.py +++ b/glymur/lib/__init__.py @@ -2,3 +2,5 @@ from . import openjp2 as openjp2 from . import openjpeg as openjpeg from . import c + +__all__ = [openjp2, openjpeg, c] diff --git a/glymur/lib/config.py b/glymur/lib/config.py index 3aca5aa..a639b38 100644 --- a/glymur/lib/config.py +++ b/glymur/lib/config.py @@ -19,18 +19,21 @@ else: from configparser import NoOptionError # default library locations for MacPorts -_macports_default_location = { - 'openjp2': '/opt/local/lib/libopenjp2.dylib', - 'openjpeg': '/opt/local/lib/libopenjpeg.dylib' -} +_macports_default_location = {'openjp2': '/opt/local/lib/libopenjp2.dylib', + 'openjpeg': '/opt/local/lib/libopenjpeg.dylib'} # default library locations on Windows -_windows_default_location = { - 'openjp2': os.path.join('C:\\', 'Program files', 'OpenJPEG 2.0', - 'bin', 'openjp2.dll'), - 'openjpeg': os.path.join('C:\\', 'Program files', 'OpenJPEG 1.5', - 'bin', 'openjpeg.dll') -} +_windows_default_location = {'openjp2': os.path.join('C:\\', + 'Program files', + 'OpenJPEG 2.0', + 'bin', + 'openjp2.dll'), + 'openjpeg': os.path.join('C:\\', + 'Program files', + 'OpenJPEG 1.5', + 'bin', + 'openjpeg.dll')} + def glymurrc_fname(): """Return the path to the configuration file. @@ -55,8 +58,9 @@ def glymurrc_fname(): # didn't find a configuration file. return None + def load_openjpeg_library(libname): - + path = read_config_file(libname) if path is not None: return load_library_handle(path) @@ -79,13 +83,15 @@ def load_openjpeg_library(libname): return load_library_handle(path) + def load_library_handle(path): """Load the library, return the ctypes handle.""" if path is None or path in ['None', 'none']: - # Either could not find a library via ctypes or user-configuration-file, - # or we could not find it in any of the default locations, or possibly - # the user intentionally does not want one of the libraries to load. + # Either could not find a library via ctypes or + # user-configuration-file, or we could not find it in any of the + # default locations, or possibly the user intentionally does not want + # one of the libraries to load. return None try: @@ -94,10 +100,10 @@ def load_library_handle(path): else: opj_lib = ctypes.CDLL(path) except (TypeError, OSError): - msg = 'The library specified by configuration file at {0} could not be ' - msg += 'loaded.' - warnings.warn(msg.format(path), UserWarning) - opj_lib = None + msg = 'The library specified by configuration file at {0} could not ' + msg += 'be loaded.' + warnings.warn(msg.format(path), UserWarning) + opj_lib = None return opj_lib @@ -130,6 +136,7 @@ def read_config_file(libname): path = None return path + def glymur_config(): """ Try to ascertain locations of openjp2, openjpeg libraries. @@ -147,6 +154,7 @@ def glymur_config(): warnings.warn(msg) return tuple(lst) + def get_configdir(): """Return string representing the configuration directory. diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py index 24122b9..d398fb2 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -410,7 +410,6 @@ class CompressionParametersType(ctypes.Structure): for j in range(self.numpocs): msg += " [#{0}]:".format(j) msg += " {0}".format(str(self.poc[j])) - msg += textwrap.indent(textstr, ' ' * 12) elif field_name in ['tcp_rates', 'tcp_distoratio']: lst = [] @@ -740,28 +739,6 @@ def encode(codec, stream): OPENJP2.opj_encode(codec, stream) -def get_cstr_info(codec): - """get the codestream information from the codec - - Wraps the openjp2 library function opj_get_cstr_info. - - Parameters - ---------- - codec : CODEC_TYPE - The jpeg2000 codec. - - Returns - ------- - cstr_info_p : CodestreamInfoV2 - Reference to codestream information. - """ - OPENJP2.opj_get_cstr_info.argtypes = [CODEC_TYPE] - OPENJP2.opj_get_cstr_info.restype = ctypes.POINTER(CodestreamInfoV2) - - cstr_info_p = OPENJP2.opj_get_cstr_info(codec) - return cstr_info_p - - def get_decoded_tile(codec, stream, imagep, tile_index): """get the decoded tile from the codec @@ -792,23 +769,6 @@ def get_decoded_tile(codec, stream, imagep, tile_index): OPENJP2.opj_get_decoded_tile(codec, stream, imagep, tile_index) -def destroy_cstr_info(cstr_info_p): - """destroy codestream information after compression or decompression - - Wraps the openjp2 library function opj_destroy_cstr_info. - - Parameters - ---------- - cstr_info_p : CodestreamInfoV2 pointer - Pointer to codestream info structure. - """ - ARGTYPES = [ctypes.POINTER(ctypes.POINTER(CodestreamInfoV2))] - OPENJP2.opj_destroy_cstr_info.argtypes = ARGTYPES - OPENJP2.opj_destroy_cstr_info.restype = ctypes.c_void_p - - OPENJP2.opj_destroy_cstr_info(ctypes.byref(cstr_info_p)) - - def end_compress(codec, stream): """End of compressing the current image. diff --git a/glymur/lib/openjpeg.py b/glymur/lib/openjpeg.py index 924cac5..602f3b9 100644 --- a/glymur/lib/openjpeg.py +++ b/glymur/lib/openjpeg.py @@ -6,8 +6,6 @@ import ctypes import sys -import numpy as np - from .config import glymur_config _, OPENJPEG = glymur_config() @@ -364,9 +362,9 @@ class DecompressionParametersType(ctypes.Structure): class ImageComptParmType(ctypes.Structure): """Component parameters structure used by the opj_image_create function. """ - _fields_ = [# XRsiz: horizontal separation of a sample of ith component + _fields_ = [("dx", ctypes.c_int), + # XRsiz: horizontal separation of a sample of ith component # with respect to the reference grid - ("dx", ctypes.c_int), # YRsiz: vertical separation of a sample of ith component with # respect to the reference grid */ @@ -527,50 +525,6 @@ def destroy_decompress(dinfo): OPENJPEG.opj_destroy_decompress(dinfo) -def image_cmptparm_t_from_np(np_image): - """Return appropriate image_cmptparm_t based on given numpy array. - """ - try: - num_comps = np_image.shape[2] - except IndexError: - num_comps = 1 - - cmpt_parm_array_t = ImageCmptparmType * num_comps - tarr = cmpt_parm_array_t() - - if np_image.dtype == np.uint8: - prec = 8 - bpp = 8 - sgnd = 0 - elif np_image.dtype == np.int8: - prec = 8 - bpp = 8 - sgnd = 1 - elif np_image.dtype == np.uint16: - prec = 16 - bpp = 16 - sgnd = 0 - elif np_image.dtype == np.int16: - prec = 16 - bpp = 16 - sgnd = 1 - else: - raise(TypeError("unhandled")) - - for j in range(0, num_comps): - tarr[j].dx = 1 - tarr[j].dy = 1 - tarr[j].w = np_image.shape[1] - tarr[j].h = np_image.shape[0] - tarr[j].x0 = 0 - tarr[j].y0 = 0 - tarr[j].prec = prec - tarr[j].bpp = bpp - tarr[j].sgnd = sgnd - - return(tarr) - - def image_create(cmptparms, cspace): """Wrapper for openjpeg library function opj_image_create. """ diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 113a47b..5666514 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -37,6 +37,7 @@ elif re.match('1.[0-6]', six.__version__) is not None: # Cannot reopen a named temporary file in windows. WINDOWS_TMP_FILE_MSG = "cannot use NamedTemporaryFile like this in windows" + class MetadataBase(unittest.TestCase): """ Base class for testing metadata. @@ -101,8 +102,8 @@ class MetadataBase(unittest.TestCase): """ verify the fields of a RGN segment """ - self.assertEqual(actual.crgn, expected.crgn) # 0 = component - self.assertEqual(actual.srgn, expected.srgn) # 0 = implicit + self.assertEqual(actual.crgn, expected.crgn) # 0 = component + self.assertEqual(actual.srgn, expected.srgn) # 0 = implicit self.assertEqual(actual.sprgn, expected.sprgn) def verifySOTsegment(self, actual, expected): @@ -125,8 +126,9 @@ class MetadataBase(unittest.TestCase): """ Verify the fields of the SIZ segment. """ - for field in ['rsiz', 'xsiz', 'ysiz', 'xosiz', 'yosiz', 'xtsiz', - 'ytsiz', 'xtosiz', 'ytosiz', 'bitdepth', 'xrsiz', 'yrsiz']: + for field in ['rsiz', 'xsiz', 'ysiz', 'xosiz', 'yosiz', 'xtsiz', + 'ytsiz', 'xtosiz', 'ytosiz', 'bitdepth', + 'xrsiz', 'yrsiz']: self.assertEqual(getattr(actual, field), getattr(expected, field)) def verifyImageHeaderBox(self, box1, box2): @@ -153,7 +155,7 @@ class MetadataBase(unittest.TestCase): else: self.assertEqual(actual.colorspace, expected.colorspace) self.assertIsNone(actual.icc_profile) - + # The Python XMP Toolkit may be used for XMP UUIDs, but only if available and # if the version is at least 2.0.0. @@ -183,7 +185,7 @@ except: # The Cinema2K/4K tests seem to need the freeimage backend to skimage.io # in order to work. Unfortunately, scikit-image/freeimage is about as wonky as # it gets. Anaconda can get totally weirded out on versions up through 3.6.4 -# on Python3 with scikit-image up through version 0.10.0. +# on Python3 with scikit-image up through version 0.10.0. NO_SKIMAGE_FREEIMAGE_SUPPORT = False try: import skimage @@ -211,7 +213,7 @@ def _indent(textstr): String to be indented. indent_level : str Number of spaces of indentation to add. - + Returns ------- indented_string : str @@ -544,7 +546,7 @@ text_gbr_34 = """Colour Specification Box (colr) @ (179, 1339) dump = r'''JPEG 2000 Signature Box (jP ) @ (0, 12) Signature: 0d0a870a File Type Box (ftyp) @ (12, 20) - Brand: jp2 + Brand: jp2 Compatibility: ['jp2 '] JP2 Header Box (jp2h) @ (32, 45) Image Header Box (ihdr) @ (40, 22) @@ -600,7 +602,6 @@ Contiguous Codestream Box (jp2c) @ (3223, 1132296) "Created by OpenJPEG version 2.0.0"''' nemo_with_codestream_header = dump.format(_indent(nemo_xmp)) -#nemo_dump_full = dump.format(_indent(nemo_xmp)) nemo_dump_short = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) File Type Box (ftyp) @ (12, 20) @@ -613,7 +614,7 @@ Contiguous Codestream Box (jp2c) @ (3223, 1132296)""" nemo_dump_no_xml = '''JPEG 2000 Signature Box (jP ) @ (0, 12) Signature: 0d0a870a File Type Box (ftyp) @ (12, 20) - Brand: jp2 + Brand: jp2 Compatibility: ['jp2 '] JP2 Header Box (jp2h) @ (32, 45) Image Header Box (ihdr) @ (40, 22) @@ -669,7 +670,7 @@ Contiguous Codestream Box (jp2c) @ (3223, 1132296) dump = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) Signature: 0d0a870a File Type Box (ftyp) @ (12, 20) - Brand: jp2 + Brand: jp2 Compatibility: ['jp2 '] JP2 Header Box (jp2h) @ (32, 45) Image Header Box (ihdr) @ (40, 22) @@ -692,7 +693,7 @@ nemo_dump_no_codestream = dump.format(_indent(nemo_xmp)) nemo_dump_no_codestream_no_xml = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) Signature: 0d0a870a File Type Box (ftyp) @ (12, 20) - Brand: jp2 + Brand: jp2 Compatibility: ['jp2 '] JP2 Header Box (jp2h) @ (32, 45) Image Header Box (ihdr) @ (40, 22) @@ -743,7 +744,7 @@ issue_183_colr = """Colour Specification Box (colr) @ (62, 12) Method: restricted ICC profile Precedence: 0 ICC Profile: None""" - + # Progression order is invalid. issue_186_progression_order = """COD marker segment @ (174, 12) @@ -908,4 +909,3 @@ goodstuff_with_full_header = r"""Codestream: Step size: [(0, 8), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10)] SOD marker segment @ (164, 0) EOC marker segment @ (115218, 0)""" - diff --git a/glymur/test/test_callbacks.py b/glymur/test/test_callbacks.py index 849d987..269a816 100644 --- a/glymur/test/test_callbacks.py +++ b/glymur/test/test_callbacks.py @@ -25,6 +25,7 @@ import glymur from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG + class TestCallbacks(unittest.TestCase): """Test suite for callbacks.""" @@ -46,7 +47,7 @@ class TestCallbacks(unittest.TestCase): tiledata = j.read(tile=0) with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with patch('sys.stdout', new=StringIO()) as fake_out: - j = glymur.Jp2k(tfile.name, data=tiledata, verbose=True) + glymur.Jp2k(tfile.name, data=tiledata, verbose=True) actual = fake_out.getvalue().strip() expected = '[INFO] tile number 1 / 1' self.assertEqual(actual, expected) @@ -60,7 +61,7 @@ class TestCallbacks(unittest.TestCase): tiledata = j[:] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with patch('sys.stdout', new=StringIO()) as fake_out: - jp2 = glymur.Jp2k(tfile.name, data=tiledata, verbose=True) + glymur.Jp2k(tfile.name, data=tiledata, verbose=True) actual = fake_out.getvalue().strip() expected = '[INFO] tile number 1 / 1' self.assertEqual(actual, expected) diff --git a/glymur/test/test_config.py b/glymur/test/test_config.py index feb3235..28a41cc 100644 --- a/glymur/test/test_config.py +++ b/glymur/test/test_config.py @@ -10,7 +10,7 @@ OPENJP2 may be present in some form or other. # unittest.mock only in Python 3.3 (python2.7/pylint import issue) # pylint: disable=E0611,F0401 -import contextlib +import contextlib import ctypes import imp import os @@ -26,41 +26,42 @@ else: import glymur from glymur import Jp2k -from .fixtures import ( - WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG, - WINDOWS_TMP_FILE_MSG -) +from .fixtures import (WARNING_INFRASTRUCTURE_ISSUE, + WARNING_INFRASTRUCTURE_MSG, + WINDOWS_TMP_FILE_MSG) + def openjpeg_not_found_by_ctypes(): """ Need to know if openjpeg library can be picked right up by ctypes for one of the tests. """ - with patch.dict('os.environ', {'DYLD_FALLBACK_LIBRARY_PATH': '/opt/local/lib'}): + with patch.dict('os.environ', + {'DYLD_FALLBACK_LIBRARY_PATH': '/opt/local/lib'}): if ctypes.util.find_library('openjpeg') is None: return True else: return False -@contextlib.contextmanager -def chdir(dirname=None): +@contextlib.contextmanager +def chdir(dirname=None): """ This context manager restores the value of the current working directory (cwd) after the enclosed code block completes or raises an exception. If a directory name is supplied to the context manager then the cwd is changed - prior to running the code block. + prior to running the code block. Shamelessly lifted from http://www.astropython.org/snippet/2009/10/chdir-context-manager """ - curdir = os.getcwd() - try: - if dirname is not None: - os.chdir(dirname) - yield - finally: - os.chdir(curdir) + curdir = os.getcwd() + try: + if dirname is not None: + os.chdir(dirname) + yield + finally: + os.chdir(curdir) @unittest.skipIf(sys.hexversion < 0x03020000, @@ -130,7 +131,7 @@ class TestSuite(unittest.TestCase): "Needs openjp2 and openjpeg before this test make sense.") @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) def test_library_specified_as_None(self): - """Verify that we can stop a library from being loaded by using None.""" + """Verify that we can stop library from being loaded by using None.""" with tempfile.TemporaryDirectory() as tdir: configdir = os.path.join(tdir, 'glymur') os.mkdir(configdir) @@ -140,7 +141,9 @@ class TestSuite(unittest.TestCase): # openjpeg instead. fptr.write('[library]\n') fptr.write('openjp2: None\n') - fptr.write('openjpeg: {0}\n'.format(glymur.lib.openjp2.OPENJPEG._name)) + msg = 'openjpeg: {0}\n' + msg = msg.format(glymur.lib.openjp2.OPENJPEG._name) + fptr.write(msg) fptr.flush() with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}): imp.reload(glymur.lib.openjp2) @@ -149,18 +152,17 @@ class TestSuite(unittest.TestCase): @unittest.skipIf(glymur.lib.openjp2.OPENJPEG is None, "Needs openjpeg before this test make sense.") - @unittest.skipIf(openjpeg_not_found_by_ctypes(), - "OpenJPEG must be easily found before this test can work.") + @unittest.skipIf(openjpeg_not_found_by_ctypes(), + "OpenJPEG must be found before this test can work.") @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) def test_config_dir_but_no_config_file(self): with tempfile.TemporaryDirectory() as tdir: configdir = os.path.join(tdir, 'glymur') os.mkdir(configdir) - fname = os.path.join(configdir, 'glymurrc') with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}): # Should still be able to load openjpeg, despite the - # configuration file being empty. + # configuration file not being there imp.reload(glymur.lib.openjpeg) self.assertIsNotNone(glymur.lib.openjp2.OPENJPEG) @@ -178,4 +180,3 @@ class TestSuite(unittest.TestCase): # Should be able to load openjp2 as before. imp.reload(glymur.lib.openjp2) self.assertEqual(glymur.lib.openjp2.OPENJP2._name, libloc) - diff --git a/glymur/test/test_glymur_warnings.py b/glymur/test/test_glymur_warnings.py index b8e5c8b..fa0c832 100644 --- a/glymur/test/test_glymur_warnings.py +++ b/glymur/test/test_glymur_warnings.py @@ -5,22 +5,19 @@ Test suite for warnings issued by glymur. # unittest doesn't work well with R0904. # pylint: disable=R0904 -import platform import os import re import struct -import sys import tempfile import unittest -import six - from glymur import Jp2k import glymur from .fixtures import opj_data_file, OPJ_DATA_ROOT from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @@ -53,12 +50,11 @@ class TestWarnings(unittest.TestCase): """ relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2' jfile = opj_data_file(relpath) - regex = re.compile(r"""Unrecognized\sbox\s\(b'XML\s'\)\sencountered.""", - re.VERBOSE) + pattern = r"""Unrecognized\sbox\s\(b'XML\s'\)\sencountered.""" + regex = re.compile(pattern, re.VERBOSE) with self.assertWarnsRegex(UserWarning, regex): Jp2k(jfile) - def test_NR_gdal_fuzzer_unchecked_numresolutions_dump(self): """ Has an invalid number of resolutions. @@ -119,7 +115,7 @@ class TestWarnings(unittest.TestCase): \(\d+\)''', re.VERBOSE) with self.assertWarnsRegex(UserWarning, regex): - jp2 = Jp2k(jfile) + Jp2k(jfile) def test_NR_broken2_jp2_dump(self): """ @@ -127,7 +123,7 @@ class TestWarnings(unittest.TestCase): """ jfile = opj_data_file('input/nonregression/broken2.jp2') regex = re.compile(r'''Invalid\smarker\sid\sencountered\sat\sbyte\s - \d+\sin\scodestream:\s*"0x[a-fA-F0-9]{4}"''', + \d+\sin\scodestream:\s*"0x[a-fA-F0-9]{4}"''', re.VERBOSE) with self.assertWarnsRegex(UserWarning, regex): Jp2k(jfile) @@ -152,7 +148,8 @@ class TestWarnings(unittest.TestCase): def test_tile_height_is_zero(self): """Zero tile height should not cause an exception.""" - filename = opj_data_file('input/nonregression/2539.pdf.SIGFPE.706.1712.jp2') + filename = 'input/nonregression/2539.pdf.SIGFPE.706.1712.jp2' + filename = opj_data_file(filename) with self.assertWarnsRegex(UserWarning, 'Invalid tile dimensions'): Jp2k(filename) @@ -177,9 +174,9 @@ class TestWarnings(unittest.TestCase): read_buffer = ifile.read() tfile.write(read_buffer) tfile.flush() - + with self.assertWarnsRegex(UserWarning, 'Unrecognized marker'): - codestream = Jp2k(tfile.name).get_codestream() + Jp2k(tfile.name).get_codestream() if __name__ == "__main__": diff --git a/glymur/test/test_icc.py b/glymur/test/test_icc.py index c6b63e8..90ffc11 100644 --- a/glymur/test/test_icc.py +++ b/glymur/test/test_icc.py @@ -6,8 +6,6 @@ ICC profile tests. # pylint: disable=R0904 import datetime -import os -import sys import unittest import numpy as np diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 28ae4fe..9843b94 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -35,10 +35,10 @@ from glymur.jp2box import JPEG2000SignatureBox from glymur.core import COLOR, OPACITY from glymur.core import RED, GREEN, BLUE, GREY, WHOLE_IMAGE -from .fixtures import ( - WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG, - WINDOWS_TMP_FILE_MSG, MetadataBase -) +from .fixtures import (WARNING_INFRASTRUCTURE_ISSUE, + WARNING_INFRASTRUCTURE_MSG, + WINDOWS_TMP_FILE_MSG, MetadataBase) + def load_tests(loader, tests, ignore): """Run doc tests as well.""" @@ -48,13 +48,15 @@ def load_tests(loader, tests, ignore): tests.addTests(doctest.DocTestSuite('glymur.jp2box')) return tests + @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) class TestDataEntryURL(unittest.TestCase): """Test suite for DataEntryURL boxes.""" def setUp(self): self.jp2file = glymur.data.nemo() - @unittest.skipIf(re.match("1.5|2", glymur.version.openjpeg_version) is None, + @unittest.skipIf(re.match("1.5|2", + glymur.version.openjpeg_version) is None, "Must have openjpeg 1.5 or higher to run") def test_wrap_greyscale(self): """A single component should be wrapped as GREYSCALE.""" @@ -70,7 +72,7 @@ class TestDataEntryURL(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile2: jp2 = j2k.wrap(tfile2.name) self.assertEqual(jp2.box[2].box[1].colorspace, - glymur.core.GREYSCALE) + glymur.core.GREYSCALE) def test_basic_url(self): """Just your most basic URL box.""" @@ -92,7 +94,7 @@ class TestDataEntryURL(unittest.TestCase): self.assertEqual(jp22.box[4].url, url) def test_null_termination(self): - """I.9.3.2 specifies that the location field must be null terminated.""" + """I.9.3.2 specifies that location field must be null terminated.""" jp2 = Jp2k(self.jp2file) url = 'http://glymur.readthedocs.org' @@ -103,12 +105,15 @@ class TestDataEntryURL(unittest.TestCase): jp22 = jp2.wrap(tfile.name, boxes=boxes) self.assertEqual(jp22.box[-1].length, 42) - - # Go to the last box. Seek past the L, T, version, and flag fields. + + # Go to the last box. Seek past the L, T, version, + # and flag fields. with open(tfile.name, 'rb') as fptr: fptr.seek(jp22.box[-1].offset + 4 + 4 + 1 + 3) - - nbytes = jp22.box[-1].offset + jp22.box[-1].length - fptr.tell() + + nbytes = (jp22.box[-1].offset + + jp22.box[-1].length - + fptr.tell()) read_buffer = fptr.read(nbytes) read_url = read_buffer.decode('utf-8') self.assertEqual(url + chr(0), read_url) @@ -128,18 +133,18 @@ class TestChannelDefinition(unittest.TestCase): data = j2k[:] # Write the first component back out to file. with tempfile.NamedTemporaryFile(suffix=".j2k", delete=False) as tfile: - grey_j2k = Jp2k(tfile.name, data=data[:, :, 0]) + Jp2k(tfile.name, data=data[:, :, 0]) cls.one_plane = tfile.name # Write the first two components back out to file. with tempfile.NamedTemporaryFile(suffix=".j2k", delete=False) as tfile: - grey_j2k = Jp2k(tfile.name, data=data[:, :, 0:1]) + Jp2k(tfile.name, data=data[:, :, 0:1]) cls.two_planes = tfile.name # Write four components back out to file. with tempfile.NamedTemporaryFile(suffix=".j2k", delete=False) as tfile: shape = (data.shape[0], data.shape[1], 1) alpha = np.zeros((shape), dtype=data.dtype) data4 = np.concatenate((data, alpha), axis=2) - rgba_jp2 = Jp2k(tfile.name, data=data4) + Jp2k(tfile.name, data=data4) cls.four_planes = tfile.name @classmethod @@ -392,7 +397,7 @@ class TestFileTypeBox(unittest.TestCase): ftyp = glymur.jp2box.FileTypeBox(brand='jp3') with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: - ftyp.write(tfile) + ftyp.write(tfile) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_cl_entry_unknown(self): @@ -402,7 +407,8 @@ class TestFileTypeBox(unittest.TestCase): ftyp = glymur.jp2box.FileTypeBox(compatibility_list=['jp3']) with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: - ftyp.write(tfile) + ftyp.write(tfile) + class TestColourSpecificationBox(unittest.TestCase): """Test suite for colr box instantiation.""" @@ -525,8 +531,8 @@ class TestPaletteBox(unittest.TestCase): bps = (8, 8, 8) signed = (False, False) with self.assertWarns(UserWarning): - pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps, - signed=signed) + glymur.jp2box.PaletteBox(palette, bits_per_component=bps, + signed=signed) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_mismatched_signed_palette(self): @@ -535,8 +541,8 @@ class TestPaletteBox(unittest.TestCase): bps = (8, 8, 8, 8) signed = (False, False, False, False) with self.assertWarns(UserWarning): - pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps, - signed=signed) + glymur.jp2box.PaletteBox(palette, bits_per_component=bps, + signed=signed) def test_writing_with_different_bitdepths(self): """Bitdepths must be the same when writing.""" @@ -792,7 +798,7 @@ class TestWrap(unittest.TestCase): # list to trigger the error. boxes[2].box = [] with self.assertRaises(IOError): - jp22 = jp2.wrap(tfile.name, boxes=boxes) + jp2.wrap(tfile.name, boxes=boxes) def test_default_layout_with_boxes(self): """basic test for rewrapping a jp2 file, boxes specified""" @@ -857,8 +863,8 @@ class TestWrap(unittest.TestCase): """A palette box must reside in a JP2 header box.""" palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.int32) bps = (8, 8, 8) - signed = (True, False, True) - pclr = glymur.jp2box.PaletteBox(palette=palette, bits_per_component=bps, + pclr = glymur.jp2box.PaletteBox(palette=palette, + bits_per_component=bps, signed=(True, False, True)) j2k = Jp2k(self.j2kfile) @@ -970,7 +976,8 @@ class TestWrap(unittest.TestCase): """Rewrap a jpx file.""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1: jpx = Jp2k(self.jpxfile) - idx = list(range(5)) + list(range(9, 12)) + list(range(6, 9)) + [12] + idx = (list(range(5)) + + list(range(9, 12)) + list(range(6, 9))) + [12] boxes = [jpx.box[j] for j in idx] jpx2 = jpx.wrap(tfile1.name, boxes=boxes) exp_ids = [box.box_id for box in boxes] @@ -1026,7 +1033,7 @@ class TestJp2Boxes(unittest.TestCase): def test_codestream_main_header_offset(self): """main_header_offset is an attribute of the CCS box""" - j = Jp2k(self.jpxfile); + j = Jp2k(self.jpxfile) self.assertEqual(j.box[5].main_header_offset, j.box[5].offset + 8) @@ -1240,7 +1247,6 @@ class TestRepr(MetadataBase): """Verify Palette box repr.""" palette = np.array([[255, 0, 1000], [0, 255, 0]], dtype=np.int32) bps = (8, 8, 16) - signed = (True, False, True) box = glymur.jp2box.PaletteBox(palette=palette, bits_per_component=bps, signed=(True, False, True)) @@ -1290,7 +1296,8 @@ class TestRepr(MetadataBase): # Since the raw_data parameter is a sequence of bytes which could be # quite long, don't bother trying to make it conform to eval(repr()). regexp = r"""glymur.jp2box.UUIDBox\(""" - regexp += """the_uuid=UUID\('00000000-0000-0000-0000-000000000000'\),\s""" + regexp += """the_uuid=""" + regexp += """UUID\('00000000-0000-0000-0000-000000000000'\),\s""" regexp += """raw_data=\)""" if sys.hexversion < 0x03000000: @@ -1307,7 +1314,8 @@ class TestRepr(MetadataBase): # Since the raw_data parameter is a sequence of bytes which could be # quite long, don't bother trying to make it conform to eval(repr()). regexp = r"""glymur.jp2box.UUIDBox\(""" - regexp += """the_uuid=UUID\('be7acfcb-97a9-42e8-9c71-999491e3afac'\),\s""" + regexp += """the_uuid=""" + regexp += """UUID\('be7acfcb-97a9-42e8-9c71-999491e3afac'\),\s""" regexp += """raw_data=\)""" if sys.hexversion < 0x03000000: diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index ce676f3..3ee4d31 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -6,7 +6,6 @@ Test suite specifically targeting JPX box layout. import ctypes import os import struct -import sys import tempfile import unittest @@ -20,6 +19,7 @@ from glymur.jp2box import ColourSpecificationBox from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") class TestJPXWrap(unittest.TestCase): """Test suite for wrapping JPX files.""" @@ -184,7 +184,7 @@ class TestJPXWrap(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: with self.assertRaises(IOError): - jpx = jp2.wrap(tfile.name, boxes=boxes) + jp2.wrap(tfile.name, boxes=boxes) def test_cgrp_neg(self): """Can't write a cgrp with anything but colr sub boxes""" @@ -204,7 +204,7 @@ class TestJPXWrap(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: with self.assertRaises(IOError): - jpx = jp2.wrap(tfile.name, boxes=boxes) + jp2.wrap(tfile.name, boxes=boxes) def test_ftbl(self): """Write a fragment table box.""" @@ -484,7 +484,7 @@ class TestJPX(unittest.TestCase): ftbl.write(tfile) def test_data_reference_requires_dtbl(self): - """The existance of a data reference box requires a ftbl box as well.""" + """The existance of data reference box requires a ftbl box as well.""" flag = 0 version = (0, 0, 0) url1 = 'file:////usr/local/bin' @@ -574,7 +574,7 @@ class TestJPX(unittest.TestCase): 131072, 65536, 32768, 16384, 8192] for j in range(len(standard_flags)): mask = (standard_masks[j] >> 16, - standard_masks[j] & 0x0000ffff>> 8, + standard_masks[j] & 0x0000ffff >> 8, standard_masks[j] & 0x000000ff) struct.pack_into('>HBBB', rreq_buffer, 17 + j * 5, standard_flags[j], *mask) @@ -599,7 +599,6 @@ class TestJPX(unittest.TestCase): self.assertEqual(jpx.box[2].standard_flag, (5, 42, 45, 2, 18, 19, 1, 8, 12, 31, 20)) - def test_nlst(self): """Verify that we can handle a number list box.""" j = Jp2k(self.jpxfile) diff --git a/glymur/test/test_jp2box_uuid.py b/glymur/test/test_jp2box_uuid.py index 0e6619a..409e43b 100644 --- a/glymur/test/test_jp2box_uuid.py +++ b/glymur/test/test_jp2box_uuid.py @@ -11,7 +11,6 @@ # pylint: disable=R0904 import os -import re import shutil import struct import sys @@ -23,29 +22,15 @@ if sys.hexversion < 0x02070000: else: import unittest -if sys.hexversion < 0x03000000: - from StringIO import StringIO -else: - from io import StringIO - -if sys.hexversion <= 0x03030000: - from mock import patch -else: - from unittest.mock import patch - import lxml.etree -from .fixtures import (HAS_PYTHON_XMP_TOOLKIT, OPJ_DATA_ROOT, - WARNING_INFRASTRUCTURE_ISSUE, +from .fixtures import (WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG, WINDOWS_TMP_FILE_MSG) -if HAS_PYTHON_XMP_TOOLKIT: - from libxmp import XMPMeta - import glymur from glymur import Jp2k -from .fixtures import OPJ_DATA_ROOT, opj_data_file, SimpleRDF +from .fixtures import SimpleRDF @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) @@ -103,6 +88,7 @@ class TestSuite(unittest.TestCase): jp2 = glymur.Jp2k(tfile.name) self.assertEqual(jp2.box[-1].data['Make'], "HTC") + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) class TestSuiteWarns(unittest.TestCase): @@ -113,7 +99,7 @@ class TestSuiteWarns(unittest.TestCase): def tearDown(self): pass - + def test_unrecognized_exif_tag(self): """Verify warning in case of unrecognized tag.""" with tempfile.NamedTemporaryFile(suffix='.jp2', mode='wb') as tfile: @@ -137,7 +123,7 @@ class TestSuiteWarns(unittest.TestCase): tfile.flush() with self.assertWarnsRegex(UserWarning, 'Unrecognized Exif tag'): - j = glymur.Jp2k(tfile.name) + glymur.Jp2k(tfile.name) def test_bad_tag_datatype(self): """Only certain datatypes are allowable""" diff --git a/glymur/test/test_jp2box_xml.py b/glymur/test/test_jp2box_xml.py index 45d02f1..84a63f3 100644 --- a/glymur/test/test_jp2box_xml.py +++ b/glymur/test/test_jp2box_xml.py @@ -17,20 +17,9 @@ Test suite specifically targeting JP2 box layout. import os import re import struct -import sys import tempfile import unittest -if sys.hexversion < 0x03000000: - from StringIO import StringIO -else: - from io import StringIO - -if sys.hexversion <= 0x03030000: - from mock import patch -else: - from unittest.mock import patch - import lxml.etree as ET import glymur @@ -43,6 +32,7 @@ from .fixtures import OPJ_DATA_ROOT, opj_data_file from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG from . import fixtures + @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) class TestXML(unittest.TestCase): """Test suite for XML boxes.""" @@ -167,7 +157,6 @@ class TestXML(unittest.TestCase): u'Россия') - class TestJp2kBadXmlFile(unittest.TestCase): """Test suite for bad XML box situations""" @@ -293,22 +282,19 @@ class TestXML_OpjDataRoot(unittest.TestCase): 'nonregression', 'issue171.jp2')) msg = 'An illegal BOM \(byte order marker\) was detected and removed ' - msg += 'from the XML contents in the box starting at byte offset \d+' + msg += 'from the XML contents in the box starting at byte offset \d+' with self.assertWarnsRegex(UserWarning, re.compile(msg)): jp2 = Jp2k(filename) self.assertIsNotNone(jp2.box[3].xml) - def test_invalid_utf8(self): """Bad byte sequence that cannot be parsed.""" + relname = '26ccf3651020967f7778238ef5af08af.SIGFPE.d25.527.jp2' filename = opj_data_file(os.path.join('input', 'nonregression', - '26ccf3651020967f7778238ef5af08af.SIGFPE.d25.527.jp2')) + relname)) with self.assertWarns((UserWarning, UserWarning)): jp2 = Jp2k(filename) self.assertIsNone(jp2.box[3].box[1].box[1].xml) - - - diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index edb19a1..cb3a9e7 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -13,7 +13,6 @@ Tests for general glymur functionality. import doctest import os import re -import shutil import struct import sys import tempfile @@ -45,6 +44,7 @@ if HAS_PYTHON_XMP_TOOLKIT: from .fixtures import OPJ_DATA_ROOT, opj_data_file from . import fixtures + # Doc tests should be run as well. def load_tests(loader, tests, ignore): # W0613: "loader" and "ignore" are necessary for the protocol @@ -77,6 +77,7 @@ class SliceProtocolBase(unittest.TestCase): self.j2k_data_r1 = self.j2k[::2, ::2] self.j2k_data_r5 = self.j2k[::32, ::32] + @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(re.match("1.5|2", glymur.version.openjpeg_version) is None, "Must have openjpeg 1.5 or higher to run") @@ -148,11 +149,6 @@ class TestSliceProtocolBaseWrite(SliceProtocolBase): @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) class TestSliceProtocolRead(SliceProtocolBase): - def test_resolution_strides_cannot_differ(self): - with self.assertRaises(IndexError): - # Strides in x/y directions cannot differ. - self.j2k[::2, ::3] - def test_resolution_strides_cannot_differ(self): with self.assertRaises(IndexError): # Strides in x/y directions cannot differ. @@ -169,8 +165,8 @@ class TestSliceProtocolRead(SliceProtocolBase): np.testing.assert_array_equal(self.j2k_data[:, :, j], band) def test_slice_in_third_dimension(self): - actual = self.j2k[:,:,1:3] - expected = self.j2k_data[:,:,1:3] + actual = self.j2k[:, :, 1:3] + expected = self.j2k_data[:, :, 1:3] np.testing.assert_array_equal(actual, expected) def test_reduce_resolution_and_slice_in_third_dimension(self): @@ -184,12 +180,12 @@ class TestSliceProtocolRead(SliceProtocolBase): np.testing.assert_array_equal(actual, expected) def test_retrieve_single_pixel(self): - actual = self.jp2[0,0] + actual = self.jp2[0, 0] expected = self.jp2_data[0, 0] np.testing.assert_array_equal(actual, expected) def test_retrieve_single_component(self): - actual = self.jp2[20,20,2] + actual = self.jp2[20, 20, 2] expected = self.jp2_data[20, 20, 2] np.testing.assert_array_equal(actual, expected) @@ -226,7 +222,7 @@ class TestSliceProtocolRead(SliceProtocolBase): def test_single_slice(self): rows = slice(3, 8) actual = self.j2k[rows] - expected = self.j2k_data[3:8, :,:] + expected = self.j2k_data[3:8, :, :] np.testing.assert_array_equal(actual, expected) @unittest.skipIf(re.match("0|1", glymur.version.openjpeg_version), @@ -235,7 +231,7 @@ class TestSliceProtocolRead(SliceProtocolBase): """ maximim rlevel - There seems to be a difference between version of openjpeg, as + There seems to be a difference between version of openjpeg, as openjp2 produces an image of size (16, 13, 3) and openjpeg produced (17, 12, 3). """ @@ -243,6 +239,7 @@ class TestSliceProtocolRead(SliceProtocolBase): expected = self.j2k_data_r5[1:17, 1:14] np.testing.assert_array_equal(actual, expected) + class TestJp2k(unittest.TestCase): """These tests should be run by just about all configuration.""" @@ -319,7 +316,7 @@ class TestJp2k(unittest.TestCase): @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(re.match('1.[0-4]', openjpeg_version) is not None, - "Not supported with OpenJPEG {0}".format(openjpeg_version)) + "Not supported on OpenJPEG {0}".format(openjpeg_version)) @unittest.skipIf(re.match('1.5.(1|2)', openjpeg_version) is not None, "Mysteriously fails in 1.5.1 and 1.5.2") def test_no_cxform_pclr_jpx(self): @@ -638,7 +635,7 @@ class TestJp2k(unittest.TestCase): self.assertEqual(ET.tostring(jp2k.box[3].xml.getroot()), b'this is a test') - @unittest.skipIf(not HAS_PYTHON_XMP_TOOLKIT, + @unittest.skipIf(not HAS_PYTHON_XMP_TOOLKIT, "Requires Python XMP Toolkit >= 2.0") def test_xmp_attribute(self): """Verify the XMP packet in the shipping example file can be read.""" @@ -654,8 +651,9 @@ class TestJp2k(unittest.TestCase): xmp = XMPMeta() xmp.parse_from_str(j.box[3].raw_data.decode('utf-8'), xmpmeta_wrap=False) - creator_tool = xmp.get_property(libxmp.consts.XMP_NS_XMP, 'CreatorTool') - self.assertEqual(creator_tool, 'Google') + creator_tool = xmp.get_property(libxmp.consts.XMP_NS_XMP, + 'CreatorTool') + self.assertEqual(creator_tool, 'Google') @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(re.match(r'''(1|2.0.0)''', @@ -674,6 +672,7 @@ class TestJp2k(unittest.TestCase): with self.assertRaises(RuntimeError): glymur.Jp2k(self.jp2file).read_bands() + @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(re.match('1.[0-4]', openjpeg_version) is not None, "Not supported with OpenJPEG {0}".format(openjpeg_version)) @@ -693,30 +692,30 @@ class TestJp2k_write(unittest.TestCase): data = np.zeros((640, 480), dtype=np.uint8) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, data=data, - cbsize=(16, 16), psizes=[(16, 16)]) + Jp2k(tfile.name, data=data, + cbsize=(16, 16), psizes=[(16, 16)]) def test_precinct_size_not_power_of_two(self): """must be power of two""" data = np.zeros((640, 480), dtype=np.uint8) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, data=data, - cbsize=(16, 16), psizes=[(48, 48)]) + Jp2k(tfile.name, data=data, + cbsize=(16, 16), psizes=[(48, 48)]) def test_unsupported_int32(self): """Should raise a runtime error if trying to write int32""" data = np.zeros((128, 128), dtype=np.int32) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(RuntimeError): - j = Jp2k(tfile.name, data=data) + Jp2k(tfile.name, data=data) def test_unsupported_uint32(self): """Should raise a runtime error if trying to write uint32""" data = np.zeros((128, 128), dtype=np.uint32) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(RuntimeError): - j = Jp2k(tfile.name, data=data) + Jp2k(tfile.name, data=data) def test_write_with_version_too_early(self): """Should raise a runtime error if trying to write with version 1.3""" @@ -726,7 +725,7 @@ class TestJp2k_write(unittest.TestCase): with patch('glymur.version.openjpeg_version', new=version): with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(RuntimeError): - j = Jp2k(tfile.name, data=data) + Jp2k(tfile.name, data=data) def test_cblkh_different_than_width(self): """Verify that we can set a code block size where height does not equal @@ -745,31 +744,31 @@ class TestJp2k_write(unittest.TestCase): """OpenJP2 only allows 2D or 3D images.""" with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, - data=np.zeros((128, 128, 2, 2), dtype=np.uint8)) + Jp2k(tfile.name, + data=np.zeros((128, 128, 2, 2), dtype=np.uint8)) def test_2d_rgb(self): """RGB must have at least 3 components.""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, - data=np.zeros((128, 128, 2), dtype=np.uint8), - colorspace='rgb') + Jp2k(tfile.name, + data=np.zeros((128, 128, 2), dtype=np.uint8), + colorspace='rgb') def test_colorspace_with_j2k(self): """Specifying a colorspace with J2K does not make sense""" with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, - data=np.zeros((128, 128, 3), dtype=np.uint8), - colorspace='rgb') + Jp2k(tfile.name, + data=np.zeros((128, 128, 3), dtype=np.uint8), + colorspace='rgb') def test_specify_rgb(self): """specify RGB explicitly""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: j = Jp2k(tfile.name, - data=np.zeros((128, 128, 3), dtype=np.uint8), - colorspace='rgb') + data=np.zeros((128, 128, 3), dtype=np.uint8), + colorspace='rgb') self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB) def test_specify_gray(self): @@ -804,7 +803,7 @@ class TestJp2k_write(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with self.assertRaises(IOError): data = np.zeros((128, 128, 3), dtype=np.uint8) - j = Jp2k(tfile.name, data=data, colorspace='ycc') + Jp2k(tfile.name, data=data, colorspace='ycc') def test_write_with_jp2_in_caps(self): """should be able to write with JP2 suffix.""" @@ -833,7 +832,7 @@ class TestJp2k_write(unittest.TestCase): expdata = j2k[:] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with self.assertRaises(IOError): - ofile = Jp2k(tfile.name, data=expdata[:, :, 0], mct=True) + Jp2k(tfile.name, data=expdata[:, :, 0], mct=True) def test_write_cprl(self): """Must be able to write a CPRL progression order file""" @@ -924,7 +923,7 @@ class TestJp2k_2_0(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: data = np.zeros((128, 128, 3), dtype=np.uint8) with self.assertRaises(IOError): - j = Jp2k(tfile.name, data=data, colorspace='cmyk') + Jp2k(tfile.name, data=data, colorspace='cmyk') @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) def test_asoc_label_box(self): @@ -933,7 +932,7 @@ class TestJp2k_2_0(unittest.TestCase): # OpenJPEG doesn't have such a file. data = Jp2k(self.jp2file)[::2, ::2] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = Jp2k(tfile.name, data=data) + Jp2k(tfile.name, data=data) with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2: @@ -1041,12 +1040,15 @@ class TestJp2k_2_1(unittest.TestCase): Invalid\svalues\sfor\scomp\s=\s0\s+ :\sdx=1\sdy=0''', re.VERBOSE) if sys.hexversion < 0x03020000: - with self.assertRaisesRegexp((IOError, OSError), regexp): + with self.assertRaisesRegexp((IOError, OSError), + regexp): j[::2, ::2] else: - with self.assertRaisesRegex((IOError, OSError), regexp): + with self.assertRaisesRegex((IOError, OSError), + regexp): j[::2, ::2] + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") class TestParsing(unittest.TestCase): @@ -1064,22 +1066,23 @@ class TestParsing(unittest.TestCase): """Should not warn if RSIZ when parsing is turned off.""" filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') glymur.set_parseoptions(codestream=False) - j = Jp2k(filename) + Jp2k(filename) glymur.set_parseoptions(codestream=True) with self.assertWarnsRegex(UserWarning, 'Invalid profile'): - jp2 = Jp2k(filename) + Jp2k(filename) def test_main_header(self): - """Verify that the main header is not loaded when parsing turned off.""" + """Verify that the main header isn't loaded when parsing turned off.""" # The hidden _main_header attribute should show up after accessing it. glymur.set_parseoptions(codestream=False) jp2 = Jp2k(self.jp2file) jp2c = jp2.box[4] self.assertIsNone(jp2c._main_header) - main_header = jp2c.main_header + jp2c.main_header self.assertIsNotNone(jp2c._main_header) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @@ -1101,13 +1104,13 @@ class TestJp2kOpjDataRootWarnings(unittest.TestCase): """Should warn in case of bad ftyp brand.""" filename = opj_data_file('input/nonregression/edf_c2_1000290.jp2') with self.assertWarns(UserWarning): - jp2 = Jp2k(filename) + Jp2k(filename) def test_invalid_approximation(self): """Should warn in case of invalid approximation.""" filename = opj_data_file('input/nonregression/edf_c2_1015644.jp2') with self.assertWarnsRegex(UserWarning, 'Invalid approximation'): - jp2 = Jp2k(filename) + Jp2k(filename) def test_invalid_colorspace(self): """ @@ -1117,13 +1120,13 @@ class TestJp2kOpjDataRootWarnings(unittest.TestCase): """ filename = opj_data_file('input/nonregression/edf_c2_1103421.jp2') with self.assertWarns(UserWarning): - jp2 = Jp2k(filename) + Jp2k(filename) def test_stupid_windows_eol_at_end(self): """Garbage characters at the end of the file.""" filename = opj_data_file('input/nonregression/issue211.jp2') with self.assertWarns(UserWarning): - jp2 = Jp2k(filename) + Jp2k(filename) @unittest.skipIf(OPJ_DATA_ROOT is None, @@ -1148,7 +1151,7 @@ class TestJp2kOpjDataRoot(unittest.TestCase): actdata = j[:] self.assertTrue(fixtures.mse(actdata, expdata) < 250) - + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_no_cxform_pclr_jp2(self): """Indices for pclr jpxfile if no color transform""" @@ -1180,7 +1183,7 @@ class TestJp2kOpjDataRoot(unittest.TestCase): j = Jp2k(filename) with self.assertRaises(RuntimeError): j[:] - + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_no_cxform_cmap(self): """Bands as physically ordered, not as physically intended""" @@ -1196,31 +1199,11 @@ class TestJp2kOpjDataRoot(unittest.TestCase): expected = np.zeros(ycbcr.shape, ycbcr.dtype) for k in range(crcby.shape[2]): - expected[:,:,crcby.shape[2] - k - 1] = crcby[:,:,k] + expected[:, :, crcby.shape[2] - k - 1] = crcby[:, :, k] np.testing.assert_array_equal(ycbcr, expected) -class TestCodestream(unittest.TestCase): - """Test suite for unusual codestream cases.""" - - def setUp(self): - self.jp2file = glymur.data.nemo() - - def tearDown(self): - pass - - def test_siz_segment_ssiz_unsigned(self): - """ssiz attribute to be removed in future release""" - j = Jp2k(self.jp2file) - codestream = j.get_codestream() - - # The ssiz attribute was simply a tuple of raw bytes. - # The first 7 bits are interpreted as the bitdepth, the MSB determines - # whether or not it is signed. - self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7)) - - @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") class TestCodestreamOpjData(unittest.TestCase): @@ -1274,7 +1257,6 @@ class TestCodestreamOpjData(unittest.TestCase): # codestream, so the last one is EOC. self.assertEqual(codestream.segment[-1].marker_id, 'EOC') - def test_siz_segment_ssiz_signed(self): """ssiz attribute to be removed in future release""" filename = os.path.join(OPJ_DATA_ROOT, 'input/conformance/p0_03.j2k') @@ -1329,6 +1311,16 @@ class TestCodestreamRepr(unittest.TestCase): self.assertEqual(newseg.bitdepth, (8, 8, 8)) self.assertEqual(newseg.signed, (False, False, False)) + def test_siz_segment_ssiz_unsigned(self): + """ssiz attribute to be removed in future release""" + j = Jp2k(self.jp2file) + codestream = j.get_codestream() + + # The ssiz attribute was simply a tuple of raw bytes. + # The first 7 bits are interpreted as the bitdepth, the MSB determines + # whether or not it is signed. + self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7)) + class TestCodestream(unittest.TestCase): """Test suite for unusual codestream cases.""" @@ -1348,112 +1340,3 @@ class TestCodestream(unittest.TestCase): # The first 7 bits are interpreted as the bitdepth, the MSB determines # whether or not it is signed. self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7)) - - -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -class TestCodestreamOpjData(unittest.TestCase): - """Test suite for unusual codestream cases. Uses OPJ_DATA_ROOT""" - - def setUp(self): - self.jp2file = glymur.data.nemo() - - def tearDown(self): - pass - - @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(OPJ_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. - read_buffer = ifile.read(45) - tfile.write(read_buffer) - - # Write the new marker segment, 0xff6f = 65391 - read_buffer = struct.pack('>HHB', int(65391), int(3), int(0)) - tfile.write(read_buffer) - - # Get the rest of the input file. - read_buffer = ifile.read() - tfile.write(read_buffer) - tfile.flush() - - codestream = Jp2k(tfile.name).get_codestream() - - self.assertEqual(codestream.segment[2].marker_id, '0xff6f') - 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(OPJ_DATA_ROOT, - 'input/nonregression/123.j2c') - j = Jp2k(filename) - 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(codestream.segment[-1].marker_id, 'EOC') - - - def test_siz_segment_ssiz_signed(self): - """ssiz attribute to be removed in future release""" - filename = os.path.join(OPJ_DATA_ROOT, 'input/conformance/p0_03.j2k') - j = Jp2k(filename) - codestream = j.get_codestream() - - # The ssiz attribute was simply a tuple of raw bytes. - # The first 7 bits are interpreted as the bitdepth, the MSB determines - # whether or not it is signed. - self.assertEqual(codestream.segment[1].ssiz, (131,)) - - -class TestCodestreamRepr(unittest.TestCase): - - def setUp(self): - self.jp2file = glymur.data.nemo() - - def tearDown(self): - pass - - def test_soc(self): - """Test SOC segment repr""" - segment = glymur.codestream.SOCsegment() - newseg = eval(repr(segment)) - self.assertEqual(newseg.marker_id, 'SOC') - - def test_siz(self): - """Test SIZ segment repr""" - kwargs = {'rsiz': 0, - 'xysiz': (2592, 1456), - 'xyosiz': (0, 0), - 'xytsiz': (2592, 1456), - 'xytosiz': (0, 0), - 'Csiz': 3, - 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': ((1, 1, 1), (1, 1, 1))} - segment = glymur.codestream.SIZsegment(**kwargs) - newseg = eval(repr(segment)) - self.assertEqual(newseg.marker_id, 'SIZ') - self.assertEqual(newseg.xsiz, 2592) - self.assertEqual(newseg.ysiz, 1456) - self.assertEqual(newseg.xosiz, 0) - self.assertEqual(newseg.yosiz, 0) - self.assertEqual(newseg.xtsiz, 2592) - self.assertEqual(newseg.ytsiz, 1456) - self.assertEqual(newseg.xtosiz, 0) - self.assertEqual(newseg.ytosiz, 0) - - self.assertEqual(newseg.xrsiz, (1, 1, 1)) - self.assertEqual(newseg.yrsiz, (1, 1, 1)) - self.assertEqual(newseg.bitdepth, (8, 8, 8)) - self.assertEqual(newseg.signed, (False, False, False)) diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 028734d..f512dfd 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -38,12 +38,11 @@ import glymur from glymur import Jp2k from glymur.jp2box import FileTypeBox, ImageHeaderBox, ColourSpecificationBox -from .fixtures import ( - OPJ_DATA_ROOT, MetadataBase, - WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG, - mse, peak_tolerance, read_pgx, opj_data_file, - OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG -) +from .fixtures import (OPJ_DATA_ROOT, MetadataBase, + WARNING_INFRASTRUCTURE_ISSUE, + WARNING_INFRASTRUCTURE_MSG, + mse, peak_tolerance, read_pgx, opj_data_file, + OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @@ -415,16 +414,17 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (203, 152), 'xyosiz': (0, 0), - 'xytsiz': (203, 152), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + 'xytsiz': (203, 152), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) + glymur.codestream.SIZsegment(**kwargs)) pargs = (glymur.core.RCME_ISO_8859_1, - "Creator: JasPer Version 1.701.0".encode()) + "Creator: JasPer Version 1.701.0".encode()) self.verifyCMEsegment(c.segment[2], - glymur.codestream.CMEsegment(*pargs)) + glymur.codestream.CMEsegment(*pargs)) # COD: Coding style default self.assertFalse(c.segment[3].scod & 2) # no sop @@ -436,7 +436,7 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[3].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[3].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[3].spcod), 9) @@ -726,7 +726,7 @@ class TestSuite2point1(unittest.TestCase): def test_NR_DEC_p1_04_j2k_57_decode(self): jfile = opj_data_file('input/conformance/p1_04.j2k') jp2k = Jp2k(jfile) - tdata = jp2k[896:1024, 896:1024] # last tile + tdata = jp2k[896:1024, 896:1024] # last tile odata = jp2k[:] np.testing.assert_array_equal(tdata, odata[896:1024, 896:1024]) @@ -798,6 +798,7 @@ class TestSuite2point1(unittest.TestCase): with self.assertRaises(IOError): j[:] + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(re.match(r'''0|1|2.0.0''', @@ -806,7 +807,7 @@ class TestSuite2point1(unittest.TestCase): class TestReadArea(unittest.TestCase): """ Runs tests introduced in version 2.0+ or that pass only in 2.0+ - + Specifically for read method with area parameter. """ @classmethod diff --git a/glymur/test/test_opj_suite_dump.py b/glymur/test/test_opj_suite_dump.py index ee1838b..6b198c1 100644 --- a/glymur/test/test_opj_suite_dump.py +++ b/glymur/test/test_opj_suite_dump.py @@ -36,14 +36,17 @@ import numpy as np import glymur from glymur import Jp2k from glymur.codestream import CMEsegment, SOTsegment, RGNsegment -from glymur.core import RCME_ISO_8859_1, RCME_BINARY +from glymur.core import (RCME_ISO_8859_1, RCME_BINARY, SRGB, + GREYSCALE, RESTRICTED_ICC_PROFILE, + ENUMERATED_COLORSPACE) from glymur.jp2box import FileTypeBox -from .fixtures import ( - MetadataBase, OPJ_DATA_ROOT, - WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG, - mse, peak_tolerance, read_pgx, opj_data_file -) +from .fixtures import (MetadataBase, OPJ_DATA_ROOT, + WARNING_INFRASTRUCTURE_ISSUE, + WARNING_INFRASTRUCTURE_MSG, + opj_data_file) + +comment1 = "Creator: AV-J2K (c) 2000,2001 Algo Vision Technology" @unittest.skipIf(OPJ_DATA_ROOT is None, @@ -82,10 +85,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (720, 243), 'xyosiz': (0, 0), - 'xytsiz': (720, 243), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 2, 2), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (720, 243), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 2, 2), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -97,7 +102,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (32, 128)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -121,9 +126,10 @@ class TestSuite(MetadataBase): self.assertEqual(actual, expected) kwargs = {'rsiz': 1, 'xysiz': (128, 128), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # QCD: Quantization default self.assertEqual(c.segment[2].sqcd & 0x1f, 0) @@ -143,7 +149,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[3].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[3].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -154,9 +160,10 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (127, 126), 'xyosiz': (0, 0), - 'xytsiz': (127, 126), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), 'xyrsiz': [(2,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (127, 126), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), 'xyrsiz': [(2,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) # sop @@ -168,7 +175,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, True, False, True, True]) + [False, False, True, False, True, True]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) @@ -178,7 +185,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[3].code_block_size), (32, 32)) # cblk self.verify_codeblock_style(c.segment[3].spcoc[3], - [False, False, True, False, True, True]) + [False, False, True, False, True, True]) self.assertEqual(c.segment[3].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -191,7 +198,8 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[4].mantissa, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - pargs = RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode() + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[5], CMEsegment(*pargs)) # One unknown marker @@ -217,9 +225,10 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (256, 256), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (4,), - 'signed': (True,), 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (4,), + 'signed': (True,), 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) @@ -231,7 +240,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -263,11 +272,11 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[6].xcrg, (65424,)) self.assertEqual(c.segment[6].ycrg, (32558,)) - pargs = RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode() + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[7], CMEsegment(*pargs)) - pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision Technology".encode()) + pargs = (RCME_ISO_8859_1, comment1.encode()) self.verifyCMEsegment(c.segment[8], CMEsegment(*pargs)) pargs = (RCME_BINARY, c.segment[9].ccme) @@ -290,10 +299,12 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (640, 480), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -305,7 +316,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, True, False, False, False]) + [False, False, True, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(c.segment[2].precinct_size, @@ -353,7 +364,7 @@ class TestSuite(MetadataBase): 2002, 1888]) pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[6], CMEsegment(*pargs)) self.verifySOTsegment(c.segment[7], SOTsegment(0, 264383, 0, 1)) @@ -367,11 +378,12 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (1024, 1024), 'xyosiz': (0, 0), - 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8, 8), - 'signed': (False, False, False, False), - 'xyrsiz': [(1, 1, 2, 2), (1, 1, 2, 2)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8, 8), + 'signed': (False, False, False, False), + 'xyrsiz': [(1, 1, 2, 2), (1, 1, 2, 2)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -383,7 +395,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -394,7 +406,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[3].code_block_size), (32, 32)) # cblk self.verify_codeblock_style(c.segment[3].spcoc[3], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[3].spcoc[4], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) @@ -404,7 +416,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[4].code_block_size), (32, 32)) # cblk self.verify_codeblock_style(c.segment[4].spcoc[3], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[4].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -441,7 +453,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[7].mantissa, [0] * 19) pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[8], CMEsegment(*pargs)) # TLM (tile-part length) @@ -460,11 +472,12 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 2, 'xysiz': (513, 129), 'xyosiz': (0, 0), - 'xytsiz': (513, 129), 'xytosiz': (0, 0), - 'bitdepth': (12, 12, 12, 12), - 'signed': (False, False, False, False), - 'xyrsiz': [(1, 2, 1, 2), (1, 1, 2, 2)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (513, 129), 'xytosiz': (0, 0), + 'bitdepth': (12, 12, 12, 12), + 'signed': (False, False, False, False), + 'xyrsiz': [(1, 2, 1, 2), (1, 1, 2, 2)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -476,7 +489,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -535,7 +548,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[7].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[7].spcoc[3], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[7].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -552,10 +565,12 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (2048, 2048), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (12, 12, 12), - 'signed': (True, True, True), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (128, 128), 'xytosiz': (0, 0), + 'bitdepth': (12, 12, 12), + 'signed': (True, True, True), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) @@ -567,7 +582,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -595,7 +610,6 @@ class TestSuite(MetadataBase): # PLT: packet length, tile part self.assertEqual(c.segment[7].zplt, 0) - #self.assertEqual(c.segment[7].iplt), 99) # SOD: start of data self.assertEqual(c.segment[8].marker_id, 'SOD') @@ -605,10 +619,12 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (513, 3072), 'xyosiz': (0, 0), - 'xytsiz': (513, 3072), 'xytosiz': (0, 0), 'bitdepth': (12, 12, 12), - 'signed': (True, True, True), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (513, 3072), 'xytosiz': (0, 0), + 'bitdepth': (12, 12, 12), + 'signed': (True, True, True), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) @@ -620,7 +636,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -631,7 +647,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[3].spcoc[3], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[3].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -641,7 +657,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[4].code_block_size), (32, 32)) # cblk self.verify_codeblock_style(c.segment[4].spcoc[3], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[4].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -651,7 +667,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[5].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[5].spcoc[3], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[5].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -696,9 +712,10 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (17, 37), 'xyosiz': (0, 0), - 'xytsiz': (17, 37), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (17, 37), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -710,7 +727,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -743,10 +760,12 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (256, 256), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(4, 4, 4), (4, 4, 4)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (128, 128), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(4, 4, 4), (4, 4, 4)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -758,7 +777,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -805,9 +824,10 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (128, 1), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -819,7 +839,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, True]) + [False, False, False, False, False, True]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(c.segment[2].precinct_size, [(128, 2)]) @@ -832,7 +852,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[3].exponent, [8]) pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) self.verifySOTsegment(c.segment[5], SOTsegment(0, 118, 0, 1)) @@ -854,10 +874,11 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (3, 5), 'xyosiz': (0, 0), - 'xytsiz': (3, 5), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (3, 5), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) @@ -869,7 +890,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, True, False, False, False]) + [False, False, True, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -882,7 +903,8 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[3].exponent, [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) self.verifySOTsegment(c.segment[5], SOTsegment(0, 162, 0, 1)) @@ -904,10 +926,12 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (1, 1), 'xyosiz': (0, 0), - 'xytsiz': (1, 1), 'xytosiz': (0, 0), 'bitdepth': tuple([8] * 257), - 'signed': tuple([False] * 257), - 'xyrsiz': [tuple([1] * 257), tuple([1] * 257)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1, 1), 'xytosiz': (0, 0), + 'bitdepth': tuple([8] * 257), + 'signed': tuple([False] * 257), + 'xyrsiz': [tuple([1] * 257), tuple([1] * 257)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -918,7 +942,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 1) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, True, False]) + [False, False, False, False, True, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -928,7 +952,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[3].spcoc[0], 1) # levels self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[3].spcoc[3], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[3].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -969,7 +993,8 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[8].ppod, (glymur.core.RLCP, glymur.core.CPRL)) - pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[9], CMEsegment(*pargs)) self.verifySOTsegment(c.segment[10], SOTsegment(0, 1537, 0, 1)) @@ -985,10 +1010,11 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (49, 49), 'xyosiz': (0, 0), - 'xytsiz': (49, 49), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (49, 49), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -999,7 +1025,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1025,10 +1051,11 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (256, 256), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (4,), - 'signed': (True,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (4,), + 'signed': (True,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) @@ -1039,7 +1066,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 1) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1072,11 +1099,11 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[6].xcrg, (65424,)) self.assertEqual(c.segment[6].ycrg, (32558,)) - pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[7], CMEsegment(*pargs)) - pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision Technology".encode()) + pargs = (RCME_ISO_8859_1, comment1.encode()) self.verifyCMEsegment(c.segment[8], CMEsegment(*pargs)) pargs = (RCME_BINARY, c.segment[9].ccme) @@ -1125,10 +1152,11 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (128, 128), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -1139,7 +1167,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 3) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1165,10 +1193,11 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 2, 'xysiz': (127, 227), 'xyosiz': (5, 128), - 'xytsiz': (127, 126), 'xytosiz': (1, 101), 'bitdepth': (8,), - 'signed': (False,), - 'xyrsiz': [(2,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (127, 126), 'xytosiz': (1, 101), 'bitdepth': (8,), + 'signed': (False,), + 'xyrsiz': [(2,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) # SOP @@ -1179,7 +1208,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 3) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, True, False, True, True]) + [False, False, True, False, True, True]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1189,7 +1218,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[3].spcoc[0], 3) # level self.assertEqual(tuple(c.segment[3].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[3].spcoc[3], - [False, False, True, False, True, True]) + [False, False, True, False, True, True]) self.assertEqual(c.segment[3].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -1201,7 +1230,8 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[4].exponent, [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[5], CMEsegment(*pargs)) self.verifySOTsegment(c.segment[6], SOTsegment(0, 4627, 0, 1)) @@ -1223,10 +1253,12 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 2, 'xysiz': (640, 480), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1238,7 +1270,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, True, False, True, False, False]) + [False, True, False, True, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(c.segment[2].precinct_size, @@ -1285,7 +1317,8 @@ class TestSuite(MetadataBase): [14, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, 9, 9, 9, 9, 9, 9]) - pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[6], CMEsegment(*pargs)) self.verifySOTsegment(c.segment[7], SOTsegment(0, 262838, 0, 1)) @@ -1305,11 +1338,12 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 2, 'xysiz': (1024, 1024), 'xyosiz': (0, 0), - 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8, 8), - 'signed': (False, False, False, False), - 'xyrsiz': [(1, 1, 2, 2), (1, 1, 2, 2)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8, 8), + 'signed': (False, False, False, False), + 'xyrsiz': [(1, 1, 2, 2), (1, 1, 2, 2)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1320,7 +1354,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 6) # level self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[2].spcod[7], - [True, False, True, False, False, False]) + [True, False, True, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1330,7 +1364,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[3].spcoc[0], 3) # level self.assertEqual(tuple(c.segment[3].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[3].spcoc[3], - [True, False, True, False, False, False]) + [True, False, True, False, False, False]) self.assertEqual(c.segment[3].spcoc[4], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) @@ -1339,7 +1373,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[4].spcoc[0], 6) # level self.assertEqual(tuple(c.segment[4].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[4].spcoc[3], - [True, False, True, False, False, False]) + [True, False, True, False, False, False]) self.assertEqual(c.segment[4].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -1375,7 +1409,8 @@ class TestSuite(MetadataBase): [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[8], CMEsegment(*pargs)) # PPM: packed packet headers, main header @@ -1400,10 +1435,11 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 2, 'xysiz': (1024, 1024), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (12,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (12,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1414,7 +1450,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 3) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1487,10 +1523,12 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 2, 'xysiz': (529, 524), 'xyosiz': (17, 12), - 'xytsiz': (37, 37), 'xytosiz': (8, 2), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (37, 37), 'xytosiz': (8, 2), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) # sop @@ -1501,7 +1539,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 7) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 8)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [True, False, False, True, True, False]) + [True, False, False, True, True, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(c.segment[2].precinct_size, [(16, 16)] * 8) @@ -1516,7 +1554,8 @@ class TestSuite(MetadataBase): [17, 17, 17, 17, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, 11, 11, 11, 11, 11, 11]) - pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) # 225 consecutive PPM segments. @@ -1543,10 +1582,11 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 2, 'xysiz': (12, 12), 'xyosiz': (0, 0), - 'xytsiz': (3, 3), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (3, 3), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) # sop @@ -1557,7 +1597,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 4) # level self.assertEqual(tuple(c.segment[2].code_block_size), (32, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, True, False, True]) + [False, False, False, True, False, True]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1573,7 +1613,8 @@ class TestSuite(MetadataBase): [14, 14, 14, 14, 13, 13, 13, 11, 11, 11, 11, 11, 11]) - pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) self.verifySOTsegment(c.segment[5], SOTsegment(0, 349, 0, 1)) @@ -1604,10 +1645,11 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 2, 'xysiz': (12, 12), 'xyosiz': (4, 0), - 'xytsiz': (12, 12), 'xytosiz': (4, 0), 'bitdepth': (8, 8), - 'signed': (False, False), - 'xyrsiz': [(4, 1), (1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (12, 12), 'xytosiz': (4, 0), 'bitdepth': (8, 8), + 'signed': (False, False), + 'xyrsiz': [(4, 1), (1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) # sop @@ -1618,7 +1660,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 1) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(c.segment[2].precinct_size, [(1, 1), (2, 2)]) @@ -1628,7 +1670,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[3].spcoc[0], 1) # level self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[3].spcoc[3], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[3].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(c.segment[3].precinct_size, [(2, 2), (4, 4)]) @@ -1640,7 +1682,8 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[4].mantissa, [0] * 4) self.assertEqual(c.segment[4].exponent, [8, 9, 9, 10]) - pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[5], CMEsegment(*pargs)) self.verifySOTsegment(c.segment[6], SOTsegment(0, 434, 0, 1)) @@ -1657,11 +1700,12 @@ class TestSuite(MetadataBase): c = jp2k.get_codestream(header_only=False) kwargs = {'rsiz': 3, 'xysiz': (1920, 1080), 'xyosiz': (0, 0), - 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), - 'bitdepth': (12, 12, 12), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), + 'bitdepth': (12, 12, 12), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1672,7 +1716,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(c.segment[2].precinct_size[0], (128, 128)) @@ -1694,7 +1738,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[4].spcoc[0], 5) # level self.assertEqual(tuple(c.segment[4].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[4].spcoc[3], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[4].spcoc[4], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) @@ -1716,7 +1760,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[6].spcoc[0], 5) # level self.assertEqual(tuple(c.segment[6].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[6].spcoc[3], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[6].spcoc[4], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) @@ -1761,10 +1805,12 @@ class TestSuite(MetadataBase): c = jp2k.get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (2592, 1944), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1775,7 +1821,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(c.segment[2].precinct_size, @@ -1794,10 +1840,11 @@ class TestSuite(MetadataBase): c = jp2k.get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (512, 512), 'xyosiz': (0, 0), - 'xytsiz': (512, 512), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (512, 512), 'xytosiz': (0, 0), 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1808,7 +1855,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1823,10 +1870,11 @@ class TestSuite(MetadataBase): c = jp2k.get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (512, 512), 'xyosiz': (0, 0), - 'xytsiz': (512, 512), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (512, 512), 'xytosiz': (0, 0), 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1837,7 +1885,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1860,10 +1908,11 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (1420, 1416), 'xyosiz': (0, 0), - 'xytsiz': (1420, 1416), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1420, 1416), 'xytosiz': (0, 0), 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1874,7 +1923,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 11) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1897,10 +1946,11 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (512, 614), 'xyosiz': (0, 0), - 'xytsiz': (512, 614), 'xytosiz': (0, 0), 'bitdepth': (12,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (512, 614), 'xytosiz': (0, 0), 'bitdepth': (12,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1911,7 +1961,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1940,10 +1990,11 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (256, 256), 'xyosiz': (0, 0), - 'xytsiz': (256, 256), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (256, 256), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1954,7 +2005,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1981,10 +2032,11 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (1420, 1416), 'xyosiz': (0, 0), - 'xytsiz': (1420, 1416), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1420, 1416), 'xytosiz': (0, 0), 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1995,7 +2047,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 11) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2017,10 +2069,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (256, 256), 'xyosiz': (0, 0), - 'xytsiz': (256, 256), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (True, True, True), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (256, 256), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (True, True, True), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2031,7 +2085,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2054,10 +2108,11 @@ class TestSuite(MetadataBase): c = jp2k.get_codestream() kwargs = {'rsiz': 0, 'xysiz': (2048, 2500), 'xyosiz': (0, 0), - 'xytsiz': (2048, 2500), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (2048, 2500), 'xytosiz': (0, 0), 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2068,7 +2123,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 8) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2101,17 +2156,17 @@ class TestSuite(MetadataBase): pargs = (RCME_ISO_8859_1, ccme.encode()) self.verifyCMEsegment(c.segment[5], CMEsegment(*pargs)) - def test_NR_MarkerIsNotCompliant_j2k_dump(self): jfile = opj_data_file('input/nonregression/MarkerIsNotCompliant.j2k') jp2k = Jp2k(jfile) c = jp2k.get_codestream() kwargs = {'rsiz': 0, 'xysiz': (1420, 1416), 'xyosiz': (0, 0), - 'xytsiz': (1420, 1416), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1420, 1416), 'xytosiz': (0, 0), 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2122,7 +2177,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 11) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2142,10 +2197,12 @@ class TestSuite(MetadataBase): c = jp2k.get_codestream() kwargs = {'rsiz': 0, 'xysiz': (1920, 1080), 'xyosiz': (0, 0), - 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2156,7 +2213,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2174,10 +2231,12 @@ class TestSuite(MetadataBase): c = jp2k.get_codestream() kwargs = {'rsiz': 0, 'xysiz': (1920, 1080), 'xyosiz': (0, 0), - 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2188,7 +2247,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2206,10 +2265,12 @@ class TestSuite(MetadataBase): c = jp2k.get_codestream() kwargs = {'rsiz': 0, 'xysiz': (1920, 1080), 'xyosiz': (0, 0), - 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2220,7 +2281,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2242,10 +2303,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (117, 117), 'xyosiz': (0, 0), - 'xytsiz': (117, 117), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8, 8), - 'signed': (False, False, False, False), - 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (117, 117), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8, 8), + 'signed': (False, False, False, False), + 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2256,7 +2319,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2278,10 +2341,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (117, 117), 'xyosiz': (0, 0), - 'xytsiz': (117, 117), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8, 8), - 'signed': (False, False, False, False), - 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (117, 117), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8, 8), + 'signed': (False, False, False, False), + 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2292,7 +2357,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2314,10 +2379,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (512, 512), 'xyosiz': (0, 0), - 'xytsiz': (512, 512), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (512, 512), 'xytosiz': (0, 0), + 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2328,7 +2395,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2354,10 +2421,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (1024, 1024), 'xyosiz': (0, 0), - 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), 'bitdepth': (12,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), + 'bitdepth': (12,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2368,7 +2437,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2394,10 +2463,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (1800, 1800), 'xyosiz': (0, 0), - 'xytsiz': (1800, 1800), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1800, 1800), 'xytosiz': (0, 0), + 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2406,10 +2477,9 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].layers, 1) # layers = 1 self.assertEqual(c.segment[2].spcod[3], 1) # mct self.assertEqual(c.segment[2].spcod[4], 11) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2431,10 +2501,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (1800, 1800), 'xyosiz': (0, 0), - 'xytsiz': (1800, 1800), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1800, 1800), 'xytosiz': (0, 0), + 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2443,10 +2515,9 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].layers, 1) # layers = 1 self.assertEqual(c.segment[2].spcod[3], 1) # mct self.assertEqual(c.segment[2].spcod[4], 11) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2468,10 +2539,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (2048, 1556), 'xyosiz': (0, 0), - 'xytsiz': (2048, 1556), 'xytosiz': (0, 0), 'bitdepth': (12, 12, 12), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (2048, 1556), 'xytosiz': (0, 0), + 'bitdepth': (12, 12, 12), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2480,10 +2553,9 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].layers, 2) # layers = 2 self.assertEqual(c.segment[2].spcod[3], 1) # mct self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk + self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(c.segment[2].precinct_size, @@ -2512,7 +2584,9 @@ class TestSuite(MetadataBase): self.verifySignatureBox(jp2.box[0]) self.verify_filetype_box(jp2.box[1], - FileTypeBox(compatibility_list=['jp2 ', 'jpxb', 'jpx '])) + FileTypeBox(compatibility_list=['jp2 ', + 'jpxb', + 'jpx '])) # Reader requirements talk. # unrestricted jpeg 2000 part 1 @@ -2521,9 +2595,9 @@ class TestSuite(MetadataBase): ihdr = glymur.jp2box.ImageHeaderBox(203, 479, colorspace_unknown=True) self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox( - colorspace=glymur.core.SRGB, - approximation=1, precedence=2) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=SRGB, + approximation=1, + precedence=2) self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) # Jp2 Header @@ -2543,10 +2617,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (479, 203), 'xyosiz': (0, 0), - 'xytsiz': (256, 203), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (256, 203), 'xytosiz': (0, 0), + 'bitdepth': (8,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2555,10 +2631,9 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].layers, 1) # layers = 1 self.assertEqual(c.segment[2].spcod[3], 0) # mct self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk + self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2581,19 +2656,22 @@ class TestSuite(MetadataBase): self.verifySignatureBox(jp2.box[0]) self.verify_filetype_box(jp2.box[1], - FileTypeBox(compatibility_list=['jp2 ', 'jpxb', 'jpx '])) + FileTypeBox(compatibility_list=['jp2 ', + 'jpxb', + 'jpx '])) # Reader requirements talk. # unrestricted jpeg 2000 part 1 self.assertTrue(5 in jp2.box[2].standard_flag) ihdr = glymur.jp2box.ImageHeaderBox(326, 431, - num_components=3, colorspace_unknown=True) + num_components=3, + colorspace_unknown=True) self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox( - colorspace=glymur.core.SRGB, - approximation=1, precedence=2) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=SRGB, + approximation=1, + precedence=2) self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) c = jp2.box[4].main_header @@ -2603,10 +2681,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (431, 326), 'xyosiz': (0, 0), - 'xytsiz': (256, 256), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (256, 256), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2615,10 +2695,9 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].layers, 1) # layers = 1 self.assertEqual(c.segment[2].spcod[3], 1) # mct self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk + self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2646,11 +2725,10 @@ class TestSuite(MetadataBase): self.verify_filetype_box(jp2.box[1], FileTypeBox()) ihdr = glymur.jp2box.ImageHeaderBox(135, 135, num_components=2, - colorspace_unknown=True) + colorspace_unknown=True) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox( - colorspace=glymur.core.GREYSCALE) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=GREYSCALE) self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) # Jp2 Header @@ -2666,10 +2744,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (135, 135), 'xyosiz': (0, 0), - 'xytsiz': (135, 135), 'xytosiz': (0, 0), 'bitdepth': (8, 8), - 'signed': (False, False), - 'xyrsiz': [(1, 1), (1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (135, 135), 'xytosiz': (0, 0), + 'bitdepth': (8, 8), + 'signed': (False, False), + 'xyrsiz': [(1, 1), (1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2681,7 +2761,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2710,20 +2790,23 @@ class TestSuite(MetadataBase): self.verifySignatureBox(jp2.box[0]) self.verify_filetype_box(jp2.box[1], - FileTypeBox(compatibility_list=['jp2 ', 'jpxb', 'jpx '])) + FileTypeBox(compatibility_list=['jp2 ', + 'jpxb', + 'jpx '])) # Reader requirements talk. # unrestricted jpeg 2000 part 1 self.assertTrue(5 in jp2.box[2].standard_flag) ihdr = glymur.jp2box.ImageHeaderBox(46, 124, bits_per_component=4, - colorspace_unknown=True) + colorspace_unknown=True) self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox( - colorspace=glymur.core.SRGB, - method=glymur.core.ENUMERATED_COLORSPACE, - approximation=1, precedence=2) + method = ENUMERATED_COLORSPACE + colr = glymur.jp2box.ColourSpecificationBox(colorspace=SRGB, + method=method, + approximation=1, + precedence=2) self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) # Jp2 Header @@ -2744,10 +2827,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (124, 46), 'xyosiz': (0, 0), - 'xytsiz': (124, 46), 'xytosiz': (0, 0), 'bitdepth': (4,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (124, 46), 'xytosiz': (0, 0), + 'bitdepth': (4,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2759,7 +2844,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2796,10 +2881,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (766, 576), 'xyosiz': (0, 0), - 'xytsiz': (766, 576), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 2, 2), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (766, 576), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 2, 2), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2811,7 +2898,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (32, 128)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2847,7 +2934,7 @@ class TestSuiteWarns(MetadataBase): relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2' jfile = opj_data_file(relpath) with self.assertWarns(UserWarning): - d = Jp2k(jfile)[:] + Jp2k(jfile)[:] self.assertTrue(True) def test_NR_broken4_jp2_dump(self): @@ -2882,7 +2969,7 @@ class TestSuiteWarns(MetadataBase): ihdr = glymur.jp2box.ImageHeaderBox(152, 203, num_components=3) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.SRGB) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=SRGB) self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) c = jp2.box[3].main_header @@ -2892,10 +2979,12 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (203, 152), 'xyosiz': (0, 0), - 'xytsiz': (203, 152), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (203, 152), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) pargs = RCME_ISO_8859_1, "Creator: JasPer Vers)on 1.701.0".encode() self.verifyCMEsegment(c.segment[2], CMEsegment(*pargs)) @@ -2910,7 +2999,7 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[3].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[3].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[3].spcod), 9) @@ -2950,7 +3039,7 @@ class TestSuiteWarns(MetadataBase): with self.assertWarns(UserWarning): # Invalid marker ID on codestream. jp2 = Jp2k(jfile) - + self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') def test_NR_file1_dump(self): @@ -2978,8 +3067,8 @@ class TestSuiteWarns(MetadataBase): ihdr = glymur.jp2box.ImageHeaderBox(512, 768, num_components=3) self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.SRGB, - approximation=1) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=SRGB, + approximation=1) self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) # XML box @@ -3006,7 +3095,7 @@ class TestSuiteWarns(MetadataBase): self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.YCC, - approximation=1) + approximation=1) self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) # Jp2 Header @@ -3036,9 +3125,8 @@ class TestSuiteWarns(MetadataBase): ihdr = glymur.jp2box.ImageHeaderBox(640, 480, num_components=3) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox( - colorspace=glymur.core.YCC, - approximation=1) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.YCC, + approximation=1) self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) # sub-sampling @@ -3068,8 +3156,8 @@ class TestSuiteWarns(MetadataBase): ihdr = glymur.jp2box.ImageHeaderBox(512, 768) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox( - colorspace=glymur.core.GREYSCALE, approximation=1) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=GREYSCALE, + approximation=1) self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) def test_NR_file5_dump(self): @@ -3091,16 +3179,18 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(ids, ['ihdr', 'colr', 'colr']) self.verifySignatureBox(jp2.box[0]) - expected = FileTypeBox( - brand='jpx ', compatibility_list=['jp2 ', 'jpx ', 'jpxb']) + expected = FileTypeBox(brand='jpx ', + compatibility_list=['jp2 ', 'jpx ', 'jpxb']) self.verify_filetype_box(jp2.box[1], expected) ihdr = glymur.jp2box.ImageHeaderBox(512, 768, num_components=3) self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox( - method=glymur.core.RESTRICTED_ICC_PROFILE, - approximation=1, icc_profile=bytes([0] * 546)) + method = RESTRICTED_ICC_PROFILE + icc_profile = bytes([0] * 546) + colr = glymur.jp2box.ColourSpecificationBox(method=method, + approximation=1, + icc_profile=icc_profile) self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 546) @@ -3121,10 +3211,10 @@ class TestSuiteWarns(MetadataBase): ihdr = glymur.jp2box.ImageHeaderBox(512, 768, bits_per_component=12) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox( - colorspace=glymur.core.GREYSCALE, - method=glymur.core.ENUMERATED_COLORSPACE, - approximation=1) + method = ENUMERATED_COLORSPACE + colr = glymur.jp2box.ColourSpecificationBox(colorspace=GREYSCALE, + method=method, + approximation=1) self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) def test_NR_file7_dump(self): @@ -3150,12 +3240,13 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') ihdr = glymur.jp2box.ImageHeaderBox(640, 480, - num_components=3, bits_per_component=16) + num_components=3, + bits_per_component=16) self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox( - method=glymur.core.RESTRICTED_ICC_PROFILE, - approximation=1) + method = RESTRICTED_ICC_PROFILE + colr = glymur.jp2box.ColourSpecificationBox(method=method, + approximation=1) self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 13332) @@ -3179,9 +3270,9 @@ class TestSuiteWarns(MetadataBase): ihdr = glymur.jp2box.ImageHeaderBox(400, 700) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox( - method=glymur.core.RESTRICTED_ICC_PROFILE, - approximation=1) + method = RESTRICTED_ICC_PROFILE + colr = glymur.jp2box.ColourSpecificationBox(method=method, + approximation=1) self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) self.assertEqual(jp2.box[2].box[1].icc_profile['Size'], 414) @@ -3234,16 +3325,15 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(jp2.box[2].box[2].mapping_type, (1, 1, 1)) self.assertEqual(jp2.box[2].box[2].palette_index, (0, 1, 2)) - colr = glymur.jp2box.ColourSpecificationBox( - colorspace=glymur.core.SRGB, - approximation=1) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=SRGB, + approximation=1) self.verifyColourSpecificationBox(jp2.box[2].box[3], colr) def test_NR_issue188_beach_64bitsbox(self): lst = ['input', 'nonregression', 'issue188_beach_64bitsbox.jp2'] jfile = opj_data_file('/'.join(lst)) with self.assertWarns(UserWarning): - # There's a warning for an unknown box. + # There's a warning for an unknown box. jp2 = Jp2k(jfile) ids = [box.box_id for box in jp2.box] @@ -3256,10 +3346,12 @@ class TestSuiteWarns(MetadataBase): self.verify_filetype_box(jp2.box[1], FileTypeBox()) ihdr = glymur.jp2box.ImageHeaderBox(200, 200, - num_components=3, colorspace_unknown=True) + num_components=3, + colorspace_unknown=True) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.SRGB) + cspace = glymur.core.SRGB + colr = glymur.jp2box.ColourSpecificationBox(colorspace=cspace) self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) # Skip the 4th box, it is uknown. @@ -3271,10 +3363,12 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (200, 200), 'xyosiz': (0, 0), - 'xytsiz': (200, 200), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (200, 200), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -3286,7 +3380,7 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -3329,10 +3423,12 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (117, 117), 'xyosiz': (0, 0), - 'xytsiz': (117, 117), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8, 8), - 'signed': (False, False, False, False), - 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (117, 117), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8, 8), + 'signed': (False, False, False, False), + 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -3344,7 +3440,7 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -3390,10 +3486,12 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (117, 117), 'xyosiz': (0, 0), - 'xytsiz': (117, 117), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8, 8), - 'signed': (False, False, False, False), - 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (117, 117), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8, 8), + 'signed': (False, False, False, False), + 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -3405,7 +3503,7 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index c7e31e5..1d3ec6e 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -10,7 +10,6 @@ seem like logical negative tests to add. import os import re -import sys import tempfile import unittest @@ -88,7 +87,6 @@ class TestSuiteNegativeWrite(unittest.TestCase): def tearDown(self): pass - @unittest.skipIf(NO_SKIMAGE_FREEIMAGE_SUPPORT, "Cannot read input image without scikit-image/freeimage") def test_cinema2K_bad_frame_rate(self): @@ -98,8 +96,7 @@ class TestSuiteNegativeWrite(unittest.TestCase): data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, data=data, cinema2k=36) - + Jp2k(tfile.name, data=data, cinema2k=36) @unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG) def test_psnr_with_cratios(self): @@ -109,8 +106,8 @@ class TestSuiteNegativeWrite(unittest.TestCase): data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, - data=data, psnr=[30, 35, 40], cratios=[2, 3, 4]) + Jp2k(tfile.name, + data=data, psnr=[30, 35, 40], cratios=[2, 3, 4]) def test_code_block_dimensions(self): """don't allow extreme codeblock sizes""" @@ -120,13 +117,13 @@ class TestSuiteNegativeWrite(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: # opj_compress doesn't allow code block area to exceed 4096. with self.assertRaises(IOError): - j = Jp2k(tfile.name, data=data, cbsize=(256, 256)) + Jp2k(tfile.name, data=data, cbsize=(256, 256)) # opj_compress doesn't allow either dimension to be less than 4. with self.assertRaises(IOError): - j = Jp2k(tfile.name, data=data, cbsize=(2048, 2)) + Jp2k(tfile.name, data=data, cbsize=(2048, 2)) with self.assertRaises(IOError): - j = Jp2k(tfile.name, data=data, cbsize=(2, 2048)) + Jp2k(tfile.name, data=data, cbsize=(2, 2048)) def test_precinct_size_not_p2(self): """precinct sizes should be powers of two.""" @@ -134,7 +131,7 @@ class TestSuiteNegativeWrite(unittest.TestCase): data = ifile[::4, ::4] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with self.assertRaises(IOError): - ofile = Jp2k(tfile.name, data=data, psizes=[(13, 13)]) + Jp2k(tfile.name, data=data, psizes=[(13, 13)]) def test_cblk_size_not_power_of_two(self): """code block sizes should be powers of two.""" @@ -142,7 +139,7 @@ class TestSuiteNegativeWrite(unittest.TestCase): data = ifile[::4, ::4] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with self.assertRaises(IOError): - ofile = Jp2k(tfile.name, data=data, cbsize=(13, 12)) + Jp2k(tfile.name, data=data, cbsize=(13, 12)) def test_cblk_size_precinct_size(self): """code block sizes should never exceed half that of precinct size.""" @@ -150,6 +147,4 @@ class TestSuiteNegativeWrite(unittest.TestCase): data = ifile[::4, ::4] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with self.assertRaises(IOError): - ofile = Jp2k(tfile.name, - data=data, cbsize=(64, 64), psizes=[(64, 64)]) - + Jp2k(tfile.name, data=data, cbsize=(64, 64), psizes=[(64, 64)]) diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index ecb7a7d..7f0fd4b 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -34,6 +34,7 @@ from glymur import Jp2k from glymur.codestream import SIZsegment from glymur.version import openjpeg_version + class CinemaBase(fixtures.MetadataBase): def verify_cinema_cod(self, cod_segment): @@ -44,14 +45,14 @@ class CinemaBase(fixtures.MetadataBase): self.assertEqual(cod_segment.layers, 1) self.assertEqual(cod_segment.spcod[3], 1) # mct self.assertEqual(cod_segment.spcod[4], 5) # levels - self.assertEqual(tuple(cod_segment.code_block_size), (32, 32)) # cblksz + self.assertEqual(tuple(cod_segment.code_block_size), (32, 32)) def check_cinema4k_codestream(self, codestream, image_size): kwargs = {'rsiz': 4, 'xysiz': image_size, 'xyosiz': (0, 0), - 'xytsiz': image_size, 'xytosiz': (0, 0), - 'bitdepth': (12, 12, 12), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + 'xytsiz': image_size, 'xytosiz': (0, 0), + 'bitdepth': (12, 12, 12), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} self.verifySizSegment(codestream.segment[1], SIZsegment(**kwargs)) self.verify_cinema_cod(codestream.segment[2]) @@ -59,9 +60,9 @@ class CinemaBase(fixtures.MetadataBase): def check_cinema2k_codestream(self, codestream, image_size): kwargs = {'rsiz': 3, 'xysiz': image_size, 'xyosiz': (0, 0), - 'xytsiz': image_size, 'xytosiz': (0, 0), - 'bitdepth': (12, 12, 12), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + 'xytsiz': image_size, 'xytosiz': (0, 0), + 'bitdepth': (12, 12, 12), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} self.verifySizSegment(codestream.segment[1], SIZsegment(**kwargs)) self.verify_cinema_cod(codestream.segment[2]) @@ -88,8 +89,8 @@ class WriteCinema(CinemaBase): data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, data=data, - cinema2k=48, cratios=[200, 100, 50]) + Jp2k(tfile.name, data=data, + cinema2k=48, cratios=[200, 100, 50]) def test_cinema4K_with_others(self): """Can't specify cinema4k with any other options.""" @@ -98,8 +99,8 @@ class WriteCinema(CinemaBase): data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, data=data, - cinema4k=True, cratios=[200, 100, 50]) + Jp2k(tfile.name, data=data, + cinema4k=True, cratios=[200, 100, 50]) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @@ -135,7 +136,8 @@ class WriteCinemaWarns(CinemaBase): infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertWarnsRegex(UserWarning, 'OpenJPEG library warning'): + with self.assertWarnsRegex(UserWarning, + 'OpenJPEG library warning'): j = Jp2k(tfile.name, data=data, cinema2k=48) codestream = j.get_codestream() @@ -146,7 +148,8 @@ class WriteCinemaWarns(CinemaBase): infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertWarnsRegex(UserWarning, 'OpenJPEG library warning'): + with self.assertWarnsRegex(UserWarning, + 'OpenJPEG library warning'): j = Jp2k(tfile.name, data=data, cinema2k=48) codestream = j.get_codestream() @@ -157,7 +160,8 @@ class WriteCinemaWarns(CinemaBase): infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertWarnsRegex(UserWarning, 'OpenJPEG library warning'): + with self.assertWarnsRegex(UserWarning, + 'OpenJPEG library warning'): j = Jp2k(tfile.name, data=data, cinema2k=24) codestream = j.get_codestream() @@ -168,7 +172,8 @@ class WriteCinemaWarns(CinemaBase): infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertWarnsRegex(UserWarning, 'OpenJPEG library warning'): + with self.assertWarnsRegex(UserWarning, + 'OpenJPEG library warning'): # OpenJPEG library warning: The desired maximum codestream # size has limited at least one of the desired quality layers j = Jp2k(tfile.name, data=data, cinema2k=24) @@ -216,7 +221,7 @@ class TestNegative2pointzero(unittest.TestCase): with patch('glymur.version.openjpeg_version', new=version): with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, data=data, cinema2k=48) + Jp2k(tfile.name, data=data, cinema2k=48) @unittest.skipIf(re.match(r'''1.[0-4]''', openjpeg_version) is not None, @@ -248,7 +253,6 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - def test_NR_ENC_Bretagne1_ppm_1_encode(self): """NR-ENC-Bretagne1.ppm-1-encode""" infile = opj_data_file('input/nonregression/Bretagne1.ppm') @@ -260,10 +264,11 @@ class TestSuiteWrite(fixtures.MetadataBase): c = j.get_codestream() kwargs = {'rsiz': 0, 'xysiz': (640, 480), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -275,7 +280,7 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblksz self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -291,10 +296,11 @@ class TestSuiteWrite(fixtures.MetadataBase): codestream = j.get_codestream() kwargs = {'rsiz': 0, 'xysiz': (640, 480), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -306,7 +312,8 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, + False, False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -316,18 +323,19 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/Bretagne1.ppm') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, - data=data, - psnr=[30, 35, 40], cbsize=(16, 16), psizes=[(64, 64)]) + j = Jp2k(tfile.name, + data=data, + psnr=[30, 35, 40], cbsize=(16, 16), psizes=[(64, 64)]) # Should be three layers. codestream = j.get_codestream() kwargs = {'rsiz': 0, 'xysiz': (640, 480), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -339,7 +347,8 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(codestream.segment[2].code_block_size), (16, 16)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, + False, False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(codestream.segment[2].precinct_size, @@ -352,20 +361,21 @@ class TestSuiteWrite(fixtures.MetadataBase): data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, - data=data, - psizes=[(128, 128)] * 3, - cratios=[100, 20, 2], - tilesize=(480, 640), - cbsize=(32, 32)) + data=data, + psizes=[(128, 128)] * 3, + cratios=[100, 20, 2], + tilesize=(480, 640), + cbsize=(32, 32)) # Should be three layers. codestream = j.get_codestream() kwargs = {'rsiz': 0, 'xysiz': (2592, 1944), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -377,7 +387,8 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(codestream.segment[2].code_block_size), (32, 32)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, + False, False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(codestream.segment[2].precinct_size, @@ -393,10 +404,11 @@ class TestSuiteWrite(fixtures.MetadataBase): codestream = j.get_codestream() kwargs = {'rsiz': 0, 'xysiz': (2592, 1944), 'xyosiz': (0, 0), - 'xytsiz': (127, 127), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (127, 127), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -408,7 +420,8 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, + False, False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -423,10 +436,11 @@ class TestSuiteWrite(fixtures.MetadataBase): codestream = j.get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (5183, 3887), 'xyosiz': (0, 0), - 'xytsiz': (5183, 3887), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(2, 2, 2), (2, 2, 2)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (5183, 3887), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(2, 2, 2), (2, 2, 2)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(codestream.segment[2].scod & 2) # sop @@ -438,7 +452,8 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, + False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -458,10 +473,11 @@ class TestSuiteWrite(fixtures.MetadataBase): codestream = j.get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (2592, 1944), 'xyosiz': (0, 0), - 'xytsiz': (2592, 1944), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (2592, 1944), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -471,9 +487,10 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(codestream.segment[2].spcod[3], 1) # mct self.assertEqual(codestream.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), - (64, 64)) # cblksz + (64, 64)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, True, True, False, False, True]) + [False, True, True, + False, False, True]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -488,15 +505,16 @@ class TestSuiteWrite(fixtures.MetadataBase): data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, - data=data, grid_offset=[300, 150], cratios=[800]) + data=data, grid_offset=[300, 150], cratios=[800]) codestream = j.get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (2742, 2244), 'xyosiz': (150, 300), - 'xytsiz': (2742, 2244), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (2742, 2244), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -508,7 +526,8 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, + False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -523,10 +542,11 @@ class TestSuiteWrite(fixtures.MetadataBase): codestream = j.get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (2592, 1944), 'xyosiz': (0, 0), - 'xytsiz': (2592, 1944), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (2592, 1944), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -538,7 +558,8 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, + False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -553,10 +574,11 @@ class TestSuiteWrite(fixtures.MetadataBase): codestream = j.get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (640, 480), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -568,7 +590,8 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, + False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -578,7 +601,7 @@ class TestSuiteWrite(fixtures.MetadataBase): data = read_image(opj_data_file('input/nonregression/Rome.bmp')) with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: jp2 = Jp2k(tfile.name, - data=data, psnr=[30, 35, 50], prog='LRCP', numres=3) + data=data, psnr=[30, 35, 50], prog='LRCP', numres=3) ids = [box.box_id for box in jp2.box] self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) @@ -616,10 +639,11 @@ class TestSuiteWrite(fixtures.MetadataBase): codestream = jp2.box[3].main_header kwargs = {'rsiz': 0, 'xysiz': (640, 480), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -631,7 +655,8 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, + False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -648,10 +673,11 @@ class TestSuiteWrite(fixtures.MetadataBase): codestream = j.get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (1024, 1024), 'xyosiz': (0, 0), - 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), - 'bitdepth': (16,), 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), + 'bitdepth': (16,), 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -663,7 +689,8 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, + False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 5eedfe5..8b7b194 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -32,11 +32,12 @@ import lxml.etree as ET import glymur from glymur import Jp2k, command_line from . import fixtures -from .fixtures import ( - OPJ_DATA_ROOT, opj_data_file, - WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG, - WINDOWS_TMP_FILE_MSG, text_gbr_27, text_gbr_33, text_gbr_34 -) +from .fixtures import (OPJ_DATA_ROOT, opj_data_file, + WARNING_INFRASTRUCTURE_ISSUE, + WARNING_INFRASTRUCTURE_MSG, + WINDOWS_TMP_FILE_MSG, + text_gbr_27, text_gbr_33, text_gbr_34) + @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) class TestPrinting(unittest.TestCase): @@ -52,23 +53,11 @@ class TestPrinting(unittest.TestCase): def tearDown(self): pass - def test_codestream(self): - """Should be able to print a raw codestream.""" - j = glymur.Jp2k(self.j2kfile) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(j) - actual = fake_out.getvalue().strip() - # Remove the file line, as that is filesystem-dependent. - lines = actual.split('\n') - actual = '\n'.join(lines[1:]) - - self.assertEqual(actual, fixtures.codestream) - def test_version_info(self): """Should be able to print(glymur.version.info)""" with patch('sys.stdout', new=StringIO()) as fake_out: print(glymur.version.info) - actual = fake_out.getvalue().strip() + fake_out.getvalue().strip() self.assertTrue(True) @@ -78,7 +67,7 @@ class TestPrinting(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile: with open(self.jpxfile, 'rb') as ifile: tfile.write(ifile.read()) - + # Add the header for an unknown superbox. write_buffer = struct.pack('>I4s', 20, 'grp '.encode()) tfile.write(write_buffer) @@ -107,7 +96,8 @@ class TestPrinting(unittest.TestCase): with self.assertRaises(TypeError): glymur.set_printoptions(hi='low') - @unittest.skipIf(re.match("1.5|2", glymur.version.openjpeg_version) is None, + @unittest.skipIf(re.match("1.5|2", + glymur.version.openjpeg_version) is None, "Must have openjpeg 1.5 or higher to run") def test_asoc_label_box(self): """verify printing of asoc, label boxes""" @@ -115,9 +105,8 @@ class TestPrinting(unittest.TestCase): # OpenJPEG doesn't have such a file. data = glymur.Jp2k(self.jp2file)[::2, ::2] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = glymur.Jp2k(tfile.name, data=data) - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2: + j = glymur.Jp2k(tfile.name, data=data) # Offset of the codestream is where we start. wbuffer = tfile.read(77) @@ -419,7 +408,7 @@ class TestPrinting(unittest.TestCase): @unittest.skipIf(sys.hexversion < 0x03000000, "Only trusting python3 for printing non-ascii chars") def test_xml_cyrrilic(self): - """Should be able to print an XMLBox with utf-8 encoding (cyrrillic).""" + """Should be able to print XMLBox with utf-8 encoding (cyrrillic).""" # Seems to be inconsistencies between different versions of python2.x # as to what gets printed. # @@ -437,7 +426,8 @@ class TestPrinting(unittest.TestCase): actual = fake_out.getvalue().strip() if sys.hexversion < 0x03000000: lines = ["XML Box (xml ) @ (-1, 0)", - " Россия"] + (" Росс", + "ия")] else: lines = ["XML Box (xml ) @ (-1, 0)", " Россия"] @@ -613,12 +603,14 @@ class TestPrinting(unittest.TestCase): lines = ["UUID Box (uuid) @ (1135519, 76)", " UUID: 4a706754-6966-6645-7869-662d3e4a5032 (EXIF)", - " UUID Data: OrderedDict([('ImageWidth', 256), ('ImageLength', 512), ('Make', 'HTC')])"] + (" UUID Data: OrderedDict([('ImageWidth', 256)," + " ('ImageLength', 512), ('Make', 'HTC')])")] expected = '\n'.join(lines) self.assertEqual(actual, expected) + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @@ -826,6 +818,7 @@ class TestPrintingOpjDataRoot(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @@ -852,7 +845,7 @@ class TestPrintingOpjDataRootWarns(unittest.TestCase): filename = opj_data_file('input/nonregression/edf_c2_1103421.jp2') with self.assertWarns(UserWarning): jp2 = Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: + with patch('sys.stdout', new=StringIO()): print(jp2) def test_bad_rsiz(self): @@ -860,7 +853,7 @@ class TestPrintingOpjDataRootWarns(unittest.TestCase): filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') with self.assertWarns(UserWarning): j = Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: + with patch('sys.stdout', new=StringIO()): print(j) def test_bad_wavelet_transform(self): @@ -868,7 +861,7 @@ class TestPrintingOpjDataRootWarns(unittest.TestCase): filename = opj_data_file('input/nonregression/edf_c2_10025.jp2') with self.assertWarns(UserWarning): jp2 = Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: + with patch('sys.stdout', new=StringIO()): print(jp2) def test_invalid_progression_order(self): @@ -1029,7 +1022,7 @@ class TestPrintingOpjDataRootWarns(unittest.TestCase): 'issue171.jp2')) with self.assertWarns(UserWarning): jp2 = Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: + with patch('sys.stdout', new=StringIO()): # No need to verify, it's enough that we don't error out. print(jp2) @@ -1112,4 +1105,3 @@ class TestJp2dump(unittest.TestCase): command_line.main() actual = fake_out.getvalue().strip() self.assertRegex(actual, "File: .*") - From 13cc6aa7e5a774a5428d5f9968197923790ee728 Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 22 Dec 2014 23:20:48 -0500 Subject: [PATCH 304/326] pep8 work --- glymur/lib/__init__.py | 2 + glymur/lib/config.py | 44 +- glymur/lib/openjp2.py | 40 -- glymur/lib/openjpeg.py | 50 +- glymur/test/fixtures.py | 22 +- glymur/test/test_callbacks.py | 5 +- glymur/test/test_config.py | 47 +- glymur/test/test_glymur_warnings.py | 21 +- glymur/test/test_icc.py | 2 - glymur/test/test_jp2box.py | 64 ++- glymur/test/test_jp2box_jpx.py | 11 +- glymur/test/test_jp2box_uuid.py | 24 +- glymur/test/test_jp2box_xml.py | 22 +- glymur/test/test_jp2k.py | 243 +++----- glymur/test/test_opj_suite.py | 31 +- glymur/test/test_opj_suite_dump.py | 838 ++++++++++++++++------------ glymur/test/test_opj_suite_neg.py | 23 +- glymur/test/test_opj_suite_write.py | 203 ++++--- glymur/test/test_printing.py | 55 +- 19 files changed, 822 insertions(+), 925 deletions(-) diff --git a/glymur/lib/__init__.py b/glymur/lib/__init__.py index a283f7f..ddce813 100644 --- a/glymur/lib/__init__.py +++ b/glymur/lib/__init__.py @@ -2,3 +2,5 @@ from . import openjp2 as openjp2 from . import openjpeg as openjpeg from . import c + +__all__ = [openjp2, openjpeg, c] diff --git a/glymur/lib/config.py b/glymur/lib/config.py index 3aca5aa..a639b38 100644 --- a/glymur/lib/config.py +++ b/glymur/lib/config.py @@ -19,18 +19,21 @@ else: from configparser import NoOptionError # default library locations for MacPorts -_macports_default_location = { - 'openjp2': '/opt/local/lib/libopenjp2.dylib', - 'openjpeg': '/opt/local/lib/libopenjpeg.dylib' -} +_macports_default_location = {'openjp2': '/opt/local/lib/libopenjp2.dylib', + 'openjpeg': '/opt/local/lib/libopenjpeg.dylib'} # default library locations on Windows -_windows_default_location = { - 'openjp2': os.path.join('C:\\', 'Program files', 'OpenJPEG 2.0', - 'bin', 'openjp2.dll'), - 'openjpeg': os.path.join('C:\\', 'Program files', 'OpenJPEG 1.5', - 'bin', 'openjpeg.dll') -} +_windows_default_location = {'openjp2': os.path.join('C:\\', + 'Program files', + 'OpenJPEG 2.0', + 'bin', + 'openjp2.dll'), + 'openjpeg': os.path.join('C:\\', + 'Program files', + 'OpenJPEG 1.5', + 'bin', + 'openjpeg.dll')} + def glymurrc_fname(): """Return the path to the configuration file. @@ -55,8 +58,9 @@ def glymurrc_fname(): # didn't find a configuration file. return None + def load_openjpeg_library(libname): - + path = read_config_file(libname) if path is not None: return load_library_handle(path) @@ -79,13 +83,15 @@ def load_openjpeg_library(libname): return load_library_handle(path) + def load_library_handle(path): """Load the library, return the ctypes handle.""" if path is None or path in ['None', 'none']: - # Either could not find a library via ctypes or user-configuration-file, - # or we could not find it in any of the default locations, or possibly - # the user intentionally does not want one of the libraries to load. + # Either could not find a library via ctypes or + # user-configuration-file, or we could not find it in any of the + # default locations, or possibly the user intentionally does not want + # one of the libraries to load. return None try: @@ -94,10 +100,10 @@ def load_library_handle(path): else: opj_lib = ctypes.CDLL(path) except (TypeError, OSError): - msg = 'The library specified by configuration file at {0} could not be ' - msg += 'loaded.' - warnings.warn(msg.format(path), UserWarning) - opj_lib = None + msg = 'The library specified by configuration file at {0} could not ' + msg += 'be loaded.' + warnings.warn(msg.format(path), UserWarning) + opj_lib = None return opj_lib @@ -130,6 +136,7 @@ def read_config_file(libname): path = None return path + def glymur_config(): """ Try to ascertain locations of openjp2, openjpeg libraries. @@ -147,6 +154,7 @@ def glymur_config(): warnings.warn(msg) return tuple(lst) + def get_configdir(): """Return string representing the configuration directory. diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py index 24122b9..d398fb2 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -410,7 +410,6 @@ class CompressionParametersType(ctypes.Structure): for j in range(self.numpocs): msg += " [#{0}]:".format(j) msg += " {0}".format(str(self.poc[j])) - msg += textwrap.indent(textstr, ' ' * 12) elif field_name in ['tcp_rates', 'tcp_distoratio']: lst = [] @@ -740,28 +739,6 @@ def encode(codec, stream): OPENJP2.opj_encode(codec, stream) -def get_cstr_info(codec): - """get the codestream information from the codec - - Wraps the openjp2 library function opj_get_cstr_info. - - Parameters - ---------- - codec : CODEC_TYPE - The jpeg2000 codec. - - Returns - ------- - cstr_info_p : CodestreamInfoV2 - Reference to codestream information. - """ - OPENJP2.opj_get_cstr_info.argtypes = [CODEC_TYPE] - OPENJP2.opj_get_cstr_info.restype = ctypes.POINTER(CodestreamInfoV2) - - cstr_info_p = OPENJP2.opj_get_cstr_info(codec) - return cstr_info_p - - def get_decoded_tile(codec, stream, imagep, tile_index): """get the decoded tile from the codec @@ -792,23 +769,6 @@ def get_decoded_tile(codec, stream, imagep, tile_index): OPENJP2.opj_get_decoded_tile(codec, stream, imagep, tile_index) -def destroy_cstr_info(cstr_info_p): - """destroy codestream information after compression or decompression - - Wraps the openjp2 library function opj_destroy_cstr_info. - - Parameters - ---------- - cstr_info_p : CodestreamInfoV2 pointer - Pointer to codestream info structure. - """ - ARGTYPES = [ctypes.POINTER(ctypes.POINTER(CodestreamInfoV2))] - OPENJP2.opj_destroy_cstr_info.argtypes = ARGTYPES - OPENJP2.opj_destroy_cstr_info.restype = ctypes.c_void_p - - OPENJP2.opj_destroy_cstr_info(ctypes.byref(cstr_info_p)) - - def end_compress(codec, stream): """End of compressing the current image. diff --git a/glymur/lib/openjpeg.py b/glymur/lib/openjpeg.py index 924cac5..602f3b9 100644 --- a/glymur/lib/openjpeg.py +++ b/glymur/lib/openjpeg.py @@ -6,8 +6,6 @@ import ctypes import sys -import numpy as np - from .config import glymur_config _, OPENJPEG = glymur_config() @@ -364,9 +362,9 @@ class DecompressionParametersType(ctypes.Structure): class ImageComptParmType(ctypes.Structure): """Component parameters structure used by the opj_image_create function. """ - _fields_ = [# XRsiz: horizontal separation of a sample of ith component + _fields_ = [("dx", ctypes.c_int), + # XRsiz: horizontal separation of a sample of ith component # with respect to the reference grid - ("dx", ctypes.c_int), # YRsiz: vertical separation of a sample of ith component with # respect to the reference grid */ @@ -527,50 +525,6 @@ def destroy_decompress(dinfo): OPENJPEG.opj_destroy_decompress(dinfo) -def image_cmptparm_t_from_np(np_image): - """Return appropriate image_cmptparm_t based on given numpy array. - """ - try: - num_comps = np_image.shape[2] - except IndexError: - num_comps = 1 - - cmpt_parm_array_t = ImageCmptparmType * num_comps - tarr = cmpt_parm_array_t() - - if np_image.dtype == np.uint8: - prec = 8 - bpp = 8 - sgnd = 0 - elif np_image.dtype == np.int8: - prec = 8 - bpp = 8 - sgnd = 1 - elif np_image.dtype == np.uint16: - prec = 16 - bpp = 16 - sgnd = 0 - elif np_image.dtype == np.int16: - prec = 16 - bpp = 16 - sgnd = 1 - else: - raise(TypeError("unhandled")) - - for j in range(0, num_comps): - tarr[j].dx = 1 - tarr[j].dy = 1 - tarr[j].w = np_image.shape[1] - tarr[j].h = np_image.shape[0] - tarr[j].x0 = 0 - tarr[j].y0 = 0 - tarr[j].prec = prec - tarr[j].bpp = bpp - tarr[j].sgnd = sgnd - - return(tarr) - - def image_create(cmptparms, cspace): """Wrapper for openjpeg library function opj_image_create. """ diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 113a47b..cf78051 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -37,6 +37,7 @@ elif re.match('1.[0-6]', six.__version__) is not None: # Cannot reopen a named temporary file in windows. WINDOWS_TMP_FILE_MSG = "cannot use NamedTemporaryFile like this in windows" + class MetadataBase(unittest.TestCase): """ Base class for testing metadata. @@ -101,8 +102,8 @@ class MetadataBase(unittest.TestCase): """ verify the fields of a RGN segment """ - self.assertEqual(actual.crgn, expected.crgn) # 0 = component - self.assertEqual(actual.srgn, expected.srgn) # 0 = implicit + self.assertEqual(actual.crgn, expected.crgn) # 0 = component + self.assertEqual(actual.srgn, expected.srgn) # 0 = implicit self.assertEqual(actual.sprgn, expected.sprgn) def verifySOTsegment(self, actual, expected): @@ -125,8 +126,9 @@ class MetadataBase(unittest.TestCase): """ Verify the fields of the SIZ segment. """ - for field in ['rsiz', 'xsiz', 'ysiz', 'xosiz', 'yosiz', 'xtsiz', - 'ytsiz', 'xtosiz', 'ytosiz', 'bitdepth', 'xrsiz', 'yrsiz']: + for field in ['rsiz', 'xsiz', 'ysiz', 'xosiz', 'yosiz', 'xtsiz', + 'ytsiz', 'xtosiz', 'ytosiz', 'bitdepth', + 'xrsiz', 'yrsiz']: self.assertEqual(getattr(actual, field), getattr(expected, field)) def verifyImageHeaderBox(self, box1, box2): @@ -153,7 +155,7 @@ class MetadataBase(unittest.TestCase): else: self.assertEqual(actual.colorspace, expected.colorspace) self.assertIsNone(actual.icc_profile) - + # The Python XMP Toolkit may be used for XMP UUIDs, but only if available and # if the version is at least 2.0.0. @@ -183,7 +185,7 @@ except: # The Cinema2K/4K tests seem to need the freeimage backend to skimage.io # in order to work. Unfortunately, scikit-image/freeimage is about as wonky as # it gets. Anaconda can get totally weirded out on versions up through 3.6.4 -# on Python3 with scikit-image up through version 0.10.0. +# on Python3 with scikit-image up through version 0.10.0. NO_SKIMAGE_FREEIMAGE_SUPPORT = False try: import skimage @@ -211,7 +213,7 @@ def _indent(textstr): String to be indented. indent_level : str Number of spaces of indentation to add. - + Returns ------- indented_string : str @@ -600,7 +602,6 @@ Contiguous Codestream Box (jp2c) @ (3223, 1132296) "Created by OpenJPEG version 2.0.0"''' nemo_with_codestream_header = dump.format(_indent(nemo_xmp)) -#nemo_dump_full = dump.format(_indent(nemo_xmp)) nemo_dump_short = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) File Type Box (ftyp) @ (12, 20) @@ -613,7 +614,7 @@ Contiguous Codestream Box (jp2c) @ (3223, 1132296)""" nemo_dump_no_xml = '''JPEG 2000 Signature Box (jP ) @ (0, 12) Signature: 0d0a870a File Type Box (ftyp) @ (12, 20) - Brand: jp2 + Brand: jp2 Compatibility: ['jp2 '] JP2 Header Box (jp2h) @ (32, 45) Image Header Box (ihdr) @ (40, 22) @@ -743,7 +744,7 @@ issue_183_colr = """Colour Specification Box (colr) @ (62, 12) Method: restricted ICC profile Precedence: 0 ICC Profile: None""" - + # Progression order is invalid. issue_186_progression_order = """COD marker segment @ (174, 12) @@ -908,4 +909,3 @@ goodstuff_with_full_header = r"""Codestream: Step size: [(0, 8), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10)] SOD marker segment @ (164, 0) EOC marker segment @ (115218, 0)""" - diff --git a/glymur/test/test_callbacks.py b/glymur/test/test_callbacks.py index 849d987..269a816 100644 --- a/glymur/test/test_callbacks.py +++ b/glymur/test/test_callbacks.py @@ -25,6 +25,7 @@ import glymur from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG + class TestCallbacks(unittest.TestCase): """Test suite for callbacks.""" @@ -46,7 +47,7 @@ class TestCallbacks(unittest.TestCase): tiledata = j.read(tile=0) with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with patch('sys.stdout', new=StringIO()) as fake_out: - j = glymur.Jp2k(tfile.name, data=tiledata, verbose=True) + glymur.Jp2k(tfile.name, data=tiledata, verbose=True) actual = fake_out.getvalue().strip() expected = '[INFO] tile number 1 / 1' self.assertEqual(actual, expected) @@ -60,7 +61,7 @@ class TestCallbacks(unittest.TestCase): tiledata = j[:] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with patch('sys.stdout', new=StringIO()) as fake_out: - jp2 = glymur.Jp2k(tfile.name, data=tiledata, verbose=True) + glymur.Jp2k(tfile.name, data=tiledata, verbose=True) actual = fake_out.getvalue().strip() expected = '[INFO] tile number 1 / 1' self.assertEqual(actual, expected) diff --git a/glymur/test/test_config.py b/glymur/test/test_config.py index feb3235..28a41cc 100644 --- a/glymur/test/test_config.py +++ b/glymur/test/test_config.py @@ -10,7 +10,7 @@ OPENJP2 may be present in some form or other. # unittest.mock only in Python 3.3 (python2.7/pylint import issue) # pylint: disable=E0611,F0401 -import contextlib +import contextlib import ctypes import imp import os @@ -26,41 +26,42 @@ else: import glymur from glymur import Jp2k -from .fixtures import ( - WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG, - WINDOWS_TMP_FILE_MSG -) +from .fixtures import (WARNING_INFRASTRUCTURE_ISSUE, + WARNING_INFRASTRUCTURE_MSG, + WINDOWS_TMP_FILE_MSG) + def openjpeg_not_found_by_ctypes(): """ Need to know if openjpeg library can be picked right up by ctypes for one of the tests. """ - with patch.dict('os.environ', {'DYLD_FALLBACK_LIBRARY_PATH': '/opt/local/lib'}): + with patch.dict('os.environ', + {'DYLD_FALLBACK_LIBRARY_PATH': '/opt/local/lib'}): if ctypes.util.find_library('openjpeg') is None: return True else: return False -@contextlib.contextmanager -def chdir(dirname=None): +@contextlib.contextmanager +def chdir(dirname=None): """ This context manager restores the value of the current working directory (cwd) after the enclosed code block completes or raises an exception. If a directory name is supplied to the context manager then the cwd is changed - prior to running the code block. + prior to running the code block. Shamelessly lifted from http://www.astropython.org/snippet/2009/10/chdir-context-manager """ - curdir = os.getcwd() - try: - if dirname is not None: - os.chdir(dirname) - yield - finally: - os.chdir(curdir) + curdir = os.getcwd() + try: + if dirname is not None: + os.chdir(dirname) + yield + finally: + os.chdir(curdir) @unittest.skipIf(sys.hexversion < 0x03020000, @@ -130,7 +131,7 @@ class TestSuite(unittest.TestCase): "Needs openjp2 and openjpeg before this test make sense.") @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) def test_library_specified_as_None(self): - """Verify that we can stop a library from being loaded by using None.""" + """Verify that we can stop library from being loaded by using None.""" with tempfile.TemporaryDirectory() as tdir: configdir = os.path.join(tdir, 'glymur') os.mkdir(configdir) @@ -140,7 +141,9 @@ class TestSuite(unittest.TestCase): # openjpeg instead. fptr.write('[library]\n') fptr.write('openjp2: None\n') - fptr.write('openjpeg: {0}\n'.format(glymur.lib.openjp2.OPENJPEG._name)) + msg = 'openjpeg: {0}\n' + msg = msg.format(glymur.lib.openjp2.OPENJPEG._name) + fptr.write(msg) fptr.flush() with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}): imp.reload(glymur.lib.openjp2) @@ -149,18 +152,17 @@ class TestSuite(unittest.TestCase): @unittest.skipIf(glymur.lib.openjp2.OPENJPEG is None, "Needs openjpeg before this test make sense.") - @unittest.skipIf(openjpeg_not_found_by_ctypes(), - "OpenJPEG must be easily found before this test can work.") + @unittest.skipIf(openjpeg_not_found_by_ctypes(), + "OpenJPEG must be found before this test can work.") @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) def test_config_dir_but_no_config_file(self): with tempfile.TemporaryDirectory() as tdir: configdir = os.path.join(tdir, 'glymur') os.mkdir(configdir) - fname = os.path.join(configdir, 'glymurrc') with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}): # Should still be able to load openjpeg, despite the - # configuration file being empty. + # configuration file not being there imp.reload(glymur.lib.openjpeg) self.assertIsNotNone(glymur.lib.openjp2.OPENJPEG) @@ -178,4 +180,3 @@ class TestSuite(unittest.TestCase): # Should be able to load openjp2 as before. imp.reload(glymur.lib.openjp2) self.assertEqual(glymur.lib.openjp2.OPENJP2._name, libloc) - diff --git a/glymur/test/test_glymur_warnings.py b/glymur/test/test_glymur_warnings.py index b8e5c8b..fa0c832 100644 --- a/glymur/test/test_glymur_warnings.py +++ b/glymur/test/test_glymur_warnings.py @@ -5,22 +5,19 @@ Test suite for warnings issued by glymur. # unittest doesn't work well with R0904. # pylint: disable=R0904 -import platform import os import re import struct -import sys import tempfile import unittest -import six - from glymur import Jp2k import glymur from .fixtures import opj_data_file, OPJ_DATA_ROOT from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @@ -53,12 +50,11 @@ class TestWarnings(unittest.TestCase): """ relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2' jfile = opj_data_file(relpath) - regex = re.compile(r"""Unrecognized\sbox\s\(b'XML\s'\)\sencountered.""", - re.VERBOSE) + pattern = r"""Unrecognized\sbox\s\(b'XML\s'\)\sencountered.""" + regex = re.compile(pattern, re.VERBOSE) with self.assertWarnsRegex(UserWarning, regex): Jp2k(jfile) - def test_NR_gdal_fuzzer_unchecked_numresolutions_dump(self): """ Has an invalid number of resolutions. @@ -119,7 +115,7 @@ class TestWarnings(unittest.TestCase): \(\d+\)''', re.VERBOSE) with self.assertWarnsRegex(UserWarning, regex): - jp2 = Jp2k(jfile) + Jp2k(jfile) def test_NR_broken2_jp2_dump(self): """ @@ -127,7 +123,7 @@ class TestWarnings(unittest.TestCase): """ jfile = opj_data_file('input/nonregression/broken2.jp2') regex = re.compile(r'''Invalid\smarker\sid\sencountered\sat\sbyte\s - \d+\sin\scodestream:\s*"0x[a-fA-F0-9]{4}"''', + \d+\sin\scodestream:\s*"0x[a-fA-F0-9]{4}"''', re.VERBOSE) with self.assertWarnsRegex(UserWarning, regex): Jp2k(jfile) @@ -152,7 +148,8 @@ class TestWarnings(unittest.TestCase): def test_tile_height_is_zero(self): """Zero tile height should not cause an exception.""" - filename = opj_data_file('input/nonregression/2539.pdf.SIGFPE.706.1712.jp2') + filename = 'input/nonregression/2539.pdf.SIGFPE.706.1712.jp2' + filename = opj_data_file(filename) with self.assertWarnsRegex(UserWarning, 'Invalid tile dimensions'): Jp2k(filename) @@ -177,9 +174,9 @@ class TestWarnings(unittest.TestCase): read_buffer = ifile.read() tfile.write(read_buffer) tfile.flush() - + with self.assertWarnsRegex(UserWarning, 'Unrecognized marker'): - codestream = Jp2k(tfile.name).get_codestream() + Jp2k(tfile.name).get_codestream() if __name__ == "__main__": diff --git a/glymur/test/test_icc.py b/glymur/test/test_icc.py index c6b63e8..90ffc11 100644 --- a/glymur/test/test_icc.py +++ b/glymur/test/test_icc.py @@ -6,8 +6,6 @@ ICC profile tests. # pylint: disable=R0904 import datetime -import os -import sys import unittest import numpy as np diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 28ae4fe..9843b94 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -35,10 +35,10 @@ from glymur.jp2box import JPEG2000SignatureBox from glymur.core import COLOR, OPACITY from glymur.core import RED, GREEN, BLUE, GREY, WHOLE_IMAGE -from .fixtures import ( - WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG, - WINDOWS_TMP_FILE_MSG, MetadataBase -) +from .fixtures import (WARNING_INFRASTRUCTURE_ISSUE, + WARNING_INFRASTRUCTURE_MSG, + WINDOWS_TMP_FILE_MSG, MetadataBase) + def load_tests(loader, tests, ignore): """Run doc tests as well.""" @@ -48,13 +48,15 @@ def load_tests(loader, tests, ignore): tests.addTests(doctest.DocTestSuite('glymur.jp2box')) return tests + @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) class TestDataEntryURL(unittest.TestCase): """Test suite for DataEntryURL boxes.""" def setUp(self): self.jp2file = glymur.data.nemo() - @unittest.skipIf(re.match("1.5|2", glymur.version.openjpeg_version) is None, + @unittest.skipIf(re.match("1.5|2", + glymur.version.openjpeg_version) is None, "Must have openjpeg 1.5 or higher to run") def test_wrap_greyscale(self): """A single component should be wrapped as GREYSCALE.""" @@ -70,7 +72,7 @@ class TestDataEntryURL(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile2: jp2 = j2k.wrap(tfile2.name) self.assertEqual(jp2.box[2].box[1].colorspace, - glymur.core.GREYSCALE) + glymur.core.GREYSCALE) def test_basic_url(self): """Just your most basic URL box.""" @@ -92,7 +94,7 @@ class TestDataEntryURL(unittest.TestCase): self.assertEqual(jp22.box[4].url, url) def test_null_termination(self): - """I.9.3.2 specifies that the location field must be null terminated.""" + """I.9.3.2 specifies that location field must be null terminated.""" jp2 = Jp2k(self.jp2file) url = 'http://glymur.readthedocs.org' @@ -103,12 +105,15 @@ class TestDataEntryURL(unittest.TestCase): jp22 = jp2.wrap(tfile.name, boxes=boxes) self.assertEqual(jp22.box[-1].length, 42) - - # Go to the last box. Seek past the L, T, version, and flag fields. + + # Go to the last box. Seek past the L, T, version, + # and flag fields. with open(tfile.name, 'rb') as fptr: fptr.seek(jp22.box[-1].offset + 4 + 4 + 1 + 3) - - nbytes = jp22.box[-1].offset + jp22.box[-1].length - fptr.tell() + + nbytes = (jp22.box[-1].offset + + jp22.box[-1].length - + fptr.tell()) read_buffer = fptr.read(nbytes) read_url = read_buffer.decode('utf-8') self.assertEqual(url + chr(0), read_url) @@ -128,18 +133,18 @@ class TestChannelDefinition(unittest.TestCase): data = j2k[:] # Write the first component back out to file. with tempfile.NamedTemporaryFile(suffix=".j2k", delete=False) as tfile: - grey_j2k = Jp2k(tfile.name, data=data[:, :, 0]) + Jp2k(tfile.name, data=data[:, :, 0]) cls.one_plane = tfile.name # Write the first two components back out to file. with tempfile.NamedTemporaryFile(suffix=".j2k", delete=False) as tfile: - grey_j2k = Jp2k(tfile.name, data=data[:, :, 0:1]) + Jp2k(tfile.name, data=data[:, :, 0:1]) cls.two_planes = tfile.name # Write four components back out to file. with tempfile.NamedTemporaryFile(suffix=".j2k", delete=False) as tfile: shape = (data.shape[0], data.shape[1], 1) alpha = np.zeros((shape), dtype=data.dtype) data4 = np.concatenate((data, alpha), axis=2) - rgba_jp2 = Jp2k(tfile.name, data=data4) + Jp2k(tfile.name, data=data4) cls.four_planes = tfile.name @classmethod @@ -392,7 +397,7 @@ class TestFileTypeBox(unittest.TestCase): ftyp = glymur.jp2box.FileTypeBox(brand='jp3') with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: - ftyp.write(tfile) + ftyp.write(tfile) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_cl_entry_unknown(self): @@ -402,7 +407,8 @@ class TestFileTypeBox(unittest.TestCase): ftyp = glymur.jp2box.FileTypeBox(compatibility_list=['jp3']) with self.assertRaises(IOError): with tempfile.TemporaryFile() as tfile: - ftyp.write(tfile) + ftyp.write(tfile) + class TestColourSpecificationBox(unittest.TestCase): """Test suite for colr box instantiation.""" @@ -525,8 +531,8 @@ class TestPaletteBox(unittest.TestCase): bps = (8, 8, 8) signed = (False, False) with self.assertWarns(UserWarning): - pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps, - signed=signed) + glymur.jp2box.PaletteBox(palette, bits_per_component=bps, + signed=signed) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_mismatched_signed_palette(self): @@ -535,8 +541,8 @@ class TestPaletteBox(unittest.TestCase): bps = (8, 8, 8, 8) signed = (False, False, False, False) with self.assertWarns(UserWarning): - pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps, - signed=signed) + glymur.jp2box.PaletteBox(palette, bits_per_component=bps, + signed=signed) def test_writing_with_different_bitdepths(self): """Bitdepths must be the same when writing.""" @@ -792,7 +798,7 @@ class TestWrap(unittest.TestCase): # list to trigger the error. boxes[2].box = [] with self.assertRaises(IOError): - jp22 = jp2.wrap(tfile.name, boxes=boxes) + jp2.wrap(tfile.name, boxes=boxes) def test_default_layout_with_boxes(self): """basic test for rewrapping a jp2 file, boxes specified""" @@ -857,8 +863,8 @@ class TestWrap(unittest.TestCase): """A palette box must reside in a JP2 header box.""" palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.int32) bps = (8, 8, 8) - signed = (True, False, True) - pclr = glymur.jp2box.PaletteBox(palette=palette, bits_per_component=bps, + pclr = glymur.jp2box.PaletteBox(palette=palette, + bits_per_component=bps, signed=(True, False, True)) j2k = Jp2k(self.j2kfile) @@ -970,7 +976,8 @@ class TestWrap(unittest.TestCase): """Rewrap a jpx file.""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1: jpx = Jp2k(self.jpxfile) - idx = list(range(5)) + list(range(9, 12)) + list(range(6, 9)) + [12] + idx = (list(range(5)) + + list(range(9, 12)) + list(range(6, 9))) + [12] boxes = [jpx.box[j] for j in idx] jpx2 = jpx.wrap(tfile1.name, boxes=boxes) exp_ids = [box.box_id for box in boxes] @@ -1026,7 +1033,7 @@ class TestJp2Boxes(unittest.TestCase): def test_codestream_main_header_offset(self): """main_header_offset is an attribute of the CCS box""" - j = Jp2k(self.jpxfile); + j = Jp2k(self.jpxfile) self.assertEqual(j.box[5].main_header_offset, j.box[5].offset + 8) @@ -1240,7 +1247,6 @@ class TestRepr(MetadataBase): """Verify Palette box repr.""" palette = np.array([[255, 0, 1000], [0, 255, 0]], dtype=np.int32) bps = (8, 8, 16) - signed = (True, False, True) box = glymur.jp2box.PaletteBox(palette=palette, bits_per_component=bps, signed=(True, False, True)) @@ -1290,7 +1296,8 @@ class TestRepr(MetadataBase): # Since the raw_data parameter is a sequence of bytes which could be # quite long, don't bother trying to make it conform to eval(repr()). regexp = r"""glymur.jp2box.UUIDBox\(""" - regexp += """the_uuid=UUID\('00000000-0000-0000-0000-000000000000'\),\s""" + regexp += """the_uuid=""" + regexp += """UUID\('00000000-0000-0000-0000-000000000000'\),\s""" regexp += """raw_data=\)""" if sys.hexversion < 0x03000000: @@ -1307,7 +1314,8 @@ class TestRepr(MetadataBase): # Since the raw_data parameter is a sequence of bytes which could be # quite long, don't bother trying to make it conform to eval(repr()). regexp = r"""glymur.jp2box.UUIDBox\(""" - regexp += """the_uuid=UUID\('be7acfcb-97a9-42e8-9c71-999491e3afac'\),\s""" + regexp += """the_uuid=""" + regexp += """UUID\('be7acfcb-97a9-42e8-9c71-999491e3afac'\),\s""" regexp += """raw_data=\)""" if sys.hexversion < 0x03000000: diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index ce676f3..3ee4d31 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -6,7 +6,6 @@ Test suite specifically targeting JPX box layout. import ctypes import os import struct -import sys import tempfile import unittest @@ -20,6 +19,7 @@ from glymur.jp2box import ColourSpecificationBox from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") class TestJPXWrap(unittest.TestCase): """Test suite for wrapping JPX files.""" @@ -184,7 +184,7 @@ class TestJPXWrap(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: with self.assertRaises(IOError): - jpx = jp2.wrap(tfile.name, boxes=boxes) + jp2.wrap(tfile.name, boxes=boxes) def test_cgrp_neg(self): """Can't write a cgrp with anything but colr sub boxes""" @@ -204,7 +204,7 @@ class TestJPXWrap(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: with self.assertRaises(IOError): - jpx = jp2.wrap(tfile.name, boxes=boxes) + jp2.wrap(tfile.name, boxes=boxes) def test_ftbl(self): """Write a fragment table box.""" @@ -484,7 +484,7 @@ class TestJPX(unittest.TestCase): ftbl.write(tfile) def test_data_reference_requires_dtbl(self): - """The existance of a data reference box requires a ftbl box as well.""" + """The existance of data reference box requires a ftbl box as well.""" flag = 0 version = (0, 0, 0) url1 = 'file:////usr/local/bin' @@ -574,7 +574,7 @@ class TestJPX(unittest.TestCase): 131072, 65536, 32768, 16384, 8192] for j in range(len(standard_flags)): mask = (standard_masks[j] >> 16, - standard_masks[j] & 0x0000ffff>> 8, + standard_masks[j] & 0x0000ffff >> 8, standard_masks[j] & 0x000000ff) struct.pack_into('>HBBB', rreq_buffer, 17 + j * 5, standard_flags[j], *mask) @@ -599,7 +599,6 @@ class TestJPX(unittest.TestCase): self.assertEqual(jpx.box[2].standard_flag, (5, 42, 45, 2, 18, 19, 1, 8, 12, 31, 20)) - def test_nlst(self): """Verify that we can handle a number list box.""" j = Jp2k(self.jpxfile) diff --git a/glymur/test/test_jp2box_uuid.py b/glymur/test/test_jp2box_uuid.py index 0e6619a..409e43b 100644 --- a/glymur/test/test_jp2box_uuid.py +++ b/glymur/test/test_jp2box_uuid.py @@ -11,7 +11,6 @@ # pylint: disable=R0904 import os -import re import shutil import struct import sys @@ -23,29 +22,15 @@ if sys.hexversion < 0x02070000: else: import unittest -if sys.hexversion < 0x03000000: - from StringIO import StringIO -else: - from io import StringIO - -if sys.hexversion <= 0x03030000: - from mock import patch -else: - from unittest.mock import patch - import lxml.etree -from .fixtures import (HAS_PYTHON_XMP_TOOLKIT, OPJ_DATA_ROOT, - WARNING_INFRASTRUCTURE_ISSUE, +from .fixtures import (WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG, WINDOWS_TMP_FILE_MSG) -if HAS_PYTHON_XMP_TOOLKIT: - from libxmp import XMPMeta - import glymur from glymur import Jp2k -from .fixtures import OPJ_DATA_ROOT, opj_data_file, SimpleRDF +from .fixtures import SimpleRDF @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) @@ -103,6 +88,7 @@ class TestSuite(unittest.TestCase): jp2 = glymur.Jp2k(tfile.name) self.assertEqual(jp2.box[-1].data['Make'], "HTC") + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) class TestSuiteWarns(unittest.TestCase): @@ -113,7 +99,7 @@ class TestSuiteWarns(unittest.TestCase): def tearDown(self): pass - + def test_unrecognized_exif_tag(self): """Verify warning in case of unrecognized tag.""" with tempfile.NamedTemporaryFile(suffix='.jp2', mode='wb') as tfile: @@ -137,7 +123,7 @@ class TestSuiteWarns(unittest.TestCase): tfile.flush() with self.assertWarnsRegex(UserWarning, 'Unrecognized Exif tag'): - j = glymur.Jp2k(tfile.name) + glymur.Jp2k(tfile.name) def test_bad_tag_datatype(self): """Only certain datatypes are allowable""" diff --git a/glymur/test/test_jp2box_xml.py b/glymur/test/test_jp2box_xml.py index 45d02f1..84a63f3 100644 --- a/glymur/test/test_jp2box_xml.py +++ b/glymur/test/test_jp2box_xml.py @@ -17,20 +17,9 @@ Test suite specifically targeting JP2 box layout. import os import re import struct -import sys import tempfile import unittest -if sys.hexversion < 0x03000000: - from StringIO import StringIO -else: - from io import StringIO - -if sys.hexversion <= 0x03030000: - from mock import patch -else: - from unittest.mock import patch - import lxml.etree as ET import glymur @@ -43,6 +32,7 @@ from .fixtures import OPJ_DATA_ROOT, opj_data_file from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG from . import fixtures + @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) class TestXML(unittest.TestCase): """Test suite for XML boxes.""" @@ -167,7 +157,6 @@ class TestXML(unittest.TestCase): u'Россия') - class TestJp2kBadXmlFile(unittest.TestCase): """Test suite for bad XML box situations""" @@ -293,22 +282,19 @@ class TestXML_OpjDataRoot(unittest.TestCase): 'nonregression', 'issue171.jp2')) msg = 'An illegal BOM \(byte order marker\) was detected and removed ' - msg += 'from the XML contents in the box starting at byte offset \d+' + msg += 'from the XML contents in the box starting at byte offset \d+' with self.assertWarnsRegex(UserWarning, re.compile(msg)): jp2 = Jp2k(filename) self.assertIsNotNone(jp2.box[3].xml) - def test_invalid_utf8(self): """Bad byte sequence that cannot be parsed.""" + relname = '26ccf3651020967f7778238ef5af08af.SIGFPE.d25.527.jp2' filename = opj_data_file(os.path.join('input', 'nonregression', - '26ccf3651020967f7778238ef5af08af.SIGFPE.d25.527.jp2')) + relname)) with self.assertWarns((UserWarning, UserWarning)): jp2 = Jp2k(filename) self.assertIsNone(jp2.box[3].box[1].box[1].xml) - - - diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index edb19a1..cb3a9e7 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -13,7 +13,6 @@ Tests for general glymur functionality. import doctest import os import re -import shutil import struct import sys import tempfile @@ -45,6 +44,7 @@ if HAS_PYTHON_XMP_TOOLKIT: from .fixtures import OPJ_DATA_ROOT, opj_data_file from . import fixtures + # Doc tests should be run as well. def load_tests(loader, tests, ignore): # W0613: "loader" and "ignore" are necessary for the protocol @@ -77,6 +77,7 @@ class SliceProtocolBase(unittest.TestCase): self.j2k_data_r1 = self.j2k[::2, ::2] self.j2k_data_r5 = self.j2k[::32, ::32] + @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(re.match("1.5|2", glymur.version.openjpeg_version) is None, "Must have openjpeg 1.5 or higher to run") @@ -148,11 +149,6 @@ class TestSliceProtocolBaseWrite(SliceProtocolBase): @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) class TestSliceProtocolRead(SliceProtocolBase): - def test_resolution_strides_cannot_differ(self): - with self.assertRaises(IndexError): - # Strides in x/y directions cannot differ. - self.j2k[::2, ::3] - def test_resolution_strides_cannot_differ(self): with self.assertRaises(IndexError): # Strides in x/y directions cannot differ. @@ -169,8 +165,8 @@ class TestSliceProtocolRead(SliceProtocolBase): np.testing.assert_array_equal(self.j2k_data[:, :, j], band) def test_slice_in_third_dimension(self): - actual = self.j2k[:,:,1:3] - expected = self.j2k_data[:,:,1:3] + actual = self.j2k[:, :, 1:3] + expected = self.j2k_data[:, :, 1:3] np.testing.assert_array_equal(actual, expected) def test_reduce_resolution_and_slice_in_third_dimension(self): @@ -184,12 +180,12 @@ class TestSliceProtocolRead(SliceProtocolBase): np.testing.assert_array_equal(actual, expected) def test_retrieve_single_pixel(self): - actual = self.jp2[0,0] + actual = self.jp2[0, 0] expected = self.jp2_data[0, 0] np.testing.assert_array_equal(actual, expected) def test_retrieve_single_component(self): - actual = self.jp2[20,20,2] + actual = self.jp2[20, 20, 2] expected = self.jp2_data[20, 20, 2] np.testing.assert_array_equal(actual, expected) @@ -226,7 +222,7 @@ class TestSliceProtocolRead(SliceProtocolBase): def test_single_slice(self): rows = slice(3, 8) actual = self.j2k[rows] - expected = self.j2k_data[3:8, :,:] + expected = self.j2k_data[3:8, :, :] np.testing.assert_array_equal(actual, expected) @unittest.skipIf(re.match("0|1", glymur.version.openjpeg_version), @@ -235,7 +231,7 @@ class TestSliceProtocolRead(SliceProtocolBase): """ maximim rlevel - There seems to be a difference between version of openjpeg, as + There seems to be a difference between version of openjpeg, as openjp2 produces an image of size (16, 13, 3) and openjpeg produced (17, 12, 3). """ @@ -243,6 +239,7 @@ class TestSliceProtocolRead(SliceProtocolBase): expected = self.j2k_data_r5[1:17, 1:14] np.testing.assert_array_equal(actual, expected) + class TestJp2k(unittest.TestCase): """These tests should be run by just about all configuration.""" @@ -319,7 +316,7 @@ class TestJp2k(unittest.TestCase): @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(re.match('1.[0-4]', openjpeg_version) is not None, - "Not supported with OpenJPEG {0}".format(openjpeg_version)) + "Not supported on OpenJPEG {0}".format(openjpeg_version)) @unittest.skipIf(re.match('1.5.(1|2)', openjpeg_version) is not None, "Mysteriously fails in 1.5.1 and 1.5.2") def test_no_cxform_pclr_jpx(self): @@ -638,7 +635,7 @@ class TestJp2k(unittest.TestCase): self.assertEqual(ET.tostring(jp2k.box[3].xml.getroot()), b'this is a test') - @unittest.skipIf(not HAS_PYTHON_XMP_TOOLKIT, + @unittest.skipIf(not HAS_PYTHON_XMP_TOOLKIT, "Requires Python XMP Toolkit >= 2.0") def test_xmp_attribute(self): """Verify the XMP packet in the shipping example file can be read.""" @@ -654,8 +651,9 @@ class TestJp2k(unittest.TestCase): xmp = XMPMeta() xmp.parse_from_str(j.box[3].raw_data.decode('utf-8'), xmpmeta_wrap=False) - creator_tool = xmp.get_property(libxmp.consts.XMP_NS_XMP, 'CreatorTool') - self.assertEqual(creator_tool, 'Google') + creator_tool = xmp.get_property(libxmp.consts.XMP_NS_XMP, + 'CreatorTool') + self.assertEqual(creator_tool, 'Google') @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(re.match(r'''(1|2.0.0)''', @@ -674,6 +672,7 @@ class TestJp2k(unittest.TestCase): with self.assertRaises(RuntimeError): glymur.Jp2k(self.jp2file).read_bands() + @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(re.match('1.[0-4]', openjpeg_version) is not None, "Not supported with OpenJPEG {0}".format(openjpeg_version)) @@ -693,30 +692,30 @@ class TestJp2k_write(unittest.TestCase): data = np.zeros((640, 480), dtype=np.uint8) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, data=data, - cbsize=(16, 16), psizes=[(16, 16)]) + Jp2k(tfile.name, data=data, + cbsize=(16, 16), psizes=[(16, 16)]) def test_precinct_size_not_power_of_two(self): """must be power of two""" data = np.zeros((640, 480), dtype=np.uint8) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, data=data, - cbsize=(16, 16), psizes=[(48, 48)]) + Jp2k(tfile.name, data=data, + cbsize=(16, 16), psizes=[(48, 48)]) def test_unsupported_int32(self): """Should raise a runtime error if trying to write int32""" data = np.zeros((128, 128), dtype=np.int32) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(RuntimeError): - j = Jp2k(tfile.name, data=data) + Jp2k(tfile.name, data=data) def test_unsupported_uint32(self): """Should raise a runtime error if trying to write uint32""" data = np.zeros((128, 128), dtype=np.uint32) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(RuntimeError): - j = Jp2k(tfile.name, data=data) + Jp2k(tfile.name, data=data) def test_write_with_version_too_early(self): """Should raise a runtime error if trying to write with version 1.3""" @@ -726,7 +725,7 @@ class TestJp2k_write(unittest.TestCase): with patch('glymur.version.openjpeg_version', new=version): with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(RuntimeError): - j = Jp2k(tfile.name, data=data) + Jp2k(tfile.name, data=data) def test_cblkh_different_than_width(self): """Verify that we can set a code block size where height does not equal @@ -745,31 +744,31 @@ class TestJp2k_write(unittest.TestCase): """OpenJP2 only allows 2D or 3D images.""" with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, - data=np.zeros((128, 128, 2, 2), dtype=np.uint8)) + Jp2k(tfile.name, + data=np.zeros((128, 128, 2, 2), dtype=np.uint8)) def test_2d_rgb(self): """RGB must have at least 3 components.""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, - data=np.zeros((128, 128, 2), dtype=np.uint8), - colorspace='rgb') + Jp2k(tfile.name, + data=np.zeros((128, 128, 2), dtype=np.uint8), + colorspace='rgb') def test_colorspace_with_j2k(self): """Specifying a colorspace with J2K does not make sense""" with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, - data=np.zeros((128, 128, 3), dtype=np.uint8), - colorspace='rgb') + Jp2k(tfile.name, + data=np.zeros((128, 128, 3), dtype=np.uint8), + colorspace='rgb') def test_specify_rgb(self): """specify RGB explicitly""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: j = Jp2k(tfile.name, - data=np.zeros((128, 128, 3), dtype=np.uint8), - colorspace='rgb') + data=np.zeros((128, 128, 3), dtype=np.uint8), + colorspace='rgb') self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB) def test_specify_gray(self): @@ -804,7 +803,7 @@ class TestJp2k_write(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with self.assertRaises(IOError): data = np.zeros((128, 128, 3), dtype=np.uint8) - j = Jp2k(tfile.name, data=data, colorspace='ycc') + Jp2k(tfile.name, data=data, colorspace='ycc') def test_write_with_jp2_in_caps(self): """should be able to write with JP2 suffix.""" @@ -833,7 +832,7 @@ class TestJp2k_write(unittest.TestCase): expdata = j2k[:] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with self.assertRaises(IOError): - ofile = Jp2k(tfile.name, data=expdata[:, :, 0], mct=True) + Jp2k(tfile.name, data=expdata[:, :, 0], mct=True) def test_write_cprl(self): """Must be able to write a CPRL progression order file""" @@ -924,7 +923,7 @@ class TestJp2k_2_0(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: data = np.zeros((128, 128, 3), dtype=np.uint8) with self.assertRaises(IOError): - j = Jp2k(tfile.name, data=data, colorspace='cmyk') + Jp2k(tfile.name, data=data, colorspace='cmyk') @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) def test_asoc_label_box(self): @@ -933,7 +932,7 @@ class TestJp2k_2_0(unittest.TestCase): # OpenJPEG doesn't have such a file. data = Jp2k(self.jp2file)[::2, ::2] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = Jp2k(tfile.name, data=data) + Jp2k(tfile.name, data=data) with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2: @@ -1041,12 +1040,15 @@ class TestJp2k_2_1(unittest.TestCase): Invalid\svalues\sfor\scomp\s=\s0\s+ :\sdx=1\sdy=0''', re.VERBOSE) if sys.hexversion < 0x03020000: - with self.assertRaisesRegexp((IOError, OSError), regexp): + with self.assertRaisesRegexp((IOError, OSError), + regexp): j[::2, ::2] else: - with self.assertRaisesRegex((IOError, OSError), regexp): + with self.assertRaisesRegex((IOError, OSError), + regexp): j[::2, ::2] + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") class TestParsing(unittest.TestCase): @@ -1064,22 +1066,23 @@ class TestParsing(unittest.TestCase): """Should not warn if RSIZ when parsing is turned off.""" filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') glymur.set_parseoptions(codestream=False) - j = Jp2k(filename) + Jp2k(filename) glymur.set_parseoptions(codestream=True) with self.assertWarnsRegex(UserWarning, 'Invalid profile'): - jp2 = Jp2k(filename) + Jp2k(filename) def test_main_header(self): - """Verify that the main header is not loaded when parsing turned off.""" + """Verify that the main header isn't loaded when parsing turned off.""" # The hidden _main_header attribute should show up after accessing it. glymur.set_parseoptions(codestream=False) jp2 = Jp2k(self.jp2file) jp2c = jp2.box[4] self.assertIsNone(jp2c._main_header) - main_header = jp2c.main_header + jp2c.main_header self.assertIsNotNone(jp2c._main_header) + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @@ -1101,13 +1104,13 @@ class TestJp2kOpjDataRootWarnings(unittest.TestCase): """Should warn in case of bad ftyp brand.""" filename = opj_data_file('input/nonregression/edf_c2_1000290.jp2') with self.assertWarns(UserWarning): - jp2 = Jp2k(filename) + Jp2k(filename) def test_invalid_approximation(self): """Should warn in case of invalid approximation.""" filename = opj_data_file('input/nonregression/edf_c2_1015644.jp2') with self.assertWarnsRegex(UserWarning, 'Invalid approximation'): - jp2 = Jp2k(filename) + Jp2k(filename) def test_invalid_colorspace(self): """ @@ -1117,13 +1120,13 @@ class TestJp2kOpjDataRootWarnings(unittest.TestCase): """ filename = opj_data_file('input/nonregression/edf_c2_1103421.jp2') with self.assertWarns(UserWarning): - jp2 = Jp2k(filename) + Jp2k(filename) def test_stupid_windows_eol_at_end(self): """Garbage characters at the end of the file.""" filename = opj_data_file('input/nonregression/issue211.jp2') with self.assertWarns(UserWarning): - jp2 = Jp2k(filename) + Jp2k(filename) @unittest.skipIf(OPJ_DATA_ROOT is None, @@ -1148,7 +1151,7 @@ class TestJp2kOpjDataRoot(unittest.TestCase): actdata = j[:] self.assertTrue(fixtures.mse(actdata, expdata) < 250) - + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_no_cxform_pclr_jp2(self): """Indices for pclr jpxfile if no color transform""" @@ -1180,7 +1183,7 @@ class TestJp2kOpjDataRoot(unittest.TestCase): j = Jp2k(filename) with self.assertRaises(RuntimeError): j[:] - + @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_no_cxform_cmap(self): """Bands as physically ordered, not as physically intended""" @@ -1196,31 +1199,11 @@ class TestJp2kOpjDataRoot(unittest.TestCase): expected = np.zeros(ycbcr.shape, ycbcr.dtype) for k in range(crcby.shape[2]): - expected[:,:,crcby.shape[2] - k - 1] = crcby[:,:,k] + expected[:, :, crcby.shape[2] - k - 1] = crcby[:, :, k] np.testing.assert_array_equal(ycbcr, expected) -class TestCodestream(unittest.TestCase): - """Test suite for unusual codestream cases.""" - - def setUp(self): - self.jp2file = glymur.data.nemo() - - def tearDown(self): - pass - - def test_siz_segment_ssiz_unsigned(self): - """ssiz attribute to be removed in future release""" - j = Jp2k(self.jp2file) - codestream = j.get_codestream() - - # The ssiz attribute was simply a tuple of raw bytes. - # The first 7 bits are interpreted as the bitdepth, the MSB determines - # whether or not it is signed. - self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7)) - - @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") class TestCodestreamOpjData(unittest.TestCase): @@ -1274,7 +1257,6 @@ class TestCodestreamOpjData(unittest.TestCase): # codestream, so the last one is EOC. self.assertEqual(codestream.segment[-1].marker_id, 'EOC') - def test_siz_segment_ssiz_signed(self): """ssiz attribute to be removed in future release""" filename = os.path.join(OPJ_DATA_ROOT, 'input/conformance/p0_03.j2k') @@ -1329,6 +1311,16 @@ class TestCodestreamRepr(unittest.TestCase): self.assertEqual(newseg.bitdepth, (8, 8, 8)) self.assertEqual(newseg.signed, (False, False, False)) + def test_siz_segment_ssiz_unsigned(self): + """ssiz attribute to be removed in future release""" + j = Jp2k(self.jp2file) + codestream = j.get_codestream() + + # The ssiz attribute was simply a tuple of raw bytes. + # The first 7 bits are interpreted as the bitdepth, the MSB determines + # whether or not it is signed. + self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7)) + class TestCodestream(unittest.TestCase): """Test suite for unusual codestream cases.""" @@ -1348,112 +1340,3 @@ class TestCodestream(unittest.TestCase): # The first 7 bits are interpreted as the bitdepth, the MSB determines # whether or not it is signed. self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7)) - - -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -class TestCodestreamOpjData(unittest.TestCase): - """Test suite for unusual codestream cases. Uses OPJ_DATA_ROOT""" - - def setUp(self): - self.jp2file = glymur.data.nemo() - - def tearDown(self): - pass - - @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(OPJ_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. - read_buffer = ifile.read(45) - tfile.write(read_buffer) - - # Write the new marker segment, 0xff6f = 65391 - read_buffer = struct.pack('>HHB', int(65391), int(3), int(0)) - tfile.write(read_buffer) - - # Get the rest of the input file. - read_buffer = ifile.read() - tfile.write(read_buffer) - tfile.flush() - - codestream = Jp2k(tfile.name).get_codestream() - - self.assertEqual(codestream.segment[2].marker_id, '0xff6f') - 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(OPJ_DATA_ROOT, - 'input/nonregression/123.j2c') - j = Jp2k(filename) - 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(codestream.segment[-1].marker_id, 'EOC') - - - def test_siz_segment_ssiz_signed(self): - """ssiz attribute to be removed in future release""" - filename = os.path.join(OPJ_DATA_ROOT, 'input/conformance/p0_03.j2k') - j = Jp2k(filename) - codestream = j.get_codestream() - - # The ssiz attribute was simply a tuple of raw bytes. - # The first 7 bits are interpreted as the bitdepth, the MSB determines - # whether or not it is signed. - self.assertEqual(codestream.segment[1].ssiz, (131,)) - - -class TestCodestreamRepr(unittest.TestCase): - - def setUp(self): - self.jp2file = glymur.data.nemo() - - def tearDown(self): - pass - - def test_soc(self): - """Test SOC segment repr""" - segment = glymur.codestream.SOCsegment() - newseg = eval(repr(segment)) - self.assertEqual(newseg.marker_id, 'SOC') - - def test_siz(self): - """Test SIZ segment repr""" - kwargs = {'rsiz': 0, - 'xysiz': (2592, 1456), - 'xyosiz': (0, 0), - 'xytsiz': (2592, 1456), - 'xytosiz': (0, 0), - 'Csiz': 3, - 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': ((1, 1, 1), (1, 1, 1))} - segment = glymur.codestream.SIZsegment(**kwargs) - newseg = eval(repr(segment)) - self.assertEqual(newseg.marker_id, 'SIZ') - self.assertEqual(newseg.xsiz, 2592) - self.assertEqual(newseg.ysiz, 1456) - self.assertEqual(newseg.xosiz, 0) - self.assertEqual(newseg.yosiz, 0) - self.assertEqual(newseg.xtsiz, 2592) - self.assertEqual(newseg.ytsiz, 1456) - self.assertEqual(newseg.xtosiz, 0) - self.assertEqual(newseg.ytosiz, 0) - - self.assertEqual(newseg.xrsiz, (1, 1, 1)) - self.assertEqual(newseg.yrsiz, (1, 1, 1)) - self.assertEqual(newseg.bitdepth, (8, 8, 8)) - self.assertEqual(newseg.signed, (False, False, False)) diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 028734d..f512dfd 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -38,12 +38,11 @@ import glymur from glymur import Jp2k from glymur.jp2box import FileTypeBox, ImageHeaderBox, ColourSpecificationBox -from .fixtures import ( - OPJ_DATA_ROOT, MetadataBase, - WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG, - mse, peak_tolerance, read_pgx, opj_data_file, - OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG -) +from .fixtures import (OPJ_DATA_ROOT, MetadataBase, + WARNING_INFRASTRUCTURE_ISSUE, + WARNING_INFRASTRUCTURE_MSG, + mse, peak_tolerance, read_pgx, opj_data_file, + OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) @@ -415,16 +414,17 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (203, 152), 'xyosiz': (0, 0), - 'xytsiz': (203, 152), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + 'xytsiz': (203, 152), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) + glymur.codestream.SIZsegment(**kwargs)) pargs = (glymur.core.RCME_ISO_8859_1, - "Creator: JasPer Version 1.701.0".encode()) + "Creator: JasPer Version 1.701.0".encode()) self.verifyCMEsegment(c.segment[2], - glymur.codestream.CMEsegment(*pargs)) + glymur.codestream.CMEsegment(*pargs)) # COD: Coding style default self.assertFalse(c.segment[3].scod & 2) # no sop @@ -436,7 +436,7 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[3].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[3].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[3].spcod), 9) @@ -726,7 +726,7 @@ class TestSuite2point1(unittest.TestCase): def test_NR_DEC_p1_04_j2k_57_decode(self): jfile = opj_data_file('input/conformance/p1_04.j2k') jp2k = Jp2k(jfile) - tdata = jp2k[896:1024, 896:1024] # last tile + tdata = jp2k[896:1024, 896:1024] # last tile odata = jp2k[:] np.testing.assert_array_equal(tdata, odata[896:1024, 896:1024]) @@ -798,6 +798,7 @@ class TestSuite2point1(unittest.TestCase): with self.assertRaises(IOError): j[:] + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(re.match(r'''0|1|2.0.0''', @@ -806,7 +807,7 @@ class TestSuite2point1(unittest.TestCase): class TestReadArea(unittest.TestCase): """ Runs tests introduced in version 2.0+ or that pass only in 2.0+ - + Specifically for read method with area parameter. """ @classmethod diff --git a/glymur/test/test_opj_suite_dump.py b/glymur/test/test_opj_suite_dump.py index ee1838b..6b198c1 100644 --- a/glymur/test/test_opj_suite_dump.py +++ b/glymur/test/test_opj_suite_dump.py @@ -36,14 +36,17 @@ import numpy as np import glymur from glymur import Jp2k from glymur.codestream import CMEsegment, SOTsegment, RGNsegment -from glymur.core import RCME_ISO_8859_1, RCME_BINARY +from glymur.core import (RCME_ISO_8859_1, RCME_BINARY, SRGB, + GREYSCALE, RESTRICTED_ICC_PROFILE, + ENUMERATED_COLORSPACE) from glymur.jp2box import FileTypeBox -from .fixtures import ( - MetadataBase, OPJ_DATA_ROOT, - WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG, - mse, peak_tolerance, read_pgx, opj_data_file -) +from .fixtures import (MetadataBase, OPJ_DATA_ROOT, + WARNING_INFRASTRUCTURE_ISSUE, + WARNING_INFRASTRUCTURE_MSG, + opj_data_file) + +comment1 = "Creator: AV-J2K (c) 2000,2001 Algo Vision Technology" @unittest.skipIf(OPJ_DATA_ROOT is None, @@ -82,10 +85,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (720, 243), 'xyosiz': (0, 0), - 'xytsiz': (720, 243), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 2, 2), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (720, 243), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 2, 2), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -97,7 +102,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (32, 128)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -121,9 +126,10 @@ class TestSuite(MetadataBase): self.assertEqual(actual, expected) kwargs = {'rsiz': 1, 'xysiz': (128, 128), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # QCD: Quantization default self.assertEqual(c.segment[2].sqcd & 0x1f, 0) @@ -143,7 +149,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[3].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[3].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -154,9 +160,10 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (127, 126), 'xyosiz': (0, 0), - 'xytsiz': (127, 126), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), 'xyrsiz': [(2,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (127, 126), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), 'xyrsiz': [(2,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) # sop @@ -168,7 +175,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, True, False, True, True]) + [False, False, True, False, True, True]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) @@ -178,7 +185,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[3].code_block_size), (32, 32)) # cblk self.verify_codeblock_style(c.segment[3].spcoc[3], - [False, False, True, False, True, True]) + [False, False, True, False, True, True]) self.assertEqual(c.segment[3].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -191,7 +198,8 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[4].mantissa, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - pargs = RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode() + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[5], CMEsegment(*pargs)) # One unknown marker @@ -217,9 +225,10 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (256, 256), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (4,), - 'signed': (True,), 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (4,), + 'signed': (True,), 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) @@ -231,7 +240,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -263,11 +272,11 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[6].xcrg, (65424,)) self.assertEqual(c.segment[6].ycrg, (32558,)) - pargs = RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode() + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[7], CMEsegment(*pargs)) - pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision Technology".encode()) + pargs = (RCME_ISO_8859_1, comment1.encode()) self.verifyCMEsegment(c.segment[8], CMEsegment(*pargs)) pargs = (RCME_BINARY, c.segment[9].ccme) @@ -290,10 +299,12 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (640, 480), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -305,7 +316,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, True, False, False, False]) + [False, False, True, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(c.segment[2].precinct_size, @@ -353,7 +364,7 @@ class TestSuite(MetadataBase): 2002, 1888]) pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[6], CMEsegment(*pargs)) self.verifySOTsegment(c.segment[7], SOTsegment(0, 264383, 0, 1)) @@ -367,11 +378,12 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (1024, 1024), 'xyosiz': (0, 0), - 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8, 8), - 'signed': (False, False, False, False), - 'xyrsiz': [(1, 1, 2, 2), (1, 1, 2, 2)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8, 8), + 'signed': (False, False, False, False), + 'xyrsiz': [(1, 1, 2, 2), (1, 1, 2, 2)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -383,7 +395,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -394,7 +406,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[3].code_block_size), (32, 32)) # cblk self.verify_codeblock_style(c.segment[3].spcoc[3], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[3].spcoc[4], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) @@ -404,7 +416,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[4].code_block_size), (32, 32)) # cblk self.verify_codeblock_style(c.segment[4].spcoc[3], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[4].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -441,7 +453,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[7].mantissa, [0] * 19) pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[8], CMEsegment(*pargs)) # TLM (tile-part length) @@ -460,11 +472,12 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 2, 'xysiz': (513, 129), 'xyosiz': (0, 0), - 'xytsiz': (513, 129), 'xytosiz': (0, 0), - 'bitdepth': (12, 12, 12, 12), - 'signed': (False, False, False, False), - 'xyrsiz': [(1, 2, 1, 2), (1, 1, 2, 2)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (513, 129), 'xytosiz': (0, 0), + 'bitdepth': (12, 12, 12, 12), + 'signed': (False, False, False, False), + 'xyrsiz': [(1, 2, 1, 2), (1, 1, 2, 2)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -476,7 +489,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -535,7 +548,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[7].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[7].spcoc[3], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[7].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -552,10 +565,12 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (2048, 2048), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (12, 12, 12), - 'signed': (True, True, True), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (128, 128), 'xytosiz': (0, 0), + 'bitdepth': (12, 12, 12), + 'signed': (True, True, True), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) @@ -567,7 +582,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -595,7 +610,6 @@ class TestSuite(MetadataBase): # PLT: packet length, tile part self.assertEqual(c.segment[7].zplt, 0) - #self.assertEqual(c.segment[7].iplt), 99) # SOD: start of data self.assertEqual(c.segment[8].marker_id, 'SOD') @@ -605,10 +619,12 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (513, 3072), 'xyosiz': (0, 0), - 'xytsiz': (513, 3072), 'xytosiz': (0, 0), 'bitdepth': (12, 12, 12), - 'signed': (True, True, True), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (513, 3072), 'xytosiz': (0, 0), + 'bitdepth': (12, 12, 12), + 'signed': (True, True, True), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) @@ -620,7 +636,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -631,7 +647,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[3].spcoc[3], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[3].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -641,7 +657,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[4].code_block_size), (32, 32)) # cblk self.verify_codeblock_style(c.segment[4].spcoc[3], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[4].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -651,7 +667,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[5].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[5].spcoc[3], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[5].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -696,9 +712,10 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (17, 37), 'xyosiz': (0, 0), - 'xytsiz': (17, 37), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (17, 37), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -710,7 +727,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -743,10 +760,12 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (256, 256), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(4, 4, 4), (4, 4, 4)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (128, 128), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(4, 4, 4), (4, 4, 4)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -758,7 +777,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -805,9 +824,10 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (128, 1), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -819,7 +839,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, True]) + [False, False, False, False, False, True]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(c.segment[2].precinct_size, [(128, 2)]) @@ -832,7 +852,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[3].exponent, [8]) pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) self.verifySOTsegment(c.segment[5], SOTsegment(0, 118, 0, 1)) @@ -854,10 +874,11 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (3, 5), 'xyosiz': (0, 0), - 'xytsiz': (3, 5), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (3, 5), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) @@ -869,7 +890,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, True, False, False, False]) + [False, False, True, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -882,7 +903,8 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[3].exponent, [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) self.verifySOTsegment(c.segment[5], SOTsegment(0, 162, 0, 1)) @@ -904,10 +926,12 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (1, 1), 'xyosiz': (0, 0), - 'xytsiz': (1, 1), 'xytosiz': (0, 0), 'bitdepth': tuple([8] * 257), - 'signed': tuple([False] * 257), - 'xyrsiz': [tuple([1] * 257), tuple([1] * 257)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1, 1), 'xytosiz': (0, 0), + 'bitdepth': tuple([8] * 257), + 'signed': tuple([False] * 257), + 'xyrsiz': [tuple([1] * 257), tuple([1] * 257)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -918,7 +942,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 1) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, True, False]) + [False, False, False, False, True, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -928,7 +952,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[3].spcoc[0], 1) # levels self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[3].spcoc[3], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[3].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -969,7 +993,8 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[8].ppod, (glymur.core.RLCP, glymur.core.CPRL)) - pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[9], CMEsegment(*pargs)) self.verifySOTsegment(c.segment[10], SOTsegment(0, 1537, 0, 1)) @@ -985,10 +1010,11 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (49, 49), 'xyosiz': (0, 0), - 'xytsiz': (49, 49), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (49, 49), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -999,7 +1025,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1025,10 +1051,11 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 1, 'xysiz': (256, 256), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (4,), - 'signed': (True,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (4,), + 'signed': (True,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) @@ -1039,7 +1066,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 1) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1072,11 +1099,11 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[6].xcrg, (65424,)) self.assertEqual(c.segment[6].ycrg, (32558,)) - pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[7], CMEsegment(*pargs)) - pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision Technology".encode()) + pargs = (RCME_ISO_8859_1, comment1.encode()) self.verifyCMEsegment(c.segment[8], CMEsegment(*pargs)) pargs = (RCME_BINARY, c.segment[9].ccme) @@ -1125,10 +1152,11 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (128, 128), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) @@ -1139,7 +1167,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 3) # levels self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1165,10 +1193,11 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 2, 'xysiz': (127, 227), 'xyosiz': (5, 128), - 'xytsiz': (127, 126), 'xytosiz': (1, 101), 'bitdepth': (8,), - 'signed': (False,), - 'xyrsiz': [(2,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (127, 126), 'xytosiz': (1, 101), 'bitdepth': (8,), + 'signed': (False,), + 'xyrsiz': [(2,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) # SOP @@ -1179,7 +1208,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 3) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, True, False, True, True]) + [False, False, True, False, True, True]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1189,7 +1218,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[3].spcoc[0], 3) # level self.assertEqual(tuple(c.segment[3].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[3].spcoc[3], - [False, False, True, False, True, True]) + [False, False, True, False, True, True]) self.assertEqual(c.segment[3].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -1201,7 +1230,8 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[4].exponent, [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[5], CMEsegment(*pargs)) self.verifySOTsegment(c.segment[6], SOTsegment(0, 4627, 0, 1)) @@ -1223,10 +1253,12 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 2, 'xysiz': (640, 480), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1238,7 +1270,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, True, False, True, False, False]) + [False, True, False, True, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(c.segment[2].precinct_size, @@ -1285,7 +1317,8 @@ class TestSuite(MetadataBase): [14, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, 9, 9, 9, 9, 9, 9]) - pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[6], CMEsegment(*pargs)) self.verifySOTsegment(c.segment[7], SOTsegment(0, 262838, 0, 1)) @@ -1305,11 +1338,12 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 2, 'xysiz': (1024, 1024), 'xyosiz': (0, 0), - 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8, 8), - 'signed': (False, False, False, False), - 'xyrsiz': [(1, 1, 2, 2), (1, 1, 2, 2)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8, 8), + 'signed': (False, False, False, False), + 'xyrsiz': [(1, 1, 2, 2), (1, 1, 2, 2)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1320,7 +1354,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 6) # level self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[2].spcod[7], - [True, False, True, False, False, False]) + [True, False, True, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1330,7 +1364,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[3].spcoc[0], 3) # level self.assertEqual(tuple(c.segment[3].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[3].spcoc[3], - [True, False, True, False, False, False]) + [True, False, True, False, False, False]) self.assertEqual(c.segment[3].spcoc[4], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) @@ -1339,7 +1373,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[4].spcoc[0], 6) # level self.assertEqual(tuple(c.segment[4].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[4].spcoc[3], - [True, False, True, False, False, False]) + [True, False, True, False, False, False]) self.assertEqual(c.segment[4].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) @@ -1375,7 +1409,8 @@ class TestSuite(MetadataBase): [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[8], CMEsegment(*pargs)) # PPM: packed packet headers, main header @@ -1400,10 +1435,11 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 2, 'xysiz': (1024, 1024), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (12,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (12,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1414,7 +1450,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 3) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1487,10 +1523,12 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 2, 'xysiz': (529, 524), 'xyosiz': (17, 12), - 'xytsiz': (37, 37), 'xytosiz': (8, 2), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (37, 37), 'xytosiz': (8, 2), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) # sop @@ -1501,7 +1539,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 7) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 8)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [True, False, False, True, True, False]) + [True, False, False, True, True, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(c.segment[2].precinct_size, [(16, 16)] * 8) @@ -1516,7 +1554,8 @@ class TestSuite(MetadataBase): [17, 17, 17, 17, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, 11, 11, 11, 11, 11, 11]) - pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) # 225 consecutive PPM segments. @@ -1543,10 +1582,11 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 2, 'xysiz': (12, 12), 'xyosiz': (0, 0), - 'xytsiz': (3, 3), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (3, 3), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) # sop @@ -1557,7 +1597,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 4) # level self.assertEqual(tuple(c.segment[2].code_block_size), (32, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, True, False, True]) + [False, False, False, True, False, True]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1573,7 +1613,8 @@ class TestSuite(MetadataBase): [14, 14, 14, 14, 13, 13, 13, 11, 11, 11, 11, 11, 11]) - pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) self.verifySOTsegment(c.segment[5], SOTsegment(0, 349, 0, 1)) @@ -1604,10 +1645,11 @@ class TestSuite(MetadataBase): c = Jp2k(jfile).get_codestream(header_only=False) kwargs = {'rsiz': 2, 'xysiz': (12, 12), 'xyosiz': (4, 0), - 'xytsiz': (12, 12), 'xytosiz': (4, 0), 'bitdepth': (8, 8), - 'signed': (False, False), - 'xyrsiz': [(4, 1), (1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (12, 12), 'xytosiz': (4, 0), 'bitdepth': (8, 8), + 'signed': (False, False), + 'xyrsiz': [(4, 1), (1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(c.segment[2].scod & 2) # sop @@ -1618,7 +1660,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 1) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(c.segment[2].precinct_size, [(1, 1), (2, 2)]) @@ -1628,7 +1670,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[3].spcoc[0], 1) # level self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[3].spcoc[3], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[3].spcoc[4], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(c.segment[3].precinct_size, [(2, 2), (4, 4)]) @@ -1640,7 +1682,8 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[4].mantissa, [0] * 4) self.assertEqual(c.segment[4].exponent, [8, 9, 9, 10]) - pargs = (RCME_ISO_8859_1, "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) + pargs = (RCME_ISO_8859_1, + "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) self.verifyCMEsegment(c.segment[5], CMEsegment(*pargs)) self.verifySOTsegment(c.segment[6], SOTsegment(0, 434, 0, 1)) @@ -1657,11 +1700,12 @@ class TestSuite(MetadataBase): c = jp2k.get_codestream(header_only=False) kwargs = {'rsiz': 3, 'xysiz': (1920, 1080), 'xyosiz': (0, 0), - 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), - 'bitdepth': (12, 12, 12), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), + 'bitdepth': (12, 12, 12), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1672,7 +1716,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(c.segment[2].precinct_size[0], (128, 128)) @@ -1694,7 +1738,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[4].spcoc[0], 5) # level self.assertEqual(tuple(c.segment[4].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[4].spcoc[3], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[4].spcoc[4], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) @@ -1716,7 +1760,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[6].spcoc[0], 5) # level self.assertEqual(tuple(c.segment[6].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[6].spcoc[3], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[6].spcoc[4], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) @@ -1761,10 +1805,12 @@ class TestSuite(MetadataBase): c = jp2k.get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (2592, 1944), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1775,7 +1821,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(c.segment[2].precinct_size, @@ -1794,10 +1840,11 @@ class TestSuite(MetadataBase): c = jp2k.get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (512, 512), 'xyosiz': (0, 0), - 'xytsiz': (512, 512), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (512, 512), 'xytosiz': (0, 0), 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1808,7 +1855,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1823,10 +1870,11 @@ class TestSuite(MetadataBase): c = jp2k.get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (512, 512), 'xyosiz': (0, 0), - 'xytsiz': (512, 512), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (512, 512), 'xytosiz': (0, 0), 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1837,7 +1885,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1860,10 +1908,11 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (1420, 1416), 'xyosiz': (0, 0), - 'xytsiz': (1420, 1416), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1420, 1416), 'xytosiz': (0, 0), 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1874,7 +1923,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 11) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1897,10 +1946,11 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (512, 614), 'xyosiz': (0, 0), - 'xytsiz': (512, 614), 'xytosiz': (0, 0), 'bitdepth': (12,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (512, 614), 'xytosiz': (0, 0), 'bitdepth': (12,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1911,7 +1961,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1940,10 +1990,11 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (256, 256), 'xyosiz': (0, 0), - 'xytsiz': (256, 256), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (256, 256), 'xytosiz': (0, 0), 'bitdepth': (8,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1954,7 +2005,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -1981,10 +2032,11 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (1420, 1416), 'xyosiz': (0, 0), - 'xytsiz': (1420, 1416), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1420, 1416), 'xytosiz': (0, 0), 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -1995,7 +2047,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 11) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2017,10 +2069,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (256, 256), 'xyosiz': (0, 0), - 'xytsiz': (256, 256), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (True, True, True), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (256, 256), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (True, True, True), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2031,7 +2085,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2054,10 +2108,11 @@ class TestSuite(MetadataBase): c = jp2k.get_codestream() kwargs = {'rsiz': 0, 'xysiz': (2048, 2500), 'xyosiz': (0, 0), - 'xytsiz': (2048, 2500), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (2048, 2500), 'xytosiz': (0, 0), 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2068,7 +2123,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 8) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2101,17 +2156,17 @@ class TestSuite(MetadataBase): pargs = (RCME_ISO_8859_1, ccme.encode()) self.verifyCMEsegment(c.segment[5], CMEsegment(*pargs)) - def test_NR_MarkerIsNotCompliant_j2k_dump(self): jfile = opj_data_file('input/nonregression/MarkerIsNotCompliant.j2k') jp2k = Jp2k(jfile) c = jp2k.get_codestream() kwargs = {'rsiz': 0, 'xysiz': (1420, 1416), 'xyosiz': (0, 0), - 'xytsiz': (1420, 1416), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1420, 1416), 'xytosiz': (0, 0), 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2122,7 +2177,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 11) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2142,10 +2197,12 @@ class TestSuite(MetadataBase): c = jp2k.get_codestream() kwargs = {'rsiz': 0, 'xysiz': (1920, 1080), 'xyosiz': (0, 0), - 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2156,7 +2213,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2174,10 +2231,12 @@ class TestSuite(MetadataBase): c = jp2k.get_codestream() kwargs = {'rsiz': 0, 'xysiz': (1920, 1080), 'xyosiz': (0, 0), - 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2188,7 +2247,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2206,10 +2265,12 @@ class TestSuite(MetadataBase): c = jp2k.get_codestream() kwargs = {'rsiz': 0, 'xysiz': (1920, 1080), 'xyosiz': (0, 0), - 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2220,7 +2281,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2242,10 +2303,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (117, 117), 'xyosiz': (0, 0), - 'xytsiz': (117, 117), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8, 8), - 'signed': (False, False, False, False), - 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (117, 117), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8, 8), + 'signed': (False, False, False, False), + 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2256,7 +2319,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2278,10 +2341,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (117, 117), 'xyosiz': (0, 0), - 'xytsiz': (117, 117), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8, 8), - 'signed': (False, False, False, False), - 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (117, 117), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8, 8), + 'signed': (False, False, False, False), + 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2292,7 +2357,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2314,10 +2379,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (512, 512), 'xyosiz': (0, 0), - 'xytsiz': (512, 512), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (512, 512), 'xytosiz': (0, 0), + 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2328,7 +2395,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2354,10 +2421,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (1024, 1024), 'xyosiz': (0, 0), - 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), 'bitdepth': (12,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), + 'bitdepth': (12,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2368,7 +2437,7 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].spcod[4], 5) # level self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2394,10 +2463,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (1800, 1800), 'xyosiz': (0, 0), - 'xytsiz': (1800, 1800), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1800, 1800), 'xytosiz': (0, 0), + 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2406,10 +2477,9 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].layers, 1) # layers = 1 self.assertEqual(c.segment[2].spcod[3], 1) # mct self.assertEqual(c.segment[2].spcod[4], 11) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2431,10 +2501,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (1800, 1800), 'xyosiz': (0, 0), - 'xytsiz': (1800, 1800), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1800, 1800), 'xytosiz': (0, 0), + 'bitdepth': (16,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2443,10 +2515,9 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].layers, 1) # layers = 1 self.assertEqual(c.segment[2].spcod[3], 1) # mct self.assertEqual(c.segment[2].spcod[4], 11) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk + self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2468,10 +2539,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (2048, 1556), 'xyosiz': (0, 0), - 'xytsiz': (2048, 1556), 'xytosiz': (0, 0), 'bitdepth': (12, 12, 12), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (2048, 1556), 'xytosiz': (0, 0), + 'bitdepth': (12, 12, 12), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2480,10 +2553,9 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].layers, 2) # layers = 2 self.assertEqual(c.segment[2].spcod[3], 1) # mct self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk + self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(c.segment[2].precinct_size, @@ -2512,7 +2584,9 @@ class TestSuite(MetadataBase): self.verifySignatureBox(jp2.box[0]) self.verify_filetype_box(jp2.box[1], - FileTypeBox(compatibility_list=['jp2 ', 'jpxb', 'jpx '])) + FileTypeBox(compatibility_list=['jp2 ', + 'jpxb', + 'jpx '])) # Reader requirements talk. # unrestricted jpeg 2000 part 1 @@ -2521,9 +2595,9 @@ class TestSuite(MetadataBase): ihdr = glymur.jp2box.ImageHeaderBox(203, 479, colorspace_unknown=True) self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox( - colorspace=glymur.core.SRGB, - approximation=1, precedence=2) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=SRGB, + approximation=1, + precedence=2) self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) # Jp2 Header @@ -2543,10 +2617,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (479, 203), 'xyosiz': (0, 0), - 'xytsiz': (256, 203), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (256, 203), 'xytosiz': (0, 0), + 'bitdepth': (8,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2555,10 +2631,9 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].layers, 1) # layers = 1 self.assertEqual(c.segment[2].spcod[3], 0) # mct self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk + self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2581,19 +2656,22 @@ class TestSuite(MetadataBase): self.verifySignatureBox(jp2.box[0]) self.verify_filetype_box(jp2.box[1], - FileTypeBox(compatibility_list=['jp2 ', 'jpxb', 'jpx '])) + FileTypeBox(compatibility_list=['jp2 ', + 'jpxb', + 'jpx '])) # Reader requirements talk. # unrestricted jpeg 2000 part 1 self.assertTrue(5 in jp2.box[2].standard_flag) ihdr = glymur.jp2box.ImageHeaderBox(326, 431, - num_components=3, colorspace_unknown=True) + num_components=3, + colorspace_unknown=True) self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox( - colorspace=glymur.core.SRGB, - approximation=1, precedence=2) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=SRGB, + approximation=1, + precedence=2) self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) c = jp2.box[4].main_header @@ -2603,10 +2681,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (431, 326), 'xyosiz': (0, 0), - 'xytsiz': (256, 256), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (256, 256), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2615,10 +2695,9 @@ class TestSuite(MetadataBase): self.assertEqual(c.segment[2].layers, 1) # layers = 1 self.assertEqual(c.segment[2].spcod[3], 1) # mct self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk + self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2646,11 +2725,10 @@ class TestSuite(MetadataBase): self.verify_filetype_box(jp2.box[1], FileTypeBox()) ihdr = glymur.jp2box.ImageHeaderBox(135, 135, num_components=2, - colorspace_unknown=True) + colorspace_unknown=True) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox( - colorspace=glymur.core.GREYSCALE) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=GREYSCALE) self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) # Jp2 Header @@ -2666,10 +2744,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (135, 135), 'xyosiz': (0, 0), - 'xytsiz': (135, 135), 'xytosiz': (0, 0), 'bitdepth': (8, 8), - 'signed': (False, False), - 'xyrsiz': [(1, 1), (1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (135, 135), 'xytosiz': (0, 0), + 'bitdepth': (8, 8), + 'signed': (False, False), + 'xyrsiz': [(1, 1), (1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2681,7 +2761,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2710,20 +2790,23 @@ class TestSuite(MetadataBase): self.verifySignatureBox(jp2.box[0]) self.verify_filetype_box(jp2.box[1], - FileTypeBox(compatibility_list=['jp2 ', 'jpxb', 'jpx '])) + FileTypeBox(compatibility_list=['jp2 ', + 'jpxb', + 'jpx '])) # Reader requirements talk. # unrestricted jpeg 2000 part 1 self.assertTrue(5 in jp2.box[2].standard_flag) ihdr = glymur.jp2box.ImageHeaderBox(46, 124, bits_per_component=4, - colorspace_unknown=True) + colorspace_unknown=True) self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox( - colorspace=glymur.core.SRGB, - method=glymur.core.ENUMERATED_COLORSPACE, - approximation=1, precedence=2) + method = ENUMERATED_COLORSPACE + colr = glymur.jp2box.ColourSpecificationBox(colorspace=SRGB, + method=method, + approximation=1, + precedence=2) self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) # Jp2 Header @@ -2744,10 +2827,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (124, 46), 'xyosiz': (0, 0), - 'xytsiz': (124, 46), 'xytosiz': (0, 0), 'bitdepth': (4,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (124, 46), 'xytosiz': (0, 0), + 'bitdepth': (4,), + 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2759,7 +2844,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2796,10 +2881,12 @@ class TestSuite(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (766, 576), 'xyosiz': (0, 0), - 'xytsiz': (766, 576), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 2, 2), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (766, 576), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 2, 2), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -2811,7 +2898,7 @@ class TestSuite(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (32, 128)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -2847,7 +2934,7 @@ class TestSuiteWarns(MetadataBase): relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2' jfile = opj_data_file(relpath) with self.assertWarns(UserWarning): - d = Jp2k(jfile)[:] + Jp2k(jfile)[:] self.assertTrue(True) def test_NR_broken4_jp2_dump(self): @@ -2882,7 +2969,7 @@ class TestSuiteWarns(MetadataBase): ihdr = glymur.jp2box.ImageHeaderBox(152, 203, num_components=3) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.SRGB) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=SRGB) self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) c = jp2.box[3].main_header @@ -2892,10 +2979,12 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (203, 152), 'xyosiz': (0, 0), - 'xytsiz': (203, 152), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (203, 152), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) pargs = RCME_ISO_8859_1, "Creator: JasPer Vers)on 1.701.0".encode() self.verifyCMEsegment(c.segment[2], CMEsegment(*pargs)) @@ -2910,7 +2999,7 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[3].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[3].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[3].spcod), 9) @@ -2950,7 +3039,7 @@ class TestSuiteWarns(MetadataBase): with self.assertWarns(UserWarning): # Invalid marker ID on codestream. jp2 = Jp2k(jfile) - + self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') def test_NR_file1_dump(self): @@ -2978,8 +3067,8 @@ class TestSuiteWarns(MetadataBase): ihdr = glymur.jp2box.ImageHeaderBox(512, 768, num_components=3) self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.SRGB, - approximation=1) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=SRGB, + approximation=1) self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) # XML box @@ -3006,7 +3095,7 @@ class TestSuiteWarns(MetadataBase): self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.YCC, - approximation=1) + approximation=1) self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) # Jp2 Header @@ -3036,9 +3125,8 @@ class TestSuiteWarns(MetadataBase): ihdr = glymur.jp2box.ImageHeaderBox(640, 480, num_components=3) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox( - colorspace=glymur.core.YCC, - approximation=1) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.YCC, + approximation=1) self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) # sub-sampling @@ -3068,8 +3156,8 @@ class TestSuiteWarns(MetadataBase): ihdr = glymur.jp2box.ImageHeaderBox(512, 768) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox( - colorspace=glymur.core.GREYSCALE, approximation=1) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=GREYSCALE, + approximation=1) self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) def test_NR_file5_dump(self): @@ -3091,16 +3179,18 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(ids, ['ihdr', 'colr', 'colr']) self.verifySignatureBox(jp2.box[0]) - expected = FileTypeBox( - brand='jpx ', compatibility_list=['jp2 ', 'jpx ', 'jpxb']) + expected = FileTypeBox(brand='jpx ', + compatibility_list=['jp2 ', 'jpx ', 'jpxb']) self.verify_filetype_box(jp2.box[1], expected) ihdr = glymur.jp2box.ImageHeaderBox(512, 768, num_components=3) self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox( - method=glymur.core.RESTRICTED_ICC_PROFILE, - approximation=1, icc_profile=bytes([0] * 546)) + method = RESTRICTED_ICC_PROFILE + icc_profile = bytes([0] * 546) + colr = glymur.jp2box.ColourSpecificationBox(method=method, + approximation=1, + icc_profile=icc_profile) self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 546) @@ -3121,10 +3211,10 @@ class TestSuiteWarns(MetadataBase): ihdr = glymur.jp2box.ImageHeaderBox(512, 768, bits_per_component=12) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox( - colorspace=glymur.core.GREYSCALE, - method=glymur.core.ENUMERATED_COLORSPACE, - approximation=1) + method = ENUMERATED_COLORSPACE + colr = glymur.jp2box.ColourSpecificationBox(colorspace=GREYSCALE, + method=method, + approximation=1) self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) def test_NR_file7_dump(self): @@ -3150,12 +3240,13 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') ihdr = glymur.jp2box.ImageHeaderBox(640, 480, - num_components=3, bits_per_component=16) + num_components=3, + bits_per_component=16) self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox( - method=glymur.core.RESTRICTED_ICC_PROFILE, - approximation=1) + method = RESTRICTED_ICC_PROFILE + colr = glymur.jp2box.ColourSpecificationBox(method=method, + approximation=1) self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 13332) @@ -3179,9 +3270,9 @@ class TestSuiteWarns(MetadataBase): ihdr = glymur.jp2box.ImageHeaderBox(400, 700) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox( - method=glymur.core.RESTRICTED_ICC_PROFILE, - approximation=1) + method = RESTRICTED_ICC_PROFILE + colr = glymur.jp2box.ColourSpecificationBox(method=method, + approximation=1) self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) self.assertEqual(jp2.box[2].box[1].icc_profile['Size'], 414) @@ -3234,16 +3325,15 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(jp2.box[2].box[2].mapping_type, (1, 1, 1)) self.assertEqual(jp2.box[2].box[2].palette_index, (0, 1, 2)) - colr = glymur.jp2box.ColourSpecificationBox( - colorspace=glymur.core.SRGB, - approximation=1) + colr = glymur.jp2box.ColourSpecificationBox(colorspace=SRGB, + approximation=1) self.verifyColourSpecificationBox(jp2.box[2].box[3], colr) def test_NR_issue188_beach_64bitsbox(self): lst = ['input', 'nonregression', 'issue188_beach_64bitsbox.jp2'] jfile = opj_data_file('/'.join(lst)) with self.assertWarns(UserWarning): - # There's a warning for an unknown box. + # There's a warning for an unknown box. jp2 = Jp2k(jfile) ids = [box.box_id for box in jp2.box] @@ -3256,10 +3346,12 @@ class TestSuiteWarns(MetadataBase): self.verify_filetype_box(jp2.box[1], FileTypeBox()) ihdr = glymur.jp2box.ImageHeaderBox(200, 200, - num_components=3, colorspace_unknown=True) + num_components=3, + colorspace_unknown=True) self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.SRGB) + cspace = glymur.core.SRGB + colr = glymur.jp2box.ColourSpecificationBox(colorspace=cspace) self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) # Skip the 4th box, it is uknown. @@ -3271,10 +3363,12 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (200, 200), 'xyosiz': (0, 0), - 'xytsiz': (200, 200), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (200, 200), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), + 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -3286,7 +3380,7 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -3329,10 +3423,12 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (117, 117), 'xyosiz': (0, 0), - 'xytsiz': (117, 117), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8, 8), - 'signed': (False, False, False, False), - 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (117, 117), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8, 8), + 'signed': (False, False, False, False), + 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -3344,7 +3440,7 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -3390,10 +3486,12 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(ids, expected) kwargs = {'rsiz': 0, 'xysiz': (117, 117), 'xyosiz': (0, 0), - 'xytsiz': (117, 117), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8, 8), - 'signed': (False, False, False, False), - 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (117, 117), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8, 8), + 'signed': (False, False, False, False), + 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -3405,7 +3503,7 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblk self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index c7e31e5..1d3ec6e 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -10,7 +10,6 @@ seem like logical negative tests to add. import os import re -import sys import tempfile import unittest @@ -88,7 +87,6 @@ class TestSuiteNegativeWrite(unittest.TestCase): def tearDown(self): pass - @unittest.skipIf(NO_SKIMAGE_FREEIMAGE_SUPPORT, "Cannot read input image without scikit-image/freeimage") def test_cinema2K_bad_frame_rate(self): @@ -98,8 +96,7 @@ class TestSuiteNegativeWrite(unittest.TestCase): data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, data=data, cinema2k=36) - + Jp2k(tfile.name, data=data, cinema2k=36) @unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG) def test_psnr_with_cratios(self): @@ -109,8 +106,8 @@ class TestSuiteNegativeWrite(unittest.TestCase): data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, - data=data, psnr=[30, 35, 40], cratios=[2, 3, 4]) + Jp2k(tfile.name, + data=data, psnr=[30, 35, 40], cratios=[2, 3, 4]) def test_code_block_dimensions(self): """don't allow extreme codeblock sizes""" @@ -120,13 +117,13 @@ class TestSuiteNegativeWrite(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: # opj_compress doesn't allow code block area to exceed 4096. with self.assertRaises(IOError): - j = Jp2k(tfile.name, data=data, cbsize=(256, 256)) + Jp2k(tfile.name, data=data, cbsize=(256, 256)) # opj_compress doesn't allow either dimension to be less than 4. with self.assertRaises(IOError): - j = Jp2k(tfile.name, data=data, cbsize=(2048, 2)) + Jp2k(tfile.name, data=data, cbsize=(2048, 2)) with self.assertRaises(IOError): - j = Jp2k(tfile.name, data=data, cbsize=(2, 2048)) + Jp2k(tfile.name, data=data, cbsize=(2, 2048)) def test_precinct_size_not_p2(self): """precinct sizes should be powers of two.""" @@ -134,7 +131,7 @@ class TestSuiteNegativeWrite(unittest.TestCase): data = ifile[::4, ::4] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with self.assertRaises(IOError): - ofile = Jp2k(tfile.name, data=data, psizes=[(13, 13)]) + Jp2k(tfile.name, data=data, psizes=[(13, 13)]) def test_cblk_size_not_power_of_two(self): """code block sizes should be powers of two.""" @@ -142,7 +139,7 @@ class TestSuiteNegativeWrite(unittest.TestCase): data = ifile[::4, ::4] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with self.assertRaises(IOError): - ofile = Jp2k(tfile.name, data=data, cbsize=(13, 12)) + Jp2k(tfile.name, data=data, cbsize=(13, 12)) def test_cblk_size_precinct_size(self): """code block sizes should never exceed half that of precinct size.""" @@ -150,6 +147,4 @@ class TestSuiteNegativeWrite(unittest.TestCase): data = ifile[::4, ::4] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with self.assertRaises(IOError): - ofile = Jp2k(tfile.name, - data=data, cbsize=(64, 64), psizes=[(64, 64)]) - + Jp2k(tfile.name, data=data, cbsize=(64, 64), psizes=[(64, 64)]) diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index ecb7a7d..7f0fd4b 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -34,6 +34,7 @@ from glymur import Jp2k from glymur.codestream import SIZsegment from glymur.version import openjpeg_version + class CinemaBase(fixtures.MetadataBase): def verify_cinema_cod(self, cod_segment): @@ -44,14 +45,14 @@ class CinemaBase(fixtures.MetadataBase): self.assertEqual(cod_segment.layers, 1) self.assertEqual(cod_segment.spcod[3], 1) # mct self.assertEqual(cod_segment.spcod[4], 5) # levels - self.assertEqual(tuple(cod_segment.code_block_size), (32, 32)) # cblksz + self.assertEqual(tuple(cod_segment.code_block_size), (32, 32)) def check_cinema4k_codestream(self, codestream, image_size): kwargs = {'rsiz': 4, 'xysiz': image_size, 'xyosiz': (0, 0), - 'xytsiz': image_size, 'xytosiz': (0, 0), - 'bitdepth': (12, 12, 12), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + 'xytsiz': image_size, 'xytosiz': (0, 0), + 'bitdepth': (12, 12, 12), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} self.verifySizSegment(codestream.segment[1], SIZsegment(**kwargs)) self.verify_cinema_cod(codestream.segment[2]) @@ -59,9 +60,9 @@ class CinemaBase(fixtures.MetadataBase): def check_cinema2k_codestream(self, codestream, image_size): kwargs = {'rsiz': 3, 'xysiz': image_size, 'xyosiz': (0, 0), - 'xytsiz': image_size, 'xytosiz': (0, 0), - 'bitdepth': (12, 12, 12), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + 'xytsiz': image_size, 'xytosiz': (0, 0), + 'bitdepth': (12, 12, 12), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} self.verifySizSegment(codestream.segment[1], SIZsegment(**kwargs)) self.verify_cinema_cod(codestream.segment[2]) @@ -88,8 +89,8 @@ class WriteCinema(CinemaBase): data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, data=data, - cinema2k=48, cratios=[200, 100, 50]) + Jp2k(tfile.name, data=data, + cinema2k=48, cratios=[200, 100, 50]) def test_cinema4K_with_others(self): """Can't specify cinema4k with any other options.""" @@ -98,8 +99,8 @@ class WriteCinema(CinemaBase): data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, data=data, - cinema4k=True, cratios=[200, 100, 50]) + Jp2k(tfile.name, data=data, + cinema4k=True, cratios=[200, 100, 50]) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @@ -135,7 +136,8 @@ class WriteCinemaWarns(CinemaBase): infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertWarnsRegex(UserWarning, 'OpenJPEG library warning'): + with self.assertWarnsRegex(UserWarning, + 'OpenJPEG library warning'): j = Jp2k(tfile.name, data=data, cinema2k=48) codestream = j.get_codestream() @@ -146,7 +148,8 @@ class WriteCinemaWarns(CinemaBase): infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertWarnsRegex(UserWarning, 'OpenJPEG library warning'): + with self.assertWarnsRegex(UserWarning, + 'OpenJPEG library warning'): j = Jp2k(tfile.name, data=data, cinema2k=48) codestream = j.get_codestream() @@ -157,7 +160,8 @@ class WriteCinemaWarns(CinemaBase): infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertWarnsRegex(UserWarning, 'OpenJPEG library warning'): + with self.assertWarnsRegex(UserWarning, + 'OpenJPEG library warning'): j = Jp2k(tfile.name, data=data, cinema2k=24) codestream = j.get_codestream() @@ -168,7 +172,8 @@ class WriteCinemaWarns(CinemaBase): infile = opj_data_file(relfile) data = skimage.io.imread(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertWarnsRegex(UserWarning, 'OpenJPEG library warning'): + with self.assertWarnsRegex(UserWarning, + 'OpenJPEG library warning'): # OpenJPEG library warning: The desired maximum codestream # size has limited at least one of the desired quality layers j = Jp2k(tfile.name, data=data, cinema2k=24) @@ -216,7 +221,7 @@ class TestNegative2pointzero(unittest.TestCase): with patch('glymur.version.openjpeg_version', new=version): with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with self.assertRaises(IOError): - j = Jp2k(tfile.name, data=data, cinema2k=48) + Jp2k(tfile.name, data=data, cinema2k=48) @unittest.skipIf(re.match(r'''1.[0-4]''', openjpeg_version) is not None, @@ -248,7 +253,6 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - def test_NR_ENC_Bretagne1_ppm_1_encode(self): """NR-ENC-Bretagne1.ppm-1-encode""" infile = opj_data_file('input/nonregression/Bretagne1.ppm') @@ -260,10 +264,11 @@ class TestSuiteWrite(fixtures.MetadataBase): c = j.get_codestream() kwargs = {'rsiz': 0, 'xysiz': (640, 480), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(c.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(c.segment[2].scod & 2) # no sop @@ -275,7 +280,7 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) # cblksz self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, False, False, False]) self.assertEqual(c.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(c.segment[2].spcod), 9) @@ -291,10 +296,11 @@ class TestSuiteWrite(fixtures.MetadataBase): codestream = j.get_codestream() kwargs = {'rsiz': 0, 'xysiz': (640, 480), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -306,7 +312,8 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, + False, False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -316,18 +323,19 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/Bretagne1.ppm') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, - data=data, - psnr=[30, 35, 40], cbsize=(16, 16), psizes=[(64, 64)]) + j = Jp2k(tfile.name, + data=data, + psnr=[30, 35, 40], cbsize=(16, 16), psizes=[(64, 64)]) # Should be three layers. codestream = j.get_codestream() kwargs = {'rsiz': 0, 'xysiz': (640, 480), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -339,7 +347,8 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(codestream.segment[2].code_block_size), (16, 16)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, + False, False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(codestream.segment[2].precinct_size, @@ -352,20 +361,21 @@ class TestSuiteWrite(fixtures.MetadataBase): data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, - data=data, - psizes=[(128, 128)] * 3, - cratios=[100, 20, 2], - tilesize=(480, 640), - cbsize=(32, 32)) + data=data, + psizes=[(128, 128)] * 3, + cratios=[100, 20, 2], + tilesize=(480, 640), + cbsize=(32, 32)) # Should be three layers. codestream = j.get_codestream() kwargs = {'rsiz': 0, 'xysiz': (2592, 1944), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -377,7 +387,8 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(codestream.segment[2].code_block_size), (32, 32)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, + False, False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(codestream.segment[2].precinct_size, @@ -393,10 +404,11 @@ class TestSuiteWrite(fixtures.MetadataBase): codestream = j.get_codestream() kwargs = {'rsiz': 0, 'xysiz': (2592, 1944), 'xyosiz': (0, 0), - 'xytsiz': (127, 127), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (127, 127), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -408,7 +420,8 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, + False, False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -423,10 +436,11 @@ class TestSuiteWrite(fixtures.MetadataBase): codestream = j.get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (5183, 3887), 'xyosiz': (0, 0), - 'xytsiz': (5183, 3887), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(2, 2, 2), (2, 2, 2)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (5183, 3887), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(2, 2, 2), (2, 2, 2)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertTrue(codestream.segment[2].scod & 2) # sop @@ -438,7 +452,8 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, + False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -458,10 +473,11 @@ class TestSuiteWrite(fixtures.MetadataBase): codestream = j.get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (2592, 1944), 'xyosiz': (0, 0), - 'xytsiz': (2592, 1944), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (2592, 1944), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -471,9 +487,10 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(codestream.segment[2].spcod[3], 1) # mct self.assertEqual(codestream.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), - (64, 64)) # cblksz + (64, 64)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, True, True, False, False, True]) + [False, True, True, + False, False, True]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -488,15 +505,16 @@ class TestSuiteWrite(fixtures.MetadataBase): data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: j = Jp2k(tfile.name, - data=data, grid_offset=[300, 150], cratios=[800]) + data=data, grid_offset=[300, 150], cratios=[800]) codestream = j.get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (2742, 2244), 'xyosiz': (150, 300), - 'xytsiz': (2742, 2244), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (2742, 2244), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -508,7 +526,8 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, + False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -523,10 +542,11 @@ class TestSuiteWrite(fixtures.MetadataBase): codestream = j.get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (2592, 1944), 'xyosiz': (0, 0), - 'xytsiz': (2592, 1944), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (2592, 1944), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -538,7 +558,8 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, + False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -553,10 +574,11 @@ class TestSuiteWrite(fixtures.MetadataBase): codestream = j.get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (640, 480), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -568,7 +590,8 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, + False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -578,7 +601,7 @@ class TestSuiteWrite(fixtures.MetadataBase): data = read_image(opj_data_file('input/nonregression/Rome.bmp')) with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: jp2 = Jp2k(tfile.name, - data=data, psnr=[30, 35, 50], prog='LRCP', numres=3) + data=data, psnr=[30, 35, 50], prog='LRCP', numres=3) ids = [box.box_id for box in jp2.box] self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) @@ -616,10 +639,11 @@ class TestSuiteWrite(fixtures.MetadataBase): codestream = jp2.box[3].main_header kwargs = {'rsiz': 0, 'xysiz': (640, 480), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (640, 480), 'xytosiz': (0, 0), + 'bitdepth': (8, 8, 8), 'signed': (False, False, False), + 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -631,7 +655,8 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, + False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -648,10 +673,11 @@ class TestSuiteWrite(fixtures.MetadataBase): codestream = j.get_codestream(header_only=False) kwargs = {'rsiz': 0, 'xysiz': (1024, 1024), 'xyosiz': (0, 0), - 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), - 'bitdepth': (16,), 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(codestream.segment[1], glymur.codestream.SIZsegment(**kwargs)) + 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), + 'bitdepth': (16,), 'signed': (False,), + 'xyrsiz': [(1,), (1,)]} + self.verifySizSegment(codestream.segment[1], + glymur.codestream.SIZsegment(**kwargs)) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -663,7 +689,8 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, False, False, False]) + [False, False, False, + False, False, False]) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 5eedfe5..ab3e9fd 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -32,11 +32,12 @@ import lxml.etree as ET import glymur from glymur import Jp2k, command_line from . import fixtures -from .fixtures import ( - OPJ_DATA_ROOT, opj_data_file, - WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG, - WINDOWS_TMP_FILE_MSG, text_gbr_27, text_gbr_33, text_gbr_34 -) +from .fixtures import (OPJ_DATA_ROOT, opj_data_file, + WARNING_INFRASTRUCTURE_ISSUE, + WARNING_INFRASTRUCTURE_MSG, + WINDOWS_TMP_FILE_MSG, + text_gbr_27, text_gbr_33, text_gbr_34) + @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) class TestPrinting(unittest.TestCase): @@ -52,23 +53,11 @@ class TestPrinting(unittest.TestCase): def tearDown(self): pass - def test_codestream(self): - """Should be able to print a raw codestream.""" - j = glymur.Jp2k(self.j2kfile) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(j) - actual = fake_out.getvalue().strip() - # Remove the file line, as that is filesystem-dependent. - lines = actual.split('\n') - actual = '\n'.join(lines[1:]) - - self.assertEqual(actual, fixtures.codestream) - def test_version_info(self): """Should be able to print(glymur.version.info)""" with patch('sys.stdout', new=StringIO()) as fake_out: print(glymur.version.info) - actual = fake_out.getvalue().strip() + fake_out.getvalue().strip() self.assertTrue(True) @@ -78,7 +67,7 @@ class TestPrinting(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile: with open(self.jpxfile, 'rb') as ifile: tfile.write(ifile.read()) - + # Add the header for an unknown superbox. write_buffer = struct.pack('>I4s', 20, 'grp '.encode()) tfile.write(write_buffer) @@ -107,7 +96,8 @@ class TestPrinting(unittest.TestCase): with self.assertRaises(TypeError): glymur.set_printoptions(hi='low') - @unittest.skipIf(re.match("1.5|2", glymur.version.openjpeg_version) is None, + @unittest.skipIf(re.match("1.5|2", + glymur.version.openjpeg_version) is None, "Must have openjpeg 1.5 or higher to run") def test_asoc_label_box(self): """verify printing of asoc, label boxes""" @@ -115,9 +105,8 @@ class TestPrinting(unittest.TestCase): # OpenJPEG doesn't have such a file. data = glymur.Jp2k(self.jp2file)[::2, ::2] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = glymur.Jp2k(tfile.name, data=data) - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2: + j = glymur.Jp2k(tfile.name, data=data) # Offset of the codestream is where we start. wbuffer = tfile.read(77) @@ -419,7 +408,7 @@ class TestPrinting(unittest.TestCase): @unittest.skipIf(sys.hexversion < 0x03000000, "Only trusting python3 for printing non-ascii chars") def test_xml_cyrrilic(self): - """Should be able to print an XMLBox with utf-8 encoding (cyrrillic).""" + """Should be able to print XMLBox with utf-8 encoding (cyrrillic).""" # Seems to be inconsistencies between different versions of python2.x # as to what gets printed. # @@ -437,7 +426,8 @@ class TestPrinting(unittest.TestCase): actual = fake_out.getvalue().strip() if sys.hexversion < 0x03000000: lines = ["XML Box (xml ) @ (-1, 0)", - " Россия"] + (" Росс", + "ия")] else: lines = ["XML Box (xml ) @ (-1, 0)", " Россия"] @@ -613,12 +603,14 @@ class TestPrinting(unittest.TestCase): lines = ["UUID Box (uuid) @ (1135519, 76)", " UUID: 4a706754-6966-6645-7869-662d3e4a5032 (EXIF)", - " UUID Data: OrderedDict([('ImageWidth', 256), ('ImageLength', 512), ('Make', 'HTC')])"] + (" UUID Data: OrderedDict([('ImageWidth', 256)," + " ('ImageLength', 512), ('Make', 'HTC')])")] expected = '\n'.join(lines) self.assertEqual(actual, expected) + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @@ -826,6 +818,7 @@ class TestPrintingOpjDataRoot(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @@ -852,7 +845,7 @@ class TestPrintingOpjDataRootWarns(unittest.TestCase): filename = opj_data_file('input/nonregression/edf_c2_1103421.jp2') with self.assertWarns(UserWarning): jp2 = Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: + with patch('sys.stdout', new=StringIO()): print(jp2) def test_bad_rsiz(self): @@ -860,7 +853,7 @@ class TestPrintingOpjDataRootWarns(unittest.TestCase): filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') with self.assertWarns(UserWarning): j = Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: + with patch('sys.stdout', new=StringIO()): print(j) def test_bad_wavelet_transform(self): @@ -868,7 +861,7 @@ class TestPrintingOpjDataRootWarns(unittest.TestCase): filename = opj_data_file('input/nonregression/edf_c2_10025.jp2') with self.assertWarns(UserWarning): jp2 = Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: + with patch('sys.stdout', new=StringIO()): print(jp2) def test_invalid_progression_order(self): @@ -1029,7 +1022,7 @@ class TestPrintingOpjDataRootWarns(unittest.TestCase): 'issue171.jp2')) with self.assertWarns(UserWarning): jp2 = Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: + with patch('sys.stdout', new=StringIO()): # No need to verify, it's enough that we don't error out. print(jp2) @@ -1069,7 +1062,8 @@ class TestJp2dump(unittest.TestCase): """Verify dumping with -c 0, supressing all codestream details.""" actual = self.run_jp2dump(['', '-c', '0', self.jp2file]) - self.assertEqual(actual, fixtures.nemo_dump_no_codestream) + expected = fixtures.nemo_dump_no_codestream + self.assertEqual(actual, expected) def test_codestream_1(self): """Verify dumping with -c 1, print just the header.""" @@ -1112,4 +1106,3 @@ class TestJp2dump(unittest.TestCase): command_line.main() actual = fake_out.getvalue().strip() self.assertRegex(actual, "File: .*") - From 6f697526f24c865e3e57de090e7d8f1d08a79895 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 31 Dec 2014 22:41:02 -0500 Subject: [PATCH 305/326] pep8 work, closes #307 --- docs/source/conf.py | 83 ++++++------ glymur/__init__.py | 14 +- glymur/_uuid_io.py | 2 - glymur/codestream.py | 35 ++--- glymur/command_line.py | 1 - glymur/core.py | 2 - glymur/data/__init__.py | 1 - glymur/jp2box.py | 193 ++++++++++++++-------------- glymur/jp2k.py | 20 ++- glymur/lib/config.py | 3 - glymur/lib/openjp2.py | 2 - glymur/lib/openjpeg.py | 2 - glymur/lib/test/test_openjp2.py | 23 ++-- glymur/lib/test/test_openjpeg.py | 3 +- glymur/lib/test/test_printing.py | 4 +- glymur/test/fixtures.py | 1 - glymur/test/test_callbacks.py | 8 -- glymur/test/test_config.py | 10 -- glymur/test/test_glymur_warnings.py | 4 - glymur/test/test_icc.py | 9 -- glymur/test/test_jp2box.py | 21 +-- glymur/test/test_jp2box_uuid.py | 9 -- glymur/test/test_jp2box_xml.py | 12 -- glymur/test/test_jp2k.py | 13 -- glymur/test/test_opj_suite.py | 25 ---- glymur/test/test_opj_suite_dump.py | 25 ---- glymur/test/test_opj_suite_neg.py | 6 - glymur/test/test_opj_suite_write.py | 4 - glymur/test/test_printing.py | 11 +- setup.py | 6 +- 30 files changed, 190 insertions(+), 362 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 8a87a64..e9387f4 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -13,7 +13,6 @@ # serve to show the default. import sys -import os class Mock(object): @@ -42,12 +41,12 @@ for mod_name in MOCK_MODULES: # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath('.')) # -- General configuration ---------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. @@ -62,7 +61,7 @@ templates_path = ['_templates'] source_suffix = '.rst' # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' @@ -82,13 +81,13 @@ release = '0.7.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -96,24 +95,24 @@ exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # -- Options for HTML output -------------------------------------------------- @@ -125,26 +124,26 @@ html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -153,44 +152,44 @@ html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'glymurdoc' @@ -199,13 +198,13 @@ htmlhelp_basename = 'glymurdoc' # -- Options for LaTeX output ------------------------------------------------- # The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', +# 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', +# 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. -#'preamble': '', +# 'preamble': '', latex_elements = {} # Grouping the document tree into LaTeX files. List of tuples @@ -216,23 +215,23 @@ latex_documents = [('index', 'glymur.tex', u'glymur Documentation', # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output ------------------------------------------- @@ -245,7 +244,7 @@ man_pages = [ ] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ----------------------------------------------- @@ -258,13 +257,13 @@ texinfo_documents = [('index', 'glymur', u'glymur Documentation', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # Example configuration for intersphinx: refer to the Python standard library. diff --git a/glymur/__init__.py b/glymur/__init__.py index 9a4d8b1..d8971b2 100644 --- a/glymur/__init__.py +++ b/glymur/__init__.py @@ -1,21 +1,25 @@ """glymur - read, write, and interrogate JPEG 2000 files """ -import sys import unittest from glymur import version __version__ = version.version from .jp2k import Jp2k -from .jp2box import ( - get_printoptions, set_printoptions, - get_parseoptions, set_parseoptions -) +from .jp2box import (get_printoptions, + set_printoptions, + get_parseoptions, + set_parseoptions) from . import data + def runtests(): """Discover and run all tests for the glymur package. """ suite = unittest.defaultTestLoader.discover(__path__[0]) unittest.TextTestRunner(verbosity=2).run(suite) + + +__all__ = [__version__, Jp2k, get_printoptions, set_printoptions, + get_parseoptions, set_parseoptions, data, runtests] diff --git a/glymur/_uuid_io.py b/glymur/_uuid_io.py index 7bcf2cb..3c63b0a 100644 --- a/glymur/_uuid_io.py +++ b/glymur/_uuid_io.py @@ -3,8 +3,6 @@ Part of glymur. """ from collections import OrderedDict -import pprint -import re import struct import sys import warnings diff --git a/glymur/codestream.py b/glymur/codestream.py index aa12bd7..ea94afd 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -6,16 +6,13 @@ codestreams. # 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 @@ -31,16 +28,15 @@ from .core import (LRCP, RLCP, RPCL, PCRL, CPRL, from .lib import openjp2 as opj2 _factory = lambda x: '{0} (invalid)'.format(x) -_PROGRESSION_ORDER_DISPLAY = _Keydefaultdict(_factory, - { LRCP: 'LRCP', - RLCP: 'RLCP', - RPCL: 'RPCL', - PCRL: 'PCRL', - CPRL: 'CPRL'}) +_PROGRESSION_ORDER_DISPLAY = _Keydefaultdict(_factory, {LRCP: 'LRCP', + RLCP: 'RLCP', + RPCL: 'RPCL', + PCRL: 'PCRL', + CPRL: 'CPRL'}) -_WAVELET_TRANSFORM_DISPLAY = _Keydefaultdict(_factory, - { WAVELET_XFORM_9X7_IRREVERSIBLE: '9-7 irreversible', - WAVELET_XFORM_5X3_REVERSIBLE: '5-3 reversible'}) +_keysvalues = {WAVELET_XFORM_9X7_IRREVERSIBLE: '9-7 irreversible', + WAVELET_XFORM_5X3_REVERSIBLE: '5-3 reversible'} +_WAVELET_TRANSFORM_DISPLAY = _Keydefaultdict(_factory, _keysvalues) _NO_PROFILE = 0 _PROFILE_0 = 1 @@ -51,12 +47,11 @@ _PROFILE_4 = 4 _KNOWN_PROFILES = [_NO_PROFILE, _PROFILE_0, _PROFILE_1, _PROFILE_3, _PROFILE_4] # How to display the codestream profile. -_CAPABILITIES_DISPLAY = _Keydefaultdict(_factory, - { _NO_PROFILE: 'no profile', - _PROFILE_0: '0', - _PROFILE_1: '1', - _PROFILE_3: 'Cinema 2K', - _PROFILE_4: 'Cinema 4K'}) +_CAPABILITIES_DISPLAY = _Keydefaultdict(_factory, {_NO_PROFILE: 'no profile', + _PROFILE_0: '0', + _PROFILE_1: '1', + _PROFILE_3: 'Cinema 2K', + _PROFILE_4: 'Cinema 4K'}) # Need a catch-all list of valid markers. # See table A-1 in ISO/IEC FCD15444-1. @@ -298,7 +293,6 @@ class Codestream(object): msg += ''.join(strs) return msg - # pylint: disable=R0201 def _parse_cme_segment(self, fptr): """Parse the CME marker segment. @@ -694,7 +688,7 @@ class Codestream(object): try: num_tiles_x = (xysiz[0] - xyosiz[0]) / (xytsiz[0] - xytosiz[0]) num_tiles_y = (xysiz[1] - xyosiz[1]) / (xytsiz[1] - xytosiz[1]) - except ZeroDivisionError as err: + except ZeroDivisionError: warnings.warn("Invalid tile dimensions.") else: numtiles = math.ceil(num_tiles_x) * math.ceil(num_tiles_y) @@ -828,7 +822,6 @@ class Codestream(object): return TLMsegment(length, offset, ztlm, ttlm, ptlm) - # pylint: disable=W0613 def _parse_reserved_marker(self, fptr): """Marker range between 0xff30 and 0xff39. """ diff --git a/glymur/command_line.py b/glymur/command_line.py index 5f0d357..ff442f3 100644 --- a/glymur/command_line.py +++ b/glymur/command_line.py @@ -3,7 +3,6 @@ Entry point for console script jp2dump. """ import argparse import os -import sys import warnings from . import Jp2k, set_printoptions, lib diff --git a/glymur/core.py b/glymur/core.py index 3327253..644dcfd 100644 --- a/glymur/core.py +++ b/glymur/core.py @@ -1,8 +1,6 @@ """Core definitions to be shared amongst the modules. """ import collections -import copy -import lxml.etree as ET class _Keydefaultdict(collections.defaultdict): diff --git a/glymur/data/__init__.py b/glymur/data/__init__.py index de1e62a..066edd2 100644 --- a/glymur/data/__init__.py +++ b/glymur/data/__init__.py @@ -43,4 +43,3 @@ def jpxfile(): """ filename = pkg_resources.resource_filename(__name__, "heliov.jpx") return filename - diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 39e1f6a..4a390a5 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -11,8 +11,6 @@ References Extensions """ -# pylint: disable=C0302,R0903,R0913,W0142 - from collections import OrderedDict import datetime import io @@ -22,7 +20,7 @@ import pprint import struct import sys import textwrap -import uuid +from uuid import UUID import warnings import lxml.etree as ET @@ -44,11 +42,13 @@ _METHOD_DISPLAY = { VENDOR_COLOR_METHOD: 'vendor color method'} _factory = lambda x: '{0} (invalid)'.format(x) -_APPROX_DISPLAY = _Keydefaultdict(_factory, - {1: 'accurately represents correct colorspace definition', - 2: 'approximates correct colorspace definition, exceptional quality', - 3: 'approximates correct colorspace definition, reasonable quality', - 4: 'approximates correct colorspace definition, poor quality'}) +_keysvalues = {1: 'accurately represents correct colorspace definition', + 2: ('approximates correct colorspace definition, ' + 'exceptional quality'), + 3: ('approximates correct colorspace definition, ' + 'reasonable quality'), + 4: 'approximates correct colorspace definition, poor quality'} +_APPROX_DISPLAY = _Keydefaultdict(_factory, _keysvalues) class Jp2kBox(object): @@ -1983,7 +1983,6 @@ class PaletteBox(Jp2kBox): *bps_signed) fptr.write(write_buffer) - bps = self.bits_per_component # All components are the same. Writing is straightforward. if self.bits_per_component[0] <= 8: write_buffer = memoryview(self.palette.astype(np.uint8)) @@ -2023,13 +2022,10 @@ class PaletteBox(Jp2kBox): # Ok the palette has the same datatype for all columns. We should # be able to efficiently read it. if bps[0] <= 8: - nbytes_per_row = ncols dtype = np.uint8 elif bps[0] <= 16: - nbytes_per_row = 2 * ncols dtype = np.uint16 elif bps[0] <= 32: - nbytes_per_row = 3 * ncols dtype = np.uint32 palette = np.frombuffer(read_buffer[3 + ncols:], dtype=dtype) @@ -2073,80 +2069,80 @@ _READER_REQUIREMENTS_DISPLAY = { 7: 'JPEG codestream as defined in ISO/IEC 10918-1', 8: 'Deprecated - does not contain opacity', 9: 'Non-premultiplied opacity channel', - 10: 'Premultiplied opacity channel', - 11: 'Chroma-key based opacity', - 12: 'Deprecated - codestream is contiguous', - 13: 'Fragmented codestream where all fragments are in file and in order', - 14: 'Fragmented codestream where all fragments are in file ' - + 'but are out of order', - 15: 'Fragmented codestream where not all fragments are within the file ' - + 'but are all in locally accessible files', - 16: 'Fragmented codestream where some fragments may be accessible ' - + 'only through a URL specified network connection', - 17: 'Compositing required to produce rendered result from multiple ' - + 'compositing layers', - 18: 'Deprecated - support for compositing is not required', - 19: 'Deprecated - contains multiple, discrete layers that should not ' - + 'be combined through either animation or compositing', - 20: 'Deprecated - compositing layers each contain only a single ' - + 'codestream', - 21: 'At least one compositing layer consists of multiple codestreams', - 22: 'Deprecated - all compositing layers are in the same colourspace', - 23: 'Colourspace transformations are required to combine compositing ' - + 'layers; not all compositing layers are in the same colourspace', - 24: 'Deprecated - rendered result created without using animation', - 25: 'Deprecated - animated, but first layer covers entire area and is ' - + 'opaque', - 26: 'First animation layer does not cover entire rendered result', - 27: 'Deprecated - animated, and no layer is reused', - 28: 'Reuse of animation layers', - 29: 'Deprecated - animated, but layers are reused', - 30: 'Some animated frames are non-persistent', - 31: 'Deprecated - rendered result created without using scaling', - 32: 'Rendered result involves scaling within a layer', - 33: 'Rendered result involves scaling between layers', - 34: 'ROI metadata', - 35: 'IPR metadata', - 36: 'Content metadata', - 37: 'History metadata', - 38: 'Creation metadata', - 39: 'JPX digital signatures', - 40: 'JPX checksums', - 41: 'Desires Graphics Arts Reproduction specified', - 42: 'Deprecated - compositing layer uses palettized colour', - 43: 'Deprecated - compositing layer uses restricted ICC profile', - 44: 'Compositing layer uses Any ICC profile', - 45: 'Deprecated - compositing layer uses sRGB enumerated colourspace', - 46: 'Deprecated - compositing layer uses sRGB-grey enumerated colourspace', - 47: 'BiLevel 1 enumerated colourspace', - 48: 'BiLevel 2 enumerated colourspace', - 49: 'YCbCr 1 enumerated colourspace', - 50: 'YCbCr 2 enumerated colourspace', - 51: 'YCbCr 3 enumerated colourspace', - 52: 'PhotoYCC enumerated colourspace', - 53: 'YCCK enumerated colourspace', - 54: 'CMY enumerated colourspace', - 55: 'CMYK enumerated colorspace', - 56: 'CIELab enumerated colourspace with default parameters', - 57: 'CIELab enumerated colourspace with non-default parameters', - 58: 'CIEJab enumerated colourspace with default parameters', - 59: 'CIEJab enumerated colourspace with non-default parameters', - 60: 'e-sRGB enumerated colorspace', - 61: 'ROMM_RGB enumerated colorspace', - 62: 'Non-square samples', - 63: 'Deprecated - compositing layers have labels', - 64: 'Deprecated - codestreams have labels', - 65: 'Deprecated - compositing layers have different colour spaces', - 66: 'Deprecated - compositing layers have different metadata', - 67: 'GIS metadata XML box', - 68: 'JPSEC extensions in codestream as specified by ISO/IEC 15444-8', - 69: 'JP3D extensions in codestream as specified by ISO/IEC 15444-10', - 70: 'Deprecated - compositing layer uses sYCC enumerated colour space', - 71: 'e-sYCC enumerated colourspace', - 72: 'JPEG 2000 Part 2 codestream as restricted by baseline conformance ' - + 'requirements in M.9.2.3', - 73: 'YPbPr(1125/60) enumerated colourspace', - 74: 'YPbPr(1250/50) enumerated colourspace'} + 10: 'Premultiplied opacity channel', + 11: 'Chroma-key based opacity', + 12: 'Deprecated - codestream is contiguous', + 13: 'Fragmented codestream where all fragments are in file and in order', + 14: ('Fragmented codestream where all fragments are in file ' + 'but are out of order'), + 15: ('Fragmented codestream where not all fragments are within the file ' + 'but are all in locally accessible files'), + 16: ('Fragmented codestream where some fragments may be accessible ' + 'only through a URL specified network connection'), + 17: ('Compositing required to produce rendered result from multiple ' + 'compositing layers'), + 18: 'Deprecated - support for compositing is not required', + 19: ('Deprecated - contains multiple, discrete layers that should not ' + 'be combined through either animation or compositing'), + 20: ('Deprecated - compositing layers each contain only a single ' + 'codestream'), + 21: 'At least one compositing layer consists of multiple codestreams', + 22: 'Deprecated - all compositing layers are in the same colourspace', + 23: ('Colourspace transformations are required to combine compositing ' + 'layers; not all compositing layers are in the same colourspace'), + 24: 'Deprecated - rendered result created without using animation', + 25: ('Deprecated - animated, but first layer covers entire area and is ' + 'opaque'), + 26: 'First animation layer does not cover entire rendered result', + 27: 'Deprecated - animated, and no layer is reused', + 28: 'Reuse of animation layers', + 29: 'Deprecated - animated, but layers are reused', + 30: 'Some animated frames are non-persistent', + 31: 'Deprecated - rendered result created without using scaling', + 32: 'Rendered result involves scaling within a layer', + 33: 'Rendered result involves scaling between layers', + 34: 'ROI metadata', + 35: 'IPR metadata', + 36: 'Content metadata', + 37: 'History metadata', + 38: 'Creation metadata', + 39: 'JPX digital signatures', + 40: 'JPX checksums', + 41: 'Desires Graphics Arts Reproduction specified', + 42: 'Deprecated - compositing layer uses palettized colour', + 43: 'Deprecated - compositing layer uses restricted ICC profile', + 44: 'Compositing layer uses Any ICC profile', + 45: 'Deprecated - compositing layer uses sRGB enumerated colourspace', + 46: 'Deprecated - compositing layer uses sRGB-grey enumerated colourspace', + 47: 'BiLevel 1 enumerated colourspace', + 48: 'BiLevel 2 enumerated colourspace', + 49: 'YCbCr 1 enumerated colourspace', + 50: 'YCbCr 2 enumerated colourspace', + 51: 'YCbCr 3 enumerated colourspace', + 52: 'PhotoYCC enumerated colourspace', + 53: 'YCCK enumerated colourspace', + 54: 'CMY enumerated colourspace', + 55: 'CMYK enumerated colorspace', + 56: 'CIELab enumerated colourspace with default parameters', + 57: 'CIELab enumerated colourspace with non-default parameters', + 58: 'CIEJab enumerated colourspace with default parameters', + 59: 'CIEJab enumerated colourspace with non-default parameters', + 60: 'e-sRGB enumerated colorspace', + 61: 'ROMM_RGB enumerated colorspace', + 62: 'Non-square samples', + 63: 'Deprecated - compositing layers have labels', + 64: 'Deprecated - codestreams have labels', + 65: 'Deprecated - compositing layers have different colour spaces', + 66: 'Deprecated - compositing layers have different metadata', + 67: 'GIS metadata XML box', + 68: 'JPSEC extensions in codestream as specified by ISO/IEC 15444-8', + 69: 'JP3D extensions in codestream as specified by ISO/IEC 15444-10', + 70: 'Deprecated - compositing layer uses sYCC enumerated colour space', + 71: 'e-sYCC enumerated colourspace', + 72: ('JPEG 2000 Part 2 codestream as restricted by baseline conformance ' + 'requirements in M.9.2.3'), + 73: 'YPbPr(1125/60) enumerated colourspace', + 74: 'YPbPr(1250/50) enumerated colourspace'} class ReaderRequirementsBox(Jp2kBox): @@ -2209,7 +2205,8 @@ class ReaderRequirementsBox(Jp2kBox): if _printoptions['short'] is True: return msg - msg += '\n Fully Understands Aspect Mask: 0x{0:x}'.format(self.fuam) + msg += '\n Fully Understands Aspect Mask: 0x{0:x}' + msg = msg.format(self.fuam) msg += '\n Display Completely Mask: 0x{0:x}'.format(self.dcm) msg += '\n Standard Features and Masks:' @@ -2265,8 +2262,8 @@ class ReaderRequirementsBox(Jp2kBox): standard_flag, standard_mask = data nflags = len(standard_flag) - vendor_offset = 1 + 2 * mask_length + 2 \ - + (2 + mask_length) * nflags + vendor_offset = (1 + 2 * mask_length + 2 + + (2 + mask_length) * nflags) data = _parse_vendor_features(read_buffer[vendor_offset:], mask_length) vendor_feature, vendor_mask = data @@ -2323,8 +2320,8 @@ def _parse_rreq3(read_buffer, length, offset): read_buffer = read_buffer[9 + num_standard_features * 10:] for j in range(num_vendor_features): uslice = slice(j * entry_length, (j + 1) * entry_length) - ubuffer = read_buffer[slice] - vendor_feature.append(uuid.UUID(bytes=ubuffer[0:16])) + ubuffer = read_buffer[uslice] + vendor_feature.append(UUID(bytes=ubuffer[0:16])) lst = struct.unpack('>BBB', ubuffer[16:]) vmask = lst[0] << 16 | lst[1] << 8 | lst[2] @@ -2392,7 +2389,7 @@ def _parse_vendor_features(read_buffer, mask_length): for j in range(num_vendor_features): uslice = slice(2 + j * entry_length, 2 + (j + 1) * entry_length) ubuffer = read_buffer[uslice] - vendor_feature.append(uuid.UUID(bytes=ubuffer[0:16])) + vendor_feature.append(UUID(bytes=ubuffer[0:16])) vmask = struct.unpack('>' + mask_format, ubuffer[16:]) vendor_mask.append(vmask) @@ -2944,7 +2941,7 @@ class UUIDListBox(Jp2kBox): ulst = [] for j in range(num_uuids): uuid_buffer = read_buffer[2 + j * 16:2 + (j + 1) * 16] - ulst.append(uuid.UUID(bytes=uuid_buffer)) + ulst.append(UUID(bytes=uuid_buffer)) return cls(ulst, length=length, offset=offset) @@ -3202,7 +3199,7 @@ class UUIDBox(Jp2kBox): """ Private function for parsing UUID payloads if possible. """ - if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): + if self.uuid == UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): self.data = _uuid_io.xml(self.raw_data) elif self.uuid.bytes == b'JpgTiffExif->JP2': self.data = _uuid_io.tiff_header(self.raw_data) @@ -3220,7 +3217,7 @@ class UUIDBox(Jp2kBox): return msg msg = '{0}\n UUID: {1}'.format(msg, self.uuid) - if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): + if self.uuid == UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): msg += ' (XMP)' elif self.uuid.bytes == b'JpgTiffExif->JP2': msg += ' (EXIF)' @@ -3228,11 +3225,11 @@ class UUIDBox(Jp2kBox): msg += ' (unknown)' if (((_printoptions['xml'] is False) and - (self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac')))): + (self.uuid == UUID('be7acfcb-97a9-42e8-9c71-999491e3afac')))): # If it's an XMP UUID, don't print the XML contents. return msg - if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): + if self.uuid == UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): line = '\n UUID Data:\n{0}' xmlstring = ET.tostring(self.data, encoding='utf-8', @@ -3275,7 +3272,7 @@ class UUIDBox(Jp2kBox): """ num_bytes = offset + length - fptr.tell() read_buffer = fptr.read(num_bytes) - the_uuid = uuid.UUID(bytes=read_buffer[0:16]) + the_uuid = UUID(bytes=read_buffer[0:16]) return cls(the_uuid, read_buffer[16:], length=length, offset=offset) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index b1e5c60..b5d61a2 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -10,13 +10,12 @@ License: MIT import sys # Exitstack not found in contextlib in 2.7 -# pylint: disable=E0611 if sys.hexversion >= 0x03030000: from contextlib import ExitStack - from itertools import compress, filterfalse + from itertools import filterfalse else: from contextlib2 import ExitStack - from itertools import compress, ifilterfalse as filterfalse + from itertools import ifilterfalse as filterfalse from collections import Counter import ctypes @@ -517,12 +516,12 @@ class Jp2k(Jp2kBox): # set image offset and reference grid image.contents.x0 = self._cparams.image_offset_x0 image.contents.y0 = self._cparams.image_offset_y0 - image.contents.x1 = image.contents.x0 \ - + (numcols - 1) * self._cparams.subsampling_dx \ - + 1 - image.contents.y1 = image.contents.y0 \ - + (numrows - 1) * self._cparams.subsampling_dy \ - + 1 + image.contents.x1 = (image.contents.x0 + + (numcols - 1) * self._cparams.subsampling_dx + + 1) + image.contents.y1 = (image.contents.y0 + + (numrows - 1) * self._cparams.subsampling_dy + + 1) # Stage the image data to the openjpeg data structure. for k in range(0, numlayers): @@ -832,7 +831,7 @@ class Jp2k(Jp2kBox): raise IOError(msg) # Find the first codestream in the file. - jp2c = [box for box in self.box if box.box_id == 'jp2c'] + jp2c = [_box for _box in self.box if _box.box_id == 'jp2c'] offset = jp2c[0].offset # Ready to write the codestream. @@ -1435,7 +1434,6 @@ class Jp2k(Jp2kBox): codestream = Codestream(fptr, self.length, header_only=header_only) else: - ftyp = self.box[1] box = [x for x in self.box if x.box_id == 'jp2c'] fptr.seek(box[0].offset) read_buffer = fptr.read(8) diff --git a/glymur/lib/config.py b/glymur/lib/config.py index a639b38..8af038a 100644 --- a/glymur/lib/config.py +++ b/glymur/lib/config.py @@ -1,9 +1,6 @@ """ 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 d398fb2..aed4db6 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -2,8 +2,6 @@ Wraps individual functions in openjp2 library. """ -# pylint: disable=C0302,R0903,W0201 - import ctypes import re import sys diff --git a/glymur/lib/openjpeg.py b/glymur/lib/openjpeg.py index 602f3b9..d2f156b 100644 --- a/glymur/lib/openjpeg.py +++ b/glymur/lib/openjpeg.py @@ -1,8 +1,6 @@ """Wraps library calls to openjpeg. """ -# pylint: disable=R0903 - import ctypes import sys diff --git a/glymur/lib/test/test_openjp2.py b/glymur/lib/test/test_openjp2.py index 988ace1..c32ee89 100644 --- a/glymur/lib/test/test_openjp2.py +++ b/glymur/lib/test/test_openjp2.py @@ -1,13 +1,8 @@ """ 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 - import os import re -import sys import tempfile import unittest @@ -21,8 +16,8 @@ from glymur.lib import openjp2 @unittest.skipIf(openjp2.OPENJP2 is None, "Missing openjp2 library.") @unittest.skipIf(re.match(r'''(1|2.0)''', - glymur.version.openjpeg_version) is not None, - "Not to be run until 2.1.0") + glymur.version.openjpeg_version) is not None, + "Not to be run until 2.1.0") class TestOpenJP2(unittest.TestCase): """Test openjp2 library functionality. @@ -152,6 +147,7 @@ class TestOpenJP2(unittest.TestCase): xtx5_setup(tfile.name) self.assertTrue(True) + def tile_encoder(**kwargs): """Fixture used by many tests.""" num_tiles = ((kwargs['image_width'] / kwargs['tile_width']) * @@ -215,7 +211,7 @@ def tile_encoder(**kwargs): openjp2.setup_encoder(codec, l_param, l_image) stream = openjp2.stream_create_default_file_stream(kwargs['filename'], - False) + False) openjp2.start_compress(codec, l_image, stream) for j in np.arange(num_tiles): @@ -226,13 +222,14 @@ def tile_encoder(**kwargs): 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(kwargs['filename'], - True) + True) dparam = openjp2.set_default_decoder_parameters() dparam.decod_format = kwargs['codec_format'] @@ -270,6 +267,7 @@ def tile_decoder(**kwargs): openjp2.stream_destroy(stream) openjp2.image_destroy(image) + def ttx0_setup(filename): """Runs tests tte0, tte0.""" kwargs = {'filename': filename, @@ -283,6 +281,7 @@ def ttx0_setup(filename): 'tile_width': 100} tile_encoder(**kwargs) + def xtx2_setup(filename): """Runs tests rta2, tte2, ttd2.""" kwargs = {'filename': filename, @@ -296,6 +295,7 @@ def xtx2_setup(filename): 'tile_width': 128} tile_encoder(**kwargs) + def xtx3_setup(filename): """Runs tests tte3, rta3.""" kwargs = {'filename': filename, @@ -309,6 +309,7 @@ def xtx3_setup(filename): 'tile_width': 128} tile_encoder(**kwargs) + def xtx4_setup(filename): """Runs tests rta4, tte4.""" kwargs = {'filename': filename, @@ -322,6 +323,7 @@ def xtx4_setup(filename): 'tile_width': 128} tile_encoder(**kwargs) + def xtx5_setup(filename): """Runs tests rta5, tte5.""" kwargs = {'filename': filename, @@ -334,6 +336,3 @@ def xtx5_setup(filename): '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 f28656c..449083f 100644 --- a/glymur/lib/test/test_openjpeg.py +++ b/glymur/lib/test/test_openjpeg.py @@ -1,8 +1,6 @@ """ Tests for OpenJPEG module. """ -# pylint: disable=E1101,R0904 - import ctypes import re import sys @@ -10,6 +8,7 @@ import unittest import glymur + @unittest.skipIf(glymur.lib.openjpeg.OPENJPEG is None, "Missing openjpeg library.") class TestOpenJPEG(unittest.TestCase): diff --git a/glymur/lib/test/test_printing.py b/glymur/lib/test/test_printing.py index fb78676..c7be21c 100644 --- a/glymur/lib/test/test_printing.py +++ b/glymur/lib/test/test_printing.py @@ -16,6 +16,7 @@ else: import glymur from . import fixtures + @unittest.skipIf(sys.hexversion < 0x03000000, "do not care about 2.7 here") @unittest.skipIf(re.match('0|1|2.0', glymur.version.openjpeg_version), "Requires openjpeg 2.1.0 or higher") @@ -72,6 +73,3 @@ class TestPrintingOpenjp2(unittest.TestCase): expected = fixtures.default_image_type self.assertRegex(actual, expected) - - - diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index cf78051..c7bab78 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -236,7 +236,6 @@ try: # The whole point of trying to import PIL is to determine if it's there # or not. We won't use it directly. - # pylint: disable=F0401,W0611 import PIL NO_READ_BACKEND = False diff --git a/glymur/test/test_callbacks.py b/glymur/test/test_callbacks.py index 269a816..cc30de8 100644 --- a/glymur/test/test_callbacks.py +++ b/glymur/test/test_callbacks.py @@ -1,12 +1,6 @@ """ 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 re import sys @@ -99,8 +93,6 @@ class TestCallbacks(unittest.TestCase): [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_config.py b/glymur/test/test_config.py index 28a41cc..59a8ef3 100644 --- a/glymur/test/test_config.py +++ b/glymur/test/test_config.py @@ -1,15 +1,6 @@ """These tests are for edge cases where OPENJPEG does not exist, but OPENJP2 may be present in some form or other. """ -# 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 contextlib import ctypes import imp @@ -98,7 +89,6 @@ class TestSuite(unittest.TestCase): # 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) diff --git a/glymur/test/test_glymur_warnings.py b/glymur/test/test_glymur_warnings.py index fa0c832..24d0413 100644 --- a/glymur/test/test_glymur_warnings.py +++ b/glymur/test/test_glymur_warnings.py @@ -1,10 +1,6 @@ """ Test suite for warnings issued by glymur. """ - -# unittest doesn't work well with R0904. -# pylint: disable=R0904 - import os import re import struct diff --git a/glymur/test/test_icc.py b/glymur/test/test_icc.py index 90ffc11..e18775c 100644 --- a/glymur/test/test_icc.py +++ b/glymur/test/test_icc.py @@ -1,10 +1,6 @@ """ ICC profile tests. """ - -# unittest doesn't work well with R0904. -# pylint: disable=R0904 - import datetime import unittest @@ -64,11 +60,6 @@ class TestICC(unittest.TestCase): """invalid ICC header data should cause UserWarning""" jfile = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2') - # assertWarns in Python 3.3 (python2.7/pylint issue) - # pylint: disable=E1101 regex = 'ICC profile header is corrupt' with self.assertWarnsRegex(UserWarning, regex): Jp2k(jfile) - -if __name__ == "__main__": - unittest.main() diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 9843b94..9ccd75b 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -1,18 +1,6 @@ """ Test suite specifically targeting JP2 box layout. """ -# E1103: return value from read may be list or np array -# pylint: disable=E1103 - -# 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 re @@ -20,7 +8,6 @@ import shutil import struct import sys import tempfile -import uuid from uuid import UUID import unittest @@ -642,7 +629,7 @@ class TestAppend(unittest.TestCase): jp2 = Jp2k(tfile.name) # Make a UUID box. Only XMP UUID boxes can currently be appended. - uuid_instance = uuid.UUID('00000000-0000-0000-0000-000000000000') + uuid_instance = UUID('00000000-0000-0000-0000-000000000000') data = b'0123456789' uuidbox = glymur.jp2box.UUIDBox(uuid_instance, data) with self.assertRaises(IOError): @@ -1234,8 +1221,8 @@ class TestRepr(MetadataBase): def test_uuidlist_box(self): """Verify __repr__ method on ulst box.""" - uuid1 = uuid.UUID('00000000-0000-0000-0000-000000000001') - uuid2 = uuid.UUID('00000000-0000-0000-0000-000000000002') + uuid1 = UUID('00000000-0000-0000-0000-000000000001') + uuid2 = UUID('00000000-0000-0000-0000-000000000002') uuids = [uuid1, uuid2] ulst = glymur.jp2box.UUIDListBox(ulst=uuids) newbox = eval(repr(ulst)) @@ -1289,7 +1276,7 @@ class TestRepr(MetadataBase): def test_uuid_box_generic(self): """Verify uuid repr method.""" - uuid_instance = uuid.UUID('00000000-0000-0000-0000-000000000000') + uuid_instance = UUID('00000000-0000-0000-0000-000000000000') data = b'0123456789' box = glymur.jp2box.UUIDBox(the_uuid=uuid_instance, raw_data=data) diff --git a/glymur/test/test_jp2box_uuid.py b/glymur/test/test_jp2box_uuid.py index 409e43b..6886c30 100644 --- a/glymur/test/test_jp2box_uuid.py +++ b/glymur/test/test_jp2box_uuid.py @@ -1,15 +1,6 @@ # -*- coding: utf-8 -*- """Test suite for printing. """ -# C0302: don't care too much about having too many lines in a test module -# pylint: disable=C0302 - -# E061: unittest.mock introduced in 3.3 (python-2.7/pylint issue) -# pylint: disable=E0611,F0401 - -# R0904: Not too many methods in unittest. -# pylint: disable=R0904 - import os import shutil import struct diff --git a/glymur/test/test_jp2box_xml.py b/glymur/test/test_jp2box_xml.py index 84a63f3..87bdd38 100644 --- a/glymur/test/test_jp2box_xml.py +++ b/glymur/test/test_jp2box_xml.py @@ -2,18 +2,6 @@ """ Test suite specifically targeting JP2 box layout. """ -# E1103: return value from read may be list or np array -# pylint: disable=E1103 - -# 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 os import re import struct diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index cb3a9e7..8827f83 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -1,15 +1,6 @@ """ 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 @@ -47,10 +38,6 @@ from . import fixtures # 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. diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index f512dfd..a121071 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -2,31 +2,6 @@ The tests defined here roughly correspond to what is in the OpenJPEG test suite. """ - -# Some test names correspond with openjpeg tests. Long names are ok in this -# case. -# pylint: disable=C0103 - -# All of these tests correspond to tests in openjpeg, so no docstring is really -# needed. -# pylint: disable=C0111 - -# This module is very long, cannot be helped. -# pylint: disable=C0302 - -# unittest fools pylint with "too many public methods" -# pylint: disable=R0904 - -# Some tests use numpy test infrastructure, which means the tests never -# reference "self", so pylint claims it should be a function. No, no, no. -# pylint: disable=R0201 - -# Many tests are pretty long and that can't be helped. -# pylint: disable=R0915 - -# asserWarns introduced in python 3.2 (python2.7/pylint issue) -# pylint: disable=E1101 - import re import sys import unittest diff --git a/glymur/test/test_opj_suite_dump.py b/glymur/test/test_opj_suite_dump.py index 6b198c1..0090ee3 100644 --- a/glymur/test/test_opj_suite_dump.py +++ b/glymur/test/test_opj_suite_dump.py @@ -2,31 +2,6 @@ The tests defined here roughly correspond to what is in the OpenJPEG test suite. """ - -# Some test names correspond with openjpeg tests. Long names are ok in this -# case. -# pylint: disable=C0103 - -# All of these tests correspond to tests in openjpeg, so no docstring is really -# needed. -# pylint: disable=C0111 - -# This module is very long, cannot be helped. -# pylint: disable=C0302 - -# unittest fools pylint with "too many public methods" -# pylint: disable=R0904 - -# Some tests use numpy test infrastructure, which means the tests never -# reference "self", so pylint claims it should be a function. No, no, no. -# pylint: disable=R0201 - -# Many tests are pretty long and that can't be helped. -# pylint: disable=R0915 - -# asserWarns introduced in python 3.2 (python2.7/pylint issue) -# pylint: disable=E1101 - import re import sys import unittest diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index 1d3ec6e..d2351a4 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -2,12 +2,6 @@ The tests here do not correspond directly to the OpenJPEG test suite, but seem like logical negative tests to add. """ -# E1101: assertWarns introduced in python 3.2 -# pylint: disable=E1101 - -# R0904: Not too many methods in unittest. -# pylint: disable=R0904 - import os import re import tempfile diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index 7f0fd4b..eabb4bc 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -2,10 +2,6 @@ The tests defined here roughly correspond to what is in the OpenJPEG test suite. """ -# C0103: method names longer that 30 chars are ok in tests, IMHO -# R0904: Seems like pylint is fooled in this situation -# pylint: disable=R0904,C0103 - import os import re import sys diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index ab3e9fd..f9fcd73 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -1,15 +1,6 @@ # -*- coding: utf-8 -*- """Test suite for printing. """ -# C0302: don't care too much about having too many lines in a test module -# pylint: disable=C0302 - -# E061: unittest.mock introduced in 3.3 (python-2.7/pylint issue) -# pylint: disable=E0611,F0401 - -# R0904: Not too many methods in unittest. -# pylint: disable=R0904 - import os import re import struct @@ -106,7 +97,7 @@ class TestPrinting(unittest.TestCase): data = glymur.Jp2k(self.jp2file)[::2, ::2] with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2: - j = glymur.Jp2k(tfile.name, data=data) + glymur.Jp2k(tfile.name, data=data) # Offset of the codestream is where we start. wbuffer = tfile.read(77) diff --git a/setup.py b/setup.py index 1a61dc3..474b6c8 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -from setuptools import setup, find_packages +from setuptools import setup import os import re import sys @@ -11,7 +11,9 @@ kwargs = {'name': 'Glymur', 'url': 'https://github.com/quintusdias/glymur', 'packages': ['glymur', 'glymur.data', 'glymur.test', 'glymur.lib', 'glymur.lib.test'], - 'package_data': {'glymur': ['data/*.jp2', 'data/*.j2k', 'data/*.jpx']}, + 'package_data': {'glymur': ['data/*.jp2', + 'data/*.j2k', + 'data/*.jpx']}, 'entry_points': { 'console_scripts': ['jp2dump=glymur.command_line:main'], }, From 20f2c0c291dce90822d4f21581aa4c3a7a275418 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 31 Dec 2014 22:45:34 -0500 Subject: [PATCH 306/326] remove coverall changes, closes #298 no further action planned --- .travis.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 822c717..406e470 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,6 @@ before_install: - sudo apt-get install -qq python-numpy - wget http://openjpeg.googlecode.com/files/openjpeg-1.5.0-Linux-x86_64.tar.gz - sudo tar -xvf openjpeg-1.5.0-Linux-x86_64.tar.gz --strip-components=1 -C / - - pip install coveralls # command to install dependencies install: @@ -20,11 +19,6 @@ install: # command to run tests script: - python -m unittest discover -after_success: - - if [[ $ENV == pythone=3.4* ]]; then - coveralls; - fi - notifications: email: "john.g.evans.ne@gmail.com" From 8440d69222385f3300892485bc6b4d2a33cef067 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 31 Dec 2014 23:29:39 -0500 Subject: [PATCH 307/326] codestream now a property of Jp2k object, closes #275 repeated calls to get_codestream removed --- docs/source/how_do_i.rst | 4 ++-- glymur/jp2k.py | 40 ++++++++++++++++++++++++---------------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index 8a62b32..33366b5 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -214,9 +214,9 @@ making use of the :py:meth:`set_printoptions` function:: UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) Contiguous Codestream Box (jp2c) @ (3223, 1132296) -It is possible to print all the gory codestream details as well, i.e. :: +It is possible to easily print the codestream header details as well, i.e. :: - >>> print(j.get_codestream()) # details not shown + >>> print(j.codestream) # details not show ... add XML metadata? ===================== diff --git a/glymur/jp2k.py b/glymur/jp2k.py index b5d61a2..de386dc 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -60,6 +60,8 @@ class Jp2k(Jp2kBox): verbose : bool whether or not to print informational messages produced by the OpenJPEG library, defaults to false + codestream : object + JP2 or J2K codestream object Examples -------- @@ -145,6 +147,7 @@ class Jp2k(Jp2kBox): self._codec_format = None self._colorspace = None self._layer = 0 + self._codestream = None if data is not None: self._shape = data.shape else: @@ -179,6 +182,16 @@ class Jp2k(Jp2kBox): raise RuntimeError(msg) self._layer = layer + @property + def codestream(self): + if self._codestream is None: + self._codestream = self.get_codestream(header_only=True) + return self._codestream + + @codestream.setter + def codestream(self, the_codestream): + self._codestream = the_codestream + @property def verbose(self): return self._verbose @@ -192,7 +205,7 @@ class Jp2k(Jp2kBox): if self._shape is not None: return self._shape - cstr = self.get_codestream(header_only=True) + cstr = self.codestream height = cstr.segment[1].ysiz width = cstr.segment[1].xsiz num_components = len(cstr.segment[1].xrsiz) @@ -233,8 +246,7 @@ class Jp2k(Jp2kBox): for box in self.box: metadata.append(str(box)) else: - codestream = self.get_codestream() - metadata.append(str(codestream)) + metadata.append(str(self.codestream)) return '\n'.join(metadata) def parse(self): @@ -865,10 +877,9 @@ class Jp2k(Jp2kBox): FileTypeBox(), JP2HeaderBox(), ContiguousCodestreamBox()] - codestream = self.get_codestream() - height = codestream.segment[1].ysiz - width = codestream.segment[1].xsiz - num_components = len(codestream.segment[1].xrsiz) + height = self.codestream.segment[1].ysiz + width = self.codestream.segment[1].xsiz + num_components = len(self.codestream.segment[1].xrsiz) if num_components < 3: colorspace = core.GREYSCALE else: @@ -907,10 +918,9 @@ class Jp2k(Jp2kBox): """ Slicing protocol. """ - codestream = self.get_codestream(header_only=True) - numrows = codestream.segment[1].ysiz - numcols = codestream.segment[1].xsiz - numbands = codestream.segment[1].Csiz + numrows = self.codestream.segment[1].ysiz + numcols = self.codestream.segment[1].xsiz + numbands = self.codestream.segment[1].Csiz if isinstance(pargs, int): # Not a very good use of this protocol, but technically legal. @@ -1072,9 +1082,8 @@ class Jp2k(Jp2kBox): 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) + dxs = np.array(self.codestream.segment[1].xrsiz) + dys = np.array(self.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 " @@ -1277,8 +1286,7 @@ class Jp2k(Jp2kBox): # Must check the specified rlevel against the maximum. if rlevel != 0: # Must check the specified rlevel against the maximum. - codestream = self.get_codestream() - max_rlevel = codestream.segment[2].spcod[4] + max_rlevel = self.codestream.segment[2].spcod[4] if rlevel == -1: # -1 is shorthand for the largest rlevel rlevel = max_rlevel From acf6b28bc96804b175b5c6ef62af17418d7c15f4 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 1 Jan 2015 09:07:19 -0500 Subject: [PATCH 308/326] fix broken test due to upstream openjpeg test suite changes, closes #308 removed test_NR_broken3_jp2_dump, it is mostly just an extreme duplicate case of test_NR_broken_jp2_dump. It must be skipped on 32bit platforms anyway. --- glymur/test/test_opj_suite.py | 2 +- glymur/test/test_opj_suite_dump.py | 87 ------------------------------ 2 files changed, 1 insertion(+), 88 deletions(-) diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index a121071..f5298f8 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -360,7 +360,7 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(jpdata.shape, (512, 768, 3)) def test_NR_broken_jp2_dump(self): - jfile = opj_data_file('input/nonregression/broken.jp2') + jfile = opj_data_file('input/nonregression/broken1.jp2') with self.assertWarns(UserWarning): # colr box has bad length. diff --git a/glymur/test/test_opj_suite_dump.py b/glymur/test/test_opj_suite_dump.py index 0090ee3..f13b128 100644 --- a/glymur/test/test_opj_suite_dump.py +++ b/glymur/test/test_opj_suite_dump.py @@ -2919,93 +2919,6 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') - @unittest.skipIf(sys.maxsize < 2**32, 'Do not run on 32-bit platforms') - def test_NR_broken3_jp2_dump(self): - """ - NR_broken3_jp2_dump - - The file in question here has a colr box with an erroneous box - length of over 1GB. Don't run it on 32-bit platforms. - """ - jfile = opj_data_file('input/nonregression/broken3.jp2') - with self.assertWarns(UserWarning): - # Bad box length. - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - self.verify_filetype_box(jp2.box[1], FileTypeBox()) - - ihdr = glymur.jp2box.ImageHeaderBox(152, 203, num_components=3) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - colr = glymur.jp2box.ColourSpecificationBox(colorspace=SRGB) - self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (203, 152), 'xyosiz': (0, 0), - 'xytsiz': (203, 152), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - pargs = RCME_ISO_8859_1, "Creator: JasPer Vers)on 1.701.0".encode() - self.verifyCMEsegment(c.segment[2], CMEsegment(*pargs)) - - # COD: Coding style default - self.assertFalse(c.segment[3].scod & 2) # no sop - self.assertFalse(c.segment[3].scod & 4) # no eph - self.assertEqual(c.segment[3].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[3].layers, 1) # layers = 1 - self.assertEqual(c.segment[3].spcod[3], 1) # mct - self.assertEqual(c.segment[3].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[3].code_block_size), - (64, 64)) # cblk - self.verify_codeblock_style(c.segment[3].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[3].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[3].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) - self.assertEqual(c.segment[4].guard_bits, 2) - self.assertEqual(c.segment[4].mantissa, [0] * 16) - self.assertEqual(c.segment[4].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 1) - self.assertEqual(c.segment[5].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[5].mantissa, [0] * 16) - self.assertEqual(c.segment[5].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 2) - self.assertEqual(c.segment[6].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[6].mantissa, [0] * 16) - self.assertEqual(c.segment[6].exponent, - [8] + [9, 9, 10] * 5) - def test_NR_broken2_jp2_dump(self): """ Invalid marker ID in the codestream. From 3e723605b9aa72d13cab28f2960882d45537a887 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 1 Jan 2015 09:07:19 -0500 Subject: [PATCH 309/326] fix broken test due to upstream openjpeg test suite changes, closes #308 removed test_NR_broken3_jp2_dump, it is mostly just an extreme duplicate case of test_NR_broken_jp2_dump. It must be skipped on 32bit platforms anyway. refactored some NR_broken tests, consolidating them --- glymur/test/test_glymur_warnings.py | 22 -------- glymur/test/test_opj_suite.py | 22 +++++--- glymur/test/test_opj_suite_dump.py | 87 ----------------------------- 3 files changed, 14 insertions(+), 117 deletions(-) diff --git a/glymur/test/test_glymur_warnings.py b/glymur/test/test_glymur_warnings.py index 24d0413..ff18c6e 100644 --- a/glymur/test/test_glymur_warnings.py +++ b/glymur/test/test_glymur_warnings.py @@ -102,28 +102,6 @@ class TestWarnings(unittest.TestCase): with self.assertWarnsRegex(UserWarning, regex): Jp2k(jfile) - def test_NR_broken_jp2_dump(self): - """ - The colr box has a ridiculously incorrect box length. - """ - jfile = opj_data_file('input/nonregression/broken.jp2') - regex = re.compile(r'''b'colr'\sbox\shas\sincorrect\sbox\slength\s - \(\d+\)''', - re.VERBOSE) - with self.assertWarnsRegex(UserWarning, regex): - Jp2k(jfile) - - def test_NR_broken2_jp2_dump(self): - """ - Invalid marker ID on codestream. - """ - jfile = opj_data_file('input/nonregression/broken2.jp2') - regex = re.compile(r'''Invalid\smarker\sid\sencountered\sat\sbyte\s - \d+\sin\scodestream:\s*"0x[a-fA-F0-9]{4}"''', - re.VERBOSE) - with self.assertWarnsRegex(UserWarning, regex): - Jp2k(jfile) - def test_bad_rsiz(self): """Should warn if RSIZ is bad. Issue196""" filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index a121071..431b480 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -359,11 +359,14 @@ class TestSuiteWarns(MetadataBase): jpdata = jp2k[:] self.assertEqual(jpdata.shape, (512, 768, 3)) - def test_NR_broken_jp2_dump(self): - jfile = opj_data_file('input/nonregression/broken.jp2') + def test_NR_broken1_jp2_dump(self): + jfile = opj_data_file('input/nonregression/broken1.jp2') - with self.assertWarns(UserWarning): - # colr box has bad length. + # The colr box has a ridiculously incorrect box length. + regex = re.compile(r'''b'colr'\sbox\shas\sincorrect\sbox\slength\s + \(\d+\)''', + re.VERBOSE) + with self.assertWarnsRegex(UserWarning, regex): jp2 = Jp2k(jfile) ids = [box.box_id for box in jp2.box] @@ -584,13 +587,16 @@ class TestSuite2point0(unittest.TestCase): @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_NR_DEC_broken2_jp2_5_decode(self): - # Null pointer access + """ + Invalid marker ID on codestream, Null pointer access upon read. + """ jfile = opj_data_file('input/nonregression/broken2.jp2') + regex = re.compile(r'''Invalid\smarker\sid\sencountered\sat\sbyte\s + \d+\sin\scodestream:\s*"0x[a-fA-F0-9]{4}"''', + re.VERBOSE) with self.assertRaises(IOError): - with self.assertWarns(UserWarning): - # Invalid marker ID. + with self.assertWarnsRegex(UserWarning, regex): Jp2k(jfile)[:] - self.assertTrue(True) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_NR_DEC_broken4_jp2_7_decode(self): diff --git a/glymur/test/test_opj_suite_dump.py b/glymur/test/test_opj_suite_dump.py index 0090ee3..f13b128 100644 --- a/glymur/test/test_opj_suite_dump.py +++ b/glymur/test/test_opj_suite_dump.py @@ -2919,93 +2919,6 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(jp2.box[-1].main_header.segment[-1].marker_id, 'QCC') - @unittest.skipIf(sys.maxsize < 2**32, 'Do not run on 32-bit platforms') - def test_NR_broken3_jp2_dump(self): - """ - NR_broken3_jp2_dump - - The file in question here has a colr box with an erroneous box - length of over 1GB. Don't run it on 32-bit platforms. - """ - jfile = opj_data_file('input/nonregression/broken3.jp2') - with self.assertWarns(UserWarning): - # Bad box length. - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - self.verify_filetype_box(jp2.box[1], FileTypeBox()) - - ihdr = glymur.jp2box.ImageHeaderBox(152, 203, num_components=3) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - colr = glymur.jp2box.ColourSpecificationBox(colorspace=SRGB) - self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) - - c = jp2.box[3].main_header - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (203, 152), 'xyosiz': (0, 0), - 'xytsiz': (203, 152), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - pargs = RCME_ISO_8859_1, "Creator: JasPer Vers)on 1.701.0".encode() - self.verifyCMEsegment(c.segment[2], CMEsegment(*pargs)) - - # COD: Coding style default - self.assertFalse(c.segment[3].scod & 2) # no sop - self.assertFalse(c.segment[3].scod & 4) # no eph - self.assertEqual(c.segment[3].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[3].layers, 1) # layers = 1 - self.assertEqual(c.segment[3].spcod[3], 1) # mct - self.assertEqual(c.segment[3].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[3].code_block_size), - (64, 64)) # cblk - self.verify_codeblock_style(c.segment[3].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[3].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[3].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) - self.assertEqual(c.segment[4].guard_bits, 2) - self.assertEqual(c.segment[4].mantissa, [0] * 16) - self.assertEqual(c.segment[4].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 1) - self.assertEqual(c.segment[5].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[5].mantissa, [0] * 16) - self.assertEqual(c.segment[5].exponent, - [8] + [9, 9, 10] * 5) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 2) - self.assertEqual(c.segment[6].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[6].mantissa, [0] * 16) - self.assertEqual(c.segment[6].exponent, - [8] + [9, 9, 10] * 5) - def test_NR_broken2_jp2_dump(self): """ Invalid marker ID in the codestream. From 80f696c6002aa2c2209df2808529eeac12ec6d7e Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 1 Jan 2015 15:14:54 -0500 Subject: [PATCH 310/326] fixed irreversible test to match upstream, closes #309 --- glymur/test/test_jp2k.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 8827f83..4dc4df4 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -1119,7 +1119,7 @@ class TestJp2kOpjDataRootWarnings(unittest.TestCase): @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") class TestJp2kOpjDataRoot(unittest.TestCase): - """These tests should be run by just about all configuration.""" + """These tests should be run by just about all configurations.""" @unittest.skipIf(re.match("0|1.[0-4]", glymur.version.openjpeg_version), "Must have openjpeg 1.5 or higher to run") @@ -1128,9 +1128,9 @@ class TestJp2kOpjDataRoot(unittest.TestCase): """Irreversible""" filename = opj_data_file('input/nonregression/issue141.rawl') expdata = np.fromfile(filename, dtype=np.uint16) - expdata.resize((2816, 2048)) + expdata.resize((32, 2048)) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, data=expdata, irreversible=True) + j = Jp2k(tfile.name, data=expdata, irreversible=True, numres=5) codestream = j.get_codestream() self.assertEqual(codestream.segment[2].spcod[8], From a921b1101df33a3a43c0510aede1c000ea4bab04 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 1 Jan 2015 22:17:55 -0500 Subject: [PATCH 311/326] refactor install_requires list, closes #310 split into install_requires and test_requires --- setup.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 474b6c8..0a62827 100644 --- a/setup.py +++ b/setup.py @@ -20,11 +20,13 @@ kwargs = {'name': 'Glymur', 'license': 'MIT', 'test_suite': 'glymur.test'} -instllrqrs = ['numpy>=1.4.1', 'lxml>=2.3.2'] +install_requires = ['numpy>=1.7.0', 'lxml>=3.0.0'] +test_requires = ['six>=1.7.0'] if sys.hexversion < 0x03030000: - instllrqrs.append('contextlib2>=0.4') - instllrqrs.append('mock>=1.0.1') -kwargs['install_requires'] = instllrqrs + install_requires.append('contextlib2>=0.4') + test_requires.append('mock>=1.0.1') +kwargs['install_requires'] = install_requires +kwargs['test_requires'] = test_requires clssfrs = ["Programming Language :: Python", "Programming Language :: Python :: 2.7", From 43da1824b6a18107251be579ecb15caed89de15a Mon Sep 17 00:00:00 2001 From: John Evans Date: Sun, 4 Jan 2015 20:21:33 -0500 Subject: [PATCH 312/326] don't error out on bad ftyp entry or colr method, closes #312 --- glymur/jp2box.py | 19 +++++++++++++------ glymur/test/test_jp2k.py | 9 +++++++++ glymur/test/test_opj_suite_dump.py | 1 - glymur/test/test_printing.py | 11 +++++++++++ 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 4a390a5..e29df28 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -35,11 +35,12 @@ from .core import (_COLORSPACE_MAP_DISPLAY, _COLOR_TYPE_MAP_DISPLAY, from . import _uuid_io -_METHOD_DISPLAY = { - ENUMERATED_COLORSPACE: 'enumerated colorspace', - RESTRICTED_ICC_PROFILE: 'restricted ICC profile', - ANY_ICC_PROFILE: 'any ICC profile', - VENDOR_COLOR_METHOD: 'vendor color method'} +_factory = lambda x: '{0} (invalid)'.format(x) +_keysvalues = {ENUMERATED_COLORSPACE: 'enumerated colorspace', + RESTRICTED_ICC_PROFILE: 'restricted ICC profile', + ANY_ICC_PROFILE: 'any ICC profile', + VENDOR_COLOR_METHOD: 'vendor color method'} +_METHOD_DISPLAY = _Keydefaultdict(_factory, _keysvalues) _factory = lambda x: '{0} (invalid)'.format(x) _keysvalues = {1: 'accurately represents correct colorspace definition', @@ -1317,7 +1318,13 @@ class FileTypeBox(Jp2kBox): for j in range(int(num_entries)): entry, = struct.unpack_from('>4s', read_buffer, 8 + j * 4) if sys.hexversion >= 0x03000000: - entry = entry.decode('utf-8') + try: + entry = entry.decode('utf-8') + except UnicodeDecodeError as err: + # The entry is invalid, but we've got code to catch this + # later on. + pass + compatibility_list.append(entry) return cls(brand=brand, minor_version=minor_version, diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 4dc4df4..4bc928b 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -272,6 +272,15 @@ class TestJp2k(unittest.TestCase): jp2 = Jp2k(jfile) self.assertEqual(jp2.shape, (128, 128)) + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_invalid_compatibility_list_entry(self): + """should not error out with invalid compatibility list entry""" + filename = opj_data_file('input/nonregression/issue397.jp2') + with self.assertWarns(UserWarning): + Jp2k(filename) + self.assertTrue(True) + def test_shape_j2k(self): """verify shape attribute for J2K file """ diff --git a/glymur/test/test_opj_suite_dump.py b/glymur/test/test_opj_suite_dump.py index f13b128..844ce9c 100644 --- a/glymur/test/test_opj_suite_dump.py +++ b/glymur/test/test_opj_suite_dump.py @@ -3,7 +3,6 @@ The tests defined here roughly correspond to what is in the OpenJPEG test suite. """ import re -import sys import unittest import numpy as np diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index f9fcd73..49fec57 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -831,6 +831,17 @@ class TestPrintingOpjDataRootWarns(unittest.TestCase): def tearDown(self): pass + def test_invalid_colour_specification_method(self): + """should not error out with invalid colour specification method""" + # Don't care so much about what the output looks like, just that we + # do not error out. + filename = opj_data_file('input/nonregression/issue397.jp2') + with self.assertWarns(UserWarning): + jp2 = Jp2k(filename) + with patch('sys.stdout', new=StringIO()): + print(jp2) + self.assertTrue(True) + def test_invalid_colorspace(self): """An invalid colorspace shouldn't cause an error.""" filename = opj_data_file('input/nonregression/edf_c2_1103421.jp2') From dc2b2bda15447c89dc3a693f703bc4edaa5defc9 Mon Sep 17 00:00:00 2001 From: John Evans Date: Sun, 4 Jan 2015 21:39:14 -0500 Subject: [PATCH 313/326] the main header will be printed by default --- glymur/command_line.py | 16 +++++++++------- glymur/jp2box.py | 13 ++++++++----- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/glymur/command_line.py b/glymur/command_line.py index ff442f3..0836108 100644 --- a/glymur/command_line.py +++ b/glymur/command_line.py @@ -13,23 +13,25 @@ def main(): Entry point for console script jp2dump. """ - description = 'Print JPEG2000 metadata.' - parser = argparse.ArgumentParser(description=description) + kwargs = {'description': 'Print JPEG2000 metadata.', + 'formatter_class': argparse.ArgumentDefaultsHelpFormatter} + parser = argparse.ArgumentParser(**kwargs) parser.add_argument('-x', '--noxml', - help='Suppress XML.', + help='suppress XML', action='store_true') parser.add_argument('-s', '--short', - help='Only print box id, offset, and length.', + help='only print box id, offset, and length', action='store_true') - chelp = 'Level of codestream information. 0 suppressed all details, ' - chelp += '1 prints headers, 2 prints the full codestream' + chelp = 'Level of codestream information. 0 suppresses all details, ' + chelp += '1 prints the main header, 2 prints the full codestream.' parser.add_argument('-c', '--codestream', help=chelp, + metavar='LEVEL', nargs=1, type=int, - default=[0]) + default=[1]) parser.add_argument('filename') diff --git a/glymur/jp2box.py b/glymur/jp2box.py index e29df28..ba204a4 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1040,10 +1040,11 @@ class ContiguousCodestreamBox(Jp2kBox): msg = Jp2kBox.__str__(self) if _printoptions['short'] is True: return msg - if _printoptions['codestream'] is False: + if _printoptions['siz']: + msg += '\n' + self._indent(str(self.main_header.segment[1]), + indent_level=4) return msg - msg += '\n Main header:' for segment in self.main_header.segment: msg += '\n' + self._indent(str(segment), indent_level=8) @@ -3360,7 +3361,7 @@ def get_parseoptions(): """ return _parseoptions -_printoptions = {'short': False, 'xml': True, 'codestream': True} +_printoptions = {'short': False, 'xml': True, 'siz': True} def set_printoptions(**kwargs): @@ -3377,8 +3378,10 @@ def set_printoptions(**kwargs): xml : bool, optional When False, printing of the XML contents of any XML boxes or UUID XMP boxes is suppressed. - codestream : bool, optional - When False, printing of the codestream contents is suppressed. + siz : bool, optional + When True, only the SIZ segment is printed. When False, the entire + codestream is printed. This option has no effect when the 'short' + option is set to True. See also -------- From e93392a10e8ec0f9c849aa129d86d5a81fd3f509 Mon Sep 17 00:00:00 2001 From: John Evans Date: Mon, 5 Jan 2015 08:13:31 -0500 Subject: [PATCH 314/326] print the main header by default --- glymur/jp2box.py | 10 +- glymur/test/fixtures.py | 184 +++++++++++++++++++++++++++++++++++ glymur/test/test_printing.py | 7 +- 3 files changed, 194 insertions(+), 7 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index ba204a4..4307f48 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1040,13 +1040,11 @@ class ContiguousCodestreamBox(Jp2kBox): msg = Jp2kBox.__str__(self) if _printoptions['short'] is True: return msg - if _printoptions['siz']: - msg += '\n' + self._indent(str(self.main_header.segment[1]), - indent_level=4) + if _printoptions['codestream'] is False: return msg for segment in self.main_header.segment: - msg += '\n' + self._indent(str(segment), indent_level=8) + msg += '\n' + self._indent(str(segment), indent_level=4) return msg @@ -1321,7 +1319,7 @@ class FileTypeBox(Jp2kBox): if sys.hexversion >= 0x03000000: try: entry = entry.decode('utf-8') - except UnicodeDecodeError as err: + except UnicodeDecodeError: # The entry is invalid, but we've got code to catch this # later on. pass @@ -3361,7 +3359,7 @@ def get_parseoptions(): """ return _parseoptions -_printoptions = {'short': False, 'xml': True, 'siz': True} +_printoptions = {'short': False, 'xml': True, 'codestream': True} def set_printoptions(**kwargs): diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index c7bab78..12b0c71 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -709,6 +709,190 @@ UUID Box (uuid) @ (77, 3146) UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) Contiguous Codestream Box (jp2c) @ (3223, 1132296)""" +nemo = """JPEG 2000 Signature Box (jP ) @ (0, 12) + Signature: 0d0a870a +File Type Box (ftyp) @ (12, 20) + Brand: jp2 + Compatibility: ['jp2 '] +JP2 Header Box (jp2h) @ (32, 45) + Image Header Box (ihdr) @ (40, 22) + Size: [1456 2592 3] + Bitdepth: 8 + Signed: False + Compression: wavelet + Colorspace Unknown: False + Colour Specification Box (colr) @ (62, 15) + Method: enumerated colorspace + Precedence: 0 + Colorspace: sRGB +UUID Box (uuid) @ (77, 3146) + UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) + UUID Data: + + + + + Google + 2013-02-09T14:47:53 + + + 1 + 72/1 + 72/1 + 2 + HTC + HTC Glacier + 2592 + 1456 + + + 8 + 8 + 8 + + + 2 + 3 + + + 1343036288/4294967295 + 1413044224/4294967295 + + + + + 2748779008/4294967295 + 1417339264/4294967295 + 1288490240/4294967295 + 2576980480/4294967295 + 644245120/4294967295 + 257698032/4294967295 + + + + + 1 + 2528 + 1424 + 353/100 + 0 + 0/1 + WGS-84 + 2013-02-09T14:47:53 + + + 76 + + + 0220 + 0100 + + + 1 + 2 + 3 + 0 + + + 42,20.56N + 71,5.29W + 2013-02-09T19:47:53Z + NETWORK + + + 2013-02-09T14:47:53 + + + + + Glymur + Python XMP Toolkit + + + + + + +Contiguous Codestream Box (jp2c) @ (3223, 1132296) + SOC marker segment @ (3231, 0) + SIZ marker segment @ (3233, 47) + Profile: no profile + Reference Grid Height, Width: (1456 x 2592) + Vertical, Horizontal Reference Grid Offset: (0 x 0) + Reference Tile Height, Width: (1456 x 2592) + Vertical, Horizontal Reference Tile Offset: (0 x 0) + Bitdepth: (8, 8, 8) + Signed: (False, False, False) + Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1)) + COD marker segment @ (3282, 12) + Coding style: + Entropy coder, without partitions + SOP marker segments: False + EPH marker segments: False + Coding style parameters: + Progression order: LRCP + Number of layers: 2 + Multiple component transformation usage: reversible + Number of resolutions: 2 + Code block height, width: (64 x 64) + Wavelet transform: 5-3 reversible + Precinct size: default, 2^15 x 2^15 + Code block context: + Selective arithmetic coding bypass: False + Reset context probabilities on coding pass boundaries: False + Termination on each coding pass: False + Vertically stripe causal context: False + Predictable termination: False + Segmentation symbols: False + QCD marker segment @ (3296, 7) + Quantization style: no quantization, 2 guard bits + Step size: [(0, 8), (0, 9), (0, 9), (0, 10)] + CME marker segment @ (3305, 37) + "Created by OpenJPEG version 2.0.0" + SOT marker segment @ (3344, 10) + Tile part index: 0 + Tile part length: 1132173 + Tile part instance: 0 + Number of tile parts: 1 + COC marker segment @ (3356, 9) + Associated component: 1 + Coding style for this component: Entropy coder, PARTITION = 0 + Coding style parameters: + Number of resolutions: 2 + Code block height, width: (64 x 64) + Wavelet transform: 5-3 reversible + Code block context: + Selective arithmetic coding bypass: False + Reset context probabilities on coding pass boundaries: False + Termination on each coding pass: False + Vertically stripe causal context: False + Predictable termination: False + Segmentation symbols: False + QCC marker segment @ (3367, 8) + Associated Component: 1 + Quantization style: no quantization, 2 guard bits + Step size: [(0, 8), (0, 9), (0, 9), (0, 10)] + COC marker segment @ (3377, 9) + Associated component: 2 + Coding style for this component: Entropy coder, PARTITION = 0 + Coding style parameters: + Number of resolutions: 2 + Code block height, width: (64 x 64) + Wavelet transform: 5-3 reversible + Code block context: + Selective arithmetic coding bypass: False + Reset context probabilities on coding pass boundaries: False + Termination on each coding pass: False + Vertically stripe causal context: False + Predictable termination: False + Segmentation symbols: False + QCC marker segment @ (3388, 8) + Associated Component: 2 + Quantization style: no quantization, 2 guard bits + Step size: [(0, 8), (0, 9), (0, 9), (0, 10)] + SOD marker segment @ (3398, 0) + EOC marker segment @ (1135517, 0)""" + # Output of reader requirements printing for text_GBR.jp2 text_GBR_rreq = r"""Reader Requirements Box (rreq) @ (40, 109) Fully Understands Aspect Mask: 0xffff diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 49fec57..55e3018 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -1098,7 +1098,12 @@ class TestJp2dump(unittest.TestCase): """Verify dumping with -x, suppress XML.""" actual = self.run_jp2dump(['', '-x', self.jp2file]) - self.assertEqual(actual, fixtures.nemo_dump_no_codestream_no_xml) + # shave off the XML and non-main-header segments + lines = fixtures.nemo.split('\n') + expected = lines[0:18] + expected.extend(lines[104:140]) + expected = '\n'.join(expected) + self.assertEqual(actual, expected) @unittest.skipIf(sys.hexversion < 0x03000000, "assertRegex not in 2.7") def test_codestream_0_with_j2k_file(self): From 2755e8edd439765acedc7fb11cac7bbfeeae9591 Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 5 Jan 2015 20:23:52 -0500 Subject: [PATCH 315/326] Jp2dump tests passing, more need to be written --- glymur/command_line.py | 16 +++++++++---- glymur/jp2box.py | 45 ++++++++++++++++++++++++++++-------- glymur/test/test_printing.py | 40 ++++++++++++++++++++------------ 3 files changed, 72 insertions(+), 29 deletions(-) diff --git a/glymur/command_line.py b/glymur/command_line.py index 0836108..4cd080d 100644 --- a/glymur/command_line.py +++ b/glymur/command_line.py @@ -60,11 +60,19 @@ def main(): # JP2 metadata can be extensive, so don't print any warnings until we # are done with the metadata. jp2 = Jp2k(filename) - if (((jp2._codec_format == lib.openjp2.CODEC_J2K) and - (codestream_level == 0))): - print('File: {0}'.format(os.path.basename(filename))) + if jp2._codec_format == lib.openjp2.CODEC_J2K: + if codestream_level == 0: + print('File: {0}'.format(os.path.basename(filename))) + elif codestream_level == 1: + print(jp2) + elif codestream_level == 2: + print('File: {0}'.format(os.path.basename(filename))) + print(jp2.get_codestream(header_only=False)) elif print_full_codestream: - print(jp2.get_codestream(header_only=False)) + for box in jp2.box: + if box.box_id == 'jp2c': + box._get_codestream(header_only=False) + print(jp2) else: print(jp2) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 4307f48..bd60c30 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1001,18 +1001,19 @@ class ContiguousCodestreamBox(Jp2kBox): offset of the box from the start of the file. longname : str more verbose description of the box. - main_header : Codestream object - contains list of main header marker/segments + codestream : Codestream object + Contains list of codestream marker/segments. By default, only the main + header is retrieved. main_header_offset : int offset of main header from start of file """ box_id = 'jp2c' longname = 'Contiguous Codestream' - def __init__(self, main_header=None, main_header_offset=None, length=0, + def __init__(self, codestream=None, main_header_offset=None, length=0, offset=-1): Jp2kBox.__init__(self) - self._main_header = main_header + self._codestream = codestream self.length = length self.offset = offset self.main_header_offset = main_header_offset @@ -1021,16 +1022,16 @@ class ContiguousCodestreamBox(Jp2kBox): self._filename = None @property - def main_header(self): - if self._main_header is None: + def codestream(self): + if self._codestream is None: if self._filename is not None: with open(self._filename, 'rb') as fptr: fptr.seek(self.main_header_offset) - main_header = Codestream(fptr, + codestream = Codestream(fptr, self._length, header_only=True) - self._main_header = main_header - return self._main_header + self._codestream = codestream + return self._codestream def __repr__(self): msg = "glymur.jp2box.ContiguousCodeStreamBox(main_header={0})" @@ -1043,7 +1044,7 @@ class ContiguousCodestreamBox(Jp2kBox): if _printoptions['codestream'] is False: return msg - for segment in self.main_header.segment: + for segment in self.codestream.segment: msg += '\n' + self._indent(str(segment), indent_level=4) return msg @@ -1076,6 +1077,30 @@ class ContiguousCodestreamBox(Jp2kBox): box._length = length return box + def _get_codestream(self, header_only=True): + """retrieve codestream + + Parameters + ---------- + header_only : bool, optional + If True, only marker segments in the main header are parsed. + Supplying False may impose a large performance penalty. + """ + with open(self.filename, 'rb') as fptr: + fptr.seek(self.offset) + read_buffer = fptr.read(8) + (box_length, _) = struct.unpack('>I4s', read_buffer) + if box_length == 0: + # The length of the box is presumed to last until the end + # of the file. Compute the effective length of the box. + box_length = os.path.getsize(fptr.name) - fptr.tell() + 8 + elif box_length == 1: + # Seek past the XL field. + read_buffer = fptr.read(8) + box_length, = struct.unpack('>Q', read_buffer) + self._codestream = Codestream(fptr, box_length - 8, + header_only=header_only) + class DataReferenceBox(Jp2kBox): """Container for Data Reference box information. diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 55e3018..bef59ad 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -1055,25 +1055,44 @@ class TestJp2dump(unittest.TestCase): return actual def test_default_nemo(self): - """Should be able to dump a JP2 file's metadata with no codestream.""" + """by default one should get the main header""" actual = self.run_jp2dump(['', self.jp2file]) - self.assertEqual(actual, fixtures.nemo_dump_no_codestream) + # shave off the non-main-header segments + lines = fixtures.nemo.split('\n') + expected = lines[0:140] + expected = '\n'.join(expected) + self.assertEqual(actual, expected) - def test_codestream_0(self): + @unittest.skipIf(sys.hexversion < 0x03000000, "assertRegex not in 2.7") + + def test_jp2_codestream_0(self): """Verify dumping with -c 0, supressing all codestream details.""" actual = self.run_jp2dump(['', '-c', '0', self.jp2file]) expected = fixtures.nemo_dump_no_codestream self.assertEqual(actual, expected) - def test_codestream_1(self): + def test_jp2_codestream_1(self): """Verify dumping with -c 1, print just the header.""" actual = self.run_jp2dump(['', '-c', '1', self.jp2file]) - self.assertEqual(actual, fixtures.nemo_with_codestream_header) + # shave off the non-main-header segments + lines = fixtures.nemo.split('\n') + expected = lines[0:140] + expected = '\n'.join(expected) + self.assertEqual(actual, expected) - def test_codestream_2(self): + @unittest.skipIf(sys.hexversion < 0x03000000, "assertRegex not in 2.7") + def test_j2k_codestream_0(self): + """-c 0 should print just a single line when used on a codestream.""" + sys.argv = ['', '-c', '0', self.j2kfile] + with patch('sys.stdout', new=StringIO()) as fake_out: + command_line.main() + actual = fake_out.getvalue().strip() + self.assertRegex(actual, "File: .*") + + def test_j2k_codestream_2(self): """Verify dumping with -c 2, full details.""" with patch('sys.stdout', new=StringIO()) as fake_out: sys.argv = ['', '-c', '2', self.j2kfile] @@ -1104,12 +1123,3 @@ class TestJp2dump(unittest.TestCase): expected.extend(lines[104:140]) expected = '\n'.join(expected) self.assertEqual(actual, expected) - - @unittest.skipIf(sys.hexversion < 0x03000000, "assertRegex not in 2.7") - def test_codestream_0_with_j2k_file(self): - """-c 0 should print just a single line when used on a codestream.""" - sys.argv = ['', '-c', '0', self.j2kfile] - with patch('sys.stdout', new=StringIO()) as fake_out: - command_line.main() - actual = fake_out.getvalue().strip() - self.assertRegex(actual, "File: .*") From 7dfcc0fd7c77f7f90c1c44fe84ae23f10b026157 Mon Sep 17 00:00:00 2001 From: jevans Date: Mon, 5 Jan 2015 21:10:02 -0500 Subject: [PATCH 316/326] documented rename of ContiguousCodestream attribute codesream. --- docs/source/whatsnew/0.8.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/whatsnew/0.8.rst b/docs/source/whatsnew/0.8.rst index f36b593..5c87f5a 100644 --- a/docs/source/whatsnew/0.8.rst +++ b/docs/source/whatsnew/0.8.rst @@ -6,5 +6,7 @@ Changes in 0.8.0 ================= * Simplified writing images by moving data and options into the - constructor. This is backwards-incompatible with 0.7.x. + constructor. + * The main_header attribute of the ContiguousCodestream class is now called + codestream. * Deprecated :py:meth:`read` method in favor of array-style slicing. From aeec03c82986e9b0f796c8ed5978a8052fb74947 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 6 Jan 2015 09:23:23 -0500 Subject: [PATCH 317/326] blah --- glymur/test/test_printing.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 55e3018..3e5eefb 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -1055,10 +1055,15 @@ class TestJp2dump(unittest.TestCase): return actual def test_default_nemo(self): - """Should be able to dump a JP2 file's metadata with no codestream.""" + """should dump everything but non-main-header codestream segments""" actual = self.run_jp2dump(['', self.jp2file]) - self.assertEqual(actual, fixtures.nemo_dump_no_codestream) + # shave off the non-main-header segments + lines = fixtures.nemo.split('\n') + expected = lines[0:140] + expected = '\n'.join(expected) + + self.assertEqual(actual, expected) def test_codestream_0(self): """Verify dumping with -c 0, supressing all codestream details.""" @@ -1068,10 +1073,15 @@ class TestJp2dump(unittest.TestCase): self.assertEqual(actual, expected) def test_codestream_1(self): - """Verify dumping with -c 1, print just the header.""" + """Verify dumping with -c 1, same as default""" actual = self.run_jp2dump(['', '-c', '1', self.jp2file]) - self.assertEqual(actual, fixtures.nemo_with_codestream_header) + # shave off the non-main-header segments + lines = fixtures.nemo.split('\n') + expected = lines[0:140] + expected = '\n'.join(expected) + + self.assertEqual(actual, expected) def test_codestream_2(self): """Verify dumping with -c 2, full details.""" From fe5e784b585131b615c8cace4866b06e5841c8a3 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 6 Jan 2015 11:00:08 -0500 Subject: [PATCH 318/326] get all tests passing --- glymur/jp2box.py | 4 ++-- glymur/test/fixtures.py | 9 +++++++++ glymur/test/test_glymur_warnings.py | 7 +++++++ glymur/test/test_jp2box.py | 4 ++-- glymur/test/test_jp2k.py | 15 +++------------ glymur/test/test_opj_suite.py | 2 +- glymur/test/test_opj_suite_dump.py | 22 +++++++++++----------- glymur/test/test_opj_suite_write.py | 2 +- 8 files changed, 36 insertions(+), 29 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index bd60c30..c946e6b 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1034,8 +1034,8 @@ class ContiguousCodestreamBox(Jp2kBox): return self._codestream def __repr__(self): - msg = "glymur.jp2box.ContiguousCodeStreamBox(main_header={0})" - return msg.format(repr(self.main_header)) + msg = "glymur.jp2box.ContiguousCodeStreamBox(codestream={0})" + return msg.format(repr(self.codestream)) def __str__(self): msg = Jp2kBox.__str__(self) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 12b0c71..dc27b0e 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -2,6 +2,7 @@ Test fixtures common to more than one test point. """ import os +import platform import re import sys import textwrap @@ -33,6 +34,14 @@ elif re.match('1.[0-6]', six.__version__) is not None: WARNING_INFRASTRUCTURE_ISSUE = True msg = "Cannot run test with version {0} of python-six" WARNING_INFRASTRUCTURE_MSG = msg.format(six.__version__) +elif ((re.match('1.8', six.__version__) is not None) and + (sys.platform.startswith('linux')) and + (platform.linux_distribution() == ('LinuxMint', '17', 'qiana'))): + WARNING_INFRASTRUCTURE_ISSUE = True + linux_distribution = platform.linux_distribution() + msg = "Cannot run test with version {0} of python-six on {1}" + WARNING_INFRASTRUCTURE_MSG = msg.format(six.__version__, + platform.linux_distribution) # Cannot reopen a named temporary file in windows. WINDOWS_TMP_FILE_MSG = "cannot use NamedTemporaryFile like this in windows" diff --git a/glymur/test/test_glymur_warnings.py b/glymur/test/test_glymur_warnings.py index ff18c6e..1d3dc2e 100644 --- a/glymur/test/test_glymur_warnings.py +++ b/glymur/test/test_glymur_warnings.py @@ -20,6 +20,13 @@ from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG class TestWarnings(unittest.TestCase): """Test suite for warnings issued by glymur.""" + def test_invalid_compatibility_list_entry(self): + """should not error out with invalid compatibility list entry""" + filename = opj_data_file('input/nonregression/issue397.jp2') + with self.assertWarns(UserWarning): + Jp2k(filename) + self.assertTrue(True) + def test_exceeded_box_length(self): """ should warn if reading past end of a box diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 9ccd75b..e43eae6 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -1016,7 +1016,7 @@ class TestJp2Boxes(unittest.TestCase): """Raw instantiation should not produce a main_header.""" box = ContiguousCodestreamBox() self.assertEqual(box.box_id, 'jp2c') - self.assertIsNone(box.main_header) + self.assertIsNone(box.codestream) def test_codestream_main_header_offset(self): """main_header_offset is an attribute of the CCS box""" @@ -1318,7 +1318,7 @@ class TestRepr(MetadataBase): # Difficult to eval(repr()) this, so just match the general pattern. regexp = "glymur.jp2box.ContiguousCodeStreamBox" - regexp += "[(]main_header= Date: Tue, 6 Jan 2015 16:35:38 -0500 Subject: [PATCH 319/326] replaced main_header codestream attribute with codestream changed "codestream" parameter of set_parseoptions to "full_codestream" --- glymur/command_line.py | 14 ++------- glymur/jp2box.py | 61 +++++++++++++----------------------- glymur/test/test_jp2k.py | 13 ++++---- glymur/test/test_printing.py | 11 ++++++- 4 files changed, 40 insertions(+), 59 deletions(-) diff --git a/glymur/command_line.py b/glymur/command_line.py index 4cd080d..3d1d57e 100644 --- a/glymur/command_line.py +++ b/glymur/command_line.py @@ -5,7 +5,7 @@ import argparse import os import warnings -from . import Jp2k, set_printoptions, lib +from . import Jp2k, set_printoptions, set_parseoptions, lib def main(): @@ -47,11 +47,8 @@ def main(): if codestream_level == 0: set_printoptions(codestream=False) - print_full_codestream = False - elif codestream_level == 1: - print_full_codestream = False - else: - print_full_codestream = True + elif codestream_level == 2: + set_parseoptions(full_codestream=True) filename = args.filename @@ -68,11 +65,6 @@ def main(): elif codestream_level == 2: print('File: {0}'.format(os.path.basename(filename))) print(jp2.get_codestream(header_only=False)) - elif print_full_codestream: - for box in jp2.box: - if box.box_id == 'jp2c': - box._get_codestream(header_only=False) - print(jp2) else: print(jp2) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index c946e6b..1cbd53a 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1023,13 +1023,17 @@ class ContiguousCodestreamBox(Jp2kBox): @property def codestream(self): + if _parseoptions['full_codestream'] is True: + header_only = False + else: + header_only = True if self._codestream is None: if self._filename is not None: with open(self._filename, 'rb') as fptr: fptr.seek(self.main_header_offset) codestream = Codestream(fptr, self._length, - header_only=True) + header_only=header_only) self._codestream = codestream return self._codestream @@ -1067,40 +1071,16 @@ class ContiguousCodestreamBox(Jp2kBox): ContiguousCodestreamBox instance """ main_header_offset = fptr.tell() - if _parseoptions['codestream'] is True: - main_header = Codestream(fptr, length, header_only=True) + if _parseoptions['full_codestream'] is True: + codestream = Codestream(fptr, length, header_only=False) else: - main_header = None - box = cls(main_header, main_header_offset=main_header_offset, + codestream = None + box = cls(codestream, main_header_offset=main_header_offset, length=length, offset=offset) box._filename = fptr.name box._length = length return box - def _get_codestream(self, header_only=True): - """retrieve codestream - - Parameters - ---------- - header_only : bool, optional - If True, only marker segments in the main header are parsed. - Supplying False may impose a large performance penalty. - """ - with open(self.filename, 'rb') as fptr: - fptr.seek(self.offset) - read_buffer = fptr.read(8) - (box_length, _) = struct.unpack('>I4s', read_buffer) - if box_length == 0: - # The length of the box is presumed to last until the end - # of the file. Compute the effective length of the box. - box_length = os.path.getsize(fptr.name) - fptr.tell() + 8 - elif box_length == 1: - # Seek past the XL field. - read_buffer = fptr.read(8) - box_length, = struct.unpack('>Q', read_buffer) - self._codestream = Codestream(fptr, box_length - 8, - header_only=header_only) - class DataReferenceBox(Jp2kBox): """Container for Data Reference box information. @@ -3338,19 +3318,20 @@ _BOX_WITH_ID = { b'uuid': UUIDBox, b'xml ': XMLBox} -_parseoptions = {'codestream': True} +_parseoptions = {'full_codestream': False} -def set_parseoptions(codestream=True): +def set_parseoptions(full_codestream=True): """Set parsing options. These options determine the way JPEG 2000 boxes are parsed. Parameters ---------- - codestream : bool, defaults to True - When False, the codestream header is only parsed when accessed. This - can results in faster JP2/JPX parsing. + full_codestream : bool, defaults to True + When False, only the codestream header is parsed for metadata. This + can results in faster JP2/JPX parsing. When True, the entire + codestream is parsed for metadata. See also -------- @@ -3361,9 +3342,9 @@ def set_parseoptions(codestream=True): To put back the default options, you can use: >>> import glymur - >>> glymur.set_parseoptions(codestream=True) + >>> glymur.set_parseoptions(full_codestream=True) """ - _parseoptions['codestream'] = codestream + _parseoptions['full_codestream'] = full_codestream def get_parseoptions(): @@ -3401,10 +3382,10 @@ def set_printoptions(**kwargs): xml : bool, optional When False, printing of the XML contents of any XML boxes or UUID XMP boxes is suppressed. - siz : bool, optional - When True, only the SIZ segment is printed. When False, the entire - codestream is printed. This option has no effect when the 'short' - option is set to True. + codestream : bool, optional + When False, only the codestream header is printed. When True, the + entire codestream is printed. This option has no effect when the + 'short' option is set to True. See also -------- diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 53f174a..fe18022 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -1043,26 +1043,25 @@ class TestParsing(unittest.TestCase): def setUp(self): self.jp2file = glymur.data.nemo() # Reset parseoptions for every test. - glymur.set_parseoptions(codestream=True) + glymur.set_parseoptions(full_codestream=False) def tearDown(self): - glymur.set_parseoptions(codestream=True) + glymur.set_parseoptions(full_codestream=False) @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_bad_rsiz(self): """Should not warn if RSIZ when parsing is turned off.""" filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') - glymur.set_parseoptions(codestream=False) - Jp2k(filename) + glymur.set_parseoptions(full_codestream=False) + jp2 = Jp2k(filename) - glymur.set_parseoptions(codestream=True) + glymur.set_parseoptions(full_codestream=True) with self.assertWarnsRegex(UserWarning, 'Invalid profile'): Jp2k(filename) def test_main_header(self): - """Verify that the main header isn't loaded when parsing turned off.""" + """verify that the main header isn't loaded during normal parsing""" # The hidden _main_header attribute should show up after accessing it. - glymur.set_parseoptions(codestream=False) jp2 = Jp2k(self.jp2file) jp2c = jp2.box[4] self.assertIsNone(jp2c._codestream) diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index bef59ad..c141d65 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -1040,9 +1040,10 @@ class TestJp2dump(unittest.TestCase): # Reset printoptions for every test. glymur.set_printoptions(short=False, xml=True, codestream=True) + glymur.set_parseoptions(full_codestream=False) def tearDown(self): - pass + glymur.set_parseoptions(full_codestream=False) def run_jp2dump(self, args): sys.argv = args @@ -1083,6 +1084,14 @@ class TestJp2dump(unittest.TestCase): expected = '\n'.join(expected) self.assertEqual(actual, expected) + def test_jp2_codestream_2(self): + """Verify dumping with -c 2, print entire jp2 jacket, codestream.""" + actual = self.run_jp2dump(['', '-c', '2', self.jp2file]) + + # shave off the non-main-header segments + expected = fixtures.nemo + self.assertEqual(actual, expected) + @unittest.skipIf(sys.hexversion < 0x03000000, "assertRegex not in 2.7") def test_j2k_codestream_0(self): """-c 0 should print just a single line when used on a codestream.""" From 4ceb4dce61b0f69719d5cdacdf4cc24e2ee3da86 Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 6 Jan 2015 16:37:55 -0500 Subject: [PATCH 320/326] pep8 work --- glymur/jp2box.py | 3 +-- glymur/test/test_jp2k.py | 2 +- glymur/test/test_printing.py | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 1cbd53a..bc23ebe 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1031,8 +1031,7 @@ class ContiguousCodestreamBox(Jp2kBox): if self._filename is not None: with open(self._filename, 'rb') as fptr: fptr.seek(self.main_header_offset) - codestream = Codestream(fptr, - self._length, + codestream = Codestream(fptr, self._length, header_only=header_only) self._codestream = codestream return self._codestream diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index fe18022..8affe7d 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -1053,7 +1053,7 @@ class TestParsing(unittest.TestCase): """Should not warn if RSIZ when parsing is turned off.""" filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') glymur.set_parseoptions(full_codestream=False) - jp2 = Jp2k(filename) + Jp2k(filename) glymur.set_parseoptions(full_codestream=True) with self.assertWarnsRegex(UserWarning, 'Invalid profile'): diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index c141d65..dfdd505 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -1066,7 +1066,6 @@ class TestJp2dump(unittest.TestCase): self.assertEqual(actual, expected) @unittest.skipIf(sys.hexversion < 0x03000000, "assertRegex not in 2.7") - def test_jp2_codestream_0(self): """Verify dumping with -c 0, supressing all codestream details.""" actual = self.run_jp2dump(['', '-c', '0', self.jp2file]) From 059bee50e2bd9a3a5b69f6b87f1cb868c27af31b Mon Sep 17 00:00:00 2001 From: John Evans Date: Tue, 6 Jan 2015 19:55:59 -0500 Subject: [PATCH 321/326] all tests passing on opensuse --- glymur/test/test_glymur_warnings.py | 16 ++++++++-------- glymur/test/test_jp2k.py | 9 ++++++++- glymur/test/test_opj_suite_dump.py | 27 ++++++++++++++++++--------- glymur/test/test_printing.py | 4 ++-- 4 files changed, 36 insertions(+), 20 deletions(-) diff --git a/glymur/test/test_glymur_warnings.py b/glymur/test/test_glymur_warnings.py index 1d3dc2e..8086004 100644 --- a/glymur/test/test_glymur_warnings.py +++ b/glymur/test/test_glymur_warnings.py @@ -69,7 +69,7 @@ class TestWarnings(unittest.TestCase): \(\d+\)\.""", re.VERBOSE) with self.assertWarnsRegex(UserWarning, regex): - Jp2k(jfile) + Jp2k(jfile).get_codestream() @unittest.skipIf(re.match("1.5|2.0.0", glymur.version.openjpeg_version), "Test not passing on 1.5.x, not introduced until 2.x") @@ -84,7 +84,7 @@ class TestWarnings(unittest.TestCase): \(\d+\)\.""", re.VERBOSE) with self.assertWarnsRegex(UserWarning, regex): - Jp2k(jfile) + Jp2k(jfile).get_codestream() def test_NR_gdal_fuzzer_check_comp_dx_dy_jp2_dump(self): """ @@ -97,7 +97,7 @@ class TestWarnings(unittest.TestCase): dx=\d+,\s*dy=\d+""", re.VERBOSE) with self.assertWarnsRegex(UserWarning, regex): - Jp2k(jfile) + Jp2k(jfile).get_codestream() def test_NR_gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc_patch_jp2(self): lst = ['input', 'nonregression', @@ -107,32 +107,32 @@ class TestWarnings(unittest.TestCase): number\sof\scomponents\sis\sonly\s\d+""", re.VERBOSE) with self.assertWarnsRegex(UserWarning, regex): - Jp2k(jfile) + Jp2k(jfile).get_codestream() def test_bad_rsiz(self): """Should warn if RSIZ is bad. Issue196""" filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') with self.assertWarnsRegex(UserWarning, 'Invalid profile'): - Jp2k(filename) + Jp2k(filename).get_codestream() def test_bad_wavelet_transform(self): """Should warn if wavelet transform is bad. Issue195""" filename = opj_data_file('input/nonregression/edf_c2_10025.jp2') with self.assertWarnsRegex(UserWarning, 'Invalid wavelet transform'): - Jp2k(filename) + Jp2k(filename).get_codestream() def test_invalid_progression_order(self): """Should still be able to parse even if prog order is invalid.""" jfile = opj_data_file('input/nonregression/2977.pdf.asan.67.2198.jp2') with self.assertWarnsRegex(UserWarning, 'Invalid progression order'): - Jp2k(jfile) + Jp2k(jfile).get_codestream() def test_tile_height_is_zero(self): """Zero tile height should not cause an exception.""" filename = 'input/nonregression/2539.pdf.SIGFPE.706.1712.jp2' filename = opj_data_file(filename) with self.assertWarnsRegex(UserWarning, 'Invalid tile dimensions'): - Jp2k(filename) + Jp2k(filename).get_codestream() @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_unknown_marker_segment(self): diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 8affe7d..668f8d5 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -36,6 +36,9 @@ from .fixtures import OPJ_DATA_ROOT, opj_data_file from . import fixtures +def docTearDown(doctest_obj): + glymur.set_parseoptions(full_codestream=False) + # Doc tests should be run as well. def load_tests(loader, tests, ignore): """Should run doc tests as well""" @@ -43,7 +46,8 @@ def load_tests(loader, tests, ignore): # Can't do it on windows, temporary file issue. return tests if glymur.lib.openjp2.OPENJP2 is not None: - tests.addTests(doctest.DocTestSuite('glymur.jp2k')) + tests.addTests(doctest.DocTestSuite('glymur.jp2k', + tearDown=docTearDown)) return tests @@ -1075,6 +1079,9 @@ class TestParsing(unittest.TestCase): class TestJp2kOpjDataRootWarnings(unittest.TestCase): """These tests should be run by just about all configuration.""" + def tearDown(self): + glymur.set_parseoptions(full_codestream=False) + def test_undecodeable_box_id(self): """Should warn in case of undecodeable box ID but not error out.""" filename = opj_data_file('input/nonregression/edf_c2_1013627.jp2') diff --git a/glymur/test/test_opj_suite_dump.py b/glymur/test/test_opj_suite_dump.py index 8920473..7ac513f 100644 --- a/glymur/test/test_opj_suite_dump.py +++ b/glymur/test/test_opj_suite_dump.py @@ -4,6 +4,7 @@ suite. """ import re import unittest +import warnings import numpy as np @@ -2913,21 +2914,29 @@ class TestSuiteWarns(MetadataBase): def test_NR_broken4_jp2_dump(self): jfile = opj_data_file('input/nonregression/broken4.jp2') - with self.assertWarns(UserWarning): - jp2 = Jp2k(jfile) - - self.assertEqual(jp2.box[-1].codestream.segment[-1].marker_id, 'QCC') + with warnings.catch_warnings(): + # Suppress a warning, all we really care is parsing the entire + # file. + warnings.simplefilter("ignore") + with self.assertWarns(UserWarning): + jp2 = Jp2k(jfile) + self.assertEqual(jp2.box[-1].codestream.segment[-1].marker_id, + 'QCC') def test_NR_broken2_jp2_dump(self): """ Invalid marker ID in the codestream. """ jfile = opj_data_file('input/nonregression/broken2.jp2') - with self.assertWarns(UserWarning): - # Invalid marker ID on codestream. - jp2 = Jp2k(jfile) - - self.assertEqual(jp2.box[-1].codestream.segment[-1].marker_id, 'QCC') + with warnings.catch_warnings(): + # Suppress a warning, all we really care is parsing the entire + # file. + warnings.simplefilter("ignore") + with self.assertWarns(UserWarning): + # Invalid marker ID on codestream. + jp2 = Jp2k(jfile) + self.assertEqual(jp2.box[-1].codestream.segment[-1].marker_id, + 'QCC') def test_NR_file1_dump(self): jfile = opj_data_file('input/conformance/file1.jp2') diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index dfdd505..5310fa9 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -42,7 +42,7 @@ class TestPrinting(unittest.TestCase): glymur.set_printoptions(short=False, xml=True, codestream=True) def tearDown(self): - pass + glymur.set_parseoptions(full_codestream=False) def test_version_info(self): """Should be able to print(glymur.version.info)""" @@ -854,7 +854,7 @@ class TestPrintingOpjDataRootWarns(unittest.TestCase): """Should still be able to print if rsiz is bad, issue196""" filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') with self.assertWarns(UserWarning): - j = Jp2k(filename) + j = Jp2k(filename).get_codestream() with patch('sys.stdout', new=StringIO()): print(j) From 4d680cf90ba561e20f688edc7beb30aa4a2c1a98 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 7 Jan 2015 08:40:08 -0500 Subject: [PATCH 322/326] refactor shape property to be less dependent on property, closes #315 --- docs/source/detailed_installation.rst | 5 ++- glymur/jp2k.py | 50 +++++++++++++++------------ 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index 1a9281c..7068b56 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -15,7 +15,7 @@ or if you use windows, then read on. Glymur uses ctypes to access the openjp2/openjpeg libraries, and because ctypes accesses libraries in a platform-dependent manner, -it is recommended that if you compile and install OpenJPEG into a +it is recommended that **if** you compile and install OpenJPEG into a non-standard location, you should then create a configuration file to help Glymur properly find the openjpeg or openjp2 libraries (linux users or macports users don’t need to bother with this if @@ -50,6 +50,9 @@ installed in a non-standard place, i.e. :: [library] openjpeg: /somewhere/lib/libopenjpeg.so +Once again, you should not have to bother with a configuration file if you use +mac or linux and OpenJPEG is provided by your package manager. + ''''''' Testing ''''''' diff --git a/glymur/jp2k.py b/glymur/jp2k.py index de386dc..d45f0db 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -205,28 +205,30 @@ class Jp2k(Jp2kBox): if self._shape is not None: return self._shape - cstr = self.codestream - height = cstr.segment[1].ysiz - width = cstr.segment[1].xsiz - num_components = len(cstr.segment[1].xrsiz) + if self._codec_format == opj2.CODEC_J2K: + # get the image size from the codestream + cstr = self.codestream + height = cstr.segment[1].ysiz + width = cstr.segment[1].xsiz + num_components = len(cstr.segment[1].xrsiz) + else: + # try to get the image size from the IHDR box + jp2h = [box for box in self.box if box.box_id == 'jp2h'][0] + ihdr = [box for box in jp2h.box if box.box_id == 'ihdr'][0] + + height, width = ihdr.height, ihdr.width + num_components = ihdr.num_components + + if num_components == 1: + # but if there is a PCLR box, then we need to check that as + # well, as that turns a single-channel image into a + # multi-channel image + pclr = [box for box in jp2h.box if box.box_id == 'pclr'] + if len(pclr) > 0: + num_components = len(pclr[0].signed) - # If JP2 and a palette box is present, then determine the shape from - # that. if num_components == 1: - if self._codec_format == opj2.CODEC_J2K: - # There's no palette box or component mapping in a J2K file. - # The 3rd component in the shape would then be 1, but we'll - # ignore that. - self.shape = (height, width) - else: - jp2h = [box for box in self.box if box.box_id == 'jp2h'][0] - pclr = [box for box in jp2h.box if box.box_id == 'pclr'] - if len(pclr) == 0: - # No palette box, so just one component, which we will - # ignore. - self.shape = (height, width) - else: - self.shape = (height, width, len(pclr[0].signed)) + self.shape = (height, width) else: self.shape = (height, width, num_components) @@ -918,9 +920,11 @@ class Jp2k(Jp2kBox): """ Slicing protocol. """ - numrows = self.codestream.segment[1].ysiz - numcols = self.codestream.segment[1].xsiz - numbands = self.codestream.segment[1].Csiz + if len(self.shape) == 2: + numrows, numcols = self.shape + numbands = 1 + else: + numrows, numcols, numbands = self.shape if isinstance(pargs, int): # Not a very good use of this protocol, but technically legal. From 4686c40c57a7fcd4b643496eb0714cbc629f5dee Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 8 Jan 2015 12:07:10 -0500 Subject: [PATCH 323/326] fix test failure, not a real bug, closes #316 Had assumed that the error was due to parse_options not being properly reset, but that was not the case. Seems to have just been bad expected data. --- glymur/jp2box.py | 2 +- glymur/jp2k.py | 22 +++++++++++----------- glymur/test/fixtures.py | 4 ++-- glymur/test/test_jp2k.py | 4 +--- glymur/test/test_printing.py | 6 ++++-- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index bc23ebe..98b0e6a 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1032,7 +1032,7 @@ class ContiguousCodestreamBox(Jp2kBox): with open(self._filename, 'rb') as fptr: fptr.seek(self.main_header_offset) codestream = Codestream(fptr, self._length, - header_only=header_only) + header_only=header_only) self._codestream = codestream return self._codestream diff --git a/glymur/jp2k.py b/glymur/jp2k.py index d45f0db..ee6eedc 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -213,19 +213,19 @@ class Jp2k(Jp2kBox): num_components = len(cstr.segment[1].xrsiz) else: # try to get the image size from the IHDR box - jp2h = [box for box in self.box if box.box_id == 'jp2h'][0] - ihdr = [box for box in jp2h.box if box.box_id == 'ihdr'][0] + jp2h = [box for box in self.box if box.box_id == 'jp2h'][0] + ihdr = [box for box in jp2h.box if box.box_id == 'ihdr'][0] - height, width = ihdr.height, ihdr.width - num_components = ihdr.num_components + height, width = ihdr.height, ihdr.width + num_components = ihdr.num_components - if num_components == 1: - # but if there is a PCLR box, then we need to check that as - # well, as that turns a single-channel image into a - # multi-channel image - pclr = [box for box in jp2h.box if box.box_id == 'pclr'] - if len(pclr) > 0: - num_components = len(pclr[0].signed) + if num_components == 1: + # but if there is a PCLR box, then we need to check that as + # well, as that turns a single-channel image into a + # multi-channel image + pclr = [box for box in jp2h.box if box.box_id == 'pclr'] + if len(pclr) > 0: + num_components = len(pclr[0].signed) if num_components == 1: self.shape = (height, width) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 8d6165e..f6b8ef8 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -34,8 +34,8 @@ elif re.match('1.[0-6]', six.__version__) is not None: WARNING_INFRASTRUCTURE_ISSUE = True msg = "Cannot run test with version {0} of python-six" WARNING_INFRASTRUCTURE_MSG = msg.format(six.__version__) -elif ((re.match('1.8', six.__version__) is not None) and - (sys.platform.startswith('linux')) and +elif ((re.match('1.8', six.__version__) is not None) and + (sys.platform.startswith('linux')) and (platform.linux_distribution() == ('LinuxMint', '17', 'qiana'))): WARNING_INFRASTRUCTURE_ISSUE = True linux_distribution = platform.linux_distribution() diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 668f8d5..52945f9 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -39,6 +39,7 @@ from . import fixtures def docTearDown(doctest_obj): glymur.set_parseoptions(full_codestream=False) + # Doc tests should be run as well. def load_tests(loader, tests, ignore): """Should run doc tests as well""" @@ -1079,9 +1080,6 @@ class TestParsing(unittest.TestCase): class TestJp2kOpjDataRootWarnings(unittest.TestCase): """These tests should be run by just about all configuration.""" - def tearDown(self): - glymur.set_parseoptions(full_codestream=False) - def test_undecodeable_box_id(self): """Should warn in case of undecodeable box ID but not error out.""" filename = opj_data_file('input/nonregression/edf_c2_1013627.jp2') diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 5310fa9..bccaa00 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -1065,12 +1065,14 @@ class TestJp2dump(unittest.TestCase): expected = '\n'.join(expected) self.assertEqual(actual, expected) - @unittest.skipIf(sys.hexversion < 0x03000000, "assertRegex not in 2.7") def test_jp2_codestream_0(self): """Verify dumping with -c 0, supressing all codestream details.""" actual = self.run_jp2dump(['', '-c', '0', self.jp2file]) - expected = fixtures.nemo_dump_no_codestream + # shave off the codestream details + lines = fixtures.nemo.split('\n') + expected = lines[0:105] + expected = '\n'.join(expected) self.assertEqual(actual, expected) def test_jp2_codestream_1(self): From 8cc11552522f08f79427d17fc6c99bfac1bad807 Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 8 Jan 2015 13:23:55 -0500 Subject: [PATCH 324/326] removed check for six version 1.8.0, closes #314 Not sure how this issue came about as it no longer seems to exist. The version of six installed on Linux Mint 17 is 1.5.2, not 1.8.0 as thought when the issue was filed. All seems ok. --- glymur/test/fixtures.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index f6b8ef8..34327fc 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -2,7 +2,6 @@ Test fixtures common to more than one test point. """ import os -import platform import re import sys import textwrap @@ -34,14 +33,6 @@ elif re.match('1.[0-6]', six.__version__) is not None: WARNING_INFRASTRUCTURE_ISSUE = True msg = "Cannot run test with version {0} of python-six" WARNING_INFRASTRUCTURE_MSG = msg.format(six.__version__) -elif ((re.match('1.8', six.__version__) is not None) and - (sys.platform.startswith('linux')) and - (platform.linux_distribution() == ('LinuxMint', '17', 'qiana'))): - WARNING_INFRASTRUCTURE_ISSUE = True - linux_distribution = platform.linux_distribution() - msg = "Cannot run test with version {0} of python-six on {1}" - WARNING_INFRASTRUCTURE_MSG = msg.format(six.__version__, - platform.linux_distribution) # Cannot reopen a named temporary file in windows. WINDOWS_TMP_FILE_MSG = "cannot use NamedTemporaryFile like this in windows" From 2ffdf716c062f78e68bb81afd10973965fd1caaa Mon Sep 17 00:00:00 2001 From: John Evans Date: Thu, 8 Jan 2015 15:30:10 -0500 Subject: [PATCH 325/326] remove test requirement for six, doc modifications, doctest fix The test requirement for six was problematic, as some versions cause problems with assertWarns and some do not. We are already filtering out versions that we know cause problems and it can unintentionally cause an upgrade superceding the system version if it remains as a "test_requires" option, so best to get rid of it. Additionally, the "test_requires" option was entirely removed as pip doesn't recognize it upon install, so "mock" would not be installed on Python2.7 via pip. The doctest fix was the real fix for #316, not sure why this did not show up when testing on linux mint --- CHANGES.txt | 5 ++ docs/source/api.rst | 116 ----------------------------------- docs/source/conf.py | 4 +- docs/source/how_do_i.rst | 8 +-- docs/source/index.rst | 1 - docs/source/whatsnew/0.8.rst | 16 ++++- glymur/test/test_jp2box.py | 7 ++- glymur/version.py | 2 +- setup.py | 4 +- 9 files changed, 33 insertions(+), 130 deletions(-) delete mode 100644 docs/source/api.rst diff --git a/CHANGES.txt b/CHANGES.txt index 758941b..98e6730 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,8 @@ +Jan 08, 2015 - v0.8.0rc1 Reduced number of steps required for + writing images. Deprecated old read and write methods in favor + of array-style slicing. Added ignore_pclr_cmap_cdef, verbose, + shape, codestream, layer properties. + Oct 06, 2014 - v0.7.2 Added ellipsis support in array-style slicing. Oct 02, 2014 - v0.7.1 Fixed README to mention Python 3.4 diff --git a/docs/source/api.rst b/docs/source/api.rst deleted file mode 100644 index 7584718..0000000 --- a/docs/source/api.rst +++ /dev/null @@ -1,116 +0,0 @@ ---- -API ---- - -Jp2k ----- -.. autoclass:: glymur.Jp2k - :members: read, write, wrap, read_bands, get_codestream - -Individual Boxes ----------------- -Jp2kbox -''''''' -.. autoclass:: glymur.jp2box.Jp2kBox - :members: - -AssociationBox -'''''''''''''' -.. autoclass:: glymur.jp2box.AssociationBox - :members: - -ColourSpecificationBox -'''''''''''''''''''''' -.. autoclass:: glymur.jp2box.ColourSpecificationBox - :members: - -ChannelDefinitionBox -'''''''''''''''''''''' -.. autoclass:: glymur.jp2box.ChannelDefinitionBox - :members: - -ComponentMappingBox -''''''''''''''''''' -.. autoclass:: glymur.jp2box.ComponentMappingBox - :members: - -ContiguousCodestreamBox -''''''''''''''''''''''' -.. autoclass:: glymur.jp2box.ContiguousCodestreamBox - :members: - -DataEntryURLBox -''''''''''''''' -.. autoclass:: glymur.jp2box.DataEntryURLBox - :members: - -FileTypeBox -''''''''''' -.. autoclass:: glymur.jp2box.FileTypeBox - :members: - -ImageHeaderBox -'''''''''''''' -.. autoclass:: glymur.jp2box.ImageHeaderBox - :members: - -JP2HeaderBox -'''''''''''' -.. autoclass:: glymur.jp2box.JP2HeaderBox - :members: - -JPEG2000SignatureBox -'''''''''''''''''''' -.. autoclass:: glymur.jp2box.JPEG2000SignatureBox - :members: - -LabelBox -'''''''' -.. autoclass:: glymur.jp2box.LabelBox - :members: - -PaletteBox -'''''''''' -.. autoclass:: glymur.jp2box.PaletteBox - :members: - -ReaderRequirementsBox -''''''''''''''''''''' -.. autoclass:: glymur.jp2box.ReaderRequirementsBox - :members: - -ResolutionBox -''''''''''''' -.. autoclass:: glymur.jp2box.ResolutionBox - :members: - -CaptureResolutionBox -'''''''''''''''''''' -.. autoclass:: glymur.jp2box.CaptureResolutionBox - :members: - -DisplayResolutionBox -'''''''''''''''''''' -.. autoclass:: glymur.jp2box.DisplayResolutionBox - :members: - -UUIDBox -''''''' -.. autoclass:: glymur.jp2box.UUIDBox - :members: - -UUIDInfoBox -''''''''''' -.. autoclass:: glymur.jp2box.UUIDInfoBox - :members: - -UUIDListBox -''''''''''' -.. autoclass:: glymur.jp2box.UUIDListBox - :members: - -XMLBox -'''''' -.. autoclass:: glymur.jp2box.XMLBox - :members: - diff --git a/docs/source/conf.py b/docs/source/conf.py index e9387f4..f90815d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -75,9 +75,9 @@ copyright = u'2013, John Evans' # built documents. # # The short X.Y version. -version = '0.7' +version = '0.8' # The full version, including alpha/beta/rc tags. -release = '0.7.2' +release = '0.8.0rc2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index 33366b5..88d94fa 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -26,10 +26,10 @@ It's pretty simple, just supply the image data as the 2nd argument to the Jp2k constructor. >>> import glymur, numpy as np - >>> data = np.zeros((640, 480), dtype=np.uint8) - >>> jp2 = glymur.Jp2k('zeros.jp2', data=data) + >>> jp2 = glymur.Jp2k('zeros.jp2', data=np.zeros((640, 480), dtype=np.uint8) -You should have OpenJPEG version 1.5 or more recent before writing JPEG 2000 images. +You must have OpenJPEG version 1.5 or more recent in order to write JPEG 2000 +images with glymur. ... display metadata? ===================== @@ -442,7 +442,7 @@ following 'Google' But that would be painful. A better solution is to install the Python XMP -Toolkit (make sure it is version 2.0):: +Toolkit (make sure it is at least version 2.0):: >>> from libxmp import XMPMeta >>> from libxmp.consts import XMP_NS_XMP as NS_XAP diff --git a/docs/source/index.rst b/docs/source/index.rst index 8c1fb03..62e118d 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -17,7 +17,6 @@ Contents: how_do_i whatsnew/index roadmap - api ------------------ Indices and tables diff --git a/docs/source/whatsnew/0.8.rst b/docs/source/whatsnew/0.8.rst index 5c87f5a..e7215ad 100644 --- a/docs/source/whatsnew/0.8.rst +++ b/docs/source/whatsnew/0.8.rst @@ -7,6 +7,18 @@ Changes in 0.8.0 * Simplified writing images by moving data and options into the constructor. - * The main_header attribute of the ContiguousCodestream class is now called - codestream. * Deprecated :py:meth:`read` method in favor of array-style slicing. + In order to retain certain functionality, the following parameters + to the :py:meth:`read` method have become top-level properties + + * verbose + * layer + * ignore_pclr_cmap_cdef + + * Two additional properties were introduced. + + * codestream + * shape + + + diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index e43eae6..f9e7c5c 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -27,12 +27,17 @@ from .fixtures import (WARNING_INFRASTRUCTURE_ISSUE, WINDOWS_TMP_FILE_MSG, MetadataBase) +def docTearDown(doctest_obj): + glymur.set_parseoptions(full_codestream=False) + + 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 - tests.addTests(doctest.DocTestSuite('glymur.jp2box')) + tests.addTests(doctest.DocTestSuite('glymur.jp2box', + tearDown=docTearDown)) return tests diff --git a/glymur/version.py b/glymur/version.py index 55e4b88..28d068e 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -18,7 +18,7 @@ from .lib import openjpeg as opj, openjp2 as opj2 # Do not change the format of this next line! Doing so risks breaking # setup.py -version = "0.7.2" +version = "0.8.0rc2" _sv = LooseVersion(version) version_tuple = _sv.version diff --git a/setup.py b/setup.py index 0a62827..b49ed69 100644 --- a/setup.py +++ b/setup.py @@ -21,12 +21,10 @@ kwargs = {'name': 'Glymur', 'test_suite': 'glymur.test'} install_requires = ['numpy>=1.7.0', 'lxml>=3.0.0'] -test_requires = ['six>=1.7.0'] if sys.hexversion < 0x03030000: install_requires.append('contextlib2>=0.4') - test_requires.append('mock>=1.0.1') + install_requires.append('mock>=1.0.1') kwargs['install_requires'] = install_requires -kwargs['test_requires'] = test_requires clssfrs = ["Programming Language :: Python", "Programming Language :: Python :: 2.7", From 3c7205038faed770c226f17cb7d7b8779232b98e Mon Sep 17 00:00:00 2001 From: jevans Date: Sat, 10 Jan 2015 19:58:00 -0500 Subject: [PATCH 326/326] releasing 0.8.0 --- CHANGES.txt | 6 +++--- docs/source/conf.py | 2 +- glymur/version.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 98e6730..042e351 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,6 @@ -Jan 08, 2015 - v0.8.0rc1 Reduced number of steps required for - writing images. Deprecated old read and write methods in favor - of array-style slicing. Added ignore_pclr_cmap_cdef, verbose, +Jan 10, 2015 - v0.8.0 Reduced number of steps required for writing + images. Deprecated old read and write methods in favor of + array-style slicing. Added ignore_pclr_cmap_cdef, verbose, shape, codestream, layer properties. Oct 06, 2014 - v0.7.2 Added ellipsis support in array-style slicing. diff --git a/docs/source/conf.py b/docs/source/conf.py index f90815d..76f96f6 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -77,7 +77,7 @@ copyright = u'2013, John Evans' # The short X.Y version. version = '0.8' # The full version, including alpha/beta/rc tags. -release = '0.8.0rc2' +release = '0.8.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/glymur/version.py b/glymur/version.py index 28d068e..4096ee5 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -18,7 +18,7 @@ from .lib import openjpeg as opj, openjp2 as opj2 # Do not change the format of this next line! Doing so risks breaking # setup.py -version = "0.8.0rc2" +version = "0.8.0" _sv = LooseVersion(version) version_tuple = _sv.version