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)