diff --git a/src/atm/main.py b/src/atm/main.py index d74d611..321cdea 100644 --- a/src/atm/main.py +++ b/src/atm/main.py @@ -62,6 +62,9 @@ def main(argv=None) -> None: metavar="PATH", help="Journal JSONL file (default: trades.jsonl)", ) + # debug + sub.add_parser("debug", help="Capture one frame, run detector, annotate + print result") + # report p_report = sub.add_parser("report", help="Print weekly performance report") p_report.add_argument( @@ -80,6 +83,7 @@ def main(argv=None) -> None: "label": _cmd_label, "dryrun": _cmd_dryrun, "run": _cmd_run, + "debug": _cmd_debug, "journal": _cmd_journal, "report": _cmd_report, } @@ -140,6 +144,72 @@ def _cmd_journal(args) -> None: j.add(entry) +def _cmd_debug(args) -> None: + """One-shot capture + detect. Saves annotated PNG. Prints classification.""" + from datetime import datetime + try: + from atm.detector import Detector + from atm.vision import crop_roi + except ImportError as exc: + sys.exit(f"debug module deps missing: {exc}") + + cfg = Config.load_current(Path("configs")) + capture = _build_capture(cfg) + frame = capture() + if frame is None: + sys.exit("capture returned None (window not found or region invalid)") + + out_dir = Path("logs") + out_dir.mkdir(exist_ok=True) + ts = datetime.now().strftime("%Y%m%d_%H%M%S") + + import cv2 # type: ignore[import-untyped] + full_path = out_dir / f"debug_full_{ts}.png" + cv2.imwrite(str(full_path), frame) + print(f"captured frame: {frame.shape} → {full_path}") + + # crop dot_roi + run detector + try: + dot_crop = crop_roi(frame, cfg.dot_roi) + dot_path = out_dir / f"debug_dot_roi_{ts}.png" + cv2.imwrite(str(dot_path), dot_crop) + print(f"dot_roi crop: {dot_crop.shape} → {dot_path}") + except Exception as exc: + sys.exit(f"dot_roi crop failed: {exc}") + + det = Detector(cfg, capture) + res = det.step(0.0) + print("---") + print(f"window_found: {res.window_found}") + print(f"dot_found: {res.dot_found}") + if res.match is not None: + print(f"rgb: {res.rgb}") + print(f"classified: {res.match.name} distance={res.match.distance:.1f} confidence={res.match.confidence:.2f}") + else: + print("no match (dot not found or UNKNOWN classification)") + print(f"accepted: {res.accepted} color={res.color}") + + # Annotate: draw dot_roi rect + mark detected dot + annotated = frame.copy() + x, y, w, h = cfg.dot_roi.x, cfg.dot_roi.y, cfg.dot_roi.w, cfg.dot_roi.h + cv2.rectangle(annotated, (x, y), (x + w, y + h), (0, 255, 255), 2) + if res.dot_found and res.match is not None: + # re-find dot to know exact x,y for annotation + from atm.vision import find_rightmost_dot + bg_rgb = (18, 18, 18) + if "background" in cfg.colors: + bg_rgb = cfg.colors["background"].rgb + dot_crop = crop_roi(frame, cfg.dot_roi) + pos = find_rightmost_dot(dot_crop, bg_rgb) + if pos is not None: + dx, dy = pos + cx, cy = x + dx, y + dy + cv2.circle(annotated, (cx, cy), 12, (0, 0, 255), 3) + annotated_path = out_dir / f"debug_annotated_{ts}.png" + cv2.imwrite(str(annotated_path), annotated) + print(f"annotated: {annotated_path}") + + def _cmd_report(args) -> None: try: from atm.journal import Journal