84

I'm attempting to take large (huge) images (from a digital camera), and convert them into something that I can display on the web. This seems straightforward, and probably should be. However, when I attempt to use PIL to create thumbnail versions, if my source image is taller than it is wide, the resulting image is rotated 90 degrees, such that the top of the source image is on the left of the resulting image. If the source image is wider than it is tall, the resulting image is the correct (original) orientation. Could it have to do with the 2-tuple I send in as the size? I'm using thumbnail, because it appears it was meant to preserve the aspect ratio. Or am I just being completely blind, and doing something dumb? The size tuple is 1000,1000 because I want the longest side to be shrunk to 1000 pixels, while keeping AR preserved.

Code seems simple

img = Image.open(filename)
img.thumbnail((1000,1000), Image.ANTIALIAS)
img.save(output_fname, "JPEG")

Thanks in advance for any help.

Hoopes
  • 3,943
  • 4
  • 44
  • 60
  • Adding a note for other: I think `.thumbnail()` do not rotating – I validated myself using `img.show()`. It is actually the `.save()` method which write to file. What I tried is: – instead of writing to disk-file try writing to in-memory file `from io import BytesIO; buffer = BytesIO; img.save(buffer, "JPEG"); Image.open(buffer).show()` – Grijesh Chauhan Jul 02 '21 at 20:26

14 Answers14

83

I agree with almost everything as answered by "unutbu" and Ignacio Vazquez-Abrams, however...

EXIF Orientation flag can have a value between 1 and 8 depending on how the camera was held.

Portrait photo can be taken with top of the camera on the left, or right edge, landscape photo could be taken upside down.

Here is code that takes this into account (Tested with DSLR Nikon D80)

    import Image, ExifTags

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

        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.thumbnail((THUMB_WIDTH , THUMB_HIGHT), Image.ANTIALIAS)
        image.save(os.path.join(path,fileName))

    except:
        traceback.print_exc()
storm_to
  • 1,495
  • 2
  • 16
  • 24
  • Please not that this results in a stracktrace if used for non-JPEGs or if EXIF data is not present. – Maik Hoepfel Jan 04 '13 at 11:23
  • 1
    Is something wrong with the indentation of this piece of code? – Bibhas Debnath Jun 16 '13 at 20:28
  • 2
    What's the purpose of the "for orientation... break" logic block? – Jim Oct 25 '14 at 21:54
  • @Robert It grabs the "Orientation" tag from the ExifTags collection and later uses it for testing the orientation value – storm_to Jan 05 '15 at 12:22
  • Note that rotating resets `image.format`; set it back after rotating if you're relying on it. – Mark Nov 06 '15 at 22:14
  • 3
    why iterate through all the tags, isn't it always going to be the same key? – dangel Aug 18 '19 at 22:33
  • 1
    According to https://www.media.mit.edu/pia/Research/deepview/exif.html the tag for Orientation is 274 (0x0112) once you call image._getexif(), so you can just use 274 as the key for that dict to locate the Orientation field's value – yangsiyu Apr 05 '21 at 23:20
71

Just use PIL.ImageOps.exif_transpose from Pillow.

Unlike every single function proposed in answers to this question, including my original one, it takes care to remove the orientation field from EXIF (as the image is no longer oriented in a strange way) and also to ensure the return value is a brand new Image object so changes to it can’t affect the original one.

Roman Odaisky
  • 2,811
  • 22
  • 26
  • 3
    only `import functools` is missing, still should have been the accepted answer works out of the box in Python 2.7 – Kapitein Witbaard May 16 '16 at 20:02
  • 6
    Kudos to you. This is the most complete answer, using the most proper tools. Other answers either use `rotate` instead of `transpose` or lack all eight possible states. The JPEG exit orientation is for transposing, not for rotating. http://jpegclub.org/exif_orientation.html – OdraEncoded Jun 06 '16 at 03:44
  • 1
    Indeed plugs right in. But does it reliably work? Please confirm that, because I'm getting mixed results at the moment. Here's an image (with orientation `0`) I tested it on: https://imgur.com/a/053MR – Hassan Baig Feb 08 '18 at 16:56
  • 1
    I just fixed a bug in the code where orientation is: Unknown (0). That resulted in an index of -1 which means that python returns the last item in the array which is ROTATE_90 which makes users very cross indeed. – Chris Sattinger Apr 11 '18 at 15:34
  • @Felix Well, the spec disallows the orientation to be 0. But the case still has to be handled. I fixed your fix by adding an entry to the list instead of an extra `if`, and added some comments while I was at it. – Roman Odaisky Apr 11 '18 at 22:45
  • Love the functional style :) – declension Dec 19 '18 at 11:29
  • [Spec](http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf) – Petr Vepřek Dec 22 '20 at 02:43
  • Where'd the code go? This answer is almost useless without it. – Mark Ransom May 03 '22 at 12:19
  • @MarkRansom https://pillow.readthedocs.io/en/latest/_modules/PIL/ImageOps.html#exif_transpose – Roman Odaisky May 04 '22 at 14:34
  • This answer originally had code in it, which made it useful. It was edited out, leaving only a link which would have been better just as a comment instead of an answer. – Mark Ransom May 04 '22 at 14:57
  • @MarkRansom Why should it be a comment when it directly answers the question. And I’m flattered by your high opinion of my code, but the one from Pillow is better for reasons stated in the answer. If someone really wants to copy-paste some code instead of using the library, they can copy-paste the code from the library and not from here, the license is permissive enough. – Roman Odaisky May 04 '22 at 20:10
38

xilvar's answer is very nice, but had two minor shortcomings that I wanted to fix in a rejected edit, so I'll post it as an answer.

For one, xilvar's solution fails if the file isn't a JPEG or if there is no exif data present. And for the other, it always rotated 180 degrees instead of the appropriate amount.

import Image, ExifTags

try:
    image=Image.open(os.path.join(path, fileName))
    if hasattr(image, '_getexif'): # only present in JPEGs
        for orientation in ExifTags.TAGS.keys(): 
            if ExifTags.TAGS[orientation]=='Orientation':
                break 
        e = image._getexif()       # returns None if no EXIF data
        if e is not None:
            exif=dict(e.items())
            orientation = exif[orientation] 

            if orientation == 3:   image = image.transpose(Image.ROTATE_180)
            elif orientation == 6: image = image.transpose(Image.ROTATE_270)
            elif orientation == 8: image = image.transpose(Image.ROTATE_90)

    image.thumbnail((THUMB_WIDTH , THUMB_HIGHT), Image.ANTIALIAS)
    image.save(os.path.join(path,fileName))

except:
    traceback.print_exc()
rob
  • 157
  • 1
  • 13
Maik Hoepfel
  • 1,747
  • 1
  • 13
  • 19
  • I would use `orientation = exif.get(orientation, None)` and then `if orientation is None: return` and add some logs that image exif possibly invalid. I am not saying it may cause error to everybody but it happened to me and it may be very rare. – Bartosz Dabrowski Nov 29 '13 at 15:30
  • 1
    I would use `orientation = next(k for k, v in ExifTags.TAGS.items() if v == 'Orientation')` since this script depends on this tag and PIL's `ExifTags.py`seems to have it. – kirpit Sep 04 '17 at 11:36
33

Pillow has an API to handle EXIF orientation tags automatically:

from PIL import Image, ImageOps

original_image = Image.open(filename)

fixed_image = ImageOps.exif_transpose(original_image)
Jace Browning
  • 11,699
  • 10
  • 66
  • 90
  • I tested some other solutions before this. This is the most simplest and my problem was solved, because it checks the input format too. Only valid PIL Image inputs are accepted. In my case, I destroyed exif information before with line: image = numpy.array(image) – Antti A Nov 26 '20 at 09:35
  • This was the only solution that worked for me. But in my case I was using ImageReader to read the image instead of Image. So I had to save the file into memory and open with Image() then exif_transpose, then use ImageReader(). – MassDefect_ Jan 19 '21 at 22:55
  • thanks, this is the most elegant solution without switch to a different library – cheng yang Aug 27 '21 at 10:44
  • This is most efficient solution. Works perfectly fine! Thanks! – Data_sniffer Feb 01 '23 at 05:17
21

Here's a version that works for all 8 orientations:

def flip_horizontal(im): return im.transpose(Image.FLIP_LEFT_RIGHT)
def flip_vertical(im): return im.transpose(Image.FLIP_TOP_BOTTOM)
def rotate_180(im): return im.transpose(Image.ROTATE_180)
def rotate_90(im): return im.transpose(Image.ROTATE_90)
def rotate_270(im): return im.transpose(Image.ROTATE_270)
def transpose(im): return rotate_90(flip_horizontal(im))
def transverse(im): return rotate_90(flip_vertical(im))
orientation_funcs = [None,
                 lambda x: x,
                 flip_horizontal,
                 rotate_180,
                 flip_vertical,
                 transpose,
                 rotate_270,
                 transverse,
                 rotate_90
                ]
def apply_orientation(im):
    """
    Extract the oritentation EXIF tag from the image, which should be a PIL Image instance,
    and if there is an orientation tag that would rotate the image, apply that rotation to
    the Image instance given to do an in-place rotation.

    :param Image im: Image instance to inspect
    :return: A possibly transposed image instance
    """

    try:
        kOrientationEXIFTag = 0x0112
        if hasattr(im, '_getexif'): # only present in JPEGs
            e = im._getexif()       # returns None if no EXIF data
            if e is not None:
                #log.info('EXIF data found: %r', e)
                orientation = e[kOrientationEXIFTag]
                f = orientation_funcs[orientation]
                return f(im)
    except:
        # We'd be here with an invalid orientation value or some random error?
        pass # log.exception("Error applying EXIF Orientation tag")
    return im
Dobes Vandermeer
  • 8,463
  • 5
  • 43
  • 46
  • This works, but I am wondering if there is something more OOP style that can be done here, like adding to the Image class from PIL instead. Also, I do not think it's a good idea to have that much code in a try block. You have several things that can fail and it would be nice to know which IMHO. – Tatsh Sep 26 '16 at 05:27
  • Using the plugin system, you can replace the JPEG plugin's save function with one that automatically rotates on save. Maybe not always wanted, as this tends to get rid of EXIF data, but can be handy and one less line to write. https://gist.github.com/Tatsh/9f713edc102df99fc612486a2c571a6e – Tatsh Sep 26 '16 at 06:08
21

Please note that there are better answers below.


When a picture is taller than it is wide, it means the camera was rotated. Some cameras can detect this and write that info in the picture's EXIF metadata. Some viewers take note of this metadata and display the image appropriately.

PIL can read the picture's metadata, but it does not write/copy metadata when you save an Image. Consequently, your smart image viewer will not rotate the image as it did before.

Following up on @Ignacio Vazquez-Abrams's comment, you can read the metadata using PIL this way, and rotate if necessary:

import ExifTags
import Image

img = Image.open(filename)
print(img._getexif().items())
exif=dict((ExifTags.TAGS[k], v) for k, v in img._getexif().items() if k in ExifTags.TAGS)
if not exif['Orientation']:
    img=img.rotate(90, expand=True)
img.thumbnail((1000,1000), Image.ANTIALIAS)
img.save(output_fname, "JPEG")

But be aware that the above code may not work for all cameras.

The easiest solution maybe to use some other program to make thumbnails.

phatch is a batch photo editor written in Python which can handle/preserve EXIF metadata. You could either use this program to make your thumbnails, or look at its source code to see how to do this in Python. I believe it uses the pyexiv2 to handle the EXIF metadata. pyexiv2 may be able to handle EXIF better than the PIL's ExifTags module.

imagemagick is another possibility for making batch thumbnails.

Harph
  • 2,250
  • 2
  • 17
  • 16
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • Or to read the EXIF data beforehand and apply the transformation manually. – Ignacio Vazquez-Abrams Nov 19 '10 at 19:59
  • Thanks to both of you for your answers. I'm trying to strip all EXIF data, but then add back the data if it has to be rotated. This is turning into much more of a PITA than I originally assumed it would be. Just a matter of working out the script to do it now. Thanks again! – Hoopes Nov 22 '10 at 21:37
  • Since you're resizing, you probably don't care, but don't forget that even a simple rotation is sometimes a lossy operation on jpegs. – Paul McMillan Apr 10 '11 at 21:02
  • Storm_to's answer is better. orientation has multiple value need to handle differently. – Robert Mao Sep 21 '11 at 23:23
  • Please check the answers below, several improvements have been made. I suggest a community wiki. – Maik Hoepfel Jul 25 '12 at 11:27
  • 2
    I upvoted the version that handles all 8 orientations. Also, here's a great set of test images https://github.com/recurser/exif-orientation-examples from Dave Perrett. – Scott Lawton Jun 11 '15 at 19:00
  • you save my day <3 – Dinh Phong Feb 21 '19 at 09:59
  • This works, just paste it into your answer, please https://stackoverflow.com/a/63798032/193892 – Prof. Falken Dec 29 '20 at 00:25
10

I needed a solution that takes care of all orientations, not just 3, 6 and 8.

I tried Roman Odaisky's solution - it looked comprehensive and clean. However, testing it with actual images with various orientation values sometimes led to erroneous results (e.g. this one with orientation set to 0).

Another viable solution could be Dobes Vandermeer's. But I haven't tried it, because I feel one can write the logic more simply (which I prefer).

So without further ado, here's a simpler, more maintainable (in my opinion) version:

from PIL import Image

def reorient_image(im):
    try:
        image_exif = im._getexif()
        image_orientation = image_exif[274]
        if image_orientation in (2,'2'):
            return im.transpose(Image.FLIP_LEFT_RIGHT)
        elif image_orientation in (3,'3'):
            return im.transpose(Image.ROTATE_180)
        elif image_orientation in (4,'4'):
            return im.transpose(Image.FLIP_TOP_BOTTOM)
        elif image_orientation in (5,'5'):
            return im.transpose(Image.ROTATE_90).transpose(Image.FLIP_TOP_BOTTOM)
        elif image_orientation in (6,'6'):
            return im.transpose(Image.ROTATE_270)
        elif image_orientation in (7,'7'):
            return im.transpose(Image.ROTATE_270).transpose(Image.FLIP_TOP_BOTTOM)
        elif image_orientation in (8,'8'):
            return im.transpose(Image.ROTATE_90)
        else:
            return im
    except (KeyError, AttributeError, TypeError, IndexError):
        return im

Tested, and found to work on images with all the mentioned exif orientations. However, please also do your own tests too.

Hassan Baig
  • 15,055
  • 27
  • 102
  • 205
8

I'm a noob to programming, Python and PIL so the code examples in the previous answers seem complicated to me. Instead of iterating through the tags, I just went straight to it the tag's key. In the python shell, you can see that orientation's key is 274.

>>>from PIL import ExifTags
>>>ExifTags.TAGS

I use the image._getexif() function to grab what ExifTags are in the image. If orientation tag is not present, it throws an error, so I use try/except.

Pillow's documentation says there is no difference in performance or results between rotate and transpose. I have confirmed it by timing both functions. I use rotate because it's more concise.

rotate(90) rotates counter-clockwise. The function seems to accept negative degrees.

from PIL import Image, ExifTags

# Open file with Pillow
image = Image.open('IMG_0002.jpg')

#If no ExifTags, no rotating needed.
try:
# Grab orientation value.
    image_exif = image._getexif()
    image_orientation = image_exif[274]

# Rotate depending on orientation.
    if image_orientation == 3:
        rotated = image.rotate(180)
    if image_orientation == 6:
        rotated = image.rotate(-90)
    if image_orientation == 8:
        rotated = image.rotate(90)

# Save rotated image.
    rotated.save('rotated.jpg')
except:
    pass
FeFiFoFu
  • 1,069
  • 1
  • 11
  • 15
  • This worked for me as far as rotating the image, but the aspect ratio was also inverted. Another problem I had was that when I saved over the original file, I lost the EXIF data completely. – Richard Apr 14 '18 at 17:49
6

Hoopes answer is great, but it is a lot more efficient to use the transpose method rather than rotate. Rotate does an actual filtered calculation for each pixel, effectively a complex resize of the whole image. Also, the current PIL library seems to have a bug in which a black line is added to the edges of rotated images. Transpose is a LOT faster and lacks that bug. I just tweaked hoopes answer to use transpose instead.

import Image, ExifTags

try :
    image=Image.open(os.path.join(path, fileName))
    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.rotate(Image.ROTATE_180)
    elif exif[orientation] == 8 : 
        image=image.rotate(Image.ROTATE_180)

    image.thumbnail((THUMB_WIDTH , THUMB_HIGHT), Image.ANTIALIAS)
    image.save(os.path.join(path,fileName))

except:
    traceback.print_exc()
xilvar
  • 61
  • 1
  • 1
  • 3
    you can smash that conditional down to be if exif[orientation] in [3,6,8]: image = image.transpose(Image.ROTATE_180) – Trey Stout May 02 '12 at 17:02
3

Adding to the other answers, I was having issues because I would use im.copy() before running the functions -- this would strip the necessary exif data. Make sure before you run im.copy() you save the exif data:

try:
    exif = im._getexif()
except Exception:
    exif = None

# ...
# im = im.copy() somewhere
# ...

if exif:
    im = transpose_im(im, exif)
raphaelrk
  • 757
  • 10
  • 17
1

Hello I was trying to achieve rotation of image and thanks to previous answers in this post I did it. But I upgraded solution and would like to share it. I hope someone will find this useful.

def get_rotation_code(img):
    """
    Returns rotation code which say how much photo is rotated.
    Returns None if photo does not have exif tag information. 
    Raises Exception if cannot get Orientation number from python 
    image library.
    """
    if not hasattr(img, '_getexif') or img._getexif() is None:
        return None

    for code, name in ExifTags.TAGS.iteritems():
        if name == 'Orientation':
            orientation_code = code
            break
    else:
        raise Exception('Cannot get orientation code from library.')

    return img._getexif().get(orientation_code, None)


class IncorrectRotationCode(Exception):
    pass


def rotate_image(img, rotation_code):
    """
    Returns rotated image file.

    img: PIL.Image file.
    rotation_code: is rotation code retrieved from get_rotation_code.
    """
    if rotation_code == 1:
        return img
    if rotation_code == 3:
        img = img.transpose(Image.ROTATE_180)
    elif rotation_code == 6:
        img = img.transpose(Image.ROTATE_270)
    elif rotation_code == 8:
        img = img.transpose(Image.ROTATE_90)
    else:
        raise IncorrectRotationCode('{} is unrecognized '
                                    'rotation code.'
                                    .format(rotation_code))
    return img

Use:

>>> img = Image.open('/path/to/image.jpeg')
>>> rotation_code = get_rotation_code(img)
>>> if rotation_code is not None:
...     img = rotate_image(img, rotation_code)
...     img.save('/path/to/image.jpeg')
...
Bartosz Dabrowski
  • 1,850
  • 2
  • 15
  • 21
1

There is an easier approach to all this:

    from PIL import image
    im1 = Image.open(path_to_image)
    im1.thumbnail(size1, Image.ANTIALIAS)
    y, z = im1.size
    d = z * 1.5
    if y > d:
         im1.rotate(90, expand=1)

I hope it helps :)

Ignacio Bares
  • 53
  • 1
  • 5
1

I fix the same issue with ImageOps:

from PIL import Image, ImageOps

img = Image.open(filename)
img = ImageOps.exif_transpose(img)
img.thumbnail((1000,1000), Image.ANTIALIAS)
img.save(output_fname, "JPEG")

More: https://pillow.readthedocs.io/en/stable/reference/ImageOps.html#PIL.ImageOps.exif_transpose

Radek Rojík
  • 104
  • 5
0

There are some good answers here, I just wanted to post a cleaned up version... The function assumes you've already done Image.open() somewhere, and will do image.save() elsewhere and just want a function you can drop in to fix rotation.

def _fix_image_rotation(image):
 orientation_to_rotation_map = {
     3: Image.ROTATE_180,
     6: Image.ROTATE_270,
     8: Image.ROTATE_90,
 }
 try:
     exif = _get_exif_from_image(image)
     orientation = _get_orientation_from_exif(exif)
     rotation = orientation_to_rotation_map.get(orientation)
     if rotation:
         image = image.transpose(rotation)

 except Exception as e:
     # Would like to catch specific exceptions, but PIL library is poorly documented on Exceptions thrown
     # Log error here

 finally:
     return image


def _get_exif_from_image(image):
 exif = {}

 if hasattr(image, '_getexif'):  # only jpegs have _getexif
     exif_or_none = image._getexif()
     if exif_or_none is not None:
         exif = exif_or_none

 return exif


def _get_orientation_from_exif(exif):
 ORIENTATION_TAG = 'Orientation'
 orientation_iterator = (
     exif.get(tag_key) for tag_key, tag_value in ExifTags.TAGS.items()
     if tag_value == ORIENTATION_TAG
 )
 orientation = next(orientation_iterator, None)
 return orientation
orion11
  • 375
  • 3
  • 7