"""Il runner: grafo più immagine, numeri fuori. Niente HTTP, niente database.""" import pytest # The vision tree requires Python >=3.13 (vs-task/vs-measure/vs-pm2d declare it # upstream, see pyproject.toml's `vision` extra), while backend and frontend # stay on 3.11. This must run before `import numpy` - numpy itself is only # installed alongside the vision extra - so that `uv run pytest` on the # default 3.11 environment reports this module as skipped, with a reason, # instead of erroring out of the whole collection. pytest.importorskip( "visionsuite.task", reason=( "the vision tree requires Python >=3.13 (vs-task/vs-measure/vs-pm2d " "declare it upstream); run it with " "`uv run --python 3.13 --extra vision --extra dev pytest src/vision/tests`" ), ) import numpy as np from src.vision.runner import RunOutcome, engine_version, run_graph def a_disc(radius: int = 60, size: int = 400) -> np.ndarray: """Un disco chiaro su fondo scuro: un pezzo finto ma misurabile.""" image = np.zeros((size, size), dtype=np.uint8) yy, xx = np.ogrid[:size, :size] centre = size // 2 image[(yy - centre) ** 2 + (xx - centre) ** 2 <= radius ** 2] = 255 return image def a_circle_graph() -> dict: """Un grafo di due strumenti, costruito con l'API di vs-task.""" from visionsuite.task import Task from visionsuite.task.tools import DetectCircleTool, DiameterTool task = Task(name="prova") found = task.add(DetectCircleTool(name="disco")) found.center_row, found.center_col, found.radius = 200.0, 200.0, 60.0 found.scan_size = 30.0 quota = task.add(DiameterTool(name="diametro")) quota.link("circle", found, "circle") return task.to_dict() def test_the_graph_produces_named_outputs(): outcome = run_graph(a_disc(), a_circle_graph()) assert isinstance(outcome, RunOutcome) assert outcome.failures == [] # Le chiavi sono «id dello strumento punto nome dell'uscita»: è la stessa # forma che la subtask salva in vision_output. assert any(key.endswith(".diameter") for key in outcome.outputs) diameter = next(v for k, v in outcome.outputs.items() if k.endswith(".diameter")) assert diameter == pytest.approx(120.0, abs=4.0) def test_a_broken_tool_does_not_sink_the_others(): """vs-task non ferma l'esecuzione su un errore: il runner lo riporta.""" graph = a_circle_graph() graph["tools"].append( {"id": "ghost", "kind": "DetectCircle", "name": "assente", "properties": {"center_row": 5.0, "center_col": 5.0, "radius": 900.0}} ) outcome = run_graph(a_disc(), graph) assert [f.tool_id for f in outcome.failures] == ["ghost"] assert any(key.endswith(".diameter") for key in outcome.outputs) def test_the_engine_says_which_version_it_is(): version = engine_version() assert len(version) == 40 # un SHA di git, per intero