Skip to content

Commit

Permalink
[Refactoring] Removed custom exif rotation method (#6835)
Browse files Browse the repository at this point in the history
  • Loading branch information
bsekachev authored Sep 15, 2023
1 parent b2957ab commit 657e361
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 44 deletions.
5 changes: 2 additions & 3 deletions cvat/apps/engine/frame_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@

import cv2
import numpy as np
from PIL import Image
from PIL import Image, ImageOps

from cvat.apps.engine.cache import MediaCache
from cvat.apps.engine.media_extractors import VideoReader, ZipReader
from cvat.apps.engine.mime_types import mimetypes
from cvat.apps.engine.models import DataChoice, StorageMethodChoice, DimensionType
from cvat.apps.engine.media_extractors import rotate_within_exif
from rest_framework.exceptions import ValidationError

class RandomAccessIterator:
Expand Down Expand Up @@ -175,7 +174,7 @@ def get_preview(self, frame_number):
else:
preview, _ = self.get_frame(frame_number, self.Quality.COMPRESSED, self.Type.PIL)

preview = rotate_within_exif(preview)
preview = ImageOps.exif_transpose(preview)
preview.thumbnail(PREVIEW_SIZE)

output_buf = BytesIO()
Expand Down
80 changes: 39 additions & 41 deletions cvat/apps/engine/media_extractors.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,22 +83,6 @@ def image_size_within_orientation(img: Image):
def has_exif_rotation(img: Image):
return img.getexif().get(ORIENTATION_EXIF_TAG, ORIENTATION.NORMAL_HORIZONTAL) != ORIENTATION.NORMAL_HORIZONTAL

def rotate_within_exif(img: Image):
orientation = img.getexif().get(ORIENTATION_EXIF_TAG, ORIENTATION.NORMAL_HORIZONTAL)
if orientation in [ORIENTATION.NORMAL_180_ROTATED, ORIENTATION.MIRROR_VERTICAL]:
img = img.rotate(180, expand=True)
elif orientation in [ORIENTATION.NORMAL_270_ROTATED, ORIENTATION.MIRROR_HORIZONTAL_90_ROTATED]:
img = img.rotate(90, expand=True)
elif orientation in [ORIENTATION.NORMAL_90_ROTATED, ORIENTATION.MIRROR_HORIZONTAL_270_ROTATED]:
img = img.rotate(270, expand=True)
if orientation in [
ORIENTATION.MIRROR_HORIZONTAL, ORIENTATION.MIRROR_VERTICAL,
ORIENTATION.MIRROR_HORIZONTAL_270_ROTATED ,ORIENTATION.MIRROR_HORIZONTAL_90_ROTATED,
]:
img = img.transpose(Image.FLIP_LEFT_RIGHT)

return img

class IMediaReader(ABC):
def __init__(self, source_path, step, start, stop, dimension):
self._source_path = source_path
Expand Down Expand Up @@ -127,7 +111,7 @@ def _get_preview(obj):
preview = Image.open(obj)
else:
preview = obj
preview = rotate_within_exif(preview)
preview = ImageOps.exif_transpose(preview)
# TODO - Check if the other formats work. I'm only interested in I;16 for now. Sorry @:-|
# Summary:
# Images in the Format I;16 definitely don't work. Most likely I;16B/L/N won't work as well.
Expand Down Expand Up @@ -227,8 +211,8 @@ def get_image_size(self, i):
with open(self.get_path(i), 'rb') as f:
properties = ValidateDimension.get_pcd_properties(f)
return int(properties["WIDTH"]), int(properties["HEIGHT"])
img = Image.open(self._source_path[i])
return image_size_within_orientation(img)
with Image.open(self._source_path[i]) as img:
return image_size_within_orientation(img)

def reconcile(self, source_files, step=1, start=0, stop=None, dimension=DimensionType.DIM_2D, sorting_method=None):
# FIXME
Expand Down Expand Up @@ -369,8 +353,8 @@ def get_image_size(self, i):
with open(self.get_path(i), 'rb') as f:
properties = ValidateDimension.get_pcd_properties(f)
return int(properties["WIDTH"]), int(properties["HEIGHT"])
img = Image.open(io.BytesIO(self._zip_source.read(self._source_path[i])))
return image_size_within_orientation(img)
with Image.open(io.BytesIO(self._zip_source.read(self._source_path[i]))) as img:
return image_size_within_orientation(img)

def get_image(self, i):
if self._dimension == DimensionType.DIM_3D:
Expand Down Expand Up @@ -604,8 +588,12 @@ def __init__(self, quality, dimension=DimensionType.DIM_2D):

@staticmethod
def _compress_image(image_path, quality):
image = image_path.to_image() if isinstance(image_path, av.VideoFrame) else Image.open(image_path)
image = rotate_within_exif(image)
if isinstance(image_path, av.VideoFrame):
image = image_path.to_image()
else:
with Image.open(image_path) as source_image:
image = ImageOps.exif_transpose(source_image)

# Ensure image data fits into 8bit per pixel before RGB conversion as PIL clips values on conversion
if image.mode == "I":
# Image mode is 32bit integer pixels.
Expand All @@ -632,12 +620,14 @@ def _compress_image(image_path, quality):

converted_image = image.convert('RGB')
image.close()
buf = io.BytesIO()
converted_image.save(buf, format='JPEG', quality=quality, optimize=True)
buf.seek(0)
width, height = converted_image.size
converted_image.close()
return width, height, buf
try:
buf = io.BytesIO()
converted_image.save(buf, format='JPEG', quality=quality, optimize=True)
buf.seek(0)
width, height = converted_image.size
return width, height, buf
finally:
converted_image.close()

@abstractmethod
def save_as_chunk(self, images, chunk_path):
Expand All @@ -664,17 +654,25 @@ def save_as_chunk(self, images, chunk_path):
ext = os.path.splitext(path)[1].replace('.', '')
output = io.BytesIO()
if self._dimension == DimensionType.DIM_2D:
pil_image = Image.open(image)
if has_exif_rotation(pil_image):
rot_image = rotate_within_exif(pil_image)
if rot_image.format == 'TIFF':
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
# use loseless lzw compression for tiff images
rot_image.save(output, format='TIFF', compression='tiff_lzw')
with Image.open(image) as pil_image:
if has_exif_rotation(pil_image):
rot_image = ImageOps.exif_transpose(pil_image)
try:
if rot_image.format == 'TIFF':
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
# use loseless lzw compression for tiff images
rot_image.save(output, format='TIFF', compression='tiff_lzw')
else:
rot_image.save(
output,
format=rot_image.format if rot_image.format else self.IMAGE_EXT,
quality=100,
subsampling=0
)
finally:
rot_image.close()
else:
rot_image.save(output, format=rot_image.format if rot_image.format else self.IMAGE_EXT, quality=100, subsampling=0)
else:
output = image
output = image
else:
output, ext = self._write_pcd_file(image)[0:2]
arcname = '{:06d}.{}'.format(idx, ext)
Expand All @@ -700,8 +698,8 @@ def save_as_chunk(
else:
assert isinstance(image, io.IOBase)
image_buf = io.BytesIO(image.read())
w, h = Image.open(image_buf).size

with Image.open(image_buf) as img:
w, h = img.size
extension = self.IMAGE_EXT
else:
image_buf, extension, w, h = self._write_pcd_file(image)
Expand Down

0 comments on commit 657e361

Please sign in to comment.