Skip to content

Commit 619027b

Browse files
authored
Extend as_url() to deal with PIL images (#67)
* Extend `as_url()` to deal with PIL images * Fix
1 parent 2d67ace commit 619027b

File tree

2 files changed

+63
-3
lines changed

2 files changed

+63
-3
lines changed

pyodide-e2e/src/tests/url.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import type { PyodideInterface } from "pyodide";
2+
import { beforeEach, describe, it, expect } from "vitest";
3+
import { setupPyodideForTest } from "./utils";
4+
5+
describe("as_url()", () => {
6+
let pyodide: PyodideInterface;
7+
8+
beforeEach(async () => {
9+
pyodide = await setupPyodideForTest(["numpy", "Pillow"]);
10+
});
11+
12+
it("converts a file system URL into a URL", async () => {
13+
await pyodide.runPythonAsync(`
14+
from transformers_js_py import as_url
15+
16+
with open("test.bin", "wb") as f:
17+
f.write(b"test")
18+
19+
url = as_url("test.bin")
20+
`);
21+
const url = await pyodide.globals.get("url");
22+
expect(url).toMatch(/^blob:/);
23+
});
24+
25+
it("converts a bytes variable into a URL", async () => {
26+
await pyodide.runPythonAsync(`
27+
from transformers_js_py import as_url
28+
29+
url = as_url(b"test")
30+
`);
31+
const url = await pyodide.globals.get("url");
32+
expect(url).toMatch(/^blob:/);
33+
});
34+
35+
it("converts a PIL image into a URL", async () => {
36+
await pyodide.runPythonAsync(`
37+
from PIL import Image
38+
from transformers_js_py import as_url
39+
40+
img = Image.new("RGB", (100, 100), "black")
41+
url = as_url(img)
42+
`);
43+
const url = await pyodide.globals.get("url");
44+
expect(url).toMatch(/^blob:/);
45+
});
46+
});

transformers_js_py/url.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@
1010
warnings.warn("Blob is not available in this environment")
1111
js_Blob = None
1212

13+
try:
14+
import PIL.Image as PILImage
15+
16+
PILImageImage = PILImage.Image
17+
except ImportError:
18+
PILImage = None # type: ignore
19+
PILImageImage = None # type: ignore
20+
1321

1422
def is_url(url: str) -> bool:
1523
# Check if the URL is valid, covering all the possible schemes.
@@ -21,11 +29,11 @@ def is_url(url: str) -> bool:
2129
return False
2230

2331

24-
def as_url(data_or_file_path: Union[str, bytes]) -> str:
32+
def as_url(data_or_file_path: Union[str, bytes, PILImageImage]) -> str:
2533
"""For example, `pipeline('zero-shot-image-classification')`
2634
requires a URL of the input image file in the browser environment.
27-
This function converts a file path on Pyodide's virtual file system
28-
to a URL that can be used as the input of such pipelines.
35+
This function converts an input data or a input file path on Pyodide's virtual FS
36+
into a URL that can be used as the input of such pipelines.
2937
3038
Internally, Transformers.js reads the input with this code:
3139
https://github.com/xenova/transformers.js/blob/2.6.2/src/utils/image.js#L112-L113
@@ -40,6 +48,12 @@ def as_url(data_or_file_path: Union[str, bytes]) -> str:
4048
data = f.read()
4149
elif isinstance(data_or_file_path, bytes):
4250
data = data_or_file_path
51+
elif PILImage and isinstance(data_or_file_path, PILImage.Image):
52+
import io
53+
54+
with io.BytesIO() as f:
55+
data_or_file_path.save(f, format="PNG")
56+
data = f.getvalue()
4357
else:
4458
raise TypeError(
4559
"data_or_file_path must be str or bytes, "

0 commit comments

Comments
 (0)