test: ricetta DXF su pezzo asimmetrico capovolto di 180 gradi e pose_search nel grafo (rossi prima di U2), refs #127
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019A9CSrwGAJvRpprTEtGzx1
This commit is contained in:
@@ -22,6 +22,7 @@ from src.vision.costruttori import (
|
||||
DXF_FILE,
|
||||
CostruttoreError,
|
||||
_candidate_anchors,
|
||||
_graph,
|
||||
_maschera_pezzo,
|
||||
build_dxf_compare,
|
||||
)
|
||||
@@ -36,7 +37,7 @@ PX_PER_MM = 4.0
|
||||
|
||||
|
||||
def a_scene(angle_deg: float = 0.0, shift_px: tuple[float, float] = (0.0, 0.0),
|
||||
margin_mm: float = 60.0) -> np.ndarray:
|
||||
margin_mm: float = 60.0, hole_dx_mm: float = 0.0) -> np.ndarray:
|
||||
"""Il pezzo del sample.dxf (scuro su bianco) a 4 px/mm, ruotato e traslato."""
|
||||
w = int((DXF_WIDTH_MM + 2 * margin_mm) * PX_PER_MM)
|
||||
h = int((DXF_HEIGHT_MM + 2 * margin_mm) * PX_PER_MM)
|
||||
@@ -44,7 +45,8 @@ def a_scene(angle_deg: float = 0.0, shift_px: tuple[float, float] = (0.0, 0.0),
|
||||
cx, cy = w // 2, h // 2
|
||||
hw, hh = int(DXF_WIDTH_MM * PX_PER_MM / 2), int(DXF_HEIGHT_MM * PX_PER_MM / 2)
|
||||
cv2.rectangle(img, (cx - hw, cy - hh), (cx + hw, cy + hh), 0, -1)
|
||||
cv2.circle(img, (cx, cy), int(DXF_HOLE_R_MM * PX_PER_MM), 255, -1)
|
||||
# #127: hole_dx_mm sposta il foro fuori centro (pezzo asimmetrico a 180 gradi)
|
||||
cv2.circle(img, (cx + int(hole_dx_mm * PX_PER_MM), cy), int(DXF_HOLE_R_MM * PX_PER_MM), 255, -1)
|
||||
if angle_deg or any(shift_px):
|
||||
m = cv2.getRotationMatrix2D((cx, cy), angle_deg, 1.0) # >0 = antiorario a video
|
||||
m[0, 2] += shift_px[0]
|
||||
@@ -94,6 +96,63 @@ def test_ricetta_dxf_con_riferimento_gia_ruotato(tmp_path):
|
||||
assert risultato["outputs"]["dxf.points_in_pct"] > 95.0
|
||||
|
||||
|
||||
# #127: DXF asimmetrico a 180 gradi (sample.dxf e' simmetrico, non distingue il capovolgimento)
|
||||
HOLE_DX_MM = 60.0
|
||||
|
||||
|
||||
def _asymmetric_dxf(path: Path) -> bytes:
|
||||
"""Rettangolo 200x100 mm centrato nell'origine + foro r=10 mm a x=+60 mm."""
|
||||
import ezdxf
|
||||
|
||||
doc = ezdxf.new()
|
||||
msp = doc.modelspace()
|
||||
msp.add_lwpolyline([(-100, -50), (100, -50), (100, 50), (-100, 50)], close=True)
|
||||
msp.add_circle((HOLE_DX_MM, 0), DXF_HOLE_R_MM)
|
||||
doc.saveas(str(path))
|
||||
return path.read_bytes()
|
||||
|
||||
|
||||
def _pct_a_180(tmp_path, togli_quarter: bool) -> tuple[float, float]:
|
||||
"""points_in_pct della ricetta costruita a 0 gradi: (sulla scena dritta, a 180 gradi)."""
|
||||
dxf = _asymmetric_dxf(tmp_path / "asimmetrico.dxf")
|
||||
risultato = build_dxf_compare(a_scene(0.0, hole_dx_mm=HOLE_DX_MM), dxf, PX_PER_MM, band_mm=2.0)
|
||||
graph = risultato["graph"]
|
||||
if togli_quarter: # misura di controllo: senza pose_search il pezzo a 180 va capovolto
|
||||
for tool in graph["tools"]:
|
||||
tool["values"].pop("pose_search", None)
|
||||
for nome, b64 in risultato["files"].items():
|
||||
(tmp_path / nome).write_bytes(base64.b64decode(b64))
|
||||
def pct(img):
|
||||
esito = run_graph(img, graph, calibration={"px_per_mm": PX_PER_MM}, recipe_dir=tmp_path)
|
||||
assert not esito.failures, esito.failures
|
||||
return esito.outputs["dxf.points_in_pct"]
|
||||
return pct(a_scene(0.0, hole_dx_mm=HOLE_DX_MM)), pct(a_scene(180.0, hole_dx_mm=HOLE_DX_MM))
|
||||
|
||||
|
||||
def test_ricetta_dxf_su_pezzo_capovolto_di_180(tmp_path):
|
||||
# #127: con pose_search=quarter il pezzo a 180 gradi vale quanto quello dritto
|
||||
pct_0, pct_180 = _pct_a_180(tmp_path, togli_quarter=False)
|
||||
assert pct_0 >= 95.0
|
||||
assert pct_180 >= 95.0 and abs(pct_180 - pct_0) <= 2.0
|
||||
|
||||
|
||||
def test_ricetta_dxf_senza_quarter_il_pezzo_a_180_e_capovolto(tmp_path):
|
||||
# #127: controllo del test sopra - senza pose_search il disegno finisce capovolto
|
||||
_, pct_180 = _pct_a_180(tmp_path, togli_quarter=True)
|
||||
assert pct_180 < 80.0 # misurato 67,2 (il rettangolo si sovrappone lo stesso, il foro no)
|
||||
|
||||
|
||||
def test_graph_scrive_pose_search_quarter_nel_compare_dxf():
|
||||
# #127: il CompareDxf porta pose_search=quarter solo se c'e' l'ancora
|
||||
con = _graph("a" * 64, "b" * 64, 1.0, 95.0, "dark",
|
||||
{"row": 0.0, "col": 0.0, "angle_deg": 0.0}, (1.0, 2.0, 3.0))
|
||||
dxf_tool = next(t for t in con["tools"] if t["kind"] == "CompareDxf")
|
||||
assert dxf_tool["values"]["pose_search"] == "quarter"
|
||||
senza = _graph("a" * 64, "b" * 64, 1.0, 95.0, "dark",
|
||||
{"row": 0.0, "col": 0.0, "angle_deg": 0.0}, None)
|
||||
assert [t["kind"] for t in senza["tools"]] == ["LocateBlob"]
|
||||
|
||||
|
||||
def test_immagine_senza_pezzo_solleva():
|
||||
bianca = np.full((600, 800), 255, np.uint8)
|
||||
with pytest.raises(CostruttoreError, match="pezzo"):
|
||||
|
||||
Reference in New Issue
Block a user