From 2154a58d5c7d500717ef50a3ff52a990aa3f080e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Oct 2026 17:29:43 +0000 Subject: [PATCH] Fix Python typecheck errors in graph links and EXR thumbnails Co-authored-by: MajoorWaldi <129623145+MajoorWaldi@users.noreply.github.com> --- .../features/geninfo/graph_converter.py | 4 +++- .../features/metadata/thumbnail_cache.py | 5 ++--- .../test_geninfo_subgraph_conversion.py | 13 +++++++++++++ .../test_preview_only_image_formats.py | 19 +++++++++++++++++++ 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/mjr_am_backend/features/geninfo/graph_converter.py b/mjr_am_backend/features/geninfo/graph_converter.py index 53602d68..cf2aa8db 100644 --- a/mjr_am_backend/features/geninfo/graph_converter.py +++ b/mjr_am_backend/features/geninfo/graph_converter.py @@ -559,7 +559,9 @@ def _build_link_source_map(links: Any) -> dict[Any, tuple[Any, int]]: if isinstance(link, list) and len(link) >= 3: link_to_source[link[0]] = (link[1], link[2]) elif isinstance(link, dict) and link.get("id") is not None: - link_to_source[link["id"]] = (link.get("origin_id"), link.get("origin_slot")) + origin_slot = link.get("origin_slot") + if isinstance(origin_slot, int): + link_to_source[link["id"]] = (link.get("origin_id"), origin_slot) return link_to_source diff --git a/mjr_am_backend/features/metadata/thumbnail_cache.py b/mjr_am_backend/features/metadata/thumbnail_cache.py index a9b88f78..d79ed22a 100644 --- a/mjr_am_backend/features/metadata/thumbnail_cache.py +++ b/mjr_am_backend/features/metadata/thumbnail_cache.py @@ -70,9 +70,8 @@ def _decode_exr_rgb(source: Path) -> Any | None: pixels = frame.to_ndarray(format=pixel_format) except Exception: continue - if pixels.ndim == 2: - pixels = np.stack([pixels] * 3, axis=-1) - rgb = _linear_to_srgb(pixels[..., :3]) + rgb_pixels = np.stack([pixels] * 3, axis=-1) if pixels.ndim == 2 else pixels + rgb = _linear_to_srgb(rgb_pixels[..., :3]) return (rgb * 255.0 + 0.5).astype(np.uint8) return None diff --git a/tests/features/test_geninfo_subgraph_conversion.py b/tests/features/test_geninfo_subgraph_conversion.py index 088db6be..e2e11bef 100644 --- a/tests/features/test_geninfo_subgraph_conversion.py +++ b/tests/features/test_geninfo_subgraph_conversion.py @@ -37,6 +37,19 @@ def test_dict_links_inside_subgraph_bridge_inputs_and_outputs(): assert nodes["3"]["inputs"]["images"] == ["2:6", 0] +def test_link_source_map_requires_integer_slots_for_dict_links(): + links = [ + [1, 10, 0, 20, 0, "IMAGE"], + {"id": 2, "origin_id": "node-a", "origin_slot": 0}, + {"id": 3, "origin_id": 10, "origin_slot": 2}, + {"id": 4, "origin_id": 10}, + {"id": 5, "origin_id": 10, "origin_slot": None}, + {"id": 6, "origin_id": 10, "origin_slot": "0"}, + ] + + assert gc._build_link_source_map(links) == {1: (10, 0), 2: ("node-a", 0), 3: (10, 2)} + + def test_nested_subgraph_definitions_are_expanded(): inner = _inner_subgraph("sg-inner", "Inner") outer = { diff --git a/tests/features/test_preview_only_image_formats.py b/tests/features/test_preview_only_image_formats.py index dc088731..dea6a28d 100644 --- a/tests/features/test_preview_only_image_formats.py +++ b/tests/features/test_preview_only_image_formats.py @@ -1,5 +1,6 @@ from fractions import Fraction from pathlib import Path +from unittest.mock import MagicMock import pytest from aiohttp.test_utils import make_mocked_request @@ -54,6 +55,24 @@ def test_exr_thumbnail_is_tonemapped_to_srgb_with_correct_channel_order(tmp_path assert abs(r - 188) < 10 and abs(g - 124) < 10 and abs(b - 63) < 12 +def test_decode_grayscale_exr_expands_to_rgb(tmp_path, monkeypatch): + av = pytest.importorskip("av") + np = pytest.importorskip("numpy") + pixels = np.array([[0.0, 0.5, 1.0]], dtype=np.float32) + frame = MagicMock() + frame.to_ndarray.side_effect = [ValueError("unsupported"), ValueError("unsupported"), pixels] + container = MagicMock() + container.__enter__.return_value.decode.return_value = iter([frame]) + monkeypatch.setattr(av, "open", lambda _source: container) + + rgb = thumbs._decode_exr_rgb(tmp_path / "gray.exr") + + assert rgb.shape == (1, 3, 3) + assert rgb.dtype == np.uint8 + np.testing.assert_array_equal(rgb, [[[0, 0, 0], [188, 188, 188], [255, 255, 255]]]) + assert frame.to_ndarray.call_args.kwargs == {"format": "grayf32le"} + + def test_tiff_preview_is_a_jpeg(tmp_path, thumb_dir): source = tmp_path / "scan.tif" Image.new("RGB", (40, 30), (200, 10, 10)).save(source)