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:
commit
79d2969acc
20 changed files with 1358 additions and 993 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
506
glymur/_uuid_io/Exif.py
Normal 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)
|
||||
|
||||
|
||||
|
||||
4
glymur/_uuid_io/__init__.py
Normal file
4
glymur/_uuid_io/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
"""
|
||||
Sub package for handling various types of UUIDs.
|
||||
"""
|
||||
from .Exif import tiff_header, xml
|
||||
|
|
@ -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 '
|
||||
|
|
|
|||
|
|
@ -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.
662
glymur/jp2box.py
662
glymur/jp2box.py
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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'])
|
||||
|
||||
|
|
|
|||
|
|
@ -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'}"""
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
193
glymur/test/test_jp2box_uuid.py
Normal file
193
glymur/test/test_jp2box_uuid.py
Normal 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()
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -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')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue