Skip to content

Commit c6513e5

Browse files
petercorkeclaude
andcommitted
fix: Image.metadata() missing camera-specific Exif sub-IFD tags
Image.getexif() (the modern public Pillow API) only returns the top-level IFD0 tags (Make, Model, Orientation, ...). Camera-specific tags -- FocalLength, ExposureTime, FNumber, ISOSpeedRatings, LensModel, etc. -- live in a separate "Exif" sub-IFD that getexif() does not flatten in. metadata() never fetched that sub-IFD, so img.metadata("FocalLength") always raised KeyError even though the tag is genuinely present in the file (confirmed via raw PIL: it's under Exif.get_ifd(0x8769), the fixed EXIF-spec tag id for the Exif IFD pointer). This is a Pillow API evolution, not a data problem: the old private Image._getexif() used to flatten everything into one dict; the public getexif() introduced later deliberately does not, requiring an explicit get_ifd() call for the sub-IFD. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent ee58434 commit c6513e5

2 files changed

Lines changed: 23 additions & 6 deletions

File tree

‎src/machinevisiontoolbox/ImageIO.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,14 @@ def metadata(self, key: str | None = None) -> Any:
267267
# map tag number to tag name
268268
exif[TAGS[tag]] = value
269269

270+
# Camera-specific tags (FocalLength, ExposureTime, FNumber,
271+
# ISOSpeedRatings, LensModel, ...) live in a separate "Exif" sub-IFD
272+
# that Image.getexif() does not flatten into the top-level dict --
273+
# 0x8769 is the fixed EXIF-spec tag id for the pointer to it.
274+
for tag, value in meta.get_ifd(0x8769).items():
275+
if tag in TAGS:
276+
exif[TAGS[tag]] = value
277+
270278
if key is None:
271279
return exif
272280
else:

‎tests/test_image_io.py‎

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -190,14 +190,23 @@ def test_disp(self):
190190
# im.disp(block=False)
191191
pass
192192

193-
# new test
194193
def test_metadata(self):
195194
"""Test reading image metadata"""
196-
# TODO: Test with image file that has metadata
197-
# im = Image.Read('test_image.jpg')
198-
# metadata = im.metadata()
199-
# self.assertIsNotNone(metadata)
200-
pass
195+
im = Image.Read("walls-l.png")
196+
metadata = im.metadata()
197+
self.assertIsNotNone(metadata)
198+
199+
# top-level IFD0 tags
200+
self.assertEqual(metadata["Make"], "Apple")
201+
self.assertEqual(metadata["Model"], "iPhone 5s")
202+
self.assertEqual(im.metadata("Make"), "Apple")
203+
204+
# camera-specific tags live in a separate "Exif" sub-IFD that
205+
# Image.getexif() doesn't flatten into the top-level dict --
206+
# regression test for that sub-IFD merge
207+
self.assertIn("FocalLength", metadata)
208+
self.assertAlmostEqual(im.metadata("FocalLength"), 4.15)
209+
self.assertAlmostEqual(metadata["FNumber"], 2.2)
201210

202211
# new test
203212
def test_showpixels(self):

0 commit comments

Comments
 (0)