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.
I expected the exif orientation tag to be updated with the new orientation.
Image.transpose doesn't seem to update exif.
Image.rotate seems to delete exif.
# 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]: []
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. : )
Most helpful comment
Using images from the Pillow test suite -