PIL thumbnail is rotating my image?

Question:

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.

Asked By: Hoopes

||

Answers:

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.

Answered By: unutbu

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()
Answered By: storm_to

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()
Answered By: xilvar

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()
Answered By: Maik Hoepfel

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
Answered By: Dobes Vandermeer

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')
...
Answered By: Bartosz Dabrowski

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
Answered By: FeFiFoFu

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.

Answered By: Roman Odaisky

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
Answered By: orion11

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.

Answered By: Hassan Baig

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)
Answered By: raphaelrk

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 🙂

Answered By: Ignacio Bares

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)
Answered By: Jace Browning

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

Answered By: Radek Rojík
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.