Skip to content

Commit

Permalink
Rotate Images to Upright Position in preprocess (#6676)
Browse files Browse the repository at this point in the history
* Code

* add changeset

* remove comment

* Add back try except

* Add code

* Use warning

* Use warning

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
  • Loading branch information
freddyaboulton and gradio-pr-bot committed Dec 7, 2023
1 parent 798eca5 commit fe40308
Show file tree
Hide file tree
Showing 6 changed files with 23 additions and 66 deletions.
5 changes: 5 additions & 0 deletions .changeset/evil-readers-reply.md
@@ -0,0 +1,5 @@
---
"gradio": patch
---

fix:Rotate Images to Upright Position in preprocess
10 changes: 10 additions & 0 deletions gradio/components/image.py
Expand Up @@ -9,6 +9,7 @@
import numpy as np
from gradio_client.documentation import document, set_documentation_group
from PIL import Image as _Image # using _ to minimize namespace pollution
from PIL import ImageOps

import gradio.image_utils as image_utils
from gradio import utils
Expand Down Expand Up @@ -162,6 +163,15 @@ def preprocess(
return str(file_path)

im = _Image.open(file_path)
exif = im.getexif()
# 274 is the code for image rotation and 1 means "correct orientation"
if exif.get(274, 1) != 1 and hasattr(ImageOps, "exif_transpose"):
try:
im = ImageOps.exif_transpose(im)
except Exception:
warnings.warn(
f"Failed to transpose image {file_path} based on EXIF data."
)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
im = im.convert(self.image_mode)
Expand Down
15 changes: 0 additions & 15 deletions gradio/processing_utils.py
Expand Up @@ -58,21 +58,6 @@ def extract_base64_data(x: str) -> str:
#########################


def decode_base64_to_image(encoding: str) -> Image.Image:
image_encoded = extract_base64_data(encoding)
img = Image.open(BytesIO(base64.b64decode(image_encoded)))
try:
if hasattr(ImageOps, "exif_transpose"):
img = ImageOps.exif_transpose(img)
except Exception:
log.warning(
"Failed to transpose image %s based on EXIF data.",
img,
exc_info=True,
)
return img


def encode_plot_to_base64(plt):
with BytesIO() as output_bytes:
plt.savefig(output_bytes, format="png")
Expand Down
8 changes: 8 additions & 0 deletions test/test_components.py
Expand Up @@ -640,6 +640,14 @@ def test_static(self):
component = gr.Image(None)
assert component.get_config().get("value") is None

def test_images_upright_after_preprocess(self):
component = gr.Image(type="pil")
file_path = "test/test_files/rotated_image.jpeg"
im = PIL.Image.open(file_path)
assert im.getexif().get(274) != 1
image = component.preprocess(FileData(path=file_path))
assert image == PIL.ImageOps.exif_transpose(im)


class TestPlot:
@pytest.mark.asyncio
Expand Down
Binary file added test/test_files/rotated_image.jpeg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 0 additions & 51 deletions test/test_processing_utils.py
@@ -1,6 +1,3 @@
import base64
import io
import logging
import os
import shutil
import tempfile
Expand Down Expand Up @@ -106,19 +103,6 @@ def test_save_url_to_cache_with_spaces(self, gradio_temp_dir):


class TestImagePreprocessing:
def test_decode_base64_to_image(self):
output_image = processing_utils.decode_base64_to_image(
deepcopy(media_data.BASE64_IMAGE)
)
assert isinstance(output_image, Image.Image)

b64_img_without_header = deepcopy(media_data.BASE64_IMAGE).split(",")[1]
output_image_without_header = processing_utils.decode_base64_to_image(
b64_img_without_header
)

assert output_image == output_image_without_header

def test_encode_plot_to_base64(self):
with utils.MatplotlibBackendMananger():
import matplotlib.pyplot as plt
Expand Down Expand Up @@ -197,24 +181,6 @@ def test_encode_pil_to_temp_file_metadata_color_profile(self, gradio_temp_dir):
)
assert len({img_path, img_metadata_path, img_cp1_path, img_cp2_path}) == 4

def test_encode_pil_to_base64_keeps_pnginfo(self):
input_img = Image.open("gradio/test_data/test_image.png")
input_img = input_img.convert("RGB")
input_img.info = {"key1": "value1", "key2": "value2"}

encoded_image = processing_utils.encode_pil_to_base64(input_img)
decoded_image = processing_utils.decode_base64_to_image(encoded_image)

assert decoded_image.info == input_img.info

@patch("PIL.Image.Image.getexif", return_value={274: 3})
@patch("PIL.ImageOps.exif_transpose")
def test_base64_to_image_does_rotation(self, mock_rotate, mock_exif):
input_img = Image.open("gradio/test_data/test_image.png")
base64 = processing_utils.encode_pil_to_base64(input_img)
processing_utils.decode_base64_to_image(base64)
mock_rotate.assert_called_once()

def test_resize_and_crop(self):
img = Image.open("gradio/test_data/test_image.png")
new_img = processing_utils.resize_and_crop(img, (20, 20))
Expand Down Expand Up @@ -361,20 +327,3 @@ def test_video_conversion_returns_original_video_if_fails(
)
# If the conversion succeeded it'd be .mp4
assert Path(playable_vid).suffix == ".avi"


def test_decode_base64_to_image_does_not_crash_when_image_has_bogus_exif_data(caplog):
from PIL.PngImagePlugin import PngInfo

caplog.set_level(logging.WARNING)
i = Image.new("RGB", (32, 32), "orange")
bio = io.BytesIO()
# since `exif` is the `.info` key for EXIF data parsed from a JPEG,
# adding an iTXt chunk with the same name should trigger the warning
pi = PngInfo()
pi.add_text("exif", "bogus")
i.save(bio, format="png", pnginfo=pi)
bio.seek(0)
encoded = base64.b64encode(bio.getvalue()).decode()
assert processing_utils.decode_base64_to_image(encoded).size == (32, 32)
assert "Failed to transpose image" in caplog.text

0 comments on commit fe40308

Please sign in to comment.