Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add image transformer #1901

Merged
merged 8 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions flytekit/core/type_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,8 @@
register_bigquery_handlers()
if is_imported("numpy"):
from flytekit.types import numpy # noqa: F401
if is_imported("PIL"):
from flytekit.types.file import image # noqa: F401

Check warning on line 878 in flytekit/core/type_engine.py

View check run for this annotation

Codecov / codecov/patch

flytekit/core/type_engine.py#L878

Added line #L878 was not covered by tests

@classmethod
def to_literal_type(cls, python_type: Type) -> LiteralType:
Expand Down
82 changes: 82 additions & 0 deletions flytekit/types/file/image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import pathlib
import typing
from typing import Type

Check warning on line 3 in flytekit/types/file/image.py

View check run for this annotation

Codecov / codecov/patch

flytekit/types/file/image.py#L1-L3

Added lines #L1 - L3 were not covered by tests

import PIL.Image

Check warning on line 5 in flytekit/types/file/image.py

View check run for this annotation

Codecov / codecov/patch

flytekit/types/file/image.py#L5

Added line #L5 was not covered by tests

from flytekit.core.context_manager import FlyteContext
from flytekit.core.type_engine import TypeEngine, TypeTransformer, TypeTransformerFailedError
from flytekit.models.core import types as _core_types
from flytekit.models.literals import Blob, BlobMetadata, Literal, Scalar
from flytekit.models.types import LiteralType

Check warning on line 11 in flytekit/types/file/image.py

View check run for this annotation

Codecov / codecov/patch

flytekit/types/file/image.py#L7-L11

Added lines #L7 - L11 were not covered by tests


T = typing.TypeVar("T")

Check warning on line 14 in flytekit/types/file/image.py

View check run for this annotation

Codecov / codecov/patch

flytekit/types/file/image.py#L14

Added line #L14 was not covered by tests


class PILImageTransformer(TypeTransformer[T]):

Check warning on line 17 in flytekit/types/file/image.py

View check run for this annotation

Codecov / codecov/patch

flytekit/types/file/image.py#L17

Added line #L17 was not covered by tests
"""
TypeTransformer that supports np.ndarray as a native type.
pingsutw marked this conversation as resolved.
Show resolved Hide resolved
"""

FILE_FORMAT = "image"

Check warning on line 22 in flytekit/types/file/image.py

View check run for this annotation

Codecov / codecov/patch

flytekit/types/file/image.py#L22

Added line #L22 was not covered by tests
pingsutw marked this conversation as resolved.
Show resolved Hide resolved

def __init__(self):
super().__init__(name="PIL.Image", t=PIL.Image.Image)

Check warning on line 25 in flytekit/types/file/image.py

View check run for this annotation

Codecov / codecov/patch

flytekit/types/file/image.py#L24-L25

Added lines #L24 - L25 were not covered by tests

def get_literal_type(self, t: Type[T]) -> LiteralType:
return LiteralType(

Check warning on line 28 in flytekit/types/file/image.py

View check run for this annotation

Codecov / codecov/patch

flytekit/types/file/image.py#L27-L28

Added lines #L27 - L28 were not covered by tests
blob=_core_types.BlobType(
format=self.FILE_FORMAT, dimensionality=_core_types.BlobType.BlobDimensionality.SINGLE
)
)

def to_literal(

Check warning on line 34 in flytekit/types/file/image.py

View check run for this annotation

Codecov / codecov/patch

flytekit/types/file/image.py#L34

Added line #L34 was not covered by tests
self, ctx: FlyteContext, python_val: PIL.Image.Image, python_type: Type[T], expected: LiteralType
) -> Literal:

meta = BlobMetadata(

Check warning on line 38 in flytekit/types/file/image.py

View check run for this annotation

Codecov / codecov/patch

flytekit/types/file/image.py#L38

Added line #L38 was not covered by tests
type=_core_types.BlobType(
format=self.FILE_FORMAT, dimensionality=_core_types.BlobType.BlobDimensionality.SINGLE
)
)

local_path = ctx.file_access.get_random_local_path() + ".png"
pathlib.Path(local_path).parent.mkdir(parents=True, exist_ok=True)
python_val.save(local_path)

Check warning on line 46 in flytekit/types/file/image.py

View check run for this annotation

Codecov / codecov/patch

flytekit/types/file/image.py#L44-L46

Added lines #L44 - L46 were not covered by tests
eapolinario marked this conversation as resolved.
Show resolved Hide resolved

remote_path = ctx.file_access.get_random_remote_path(local_path)
ctx.file_access.put_data(local_path, remote_path, is_multipart=False)
return Literal(scalar=Scalar(blob=Blob(metadata=meta, uri=remote_path)))

Check warning on line 50 in flytekit/types/file/image.py

View check run for this annotation

Codecov / codecov/patch

flytekit/types/file/image.py#L48-L50

Added lines #L48 - L50 were not covered by tests

def to_python_value(self, ctx: FlyteContext, lv: Literal, expected_python_type: Type[T]) -> PIL.Image.Image:
try:
uri = lv.scalar.blob.uri
except AttributeError:
raise TypeTransformerFailedError(f"Cannot convert from {lv} to {expected_python_type}")

Check warning on line 56 in flytekit/types/file/image.py

View check run for this annotation

Codecov / codecov/patch

flytekit/types/file/image.py#L52-L56

Added lines #L52 - L56 were not covered by tests

local_path = ctx.file_access.get_random_local_path()
ctx.file_access.get_data(uri, local_path, is_multipart=False)

Check warning on line 59 in flytekit/types/file/image.py

View check run for this annotation

Codecov / codecov/patch

flytekit/types/file/image.py#L58-L59

Added lines #L58 - L59 were not covered by tests

return PIL.Image.open(local_path)

Check warning on line 61 in flytekit/types/file/image.py

View check run for this annotation

Codecov / codecov/patch

flytekit/types/file/image.py#L61

Added line #L61 was not covered by tests

def guess_python_type(self, literal_type: LiteralType) -> Type[T]:

Check warning on line 63 in flytekit/types/file/image.py

View check run for this annotation

Codecov / codecov/patch

flytekit/types/file/image.py#L63

Added line #L63 was not covered by tests
if (
literal_type.blob is not None
and literal_type.blob.dimensionality == _core_types.BlobType.BlobDimensionality.SINGLE
and literal_type.blob.format == self.FILE_FORMAT
):
return PIL.Image.Image

Check warning on line 69 in flytekit/types/file/image.py

View check run for this annotation

Codecov / codecov/patch

flytekit/types/file/image.py#L69

Added line #L69 was not covered by tests

raise ValueError(f"Transformer {self} cannot reverse {literal_type}")

Check warning on line 71 in flytekit/types/file/image.py

View check run for this annotation

Codecov / codecov/patch

flytekit/types/file/image.py#L71

Added line #L71 was not covered by tests

def to_html(self, ctx: FlyteContext, python_val: PIL.Image.Image, expected_python_type: Type[T]) -> str:
import base64
from io import BytesIO
buffered = BytesIO()
python_val.save(buffered, format="PNG")
img_base64 = base64.b64encode(buffered.getvalue()).decode()
return f'<img src="data:image/png;base64,{img_base64}" alt="Rendered Image" />'

Check warning on line 79 in flytekit/types/file/image.py

View check run for this annotation

Codecov / codecov/patch

flytekit/types/file/image.py#L73-L79

Added lines #L73 - L79 were not covered by tests


TypeEngine.register(PILImageTransformer())

Check warning on line 82 in flytekit/types/file/image.py

View check run for this annotation

Codecov / codecov/patch

flytekit/types/file/image.py#L82

Added line #L82 was not covered by tests
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import markdown
import pandas as pd
import PIL
import PIL.Image
eapolinario marked this conversation as resolved.
Show resolved Hide resolved
import plotly.express as px
else:
pd = lazy_module("pandas")
Expand Down
29 changes: 29 additions & 0 deletions tests/flytekit/unit/types/file/test_image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import io

import requests

from flytekit import task, workflow
import PIL.Image


@task(disable_deck=False)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: can you use enable_deck?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't find any other place using PIL.Image in flytkeit

def t1() -> PIL.Image.Image:
url = "https://miro.medium.com/v2/resize:fit:1400/1*0T9PjBnJB9H0Y4qrllkJtQ.png"
response = requests.get(url)
image_bytes = io.BytesIO(response.content)
pingsutw marked this conversation as resolved.
Show resolved Hide resolved
im = PIL.Image.open(image_bytes)
return im


@task
def t2(im: PIL.Image.Image) -> PIL.Image.Image:
return im


@workflow
def wf():
t2(im=t1())


def test_image_transformer():
wf()
Loading