Files
CamUI/src/wafer_edge/DataSources.py
T
Your Name 02327a7a21 Add alignment state indicator and pluggable data sources
- AlignmentThresholds dataclass + VisionEngine.__init__ for injection
- alignment_state computed per snapshot (ready/aligning/fault)
- MonitorPlot circle/vector/legend driven by state color
- Status dot + label moved into POSITION VISUALIZATION panel header
- CsvSessionSource + DemoSource pluggable DataSource layer
- Synthetic DATA/ready/ and DATA/aligning/ sessions for visual testing
- Fix CAM 3 label placement (below circle, not above)
2026-05-26 14:01:47 -07:00

104 lines
4.3 KiB
Python

"""Pluggable data sources for the wafer monitor.
A DataSource produces a MonitorSnapshot on demand. Today: CSV session + synthetic
demo. Future: live MQTT from NanoBerry. App.py only talks to this interface.
"""
from __future__ import annotations
from pathlib import Path
from typing import Protocol
from .Calibration import BaselineCalibration
from .CsvSource import find_session_files, load_camera_records_from_csv
from .VisionEngine import MonitorSnapshot, VisionEngine
class DataSource(Protocol):
def latest_snapshot(self, baseline: BaselineCalibration) -> MonitorSnapshot: ...
def description(self) -> str: ...
# ---------------------------------------------------------------------------
# P2.2 — CsvSessionSource for replaying a selected session.
#
# Resolves sibling files once from the seed path using P1.1, then re-reads
# those files on every latest_snapshot() call. If discovery returns no files,
# raises FileNotFoundError so a bad operator selection is visible immediately.
# ---------------------------------------------------------------------------
class CsvSessionSource:
"""Reads a captured session from a directory of CSV files.
The operator selects the session folder (e.g. ``sessions/0540/``); this
source discovers all cameras inside it automatically. Re-reads files on
every ``latest_snapshot()`` call so "Reload Current Session" always reflects
the latest on-disk state without cache invalidation.
"""
def __init__(self, folder: Path, engine: VisionEngine) -> None:
self.folder = Path(folder)
self.engine = engine
# Discover once at construction so missing-folder errors are immediate.
paths = find_session_files(self.folder)
if not paths:
raise FileNotFoundError(f"No NanoBerry CSV files found in {self.folder}")
self._cam_count = len(paths)
def latest_snapshot(self, baseline: BaselineCalibration) -> MonitorSnapshot:
# Re-resolve on every call — supports "Reload Current Session".
paths = find_session_files(self.folder)
if not paths:
raise FileNotFoundError(f"No NanoBerry CSV files found in {self.folder}")
self._cam_count = len(paths)
records = load_camera_records_from_csv(paths, baseline)
return self.engine.snapshot_from_records(
records,
baseline,
source_label=self.description(),
)
def description(self) -> str:
return f"Session: {self.folder.name} ({self._cam_count} cams)"
# ---------------------------------------------------------------------------
# P2.3 — DemoSource as the default safe source.
#
# First launch should not depend on MASTER_SD being present or on the working
# directory. Wrapping VisionEngine.demo_snapshot() behind the same protocol
# means demo mode, CSV replay, and future MQTT all flow through P3.1 with no
# special-case read logic. The description is stable because P3.3 displays it
# directly in the status bar.
# ---------------------------------------------------------------------------
class DemoSource:
"""Synthetic perfect-world data — always works, used as fallback."""
def __init__(self, engine: VisionEngine) -> None:
self.engine = engine
def latest_snapshot(self, baseline: BaselineCalibration) -> MonitorSnapshot:
return self.engine.demo_snapshot(baseline)
def description(self) -> str:
return "Demo: Simulated Data"
# ---------------------------------------------------------------------------
# TODO P6.1: Post-scope hook for MqttSource implementing DataSource.
#
# THINKING:
# Live MQTT should enter the app as another DataSource, not by changing
# VisionEngine or App.read_wafer(). The source can own broker connection,
# background subscription, and latest-frame buffers, then convert payloads to
# CameraEdgeRecord before calling snapshot_from_records(). Keeping this as a
# P6 task protects the beta session-loader milestone from broker/threading
# risk while documenting the intended extension point.
# Cross-reference: P6.2 adds the future App.py action that constructs it.
#
# Future signature:
# class MqttSource:
# def __init__(self, broker: str, engine: VisionEngine) -> None: ...
# def latest_snapshot(self, baseline: BaselineCalibration) -> MonitorSnapshot: ...
# ---------------------------------------------------------------------------