Pillow: Image.transpose doesn't update exif? Image.rotate deletes exif?

Created on 7 Oct 2019  路  8Comments  路  Source: python-pillow/Pillow

What did you do?

Loaded this image [Originally from flickr] which contains Exif information, including the orientation tag into an PIL.Image and called image.transpose as well as image.rotate.

What did you expect to happen?

I expected the exif orientation tag to be updated with the new orientation.

What actually happened?

Image.transpose doesn't seem to update exif.
Image.rotate seems to delete exif.

What are your OS, Python and Pillow versions?

  • OS: OS X 10.16.6
  • Python: 2.7.10
  • Pillow: 6.0.0
# TEST_FILE_PATH here is the image from flickr on my local file system:
In [90]: image = Image.open(open(TEST_FILE_PATH))  
In [91]: print(image.getexif()[0x0112])
1
In [92]: image = image.transpose(Image.ROTATE_90)
In [93]: print(image.getexif()[0x0112])
1
In [94]: image = image.rotate(90)
In [95]: print(image.getexif()[0x0112])
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-95-627080b70c8f> in <module>()
----> 1 print(image.getexif()[0x0112])

/Users/liavkoren/Envs/suicidegirls/lib/python2.7/site-packages/PIL/Image.pyc in __getitem__(self, tag)
   3174
   3175     def __getitem__(self, tag):
-> 3176         return self._data[tag]
   3177
   3178     def __contains__(self, tag):

KeyError: 274

In [96]: image.getexif().items()
Out[96]: []
Exif

Most helpful comment

Using images from the Pillow test suite -

from PIL import Image, ImageOps
im = Image.open("Tests/images/hopper_orientation_3.jpg")
print(im.getexif()[0x0112])  # 3, as the filename implies
im.save("out_withoutexif.jpg")  # Saved as an upside down image, because the EXIF was not applied and the EXIF was not saved

im = Image.open("Tests/images/hopper_orientation_3.jpg")
im = ImageOps.exif_transpose(im)
im.save("out_withexiftranspose.jpg")  # Saved right side up, because the EXIF has been applied

im = Image.open("Tests/images/hopper_orientation_3.jpg")
exif = im.getexif()
im.save("out_withexiftag.jpg", exif=exif)  # Saved as an upside down image, but the EXIF orientation tag has also been saved, meaning that viewers will show this as being right side up

im = Image.open("Tests/images/hopper.jpg")
im.save("out_normal.jpg")  # Just to demonstrate that it is right side up

im = Image.open("Tests/images/hopper.jpg")
im = im.rotate(180)
print(im.getexif().get(0x0112))  # None, because there is no orientation that needs to be applied to the image
im.save("out_normal_rotated.jpg")  # Saved as an upside down image

im = Image.open("Tests/images/hopper.jpg")
exif = im.getexif()
exif[0x0112] = 3
im.save("out_normal_exifrotated.jpg", exif=exif)  # Saved as an upside down image, because although the image data is still upright, the orientation tag tells viewers to turn it upside down afterwards

All 8 comments

Hi. EXIF orientation is separate from image data. The EXIF tag is an... instruction, for which way to point the image - it is not the image itself. When you are transposing or rotating an image in Pillow, you are making changes to the image data itself. Certainly you can rotate an image in ways that can't be expressed in terms of EXIF orientation.

Pillow does not automatically apply the EXIF orientation tag. So if an image file has upside down image data, but with an EXIF orientation tag of 3 to make it right side up, Pillow reads that as an upside down image. The tag data is also read, but that does not interact with the image data unless the user causes it to.

So in the world of Pillow, rotating or transposing an image changes the image data. The EXIF orientation tag hasn't been applied, so it doesn't quite make sense for it to be given a new value.

You may be interested in ImageOps.exif_transpose - https://github.com/python-pillow/Pillow/blob/127b3221947f31e9dc869870cd1ec0fbae5da3b3/src/PIL/ImageOps.py#L527 This is a method that transposes an image according to its EXIF orientation tag. With it, you can open an image, apply the orientation, and then move on.

Thanks for the fast response, @radarhere. I guess if the intention is to have one or more methods that manipulate the image and the exif tag in synchrony vs methods that don't, that makes sense, altho my intuition about this is that the methods should be symmetrical, or that transpose itself should just take an update_exif arg, which if true would then apply the same transform to the exif tag.

Short of that, personally, I would have found mention of the exif issue useful in the docs. I don't know if I have the bandwidth to write a PR updating the code, but if you folks would like a documentation PR to address some of this, I could add a few words on this issue.

Deleting the exif info completely when you rotate an image seems like a completely different issue. Is that intended behavior?

I've created PR #4128 to change rotate so that the EXIF data is no longer removed. If the PR is merged, it will be a part of Pillow 7.0.0 when it is released on January 1.

If you would like a workaround until then, you can manually copy the image info -

from PIL import Image
im = Image.open(open(TEST_FILE_PATH))
new_im = im.rotate(90)
new_im = im.info.copy()

There is still some confusion here, so please bear with me as I try to speak clearly.

The orientation tag is not a comment on which way the image data happens to be facing. It is an instruction on which way to orient the image data so that it is seen as intended. It is not an absolute measure, it is a relative one - when the image is read, this is the transformation to be applied afterwards.

As far as I can see, one would only ever need to change either the EXIF orientation tag OR the image data itself. They do not need to be changed both at once. If I were to rotate the image data by 90 degrees, and then after that change the EXIF orientation tag to also rotate it 90 degrees, it would create a combined effect of 180 degrees.

Since Pillow does not automatically transform the image data according to the orientation tag, ImageOps.exif_transpose exists to apply the transformation and then delete the orientation tag afterwards, since the image is now naturally oriented.

If you're interested in changing the EXIF data, you are on the right path with getexif().

If you are interested in changing the image data, rotate and transpose will do the job.

All of that said, I think that the question of 'Why is Pillow showing the image from my camera the wrong way around?' is a basic one, so I've created #4132 to add a note to the documentation.

The orientation tag is not a comment on which way the image data happens to be facing. It is an instruction on which way to orient the image data so that it is seen as intended. It is not an absolute measure, it is a relative one - when the image is read, this is the transformation to be applied afterwards.

What if I apply a transform based on the exif and then write the image to disk?

Using images from the Pillow test suite -

from PIL import Image, ImageOps
im = Image.open("Tests/images/hopper_orientation_3.jpg")
print(im.getexif()[0x0112])  # 3, as the filename implies
im.save("out_withoutexif.jpg")  # Saved as an upside down image, because the EXIF was not applied and the EXIF was not saved

im = Image.open("Tests/images/hopper_orientation_3.jpg")
im = ImageOps.exif_transpose(im)
im.save("out_withexiftranspose.jpg")  # Saved right side up, because the EXIF has been applied

im = Image.open("Tests/images/hopper_orientation_3.jpg")
exif = im.getexif()
im.save("out_withexiftag.jpg", exif=exif)  # Saved as an upside down image, but the EXIF orientation tag has also been saved, meaning that viewers will show this as being right side up

im = Image.open("Tests/images/hopper.jpg")
im.save("out_normal.jpg")  # Just to demonstrate that it is right side up

im = Image.open("Tests/images/hopper.jpg")
im = im.rotate(180)
print(im.getexif().get(0x0112))  # None, because there is no orientation that needs to be applied to the image
im.save("out_normal_rotated.jpg")  # Saved as an upside down image

im = Image.open("Tests/images/hopper.jpg")
exif = im.getexif()
exif[0x0112] = 3
im.save("out_normal_exifrotated.jpg", exif=exif)  # Saved as an upside down image, because although the image data is still upright, the orientation tag tells viewers to turn it upside down afterwards

Okay. Thank you for clarifying. : )

Was this page helpful?
0 / 5 - 0 ratings

Related issues

vytisb picture vytisb  路  4Comments

SysoevDV picture SysoevDV  路  3Comments

indirectlylit picture indirectlylit  路  4Comments

readyready15728 picture readyready15728  路  4Comments

amithnikhade picture amithnikhade  路  4Comments