From 7b1d9db19a731f3178dc6e4b4701c0a430c78f73 Mon Sep 17 00:00:00 2001 From: Bowen Fu <5812640+BowenFu@users.noreply.github.com> Date: Thu, 29 Oct 2026 21:30:15 +0000 Subject: [PATCH 1/2] fix(deps): require patched aiohttp and Pillow Signed-off-by: Bowen Fu <5812640+BowenFu@users.noreply.github.com> --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6da315f417..1abeeb0cc2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", @@ -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", From 779feae3741d639e5cdcaafb1e99f41cda15c8c6 Mon Sep 17 00:00:00 2001 From: Bowen Fu <5812640+BowenFu@users.noreply.github.com> Date: Fri, 30 Oct 2026 04:39:37 +0000 Subject: [PATCH 2/2] fix(dataset): preserve integer PNG encoding with Pillow 13 Convert mode I images to I;16 before PNG encoding, preserving the clipping and bytes produced by Pillow 12. Cover signed pixel bounds, source-image immutability and the custom TIFF resize/encode path. Follows the compatibility fix in ai-dynamo/aiperf@5222c1ac567227833d17586a8cf9f0223698eadf. Signed-off-by: Bowen Fu <5812640+BowenFu@users.noreply.github.com> --- src/aiperf/dataset/utils.py | 3 +++ .../dataset/generator/test_image_generator.py | 27 +++++++++++++++++++ tests/unit/dataset/test_dataset_utils.py | 21 +++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/src/aiperf/dataset/utils.py b/src/aiperf/dataset/utils.py index d8b62cdaa6..4914554553 100644 --- a/src/aiperf/dataset/utils.py +++ b/src/aiperf/dataset/utils.py @@ -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 diff --git a/tests/unit/dataset/generator/test_image_generator.py b/tests/unit/dataset/generator/test_image_generator.py index 3980809459..3c6c99e9e4 100644 --- a/tests/unit/dataset/generator/test_image_generator.py +++ b/tests/unit/dataset/generator/test_image_generator.py @@ -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), diff --git a/tests/unit/dataset/test_dataset_utils.py b/tests/unit/dataset/test_dataset_utils.py index daad45ca9d..342fc26834 100644 --- a/tests/unit/dataset/test_dataset_utils.py +++ b/tests/unit/dataset/test_dataset_utils.py @@ -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))