Merge branch 'issue104' into devel

Conflicts:
	CHANGES.txt
	docs/source/how_do_i.rst
	glymur/codestream.py
	glymur/jp2box.py
	glymur/test/test_jp2box.py
	glymur/test/test_jp2k.py
This commit is contained in:
jevans 2014-02-08 20:29:52 -05:00
commit 79d2969acc
20 changed files with 1358 additions and 993 deletions

View file

@ -1,5 +1,5 @@
Feb 08, 2014 - Removed support for Python 2.6. Added write support for
JP2 DataEntryURL, Palette and Component Mapping boxes, JPX
Feb 08, 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

View file

@ -71,7 +71,7 @@ The **append** method can add an XML box as shown below::
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` : ::
<?xml version="1.0"?>
<favorite_things>
@ -152,10 +153,10 @@ 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?
========================================
@ -219,32 +220,142 @@ 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:
<ns0:xmpmeta xmlns:ns0="adobe:ns:meta/" xmlns:ns2="http://ns.adobe.com/xap/1.0/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" ns0:xmptk="XMP Core 4.4.0-Exiv2">
>>> print(j.box[3]) # formatting added to the XML below
<ns0:xmpmeta xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:ns0="adobe:ns:meta/"
xmlns:ns2="http://ns.adobe.com/xap/1.0/"
xmlns:ns3="http://ns.adobe.com/tiff/1.0/"
xmlns:ns4="http://ns.adobe.com/exif/1.0/"
xmlns:ns5="http://ns.adobe.com/photoshop/1.0/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
ns0:xmptk="Exempi + XMP Core 5.1.2">
<rdf:RDF>
<rdf:Description ns2:CreatorTool="glymur" rdf:about="" />
</rdf:RDF>
</ns0:xmpmeta>
<rdf:Description rdf:about="">
<ns2:CreatorTool>Google</ns2:CreatorTool>
<ns2:CreateDate>2013-02-09T14:47:53</ns2:CreateDate>
</rdf:Description>
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::
.
.
.
</ns0:xmpmeta>
>>> 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)
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
following
>>> 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
<Element '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description' at 0xb4baa93c>
>>> elt.attrib['{0}CreatorTool'.format(ns1)]
'glymur'
<Element '{http://ns.adobe.com/xap/1.0/#}CreatorTool' at 0xb50684a4>
>>> elt.text
'Google'
But that would be painful. A better solution is to install the Python XMP
Toolkit (make sure it is version 2.0)::
>>> 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'
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, 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.
>>> from libxmp import XMPFiles
>>> xf = XMPFiles()
>>> xf.open_file('PIA17145.tif')
>>> xmp = xf.get_xmp()
>>> print(xmp)
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Exempi + XMP Core 5.1.2">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:tiff="http://ns.adobe.com/tiff/1.0/">
<tiff:ImageWidth>1016</tiff:ImageWidth>
<tiff:ImageLength>1016</tiff:ImageLength>
<tiff:BitsPerSample>
<rdf:Seq>
<rdf:li>8</rdf:li>
</rdf:Seq>
</tiff:BitsPerSample>
<tiff:Compression>1</tiff:Compression>
<tiff:PhotometricInterpretation>1</tiff:PhotometricInterpretation>
<tiff:SamplesPerPixel>1</tiff:SamplesPerPixel>
<tiff:PlanarConfiguration>1</tiff:PlanarConfiguration>
<tiff:ResolutionUnit>2</tiff:ResolutionUnit>
</rdf:Description>
<rdf:Description rdf:about=""
xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:description>
<rdf:Alt>
<rdf:li xml:lang="x-default">converted PNM file</rdf:li>
</rdf:Alt>
</dc:description>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>
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. In order to do this,
though, you have to know the UUID that signifies XMP data.::
>>> import uuid
>>> 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:
<ns0:xmpmeta xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:ns0="adobe:ns:meta/" xmlns:ns2="http://ns.adobe.com/tiff/1.0/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" ns0:xmptk="Exempi + XMP Core 5.1.2">
<rdf:RDF>
<rdf:Description rdf:about="">
<ns2:ImageWidth>1016</ns2:ImageWidth>
<ns2:ImageLength>1016</ns2:ImageLength>
<ns2:BitsPerSample>
<rdf:Seq>
<rdf:li>8</rdf:li>
</rdf:Seq>
</ns2:BitsPerSample>
<ns2:Compression>1</ns2:Compression>
<ns2:PhotometricInterpretation>1</ns2:PhotometricInterpretation>
<ns2:SamplesPerPixel>1</ns2:SamplesPerPixel>
<ns2:PlanarConfiguration>1</ns2:PlanarConfiguration>
<ns2:ResolutionUnit>2</ns2:ResolutionUnit>
</rdf:Description>
<rdf:Description rdf:about="">
<dc:description>
<rdf:Alt>
<rdf:li xml:lang="x-default">converted PNM file</rdf:li>
</rdf:Alt>
</dc:description>
</rdf:Description>
</rdf:RDF>
</ns0:xmpmeta>

View file

@ -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 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.
@ -21,8 +19,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

506
glymur/_uuid_io/Exif.py Normal file
View file

@ -0,0 +1,506 @@
# -*- coding: utf-8 -*-
"""
Handlers for Exif UUIDs. Be nice if we would find a standard for this.
"""
import pprint
import re
import struct
import sys
import warnings
import xml.etree.cElementTree 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.
"""
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)
data = struct.unpack('<BB', read_buffer[6:8])
if data[0] == 73 and data[1] == 73:
# little endian
endian = '<'
elif data[0] == 77 and data[1] == 77:
# big endian
endian = '>'
else:
msg = "Bad byte order indication: {0}".format(read_buffer[6:8])
raise RuntimeError(msg)
_, offset = struct.unpack(endian + 'HI', read_buffer[8:14])
# This is the 'Exif Image' portion.
exif = _ExifImageIfd(endian, read_buffer[6:], offset)
return exif.processed_ifd
class _Ifd(object):
"""
Attributes
----------
read_buffer : bytes
Raw byte stream consisting of the UUID data.
datatype2fmt : dictionary
Class attribute, maps the TIFF enumerated datatype to the python
datatype and data width.
endian : str
Either '<' for big-endian, or '>' 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)

View file

@ -0,0 +1,4 @@
"""
Sub package for handling various types of UUIDs.
"""
from .Exif import tiff_header, xml

View file

@ -663,7 +663,6 @@ class Codestream(object):
bitdepth = tuple(((x & 0x7f) + 1) 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]
@ -1538,7 +1537,7 @@ class SIZsegment(Segment):
signed=self.signed,
xyrsiz=(self.xrsiz, self.yrsiz))
return msg
def __str__(self):
msg = Segment.__str__(self)
msg += '\n '

View file

@ -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

Binary file not shown.

View file

@ -34,6 +34,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 . import _uuid_io
_METHOD_DISPLAY = {
ENUMERATED_COLORSPACE: 'enumerated colorspace',
@ -576,11 +579,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)
@ -638,12 +641,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})"
@ -1365,11 +1368,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)
@ -1432,11 +1435,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)
@ -1496,7 +1499,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):
@ -1963,11 +1966,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})"
@ -2149,7 +2152,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')
@ -2462,11 +2465,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)
@ -2613,9 +2616,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
----------
@ -2630,7 +2636,7 @@ class UUIDBox(Jp2kBox):
the_uuid : uuid.UUID
Identifies the type of UUID box.
raw_data : byte array
This is the "payload" of data for the specified UUID.
Sequence of uninterpreted bytes as read from the UUID box.
length : int
length of the box in bytes.
offset : int
@ -2639,64 +2645,53 @@ 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
# 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)
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
else:
self.data = raw_data
self.length = length
self.offset = offset
self.data = None
try:
self._parse_raw_data()
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'):
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:
self.data = self.raw_data
def __repr__(self):
msg = "glymur.jp2box.UUIDBox(the_uuid={0}, "
msg += "raw_data=<byte array {1} elements>)"
return msg.format(repr(self.uuid), len(self.raw_data))
def __str__(self):
msg = '{0}\n'
msg += ' UUID: {1}{2}\n'
msg += ' UUID Data: {3}'
msg = '{0}\n UUID: {1}'.format(Jp2kBox.__str__(self), self.uuid)
if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'):
uuid_type = ' (XMP)'
uuid_data = _pretty_print_xml(self.data)
line = ' (XMP)\n UUID Data: {0}'
msg += line.format(_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)
msg += ' (EXIF)\n UUID Data: {0}'.format(str(self.data))
else:
uuid_type = ''
uuid_data = '{0} bytes'.format(len(self.data))
msg = msg.format(Jp2kBox.__str__(self),
self.uuid,
uuid_type,
uuid_data)
line = ' (unknown)\n UUID Data: {0} bytes'
msg += line.format(len(self.raw_data))
return msg
def write(self, fptr):
"""Write a UUID box to file.
"""
write_buffer = struct.pack('>I4s', self.length, b'uuid')
fptr.write(write_buffer)
fptr.write(self.uuid.bytes)
fptr.write(self.raw_data)
@staticmethod
def parse(fptr, offset, length):
"""Parse UUID box.
@ -2724,511 +2719,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('<BBHI', read_buffer[6:14])
if data[0] == 73 and data[1] == 73:
# little endian
self.endian = '<'
else:
# big endian
self.endian = '>'
offset = data[3]
# This is the 'Exif Image' portion.
exif = _ExifImageIfd(self.endian, read_buffer[6:], offset)
self.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 'InteroperabilityTag' in self.exif_photo.keys():
offset = self.exif_photo['InteroperabilityTag']
interop = _ExifInteroperabilityIfd(self.endian,
read_buffer[6:],
offset)
self.iop = interop.processed_ifd
if 'GPSTag' in self.exif_image.keys():
offset = self.exif_image['GPSTag']
gps = _ExifGPSInfoIfd(self.endian, read_buffer[6:], offset)
self.exif_gpsinfo = gps.processed_ifd
class _Ifd(object):
"""
Attributes
----------
read_buffer : bytes
Raw byte stream consisting of the UUID data.
datatype2fmt : dictionary
Class attribute, maps the TIFF enumerated datatype to the python
datatype and data width.
endian : str
Either '<' for big-endian, or '>' 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,
@ -3258,45 +2748,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

View file

@ -21,6 +21,7 @@ import ctypes
import math
import os
import struct
from uuid import UUID
import warnings
import numpy as np
@ -488,17 +489,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)
@ -509,11 +510,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()
@ -523,14 +524,18 @@ 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.uuid == UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'))):
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.
@ -1019,7 +1024,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)

View file

@ -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]

View file

@ -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
@ -205,7 +205,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."""
@ -214,13 +214,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."""
@ -234,7 +234,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."""
@ -325,8 +325,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)
@ -348,7 +348,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'])

View file

@ -11,6 +11,19 @@ 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(r'''[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
@ -167,3 +180,173 @@ 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:
<ns0:xmpmeta xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:ns0="adobe:ns:meta/" xmlns:ns2="http://ns.adobe.com/xap/1.0/" xmlns:ns3="http://ns.adobe.com/tiff/1.0/" xmlns:ns4="http://ns.adobe.com/exif/1.0/" xmlns:ns5="http://ns.adobe.com/photoshop/1.0/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" ns0:xmptk="Exempi + XMP Core 5.1.2">
<rdf:RDF>
<rdf:Description rdf:about="">
<ns2:CreatorTool>Google</ns2:CreatorTool>
<ns2:CreateDate>2013-02-09T14:47:53</ns2:CreateDate>
</rdf:Description>
<rdf:Description rdf:about="">
<ns3:YCbCrPositioning>1</ns3:YCbCrPositioning>
<ns3:XResolution>72/1</ns3:XResolution>
<ns3:YResolution>72/1</ns3:YResolution>
<ns3:ResolutionUnit>2</ns3:ResolutionUnit>
<ns3:Make>HTC</ns3:Make>
<ns3:Model>HTC Glacier</ns3:Model>
<ns3:ImageWidth>2592</ns3:ImageWidth>
<ns3:ImageLength>1456</ns3:ImageLength>
<ns3:BitsPerSample>
<rdf:Seq>
<rdf:li>8</rdf:li>
<rdf:li>8</rdf:li>
<rdf:li>8</rdf:li>
</rdf:Seq>
</ns3:BitsPerSample>
<ns3:PhotometricInterpretation>2</ns3:PhotometricInterpretation>
<ns3:SamplesPerPixel>3</ns3:SamplesPerPixel>
<ns3:WhitePoint>
<rdf:Seq>
<rdf:li>1343036288/4294967295</rdf:li>
<rdf:li>1413044224/4294967295</rdf:li>
</rdf:Seq>
</ns3:WhitePoint>
<ns3:PrimaryChromaticities>
<rdf:Seq>
<rdf:li>2748779008/4294967295</rdf:li>
<rdf:li>1417339264/4294967295</rdf:li>
<rdf:li>1288490240/4294967295</rdf:li>
<rdf:li>2576980480/4294967295</rdf:li>
<rdf:li>644245120/4294967295</rdf:li>
<rdf:li>257698032/4294967295</rdf:li>
</rdf:Seq>
</ns3:PrimaryChromaticities>
</rdf:Description>
<rdf:Description rdf:about="">
<ns4:ColorSpace>1</ns4:ColorSpace>
<ns4:PixelXDimension>2528</ns4:PixelXDimension>
<ns4:PixelYDimension>1424</ns4:PixelYDimension>
<ns4:FocalLength>353/100</ns4:FocalLength>
<ns4:GPSAltitudeRef>0</ns4:GPSAltitudeRef>
<ns4:GPSAltitude>0/1</ns4:GPSAltitude>
<ns4:GPSMapDatum>WGS-84</ns4:GPSMapDatum>
<ns4:DateTimeOriginal>2013-02-09T14:47:53</ns4:DateTimeOriginal>
<ns4:ISOSpeedRatings>
<rdf:Seq>
<rdf:li>76</rdf:li>
</rdf:Seq>
</ns4:ISOSpeedRatings>
<ns4:ExifVersion>0220</ns4:ExifVersion>
<ns4:FlashpixVersion>0100</ns4:FlashpixVersion>
<ns4:ComponentsConfiguration>
<rdf:Seq>
<rdf:li>1</rdf:li>
<rdf:li>2</rdf:li>
<rdf:li>3</rdf:li>
<rdf:li>0</rdf:li>
</rdf:Seq>
</ns4:ComponentsConfiguration>
<ns4:GPSLatitude>42,20.56N</ns4:GPSLatitude>
<ns4:GPSLongitude>71,5.29W</ns4:GPSLongitude>
<ns4:GPSTimeStamp>2013-02-09T19:47:53Z</ns4:GPSTimeStamp>
<ns4:GPSProcessingMethod>NETWORK</ns4:GPSProcessingMethod>
</rdf:Description>
<rdf:Description rdf:about="">
<ns5:DateCreated>2013-02-09T14:47:53</ns5:DateCreated>
</rdf:Description>
<rdf:Description rdf:about="">
<dc:Creator>
<rdf:Seq>
<rdf:li>Glymur</rdf:li>
<rdf:li>Python XMP Toolkit</rdf:li>
</rdf:Seq>
</dc:Creator>
</rdf:Description>
</rdf:RDF>
</ns0:xmpmeta>"""
SimpleRDF = """<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
<rdf:Description rdf:about='Test:XMPCoreCoverage/kSimpleRDF'
xmlns:ns1='ns:test1/' xmlns:ns2='ns:test2/'>
<ns1:SimpleProp>Simple value</ns1:SimpleProp>
<ns1:Distros>
<rdf:Bag>
<rdf:li>Suse</rdf:li>
<rdf:li>Fedora</rdf:li>
</rdf:Bag>
</ns1:Distros>
</rdf:Description>
</rdf:RDF>"""
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'}"""

View file

@ -441,7 +441,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'<data>0</data>')
@ -451,14 +451,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('<?xml version="1.0"?><data>0</data>')
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.
@ -490,19 +490,19 @@ 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'<data>0</data>')
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)
@ -946,9 +946,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)
@ -970,7 +970,7 @@ class TestRepr(unittest.TestCase):
self.assertEqual(box.vendor_feature, newbox.vendor_feature)
self.assertEqual(box.vendor_mask, newbox.vendor_mask)
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'
@ -978,9 +978,27 @@ 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=<byte\sarray\s10\selements>\)"
regexp = r"""glymur.jp2box.UUIDBox\("""
regexp += """the_uuid=UUID\('00000000-0000-0000-0000-000000000000'\),\s"""
regexp += """raw_data=<byte\sarray\s10\selements>\)"""
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_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=<byte\sarray\s3122\selements>\)"""
if sys.hexversion < 0x03000000:
self.assertRegexpMatches(repr(box), regexp)

View file

@ -43,7 +43,7 @@ class TestJPXWrap(unittest.TestCase):
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]]
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 '
@ -70,7 +70,7 @@ class TestJPXWrap(unittest.TestCase):
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]]
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
flag = 0
version = (0, 0, 0)
@ -87,7 +87,7 @@ class TestJPXWrap(unittest.TestCase):
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]]
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
flag = 0
version = (0, 0, 0)
@ -105,7 +105,7 @@ class TestJPXWrap(unittest.TestCase):
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 = [jp2.box[idx] for idx in [0, 1, 2, 4]]
boxes[1].compatibility_list.append('jp2 ')
numbers = [0, 1]
nlst = glymur.jp2box.NumberListBox(numbers)
@ -121,7 +121,7 @@ class TestJPXWrap(unittest.TestCase):
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 = [jp2.box[idx] for idx in [0, 1, 2, 4]]
boxes[1].brand = 'jpx '
numbers = [0, 1]
nlst = glymur.jp2box.NumberListBox(numbers)

View file

@ -0,0 +1,193 @@
# -*- 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 shutil
import struct
import sys
import tempfile
import uuid
import warnings
import xml.etree
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
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
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,
xml.etree.ElementTree.ElementTree))
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('<BBHI', 73, 73, 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. Corrupt it to 171.
tfile.write(struct.pack('<HHI4s', 171, 2, 3, b'HTC\x00'))
tfile.flush()
with self.assertWarns(UserWarning):
j = glymur.Jp2k(tfile.name)
@unittest.skipIf(sys.hexversion < 0x03000000, "Requires assertWarns, 3.2+")
def test_bad_tag_datatype(self):
"""Only certain datatypes are allowable"""
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', 73, 73, 42, 8)
tfile.write(xbuffer)
# We will write just a single tag.
tfile.write(struct.pack('<H', 1))
# 2000 is not an allowable TIFF datatype.
tfile.write(struct.pack('<HHI4s', 271, 2000, 3, b'HTC\x00'))
tfile.flush()
with self.assertWarns(UserWarning):
j = glymur.Jp2k(tfile.name)
self.assertEqual(j.box[-1].box_id, 'uuid')
@unittest.skipIf(sys.hexversion < 0x03000000, "Requires assertWarns, 3.2+")
def test_bad_tiff_header_byte_order_indication(self):
"""Only b'II' and b'MM' are allowed."""
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', 74, 73, 42, 8)
tfile.write(xbuffer)
# We will write just a single tag.
tfile.write(struct.pack('<H', 1))
# 271 is the Make.
tfile.write(struct.pack('<HHI4s', 271, 2, 3, b'HTC\x00'))
tfile.flush()
with self.assertWarns(UserWarning):
j = glymur.Jp2k(tfile.name)
self.assertEqual(j.box[-1].box_id, 'uuid')
def test_big_endian(self):
"""Verify read of 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")
if __name__ == "__main__":
unittest.main()

View file

@ -29,7 +29,11 @@ import pkg_resources
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 libxmp import XMPMeta
from .fixtures import OPJ_DATA_ROOT, opj_data_file
@ -103,7 +107,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)
@ -122,15 +126,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)
@ -172,7 +172,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
@ -193,9 +193,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,16 +357,24 @@ class TestJp2k(unittest.TestCase):
self.assertEqual(ET.tostring(jp2k.box[3].xml.getroot()),
b'<test>this is a test</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)
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)
name = '{0}RDF/{0}Description/{1}CreatorTool'.format(ns0, ns1)
elt = xmp.find(name)
attr_value = elt.attrib['{0}CreatorTool'.format(ns1)]
self.assertEqual(attr_value, 'glymur')
self.assertEqual(elt.text, 'Google')
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')
def test_jpx_mult_codestreams_jp2_brand(self):
"""Read JPX codestream when jp2-compatible."""
@ -381,38 +389,6 @@ class TestJp2k(unittest.TestCase):
else:
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."""
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('<H', int(171))
fptr.write(write_buffer)
# 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)
exif = j.box[3].data
# Were the tag == 271, 'Make' would be in the keys instead.
self.assertTrue(171 in exif['Image'].keys())
self.assertFalse('Make' in exif['Image'].keys())
@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version),
"Requires at least version 1.5")
@ -749,19 +725,22 @@ class TestJp2k_2_1(unittest.TestCase):
with open(self.jp2file, 'rb') as fptr:
data = fptr.read()
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
# Codestream starts at byte 3127. SIZ marker at 3137.
# COD marker at 3186. Subsampling at 3180.
tfile.write(data[0:3179])
# Codestream starts at byte 3323. SIZ marker at 3233.
# COD marker at 3282. Subsampling at 3276.
offset = 3223
tfile.write(data[0:offset+52])
# Make the DY bytes of the SIZ segment zero. That means that
# a subsampling factor is zero, which is illegal.
tfile.write(b'\x00')
tfile.write(data[3180:3182])
tfile.write(data[offset+53:offset+55])
tfile.write(b'\x00')
tfile.write(data[3184:3186])
tfile.write(data[offset+57:offset+59])
#tfile.write(data[3184:3186])
tfile.write(b'\x00')
tfile.write(data[3186:])
tfile.write(data[offset+59:])
#tfile.write(data[3186:])
tfile.flush()
with warnings.catch_warnings():
warnings.simplefilter("ignore")

View file

@ -31,7 +31,8 @@ else:
import glymur
from glymur import Jp2k
from .fixtures import OPJ_DATA_ROOT, opj_data_file
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.")
@ -226,7 +227,7 @@ class TestPrinting(unittest.TestCase):
print(codestream.segment[6])
actual = fake_out.getvalue().strip()
lines = ['COC marker segment @ (3260, 9)',
lines = ['COC marker segment @ (3356, 9)',
' Associated component: 1',
' Coding style for this component: '
+ 'Entropy coder, PARTITION = 0',
@ -254,7 +255,7 @@ class TestPrinting(unittest.TestCase):
print(codestream.segment[2])
actual = fake_out.getvalue().strip()
lines = ['COD marker segment @ (3186, 12)',
lines = ['COD marker segment @ (3282, 12)',
' Coding style:',
' Entropy coder, without partitions',
' SOP marker segments: False',
@ -280,75 +281,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):
@ -420,7 +352,7 @@ class TestPrinting(unittest.TestCase):
print(codestream.segment[-1])
actual = fake_out.getvalue().strip()
lines = ['EOC marker segment @ (1135421, 0)']
lines = ['EOC marker segment @ (1135517, 0)']
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@ -517,7 +449,7 @@ class TestPrinting(unittest.TestCase):
print(codestream.segment[7])
actual = fake_out.getvalue().strip()
lines = ['QCC marker segment @ (3271, 8)',
lines = ['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)]']
@ -533,7 +465,7 @@ class TestPrinting(unittest.TestCase):
print(codestream.segment[3])
actual = fake_out.getvalue().strip()
lines = ['QCD marker segment @ (3200, 7)',
lines = ['QCD marker segment @ (3296, 7)',
' Quantization style: no quantization, 2 guard bits',
' Step size: [(0, 8), (0, 9), (0, 9), (0, 10)]']
@ -548,7 +480,7 @@ class TestPrinting(unittest.TestCase):
print(codestream.segment[1])
actual = fake_out.getvalue().strip()
lines = ['SIZ marker segment @ (3137, 47)',
lines = ['SIZ marker segment @ (3233, 47)',
' Profile: 2',
' Reference Grid Height, Width: (1456 x 2592)',
' Vertical, Horizontal Reference Grid Offset: (0 x 0)',
@ -570,7 +502,7 @@ class TestPrinting(unittest.TestCase):
print(codestream.segment[0])
actual = fake_out.getvalue().strip()
lines = ['SOC marker segment @ (3135, 0)']
lines = ['SOC marker segment @ (3231, 0)']
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@ -582,7 +514,7 @@ class TestPrinting(unittest.TestCase):
print(codestream.segment[10])
actual = fake_out.getvalue().strip()
lines = ['SOD marker segment @ (3302, 0)']
lines = ['SOD marker segment @ (3398, 0)']
expected = '\n'.join(lines)
self.assertEqual(actual, expected)
@ -594,7 +526,7 @@ class TestPrinting(unittest.TestCase):
print(codestream.segment[5])
actual = fake_out.getvalue().strip()
lines = ['SOT marker segment @ (3248, 10)',
lines = ['SOT marker segment @ (3344, 10)',
' Tile part index: 0',
' Tile part length: 1132173',
' Tile part instance: 0',
@ -626,22 +558,10 @@ class TestPrinting(unittest.TestCase):
"""Verify the printing of a UUID/XMP box."""
j = glymur.Jp2k(self.jp2file)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.box[4])
print(j.box[3])
actual = fake_out.getvalue().strip()
lst = ['UUID Box (uuid) @ (715, 2412)',
' UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP)',
' UUID Data: ',
' <ns0:xmpmeta xmlns:ns0="adobe:ns:meta/" '
+ 'xmlns:ns2="http://ns.adobe.com/xap/1.0/" '
+ 'xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" '
+ 'ns0:xmptk="XMP Core 4.4.0-Exiv2">',
' <rdf:RDF>',
' <rdf:Description ns2:CreatorTool="glymur" '
+ 'rdf:about="" />',
' </rdf:RDF>',
' </ns0:xmpmeta>']
expected = '\n'.join(lst)
expected = nemo_xmp_box
self.assertEqual(actual, expected)
def test_codestream(self):
@ -651,8 +571,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)',
@ -662,7 +582,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',
@ -684,11 +604,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)
@ -967,12 +887,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')
@ -984,34 +902,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,
@ -1028,7 +925,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)
@ -1038,60 +935,37 @@ 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)",
" 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}"]
# 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('<BBHI', 73, 73, 42, 8)
tfile.write(xbuffer)
# We will write just three tags.
tfile.write(struct.pack('<H', 3))
# The "Make" tag is tag no. 271.
tfile.write(struct.pack('<HHII', 256, 4, 1, 256))
tfile.write(struct.pack('<HHII', 257, 4, 1, 512))
tfile.write(struct.pack('<HHI4s', 271, 2, 3, b'HTC\x00'))
tfile.flush()
j = glymur.Jp2k(tfile.name)
with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.box[5])
actual = fake_out.getvalue().strip()
lines = ["UUID Box (uuid) @ (1135519, 76)",
" UUID: 4a706754-6966-6645-7869-662d3e4a5032 (EXIF)",
" UUID Data: OrderedDict([('ImageWidth', 256), ('ImageLength', 512), ('Make', 'HTC')])"]
expected = '\n'.join(lines)

View file

@ -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

View file

@ -38,7 +38,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<version>\d*.\d*.\d*.*)"\n', contents)
kwargs['version'] = match.group('version')