Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ authors = [
requires-python = ">=3.11,<3.14"
dependencies = [
"aiofiles~=24.1.0",
"aiohttp~=3.13.3",
"aiohttp~=3.14.4",
"cyclopts>=4,<5",
"fastapi>=0.115,<1",
"ffmpeg-python~=0.2.0",
Expand All @@ -43,7 +43,7 @@ dependencies = [
"optuna>=3.6",
"orjson~=3.11.7",
"pandas~=2.3.3",
"pillow~=12.2.0",
"pillow~=13.0.0",
"plotly~=6.4.0",
"dash~=3.1.0",
"dash-bootstrap-components~=2.0.0",
Expand Down
3 changes: 3 additions & 0 deletions src/aiperf/dataset/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ def encode_image(img: Image, format: str) -> str:
# to be converted to RGB before an image can be saved as JPEG format.
if format == "JPEG" and img.mode != "RGB":
img = img.convert("RGB")
# Preserve Pillow 12's 16-bit clipping when Pillow 13 refuses mode I PNGs.
elif format == "PNG" and img.mode == "I":
img = img.convert("I;16")

buffer = BytesIO()
# Use explicit compression settings to ensure deterministic output across platforms
Expand Down
27 changes: 27 additions & 0 deletions tests/unit/dataset/generator/test_image_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,33 @@ def test_custom_directory_loads_images(self, tmp_path):
result = generator.generate()
assert result.startswith("data:image/png;base64,")

@pytest.mark.parametrize("image_format", [ImageFormat.PNG, ImageFormat.JPEG])
def test_custom_directory_integer_tiff_encodes(
self, tmp_path: Path, image_format: ImageFormat
) -> None:
"""Resize and encode a real mode I TIFF through the custom-source path."""
Image.new("I", (5, 5), color=70000).save(tmp_path / "depth.tif")
config = ImageConfig(
batch_size=1,
width=ImageWidthConfig(mean=10, stddev=0),
height=ImageHeightConfig(mean=10, stddev=0),
format=image_format,
source=tmp_path,
)
result = ImageGenerator(config).generate()

prefix = f"data:image/{image_format.name.lower()};base64,"
assert result.startswith(prefix)
with Image.open(
BytesIO(base64.b64decode(result.removeprefix(prefix)))
) as image:
assert image.size == (10, 10)
if image_format == ImageFormat.PNG:
assert image.mode == "I;16"
assert image.getextrema() == (65535, 65535)
else:
assert image.mode == "RGB"

def test_custom_directory_shuffle_cycle_uses_pool_once(self, tmp_path):
colors = {
"blue.png": (0, 0, 255),
Expand Down
21 changes: 21 additions & 0 deletions tests/unit/dataset/test_dataset_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,27 @@ def test_encode_image(self, format):
assert decoded_img.mode == "RGB"
assert decoded_img.format == format

def test_encode_integer_png_preserves_16bit_clipping(self) -> None:
"""Preserve Pillow 12's clipped PNG pixels when encoding a mode I image."""
pixels = [-5, 0, 40000, 65535, 70000]
img = Image.new("I", (5, 1))
img.putdata(pixels)

with Image.open(
BytesIO(base64.b64decode(utils.encode_image(img, "PNG")))
) as decoded:
assert decoded.format == "PNG"
assert decoded.mode == "I;16"
assert [decoded.getpixel((x, 0)) for x in range(5)] == [
0,
0,
40000,
65535,
65535,
]
assert img.mode == "I"
assert [img.getpixel((x, 0)) for x in range(5)] == pixels

def test_encode_rgba_to_jpeg(self):
"""Test that RGBA image is converted to RGB for JPEG format."""
img = Image.new("RGBA", (2, 2), color=(255, 0, 0, 128))
Expand Down
Loading