Skip to content

Commit 202c2ce

Browse files
committed
test: verify encoded pixels in image_array_to_data_uri tests
Address review: - Move the file to tests/test_plotly_utils/ so it lives with the rest of the _plotly_utils tests. - The greyscale/RGB/RGBA tests only asserted the data URI prefix, which gave a false impression of coverage. Decode the base64 payload with the vendored pypng Reader and compare the pixels against the input array, and assert the PNG colour mode (greyscale/alpha) matches the input shape. Decoding via the vendored Reader keeps the tests Pillow-free.
1 parent 1179bf3 commit 202c2ce

2 files changed

Lines changed: 88 additions & 49 deletions

File tree

tests/test_optional/test_utils/test_data_utils.py

Lines changed: 0 additions & 49 deletions
This file was deleted.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""
2+
Tests for _plotly_utils.data_utils.image_array_to_data_uri.
3+
4+
Uses the pypng backend so the tests do not require Pillow. The generated
5+
data URIs are decoded with the vendored pypng Reader so the tests assert on
6+
the encoded pixels rather than only on the data URI prefix.
7+
"""
8+
9+
import base64
10+
11+
import numpy as np
12+
import pytest
13+
14+
from _plotly_utils.data_utils import image_array_to_data_uri
15+
from _plotly_utils.png import Reader
16+
17+
PNG_PREFIX = "data:image/png;base64,"
18+
19+
20+
def decode_data_uri(uri):
21+
"""Decode a PNG data URI back into an array plus the PNG header info."""
22+
assert uri.startswith(PNG_PREFIX)
23+
png_bytes = base64.b64decode(uri[len(PNG_PREFIX) :])
24+
width, height, rows, info = Reader(bytes=png_bytes).read_flat()
25+
if info["greyscale"]:
26+
channels = 1
27+
elif info["alpha"]:
28+
channels = 4
29+
else:
30+
channels = 3
31+
decoded = np.array(list(rows), dtype=np.uint8).reshape(height, width, channels)
32+
if channels == 1:
33+
decoded = decoded[:, :, 0]
34+
return decoded, info
35+
36+
37+
def test_greyscale_array_round_trips():
38+
img = np.array([[0, 255], [128, 64]], dtype=np.uint8)
39+
40+
decoded, info = decode_data_uri(image_array_to_data_uri(img, backend="pypng"))
41+
42+
assert info["greyscale"] is True
43+
assert info["alpha"] is False
44+
np.testing.assert_array_equal(decoded, img)
45+
46+
47+
def test_rgb_array_round_trips():
48+
img = np.array(
49+
[[[255, 0, 0], [0, 255, 0]], [[0, 0, 255], [10, 20, 30]]], dtype=np.uint8
50+
)
51+
52+
decoded, info = decode_data_uri(image_array_to_data_uri(img, backend="pypng"))
53+
54+
assert info["greyscale"] is False
55+
assert info["alpha"] is False
56+
np.testing.assert_array_equal(decoded, img)
57+
58+
59+
def test_rgba_array_round_trips():
60+
img = np.array(
61+
[[[255, 0, 0, 255], [0, 255, 0, 128]], [[0, 0, 255, 0], [10, 20, 30, 40]]],
62+
dtype=np.uint8,
63+
)
64+
65+
decoded, info = decode_data_uri(image_array_to_data_uri(img, backend="pypng"))
66+
67+
assert info["greyscale"] is False
68+
assert info["alpha"] is True
69+
np.testing.assert_array_equal(decoded, img)
70+
71+
72+
@pytest.mark.parametrize("compression", [-1, 10])
73+
def test_invalid_compression_raises(compression):
74+
img = np.zeros((2, 2), dtype=np.uint8)
75+
with pytest.raises(ValueError, match="compression level"):
76+
image_array_to_data_uri(img, backend="pypng", compression=compression)
77+
78+
79+
def test_invalid_shape_raises():
80+
img = np.zeros(5, dtype=np.uint8)
81+
with pytest.raises(ValueError, match="Invalid image shape"):
82+
image_array_to_data_uri(img, backend="pypng")
83+
84+
85+
def test_jpg_without_pil_backend_raises():
86+
img = np.zeros((2, 2), dtype=np.uint8)
87+
with pytest.raises(ValueError, match="jpg binary strings"):
88+
image_array_to_data_uri(img, backend="pypng", ext="jpg")

0 commit comments

Comments
 (0)