Restore legacy TIFF API.

To have the old API that always returns tuples, and fractions as pairs,
set the `legacy_api` attribute of the IFD to True.

This should alleviate concerns about backwards compatibility.
This commit is contained in:
Antony Lee 2015-02-28 19:44:38 -08:00
parent 63b492f83f
commit c4c1b6e980
6 changed files with 154 additions and 169 deletions

View File

@ -246,10 +246,8 @@ class ImageFileDirectory(collections.MutableMapping):
* self._tagdata = {} Key: numerical tiff tag number * self._tagdata = {} Key: numerical tiff tag number
Value: undecoded byte string from file Value: undecoded byte string from file
Tags will be found in either self._tags or self._tagdata, but not Tags will be found in the private attributes self._tagdata, and in
both. The union of the two should contain all the tags from the Tiff self._tags once decoded.
image file. External classes shouldn't reference these unless they're
really sure what they're doing.
""" """
def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None): def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None):
@ -274,9 +272,17 @@ class ImageFileDirectory(collections.MutableMapping):
raise SyntaxError("not a TIFF IFD") raise SyntaxError("not a TIFF IFD")
self.reset() self.reset()
self.next, = self._unpack("L", ifh[4:]) self.next, = self._unpack("L", ifh[4:])
self._legacy_api = False
prefix = property(lambda self: self._prefix) prefix = property(lambda self: self._prefix)
offset = property(lambda self: self._offset) offset = property(lambda self: self._offset)
legacy_api = property(lambda self: self._legacy_api)
@legacy_api.setter
def legacy_api(self, value):
if value != self._legacy_api:
self._tags.clear()
self._legacy_api = value
def reset(self): def reset(self):
self._tags = {} self._tags = {}
@ -301,18 +307,18 @@ class ImageFileDirectory(collections.MutableMapping):
for code, value in self.items()) for code, value in self.items())
def __len__(self): def __len__(self):
return len(self._tagdata) + len(self._tags) return len(set(self._tagdata) | set(self._tags))
def __getitem__(self, tag): def __getitem__(self, tag):
try: if tag not in self._tags: # unpack on the fly
return self._tags[tag]
except KeyError: # unpack on the fly
data = self._tagdata[tag] data = self._tagdata[tag]
typ = self.tagtype[tag] typ = self.tagtype[tag]
size, handler = self._load_dispatch[typ] size, handler = self._load_dispatch[typ]
self[tag] = handler(self, data) # check type self[tag] = handler(self, data) # check type
del self._tagdata[tag] val = self._tags[tag]
return self[tag] if self.legacy_api and not isinstance(val, (tuple, bytes)):
val = val,
return val
def __contains__(self, tag): def __contains__(self, tag):
return tag in self._tags or tag in self._tagdata return tag in self._tags or tag in self._tagdata
@ -354,6 +360,8 @@ class ImageFileDirectory(collections.MutableMapping):
for value in values] for value in values]
values = tuple(info.cvt_enum(value) for value in values) values = tuple(info.cvt_enum(value) for value in values)
if info.length == 1: if info.length == 1:
if self.legacy_api and self.tagtype[tag] in [5, 10]:
values = values,
self._tags[tag], = values self._tags[tag], = values
else: else:
self._tags[tag] = values self._tags[tag] = values
@ -363,7 +371,7 @@ class ImageFileDirectory(collections.MutableMapping):
self._tagdata.pop(tag, None) self._tagdata.pop(tag, None)
def __iter__(self): def __iter__(self):
return itertools.chain(list(self._tags), list(self._tagdata)) return iter(set(self._tagdata) | set(self._tags))
def _unpack(self, fmt, data): def _unpack(self, fmt, data):
return struct.unpack(self._endian + fmt, data) return struct.unpack(self._endian + fmt, data)
@ -397,10 +405,19 @@ class ImageFileDirectory(collections.MutableMapping):
b"".join(self._pack(fmt, value) for value in values)) b"".join(self._pack(fmt, value) for value in values))
list(map(_register_basic, list(map(_register_basic,
[(1, "B", "byte"), (3, "H", "short"), (4, "L", "long"), [(3, "H", "short"), (4, "L", "long"),
(6, "b", "signed byte"), (8, "h", "signed short"), (6, "b", "signed byte"), (8, "h", "signed short"),
(9, "l", "signed long"), (11, "f", "float"), (12, "d", "double")])) (9, "l", "signed long"), (11, "f", "float"), (12, "d", "double")]))
@_register_loader(1, 1) # Basic type, except for the legacy API.
def load_byte(self, data):
return (data if self.legacy_api else
tuple(map(ord, data) if bytes is str else data))
@_register_writer(1) # Basic type, except for the legacy API.
def write_byte(self, data):
return data
@_register_loader(2, 1) @_register_loader(2, 1)
def load_string(self, data): def load_string(self, data):
if data.endswith(b"\0"): if data.endswith(b"\0"):
@ -414,7 +431,9 @@ class ImageFileDirectory(collections.MutableMapping):
@_register_loader(5, 8) @_register_loader(5, 8)
def load_rational(self, data): def load_rational(self, data):
vals = self._unpack("{0}L".format(len(data) // 4), data) vals = self._unpack("{0}L".format(len(data) // 4), data)
return tuple(num / denom for num, denom in zip(vals[::2], vals[1::2])) combine = lambda a, b: (a, b) if self.legacy_api else a / b
return tuple(combine(num, denom)
for num, denom in zip(vals[::2], vals[1::2]))
@_register_writer(5) @_register_writer(5)
def write_rational(self, *values): def write_rational(self, *values):
@ -432,7 +451,9 @@ class ImageFileDirectory(collections.MutableMapping):
@_register_loader(10, 8) @_register_loader(10, 8)
def load_signed_rational(self, data): def load_signed_rational(self, data):
vals = self._unpack("{0}l".format(len(data) // 4), data) vals = self._unpack("{0}l".format(len(data) // 4), data)
return tuple(num / denom for num, denom in zip(vals[::2], vals[1::2])) combine = lambda a, b: (a, b) if self.legacy_api else a / b
return tuple(combine(num, denom)
for num, denom in zip(vals[::2], vals[1::2]))
@_register_writer(10) @_register_writer(10)
def write_signed_rational(self, *values): def write_signed_rational(self, *values):
@ -1008,11 +1029,14 @@ def _save(im, fp, filename):
# inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com
if hasattr(im, 'tag'): if hasattr(im, 'tag'):
# preserve tags from original TIFF image file # preserve tags from original TIFF image file
orig_api = im.tag.legacy_api
im.tag.legacy_api = False
for key in (RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION, for key in (RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION,
IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, XMP): IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, XMP):
if key in im.tag: if key in im.tag:
ifd[key] = im.tag[key] ifd[key] = im.tag[key]
ifd.tagtype[key] = im.tag.tagtype.get(key, None) ifd.tagtype[key] = im.tag.tagtype.get(key, None)
im.tag.legacy_api = orig_api
# preserve ICC profile (should also work when saving other formats # preserve ICC profile (should also work when saving other formats
# which support profiles as TIFF) -- 2008-06-06 Florian Hoech # which support profiles as TIFF) -- 2008-06-06 Florian Hoech

View File

@ -43,11 +43,6 @@ class PillowTestCase(unittest.TestCase):
else: else:
print("=== orphaned temp file: %s" % path) print("=== orphaned temp file: %s" % path)
def assert_almost_equal(self, a, b, msg=None, eps=1e-6):
self.assertLess(
abs(a-b), eps,
msg or "got %r, expected %r" % (a, b))
def assert_deep_equal(self, a, b, msg=None): def assert_deep_equal(self, a, b, msg=None):
try: try:
self.assertEqual( self.assertEqual(

View File

@ -80,7 +80,7 @@ class TestImage(PillowTestCase):
ret = GimpGradientFile.sphere_increasing(middle, pos) ret = GimpGradientFile.sphere_increasing(middle, pos)
# Assert # Assert
self.assert_almost_equal(ret, 0.9682458365518543) self.assertAlmostEqual(ret, 0.9682458365518543)
def test_sphere_decreasing(self): def test_sphere_decreasing(self):
# Arrange # Arrange

View File

@ -1,8 +1,9 @@
from __future__ import print_function from __future__ import print_function
from helper import unittest, PillowTestCase, hopper, py3 from helper import unittest, PillowTestCase, hopper, py3
import os
import io import io
import itertools
import os
from PIL import Image, TiffImagePlugin from PIL import Image, TiffImagePlugin
@ -120,41 +121,27 @@ class TestFileLibTiff(LibTiffTestCase):
def test_write_metadata(self): def test_write_metadata(self):
""" Test metadata writing through libtiff """ """ Test metadata writing through libtiff """
img = Image.open('Tests/images/hopper_g4.tif') for legacy_api in [False, True]:
f = self.tempfile('temp.tiff') img = Image.open('Tests/images/hopper_g4.tif')
img.tag.legacy_api = legacy_api
f = self.tempfile('temp.tiff')
img.save(f, tiffinfo=img.tag) img.save(f, tiffinfo=img.tag)
original = img.tag.named()
loaded = Image.open(f) # PhotometricInterpretation is set from SAVE_INFO,
# not the original image.
ignored = ['StripByteCounts', 'RowsPerStrip', 'PageNumber',
'PhotometricInterpretation']
original = img.tag.named() loaded = Image.open(f)
reloaded = loaded.tag.named() loaded.tag.legacy_api = legacy_api
reloaded = loaded.tag.named()
# PhotometricInterpretation is set from SAVE_INFO, for tag, value in itertools.chain(reloaded.items(), original.items()):
# not the original image. if tag not in ignored:
ignored = [ val = original[tag]
'StripByteCounts', 'RowsPerStrip', self.assertEqual(val, value, msg="%s didn't roundtrip" % tag)
'PageNumber', 'PhotometricInterpretation']
for tag, value in reloaded.items():
if tag not in ignored:
if tag.endswith('Resolution'):
self.assert_almost_equal(
original[tag], value,
msg="%s didn't roundtrip" % tag)
else:
self.assertEqual(
original[tag], value, "%s didn't roundtrip" % tag)
for tag, value in original.items():
if tag not in ignored:
if tag.endswith('Resolution'):
self.assert_almost_equal(
original[tag], value,
msg="%s didn't roundtrip" % tag)
else:
self.assertEqual(
value, reloaded[tag], "%s didn't roundtrip" % tag)
def test_g3_compression(self): def test_g3_compression(self):
i = Image.open('Tests/images/hopper_g4_500.tif') i = Image.open('Tests/images/hopper_g4_500.tif')

View File

@ -70,12 +70,18 @@ class TestFileTiff(PillowTestCase):
def test_xyres_tiff(self): def test_xyres_tiff(self):
from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION
filename = "Tests/images/pil168.tif" filename = "Tests/images/pil168.tif"
im = Image.open(filename) for legacy_api in [False, True]:
# Try to read a file where X,Y_RESOLUTION are ints im = Image.open(filename)
im.tag[X_RESOLUTION] = (72,) im.tag.legacy_api = legacy_api
im.tag[Y_RESOLUTION] = (72,) if legacy_api:
im._setup() assert isinstance(im.tag[X_RESOLUTION][0], tuple)
self.assertEqual(im.info['dpi'], (72., 72.)) assert isinstance(im.tag[Y_RESOLUTION][0], tuple)
# Try to read a file where X,Y_RESOLUTION are ints
im.tag[X_RESOLUTION] = (72,)
im.tag[Y_RESOLUTION] = (72,)
im.tag.legacy_api = False # _setup assumes the new API.
im._setup()
self.assertEqual(im.info['dpi'], (72., 72.))
def test_bad_exif(self): def test_bad_exif(self):
try: try:
@ -181,7 +187,6 @@ class TestFileTiff(PillowTestCase):
self.assertEqual(im.convert('RGB').getpixel((0, 0)), (0, 0, 255)) self.assertEqual(im.convert('RGB').getpixel((0, 0)), (0, 0, 255))
def test___str__(self): def test___str__(self):
# Arrange
filename = "Tests/images/pil136.tiff" filename = "Tests/images/pil136.tiff"
im = Image.open(filename) im = Image.open(filename)
@ -194,127 +199,81 @@ class TestFileTiff(PillowTestCase):
def test_as_dict(self): def test_as_dict(self):
# Arrange # Arrange
filename = "Tests/images/pil136.tiff" filename = "Tests/images/pil136.tiff"
im = Image.open(filename) for legacy_api in [False, True]:
im = Image.open(filename)
# Act im.tag.legacy_api = legacy_api
ret = im.ifd.as_dict() self.assertEqual(
im.tag.as_dict(),
# Assert {256: (55,), 257: (43,), 258: (8, 8, 8, 8), 259: (1,),
self.assertIsInstance(ret, dict) 262: (2,), 296: (2,), 273: (8,), 338: (1,), 277: (4,),
279: (9460,), 282: ((720000, 10000),),
self.assertEqual( 283: ((720000, 10000),), 284: (1,)} if legacy_api else
ret, {256: 55, 257: 43, 258: (8, 8, 8, 8), 259: 1, 262: 2, 296: 2, {256: 55, 257: 43, 258: (8, 8, 8, 8), 259: 1,
273: (8,), 338: (1,), 277: 4, 279: (9460,), 262: 2, 296: 2, 273: (8,), 338: (1,), 277: 4,
282: 72.0, 283: 72.0, 284: 1}) 279: (9460,), 282: 72.0, 283: 72.0, 284: 1})
def test__delitem__(self): def test__delitem__(self):
# Arrange
filename = "Tests/images/pil136.tiff" filename = "Tests/images/pil136.tiff"
im = Image.open(filename) im = Image.open(filename)
len_before = len(im.ifd.as_dict()) len_before = len(im.ifd.as_dict())
# Act
del im.ifd[256] del im.ifd[256]
# Assert
len_after = len(im.ifd.as_dict()) len_after = len(im.ifd.as_dict())
self.assertEqual(len_before, len_after + 1) self.assertEqual(len_before, len_after + 1)
def test_load_byte(self): def test_load_byte(self):
# Arrange for legacy_api in [False, True]:
ifd = TiffImagePlugin.ImageFileDirectory() ifd = TiffImagePlugin.ImageFileDirectory()
data = b"abc" ifd.legacy_api = legacy_api
data = b"abc"
# Act ret = ifd.load_byte(data)
ret = ifd.load_byte(data) self.assertEqual(ret, b"abc" if legacy_api else (97, 98, 99))
# Assert
self.assertEqual(ret, (97, 98, 99))
def test_load_string(self): def test_load_string(self):
# Arrange
ifd = TiffImagePlugin.ImageFileDirectory() ifd = TiffImagePlugin.ImageFileDirectory()
data = b"abc\0" data = b"abc\0"
# Act
ret = ifd.load_string(data) ret = ifd.load_string(data)
# Assert
self.assertEqual(ret, "abc") self.assertEqual(ret, "abc")
def test_load_float(self): def test_load_float(self):
# Arrange
ifd = TiffImagePlugin.ImageFileDirectory() ifd = TiffImagePlugin.ImageFileDirectory()
data = b"abcdabcd" data = b"abcdabcd"
# Act
ret = ifd.load_float(data) ret = ifd.load_float(data)
# Assert
self.assertEqual(ret, (1.6777999408082104e+22, 1.6777999408082104e+22)) self.assertEqual(ret, (1.6777999408082104e+22, 1.6777999408082104e+22))
def test_load_double(self): def test_load_double(self):
# Arrange
ifd = TiffImagePlugin.ImageFileDirectory() ifd = TiffImagePlugin.ImageFileDirectory()
data = b"abcdefghabcdefgh" data = b"abcdefghabcdefgh"
# Act
ret = ifd.load_double(data) ret = ifd.load_double(data)
# Assert
self.assertEqual(ret, (8.540883223036124e+194, 8.540883223036124e+194)) self.assertEqual(ret, (8.540883223036124e+194, 8.540883223036124e+194))
def test_seek(self): def test_seek(self):
# Arrange
filename = "Tests/images/pil136.tiff" filename = "Tests/images/pil136.tiff"
im = Image.open(filename) im = Image.open(filename)
# Act
im.seek(-1) im.seek(-1)
# Assert
self.assertEqual(im.tell(), 0) self.assertEqual(im.tell(), 0)
def test_seek_eof(self): def test_seek_eof(self):
# Arrange
filename = "Tests/images/pil136.tiff" filename = "Tests/images/pil136.tiff"
im = Image.open(filename) im = Image.open(filename)
self.assertEqual(im.tell(), 0) self.assertEqual(im.tell(), 0)
# Act / Assert
self.assertRaises(EOFError, lambda: im.seek(1)) self.assertRaises(EOFError, lambda: im.seek(1))
def test__limit_rational_int(self): def test__limit_rational_int(self):
# Arrange
from PIL.TiffImagePlugin import _limit_rational from PIL.TiffImagePlugin import _limit_rational
value = 34 value = 34
# Act
ret = _limit_rational(value, 65536) ret = _limit_rational(value, 65536)
# Assert
self.assertEqual(ret, (34, 1)) self.assertEqual(ret, (34, 1))
def test__limit_rational_float(self): def test__limit_rational_float(self):
# Arrange
from PIL.TiffImagePlugin import _limit_rational from PIL.TiffImagePlugin import _limit_rational
value = 22.3 value = 22.3
# Act
ret = _limit_rational(value, 65536) ret = _limit_rational(value, 65536)
# Assert
self.assertEqual(ret, (223, 10)) self.assertEqual(ret, (223, 10))
def test_4bit(self): def test_4bit(self):
# Arrange
test_file = "Tests/images/hopper_gray_4bpp.tif" test_file = "Tests/images/hopper_gray_4bpp.tif"
original = hopper("L") original = hopper("L")
# Act
im = Image.open(test_file) im = Image.open(test_file)
# Assert
self.assertEqual(im.size, (128, 128)) self.assertEqual(im.size, (128, 128))
self.assertEqual(im.mode, "L") self.assertEqual(im.mode, "L")
self.assert_image_similar(im, original, 7.3) self.assert_image_similar(im, original, 7.3)
@ -324,52 +283,45 @@ class TestFileTiff(PillowTestCase):
# Test TIFF with tag 297 (Page Number) having value of 0 0. # Test TIFF with tag 297 (Page Number) having value of 0 0.
# The first number is the current page number. # The first number is the current page number.
# The second is the total number of pages, zero means not available. # The second is the total number of pages, zero means not available.
# Arrange
outfile = self.tempfile("temp.tif") outfile = self.tempfile("temp.tif")
# Created by printing a page in Chrome to PDF, then: # Created by printing a page in Chrome to PDF, then:
# /usr/bin/gs -q -sDEVICE=tiffg3 -sOutputFile=total-pages-zero.tif # /usr/bin/gs -q -sDEVICE=tiffg3 -sOutputFile=total-pages-zero.tif
# -dNOPAUSE /tmp/test.pdf -c quit # -dNOPAUSE /tmp/test.pdf -c quit
infile = "Tests/images/total-pages-zero.tif" infile = "Tests/images/total-pages-zero.tif"
im = Image.open(infile) im = Image.open(infile)
# Act / Assert
# Should not divide by zero # Should not divide by zero
im.save(outfile) im.save(outfile)
def test_with_underscores(self): def test_with_underscores(self):
# Arrange: use underscores
kwargs = {'resolution_unit': 'inch', kwargs = {'resolution_unit': 'inch',
'x_resolution': 72, 'x_resolution': 72,
'y_resolution': 36} 'y_resolution': 36}
filename = self.tempfile("temp.tif") filename = self.tempfile("temp.tif")
# Act
hopper("RGB").save(filename, **kwargs) hopper("RGB").save(filename, **kwargs)
# Assert
from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION
im = Image.open(filename) for legacy_api in [False, True]:
self.assertEqual(im.tag[X_RESOLUTION], 72) im = Image.open(filename)
self.assertEqual(im.tag[Y_RESOLUTION], 36) im.tag.legacy_api = legacy_api
self.assertEqual(im.tag[X_RESOLUTION][0][0] if legacy_api
else im.tag[X_RESOLUTION], 72)
self.assertEqual(im.tag[Y_RESOLUTION][0][0] if legacy_api
else im.tag[Y_RESOLUTION], 36)
def test_deprecation_warning_with_spaces(self): def test_deprecation_warning_with_spaces(self):
# Arrange: use spaces
kwargs = {'resolution unit': 'inch', kwargs = {'resolution unit': 'inch',
'x resolution': 36, 'x resolution': 36,
'y resolution': 72} 'y resolution': 72}
filename = self.tempfile("temp.tif") filename = self.tempfile("temp.tif")
# Act
self.assert_warning(DeprecationWarning, self.assert_warning(DeprecationWarning,
lambda: hopper("RGB").save(filename, **kwargs)) lambda: hopper("RGB").save(filename, **kwargs))
# Assert
from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION
im = Image.open(filename) for legacy_api in [False, True]:
self.assertEqual(im.tag[X_RESOLUTION], 36) im = Image.open(filename)
self.assertEqual(im.tag[Y_RESOLUTION], 72) im.tag.legacy_api = legacy_api
self.assertEqual(im.tag[X_RESOLUTION][0][0] if legacy_api
else im.tag[X_RESOLUTION], 36)
self.assertEqual(im.tag[Y_RESOLUTION][0][0] if legacy_api
else im.tag[Y_RESOLUTION], 72)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -33,36 +33,63 @@ class TestFileTiffMetadata(PillowTestCase):
img.save(f, tiffinfo=info) img.save(f, tiffinfo=info)
loaded = Image.open(f) for legacy_api in [False, True]:
loaded = Image.open(f)
loaded.tag.legacy_api = legacy_api
self.assertEqual(loaded.tag[50838], len(textdata)) self.assertEqual(loaded.tag[50838],
self.assertEqual(loaded.tag[50839], textdata) (len(textdata),) if legacy_api else len(textdata))
self.assertAlmostEqual(loaded.tag[tag_ids['RollAngle']], floatdata, self.assertEqual(loaded.tag[50839], textdata)
places=5) loaded_float = loaded.tag[tag_ids['RollAngle']]
self.assertAlmostEqual(loaded.tag[tag_ids['YawAngle']], doubledata) if legacy_api:
loaded_float = loaded_float[0]
self.assertAlmostEqual(loaded_float, floatdata, places=5)
loaded_double = loaded.tag[tag_ids['YawAngle']]
if legacy_api:
loaded_double = loaded_double[0]
self.assertAlmostEqual(loaded_double, doubledata)
def test_read_metadata(self): def test_read_metadata(self):
img = Image.open('Tests/images/hopper_g4.tif') for legacy_api in [False, True]:
img = Image.open('Tests/images/hopper_g4.tif')
img.tag.legacy_api = legacy_api
known = {'YResolution': 4294967295 / 113653537, known = {'YResolution': ((4294967295, 113653537),),
'PlanarConfiguration': 1, 'PlanarConfiguration': (1,),
'BitsPerSample': (1,), 'BitsPerSample': (1,),
'ImageLength': 128, 'ImageLength': (128,),
'Compression': 4, 'Compression': (4,),
'FillOrder': 1, 'FillOrder': (1,),
'RowsPerStrip': 128, 'RowsPerStrip': (128,),
'ResolutionUnit': 3, 'ResolutionUnit': (3,),
'PhotometricInterpretation': 0, 'PhotometricInterpretation': (0,),
'PageNumber': (0, 1), 'PageNumber': (0, 1),
'XResolution': 4294967295 / 113653537, 'XResolution': ((4294967295, 113653537),),
'ImageWidth': 128, 'ImageWidth': (128,),
'Orientation': 1, 'Orientation': (1,),
'StripByteCounts': (1968,), 'StripByteCounts': (1968,),
'SamplesPerPixel': 1, 'SamplesPerPixel': (1,),
'StripOffsets': (8,) 'StripOffsets': (8,)
} } if legacy_api else {
'YResolution': 4294967295 / 113653537,
'PlanarConfiguration': 1,
'BitsPerSample': (1,),
'ImageLength': 128,
'Compression': 4,
'FillOrder': 1,
'RowsPerStrip': 128,
'ResolutionUnit': 3,
'PhotometricInterpretation': 0,
'PageNumber': (0, 1),
'XResolution': 4294967295 / 113653537,
'ImageWidth': 128,
'Orientation': 1,
'StripByteCounts': (1968,),
'SamplesPerPixel': 1,
'StripOffsets': (8,)
}
self.assertEqual(known, img.tag.named()) self.assertEqual(known, img.tag.named())
def test_write_metadata(self): def test_write_metadata(self):
""" Test metadata writing through the python code """ """ Test metadata writing through the python code """