Repository: ocr-d/core
Issue: OcrdExif: PNG has no tag for dpi unit!

When I open a PNG image file which includes DPI meta-data, (which is supported since 2014 by Pillow but still undocumented), core currently gives me:
```
  File "ocrd_modelfactory/__init__.py", line 77, in page_from_file
    return page_from_image(input_file)
  File "ocrd_modelfactory/__init__.py", line 49, in page_from_image
    exif = exif_from_filename(input_file.local_filename)
  File "ocrd_modelfactory/__init__.py", line 33, in exif_from_filename
    ocrd_exif = OcrdExif(pil_img)
  File "ocrd_models/ocrd_exif.py", line 42, in __init__
    self.resolutionUnit = 'cm' if img.tag.get(296) == 3 else 'inches'
AttributeError: 'PngImageFile' object has no attribute 'tag'
```

And that's exactly right: That tag is only valid for JPEG. Pillow's PNG plugin converts the fixed implicit _meter_ unit to a fixed implicit _inch_ [here](https://github.com/python-pillow/Pillow/blob/ef4a0b2f4c9346db37140f102e80068abc280167/src/PIL/PngImagePlugin.py#L430).



Changed files:
tests/model/test_exif.py, ocrd_models/ocrd_models/ocrd_exif.py

diff --git a/ocrd_models/ocrd_models/ocrd_exif.py b/ocrd_models/ocrd_models/ocrd_exif.py
index 048bfbb2..53838de4 100644
--- a/ocrd_models/ocrd_models/ocrd_exif.py
+++ b/ocrd_models/ocrd_models/ocrd_exif.py
@@ -18,7 +18,7 @@ class OcrdExif():
       'F' for floating-point grayscale
       (see PIL concept `mode`)
     - `resolution` / `xResolution` / `yResolution`: pixel density
-    - `resolutionUnits`: unit of measurement (either `inches` or `cm`)
+    - `resolutionUnit`: unit of measurement (either `inches` or `cm`)
 
     """
 
@@ -39,7 +39,10 @@ class OcrdExif():
         if img.format in ('TIFF', 'PNG') and 'dpi' in img.info:
             self.xResolution = int(img.info['dpi'][0])
             self.yResolution = int(img.info['dpi'][1])
-            self.resolutionUnit = 'cm' if img.tag.get(296) == 3 else 'inches'
+            if img.format == 'TIFF':
+                self.resolutionUnit = 'cm' if img.tag.get(296) == 3 else 'inches'
+            else:
+                self.resolutionUnit = 'inches'
         elif img.format == 'JPEG' and 'jfif_density' in img.info:
             self.xResolution = img.info['jfif_density'][0]
             self.yResolution = img.info['jfif_density'][1]
@@ -47,6 +50,7 @@ class OcrdExif():
         elif img.format == 'PNG' and 'aspect' in img.info:
             self.xResolution = img.info['aspect'][0]
             self.yResolution = img.info['aspect'][1]
+            self.resolutionUnit = 'inches'
         else:
             #  if img.format == 'JPEG2000':
             #      import sys
diff --git a/tests/model/test_exif.py b/tests/model/test_exif.py
index 4fdc441a..81829f77 100644
--- a/tests/model/test_exif.py
+++ b/tests/model/test_exif.py
@@ -7,7 +7,8 @@ from ocrd_models import OcrdExif
 class TestOcrdExif(TestCase):
 
     def test_str(self):
-        exif = OcrdExif(Image.open(assets.path_to('SBB0000F29300010000/data/OCR-D-IMG/FILE_0001_IMAGE.tif')))
+        with Image.open(assets.path_to('SBB0000F29300010000/data/OCR-D-IMG/FILE_0001_IMAGE.tif')) as img:
+            exif = OcrdExif(img)
         print(str(exif.to_xml()))
         # XXX not platform-independent/stable
         #  self.assertEqual(
@@ -17,7 +18,8 @@ class TestOcrdExif(TestCase):
 
 
     def test_tiff(self):
-        exif = OcrdExif(Image.open(assets.path_to('SBB0000F29300010000/data/OCR-D-IMG/FILE_0001_IMAGE.tif')))
+        with Image.open(assets.path_to('SBB0000F29300010000/data/OCR-D-IMG/FILE_0001_IMAGE.tif')) as img:
+            exif = OcrdExif(img)
         self.assertEqual(exif.width, 2875)
         self.assertEqual(exif.height, 3749)
         self.assertEqual(exif.xResolution, 300)
@@ -25,28 +27,34 @@ class TestOcrdExif(TestCase):
         self.assertEqual(exif.resolution, 300)
         self.assertEqual(exif.compression, 'jpeg')
         self.assertEqual(exif.photometricInterpretation, 'RGB')
+        self.assertEqual(exif.resolutionUnit, 'inches')
 
     def test_png1(self):
-        exif = OcrdExif(Image.open(assets.path_to('kant_aufklaerung_1784-binarized/data/OCR-D-IMG-BIN/BIN_0020.png')))
+        with Image.open(assets.path_to('kant_aufklaerung_1784-binarized/data/OCR-D-IMG-BIN/BIN_0020.png')) as img:
+            exif = OcrdExif(img)
         self.assertEqual(exif.width, 1457)
         self.assertEqual(exif.height, 2084)
-        self.assertEqual(exif.xResolution, 300)
-        self.assertEqual(exif.yResolution, 300)
-        self.assertEqual(exif.resolution, 300)
+        self.assertEqual(exif.xResolution, 295)
+        self.assertEqual(exif.yResolution, 295)
+        self.assertEqual(exif.resolution, 295)
+        self.assertEqual(exif.resolutionUnit, 'inches')
         self.assertEqual(exif.compression, None)
         self.assertEqual(exif.photometricInterpretation, '1')
 
     def test_png2(self):
-        exif = OcrdExif(Image.open(assets.path_to('scribo-test/data/OCR-D-IMG-BIN-SAUVOLA/OCR-D-SEG-PAGE-SAUVOLA-orig_tiff-BIN_sauvola.png')))
+        with Image.open(assets.path_to('scribo-test/data/OCR-D-IMG-BIN-SAUVOLA/OCR-D-SEG-PAGE-SAUVOLA-orig_tiff-BIN_sauvola.png')) as img:
+            exif = OcrdExif(img)
         self.assertEqual(exif.width, 2097)
         self.assertEqual(exif.height, 3062)
         self.assertEqual(exif.xResolution, 1)
         self.assertEqual(exif.yResolution, 1)
         self.assertEqual(exif.resolution, 1)
         self.assertEqual(exif.photometricInterpretation, '1')
+        self.assertEqual(exif.resolutionUnit, 'inches')
 
     def test_jpg(self):
-        exif = OcrdExif(Image.open(assets.path_to('leptonica_samples/data/OCR-D-IMG/OCR-D-IMG_1555_007.jpg')))
+        with Image.open(assets.path_to('leptonica_samples/data/OCR-D-IMG/OCR-D-IMG_1555_007.jpg')) as img:
+            exif = OcrdExif(img)
         self.assertEqual(exif.width, 944)
         self.assertEqual(exif.height, 1472)
         self.assertEqual(exif.xResolution, 1)
@@ -54,15 +62,18 @@ class TestOcrdExif(TestCase):
         self.assertEqual(exif.resolution, 1)
         self.assertEqual(exif.resolutionUnit, 'inches')
         self.assertEqual(exif.photometricInterpretation, 'RGB')
+        self.assertEqual(exif.resolutionUnit, 'inches')
 
     def test_jp2(self):
-        exif = OcrdExif(Image.open(assets.path_to('kant_aufklaerung_1784-jp2/data/OCR-D-IMG/INPUT_0020.jp2')))
+        with Image.open(assets.path_to('kant_aufklaerung_1784-jp2/data/OCR-D-IMG/INPUT_0020.jp2')) as img:
+            exif = OcrdExif(img)
         self.assertEqual(exif.width, 1457)
         self.assertEqual(exif.height, 2084)
         self.assertEqual(exif.xResolution, 1)
         self.assertEqual(exif.yResolution, 1)
         self.assertEqual(exif.resolution, 1)
         self.assertEqual(exif.photometricInterpretation, 'RGB')
+        self.assertEqual(exif.resolutionUnit, 'inches')
 
 if __name__ == '__main__':
     main()