Skip to content

Commit 03ce500

Browse files
committed
add jpg compression option
1 parent cdbfefe commit 03ce500

File tree

2 files changed

+34
-7
lines changed

2 files changed

+34
-7
lines changed

packages/python/plotly/plotly/express/_imshow.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
_float_types = []
2525

2626

27-
def _array_to_b64str(img, backend="pil", compression=4):
27+
def _array_to_b64str(img, backend="pil", compression=4, ext="png"):
2828
"""Converts a numpy array of uint8 into a base64 png string.
2929
3030
Parameters
@@ -36,6 +36,8 @@ def _array_to_b64str(img, backend="pil", compression=4):
3636
otherwise pypng.
3737
compression: int, between 0 and 9
3838
compression level to be passed to the backend
39+
ext: str, 'png' or 'jpg'
40+
compression format used to generate b64 string
3941
"""
4042
# PIL and pypng error messages are quite obscure so we catch invalid compression values
4143
if compression < 0 or compression > 9:
@@ -52,6 +54,9 @@ def _array_to_b64str(img, backend="pil", compression=4):
5254
raise ValueError("Invalid image shape")
5355
if backend == "auto":
5456
backend = "pil" if pil_imported else "pypng"
57+
if ext != "png" and backend != "pil":
58+
raise ValueError("jpg binary strings are only available with PIL backend")
59+
5560
if backend == "pypng":
5661
ndim = img.ndim
5762
sh = img.shape
@@ -72,9 +77,14 @@ def _array_to_b64str(img, backend="pil", compression=4):
7277
"install pillow or use `backend='pypng'."
7378
)
7479
pil_img = Image.fromarray(img)
75-
prefix = "data:image/png;base64,"
80+
if ext == "jpg" or ext == "jpeg":
81+
prefix = "data:image/jpeg;base64,"
82+
ext = "jpeg"
83+
else:
84+
prefix = "data:image/png;base64,"
85+
ext = "png"
7686
with BytesIO() as stream:
77-
pil_img.save(stream, format="png", compress_level=compression)
87+
pil_img.save(stream, format=ext, compress_level=compression)
7888
base64_string = prefix + base64.b64encode(stream.getvalue()).decode("utf-8")
7989
return base64_string
8090

@@ -135,6 +145,7 @@ def imshow(
135145
binary_string=None,
136146
binary_backend="auto",
137147
binary_compression_level=4,
148+
binary_format="png",
138149
):
139150
"""
140151
Display an image, i.e. data on a 2D regular raster.
@@ -239,6 +250,9 @@ def imshow(
239250
test `len(fig.data[0].source)` and to time the execution of `imshow` to
240251
tune the level of compression. 0 means no compression (not recommended).
241252
253+
binary_format: str, 'png' (default) or 'jpg'
254+
compression format used to generate b64 string
255+
242256
Returns
243257
-------
244258
fig : graph_objects.Figure containing the displayed image
@@ -410,6 +424,7 @@ def imshow(
410424
img_rescaled,
411425
backend=binary_backend,
412426
compression=binary_compression_level,
427+
ext=binary_format,
413428
)
414429
trace = go.Image(source=img_str)
415430
else:

packages/python/plotly/plotly/tests/test_core/test_px/test_imshow.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def decode_image_string(image_string):
1818
if "png" in image_string[:22]:
1919
return np.asarray(Image.open(BytesIO(base64.b64decode(image_string[22:]))))
2020
elif "jpeg" in image_string[:23]:
21-
return np.asaray(Image.open(BytesIO(base64.b64decode(image_string[23:]))))
21+
return np.asarray(Image.open(BytesIO(base64.b64decode(image_string[23:]))))
2222
else:
2323
raise ValueError("image string format not recognized")
2424

@@ -61,12 +61,24 @@ def test_automatic_zmax_from_dtype():
6161

6262

6363
@pytest.mark.parametrize("binary_string", [False, True])
64-
def test_origin(binary_string):
64+
@pytest.mark.parametrize("binary_format", ["png", "jpg"])
65+
def test_origin(binary_string, binary_format):
6566
for i, img in enumerate([img_rgb, img_gray]):
66-
fig = px.imshow(img, origin="lower", binary_string=binary_string)
67+
fig = px.imshow(
68+
img,
69+
origin="lower",
70+
binary_string=binary_string,
71+
binary_format=binary_format,
72+
)
6773
assert fig.layout.yaxis.autorange is True
68-
if binary_string and i == 0:
74+
if binary_string and i == 0 and binary_format == "png":
75+
# The equality below does not hold for jpeg compression since it's lossy
6976
assert np.all(img[::-1] == decode_image_string(fig.data[0].source))
77+
if binary_string:
78+
if binary_format == "jpg":
79+
assert fig.data[0].source[:15] == "data:image/jpeg"
80+
else:
81+
assert fig.data[0].source[:14] == "data:image/png"
7082
fig = px.imshow(img_rgb, binary_string=binary_string)
7183
assert fig.layout.yaxis.autorange is None
7284
fig = px.imshow(img_gray, binary_string=binary_string)

0 commit comments

Comments
 (0)