53

I have the following scenario:

  • I am sending an image from iPhone along with the EXIF information to my Pyhon socket server.
  • I need the image to be properly oriented based on the actual orientation when the image was taken. I know IOS always saves the image as Landscape Left and adds the actual orientation as EXIF field (EXIF.Image.Orientation).
  • I am reading the EXIF field to see the actual orientation. Then I am rotating the image using wxpython to the proper orientation.

I am using pyexiv2 for EXIF manipulation.

Issue: The EXIF information incluiding the thumbnails lost while rotating the image using wxpython.

What I did: I am reading the EXIF before rotating the image. I reset the orientation field in the EXIF. Then I am putting it back after rotation.

The problem:

The thumbnail inside the EXIF is not rotated. So, the image and thumbnail have different orientations.

Questions?

Is there any module other than PIL to rotate an image keeping its EXIF info?

Is there a separate EXIF field for thumbnail orientation?

Is there a way I can just rotate the Thumbnail alone?

Thanks for your help...

ATOzTOA
  • 34,814
  • 22
  • 96
  • 117
  • If you take a look at page 56 of the [Exif 2.2 spec](http://www.exif.org/Exif2-2.PDF), you'll see that Orientation is an optional tag that can be attached to the thumbnail in the 1st IFD of the file. I don't have any experience with pyexiv2, but if you can set tags on the thumbnail through the library, I'd bet you can just set this one. – BenTrofatter Dec 14 '12 at 04:50
  • @BenTrofatter Thanks mate... I have tried this, but most of the viewers won't even look at the EXIF before displaying the image/thumbnail. That was the whole reason I rotated the image and reset the EXIF. – ATOzTOA Dec 14 '12 at 05:19

6 Answers6

122

This solution works for me: PIL thumbnail is rotating my image?

Don't need to check if it's iPhone or iPad: if photo has orientation tag – rotate it.

from PIL import Image, ExifTags

try:
    image=Image.open(filepath)

    for orientation in ExifTags.TAGS.keys():
        if ExifTags.TAGS[orientation]=='Orientation':
            break
    
    exif = image._getexif()

    if exif[orientation] == 3:
        image=image.rotate(180, expand=True)
    elif exif[orientation] == 6:
        image=image.rotate(270, expand=True)
    elif exif[orientation] == 8:
        image=image.rotate(90, expand=True)

    image.save(filepath)
    image.close()
except (AttributeError, KeyError, IndexError):
    # cases: image don't have getexif
    pass

Before:

Before

After: After

Community
  • 1
  • 1
scabbiaza
  • 1,962
  • 1
  • 15
  • 11
  • 3
    Does it rotate the thumbnail too? – ATOzTOA Nov 14 '14 at 20:26
  • 2
    deg for rotation of PIL image im0 as a very unpythonic one-liner: `deg = {3:180,6:270,8:90}.get(im0._getexif().get(274,0),0) if hasattr(im0,'_getexif') else 0` – Oliver Zendel Feb 28 '19 at 15:42
  • `exif=dict(image._getexif().items())` why do you dict a dict? Just `exif=image.getexif()` works fine. – user136036 Feb 11 '20 at 15:40
  • 2
    When I do this, the resulting image is now rotated, but the EXIF rotation information is still in the file, so when I open it with Safari the rotation is applied twice (the first time it was hardcoded in the transposed image, and the second time because Safari rotated it again after reading the EXIF). Should I drop the EXIF rotation when I transpose the image? How? Thanks! – Salvatore Iovene Mar 18 '22 at 07:33
  • I got `AttributeError: _getexif`. I think you need to change to `image.getexif()` instead now. – Louis Yang May 19 '22 at 20:48
80

If you're using Pillow >= 6.0.0, you can use the built-in ImageOps.exif_transpose function do correctly rotate an image according to its exif tag:

from PIL import ImageOps

image = ImageOps.exif_transpose(image)
GaretJax
  • 7,462
  • 1
  • 38
  • 47
  • I was hoping to use this super simple solution but I get this error: `AttributeError: module 'PIL.ImageOps' has no attribute 'exif_transpose'` – Dr K Oct 11 '19 at 20:00
  • 3
    @DrK, I updated the answer to reflect that Pillow 6.0.0+ is required to use the exif_transpose function. – GaretJax Oct 12 '19 at 21:03
  • 2
    This function does not works well for tif images. Refere [here](https://github.com/python-pillow/Pillow/issues/4346) – plhn Apr 26 '20 at 02:21
  • check the plhn comment link if this does not work using .png (PIL 7.0) – jerome Jul 05 '21 at 15:33
7

Pretty much the same answer than @scabbiaza, but using transpose instead of rotate (for performance purposes).

from PIL import Image, ExifTags

try:
    image=Image.open(filepath)
    for orientation in ExifTags.TAGS.keys():
        if ExifTags.TAGS[orientation]=='Orientation':
            break
    exif=dict(image._getexif().items())

    if exif[orientation] == 3:
        image=image.transpose(Image.ROTATE_180)
    elif exif[orientation] == 6:
        image=image.transpose(Image.ROTATE_270)
    elif exif[orientation] == 8:
        image=image.transpose(Image.ROTATE_90)
    image.save(filepath)
    image.close()

except (AttributeError, KeyError, IndexError):
    # cases: image don't have getexif
    pass
Arel
  • 1,339
  • 17
  • 22
Hyagoro
  • 75
  • 1
  • 6
  • This looks very good, and it works perfectly, thank you, the only thing I couldn't find how to set expand to False, is there a way to do that with transpose? – Georges D Jul 15 '20 at 22:32
  • @Hyagoro: I think it'd have been better if you would've just added a comment to scabbiaza's answer, instead of listing a different answer altogether – aspiring1 Sep 05 '20 at 23:46
  • @Georges I'm sorry I just saw your comment and I don't have the details in mind anymore :( – Hyagoro Sep 07 '20 at 14:19
  • @aspiring1 Yeah, you're probably right, I'll keep that in mind for my next intervention ;) – Hyagoro Sep 07 '20 at 14:22
2
from PIL import Image

def reorient_img(pil_img):
    img_exif = pil_img.getexif()

    if len(img_exif):
        if img_exif[274] == 3:
            pil_img = pil_img.transpose(Image.ROTATE_180)
        elif img_exif[274] == 6:
            pil_img = pil_img.transpose(Image.ROTATE_270)
        elif img_exif[274] == 8:
            pil_img = pil_img.transpose(Image.ROTATE_90)

    return pil_img
0

https://medium.com/@giovanni_cortes/rotate-image-in-django-when-saved-in-a-model-8fd98aac8f2a

This blog post explains it clearly. Just make sure you try keeping the @receiver.. code in forms.py or models.py as I got cannot import model/view errors .

keep the rotate_image method in models.py & @receiver.. code also in models.py.

I also got errors like No such directory. Just make sure full_path is set correctly to media folder.

I used this line

fullpath = os.path.join(os.path.dirname(BASE_DIR)) + instance.fimage.url

Pratik Gadoya
  • 1,420
  • 1
  • 16
  • 27
joelvarma
  • 150
  • 3
  • 17
0

Since this is the top answer for "python exif rotate" I'd like to add an addendum in case you only need the rotation value and not have PIL rotate the image - in my case I used QPixmap to rotate the image on a QGraphicsView, so I only needed the angle for the QPixmap transformation.
The answer above using PIL to get exif is rather slow. In my test it took 6x the time as the piexif library did (6 ms vs 1 ms) because creating/opening the PIL.Image takes a lot of time. So I'd recommend using piexif in this case.

import piexif

def get_exif_rotation_angle(picture)

    exif_dict = piexif.load(picture)
    if piexif.ImageIFD.Orientation in exif_dict["0th"]:
        orientation = exif_dict["0th"][piexif.ImageIFD.Orientation]
        if orientation == 3:
            return 180
        elif orientation == 6:
            return 90
        elif orientation == 8:
            return 270
        else:
            return None
    else:
        return None

picture can be file path or bytes object.

Ref: https://piexif.readthedocs.io/en/latest/sample.html#rotate-image-by-exif-orientation

tutuDajuju
  • 10,307
  • 6
  • 65
  • 88
user136036
  • 11,228
  • 6
  • 46
  • 46
  • Thank you for fixing the `self` I incorrectly copied.. The reason I used `274` directly instead of `piexif.ImageIFD.Orientation` is to save lookup time. – user136036 Feb 18 '20 at 21:34