Images get rotated randomly

Hi everyone,

I was experimenting with the notebook of lesson 2 and applied it on my own dataset which was a set of building images. When I assign the data to the parameter via the ImageDataBunch function and then make it show the data, some of the images appear to be rotated 90 degrees. I tried adding max_rotate = 0. to the transformation but that did not help. Does anyone have any idea on what to do?

Thanks

2 Likes

This is because of the flip_vert Transform. You can see all the options for the get_transforms function here: https://docs.fast.ai/vision.transform.html

Hi Brad, thanks for the reply. By flipping, the picture should not be rotated 90 degrees in clockwise or counterclockwise direction, but it should be mirrored upside down. Besides, the default value for flip_vert is set to be False. So, I don’t believe this has to do with the flipping.

I also tried setting do_flip to False but that helped neither. Here is a screenshot of what the data looks like (pay attention to the top-right corner):
image

flip_vert also does 90 degree rotations:

  • flip_vert : limit the flips to horizontal flips (when False) or to horizontal and vertical flips as well as 90-degrees rotations (when True)

It will be hard to help further without seeing any code.

Have you checked to make sure these source images are in the proper orientation on disk?

Thanks for the docs and help.
Looks like in some way that I don’t know some random images where rotated on disk. I will check out to see if rotating them will solve the problem.

I also had this problem (images being rotated by 90 degrees) when running the notebook of lesson 2:

https://github.com/fastai/course-v3/blob/master/nbs/dl1/lesson2-download.ipynb .

I tried get_transforms(max_rotate=None, do_flip=False) but I still have this issue.

I have verified that the images in disk are not rotated.

Anyone know the reason?

How have you verified that the images on the disk are not rotated?
There is an “Exif” tag called “orientation” which informs most modern programs as to which rotation to use for a photo. This is a property that is written upon the file and is sometimes very hard to find.

I suggest using a Python package, e.g. exif to review this tag for your photos. I wrote a code based on this StackOverflow answer for detecting all images in a directory which contain this tag and rotating them to the correct orientation without using this tag:

from exif import Image as ExifImage
from PIL import Image
from glob import glob
import os.path

transformation_funcs = {
    6: lambda img: img.rotate(-90, resample=Image.BICUBIC, expand=True),
    8: lambda img: img.rotate(90, resample=Image.BICUBIC, expand=True),
    3: lambda img: img.rotate(180, resample=Image.BICUBIC, expand=True),
    2: lambda img: img.transpose(Image.FLIP_LEFT_RIGHT),
    5: lambda img: img.rotate(-90, resample=Image.BICUBIC, expand=True).transpose(Image.FLIP_LEFT_RIGHT),
    7: lambda img: img.rotate(90, resample=Image.BICUBIC, expand=True).transpose(Image.FLIP_LEFT_RIGHT),
    4: lambda img: img.rotate(180, resample=Image.BICUBIC, expand=True).transpose(Image.FLIP_LEFT_RIGHT),
}

dir_path = [path to directory containing you images]

for img_path in glob(os.path.join(dir_path, '*.jpg')):
    print(img_path)
    with open(img_path, 'rb') as image_file:
        my_image = ExifImage(image_file)
    try:
        orientation = my_image.orientation.value
    except:
        print('  N/A skipping')
        continue
    if orientation == 1:
        print('  1 skipping')
        continue
    else:
        print(' ', orientation, 'transforming')
        
    my_image.orientation = 1
    with open(img_path, 'wb') as image_file:
        image_file.write(my_image.get_file())
        
    img = Image.open(img_path)
    img = transformation_funcs[orientation](img)
    img.save(img_path)

You need to have the exif and PIL libraries installed.
Sorry for the mess, by the way, I wrote this quick and dirty.

2 Likes

I ran into the same problem with exif orientation attributes. @shoval thanks for the neat code snippet.

Instead of manipulating files on disk, I decided to write a simple ExifImageBlock, which can be passed instead of an ImageBlock. It will simply apply the exif transformation to the pixel tensor, making sure the images have correct orientation in memory. In case anybody else is stumbling upon this issue, fee free to use the following code cell snippet.

from exif import Image as ExifImage

def exif_type_tfms(fn, cls, **kwargs):     
  def get_orientation(fn: (Path, str)):
    with open(fn, 'rb') as image_file:
      exif_img = ExifImage(image_file)
      try:
        return exif_img.orientation.value
      except AttributeError:
        # ignore orientation unset
        return 1
  def f(img, rotate=0, transpose=False):
    img = img.rotate(rotate, expand=True)
    if transpose:
      img = img.transpose(Image.FLIP_LEFT_RIGHT)
    return img

  # Image.rotate will do shorcuts on these magic angles, so no need for any 
  # specific resampling strategy
  trafo_fns = {
    1: partial(f, rotate=0),
    6: partial(f, rotate=270),
    8: partial(f, rotate=90),
    3: partial(f, rotate=180),
    2: partial(f, rotate=0, transpose=True),
    5: partial(f, rotate=270, transpose=True),
    7: partial(f, rotate=90, transpose=True),
    4: partial(f, rotate=180, transpose=True),
  }
  img = cls.create(fn, **kwargs)
  orientation = get_orientation(fn)
  img = trafo_fns[orientation](img)
  return cls(img)

def ExifImageBlock(cls=PILImage):
  """
  if images are rotated with the EXIF orentation flag
  it must be respected when loading the images

  ExifImageBlock can be pickled (which is important to dump learners)
  >>> pickle.dumps(ExifImageBlock())
  b'...
  """
  return TransformBlock(type_tfms=partial(exif_type_tfms, cls=cls), batch_tfms=IntToFloatTensor)

import doctest
doctest.run_docstring_examples(ExifImageBlock, globals(), optionflags=doctest.ELLIPSIS)
3 Likes

Indeed discussion and docs are useful and can be helpful for many people. :slightly_smiling_face: Thanks!

Here’s another version, I think it’s clearer:

def exif_transpose(image):
    """
    Transpose a PIL image accordingly if it has an EXIF Orientation tag.
    Inplace version of https://github.com/python-pillow/Pillow/blob/master/src/PIL/ImageOps.py exif_transpose()
    :param image: The image to transpose.
    :return: An image.
    """
    exif = image.getexif()
    orientation = exif.get(0x0112, 1)  # default 1
    if orientation > 1:
        method = {2: Image.FLIP_LEFT_RIGHT,
                  3: Image.ROTATE_180,
                  4: Image.FLIP_TOP_BOTTOM,
                  5: Image.TRANSPOSE,
                  6: Image.ROTATE_270,
                  7: Image.TRANSVERSE,
                  8: Image.ROTATE_90,
                  }.get(orientation)
        if method is not None:
            image = image.transpose(method)
            del exif[0x0112]
            image.info["exif"] = exif.tobytes()
    return image


def exif_type_tfms(fn, cls, **kwargs):
    img = cls.create(fn, **kwargs)
    img=exif_transpose(img)
    return cls(img)
    
def ExifImageBlock(cls=PILImage):
  """
  if images are rotated with the EXIF orentation flag
  it must be respected when loading the images

  ExifImageBlock can be pickled (which is important to dump learners)
  >>> pickle.dumps(ExifImageBlock())
  b'...
  """
  return TransformBlock(type_tfms=partial(exif_type_tfms, cls=cls), batch_tfms=IntToFloatTensor)

2 Likes