diff --git a/src/pygui/backend/comparison.py b/src/pygui/backend/comparison.py index ea441f9..8072588 100644 --- a/src/pygui/backend/comparison.py +++ b/src/pygui/backend/comparison.py @@ -17,8 +17,14 @@ Returns: x = np.array(series_a, dtype=float) y = np.array(series_b, dtype=float) - if x.size == 0 or y.size == 0: - return {"distance": float('inf'), "index_a": [],"index_b": [], "warping_path": []} + # Filter out NaN/Inf values so dtw doesn't choke on them. + mask_x = np.isfinite(x) + mask_y = np.isfinite(y) + if not mask_x.any() or not mask_y.any(): + return {"distance": float('inf'), "index_a": [], "index_b": [], "warping_path": []} + + x = x[mask_x] + y = y[mask_y] alignment = dtw(x, y, keep_internals=True) return { diff --git a/src/pygui/backend/controllers/device_controller.py b/src/pygui/backend/controllers/device_controller.py index dc8c1b0..dd4a6d4 100644 --- a/src/pygui/backend/controllers/device_controller.py +++ b/src/pygui/backend/controllers/device_controller.py @@ -58,8 +58,8 @@ class DeviceController(QObject): self._data_dir = data_dir self._service = DeviceService(settings) - # Initialize status from persisted settings (with defaults for new installations) - self._connection_status = getattr(settings, 'connection_status', "Disconnected") + # Always start fresh — never restore connection status from a prior session + self._connection_status = "Disconnected" self._operation_in_progress = False self._activity_log: list[str] = getattr(settings, 'activity_log', []) self._raw_bytes: Optional[bytes] = None @@ -87,6 +87,9 @@ class DeviceController(QObject): self._append_log(f"Last wafer: {info.get('serialNumber', 'Unknown')} ({info.get('familyCode', 'Unknown')})") self.statusRestored.emit() + # Reset connection status on fresh start + self._connection_status = "Disconnected" + # Marshal worker-thread results onto the Qt main thread. self._detectFinished.connect(self._handle_detect_finished, Qt.ConnectionType.QueuedConnection) self._readFinished.connect(self._handle_read_finished, Qt.ConnectionType.QueuedConnection) @@ -101,6 +104,12 @@ class DeviceController(QObject): """Currently available serial port names.""" return self._service.enumerate_ports() + def _set_connection_status(self, status: str) -> None: + """Update connection status and notify QML.""" + if self._connection_status != status: + self._connection_status = status + self.portsUpdated.emit(self.availablePorts) + @Property(str, notify=portsUpdated) def connectionStatus(self) -> str: """Current connection status string for display.""" @@ -223,7 +232,7 @@ class DeviceController(QObject): @slot_error_boundary def clearSession(self) -> None: """Clear the current session state, allowing a clean detect.""" - self._connection_status = "Disconnected" + self._set_connection_status("Disconnected") self._selected_port = "" self._last_wafer_info = {} self._data_row_count = 0 @@ -235,6 +244,7 @@ class DeviceController(QObject): self._append_log("Session refreshed/cleared") self._save_status() + def _set_operation_progress(self, in_progress: bool) -> None: """Set operation progress state and notify QML. @@ -252,7 +262,7 @@ class DeviceController(QObject): def refreshPorts(self) -> None: """Scan for available serial ports and emit updated list.""" ports = self._service.enumerate_ports() - self._connection_status = "Disconnected" + self._set_connection_status("Disconnected") self.portsUpdated.emit(ports) self._append_log(f"Scanned {len(ports)} serial port(s)") self._save_status() @@ -271,7 +281,7 @@ class DeviceController(QObject): return self._set_operation_progress(True) - self._connection_status = "Detecting..." + self._set_connection_status("Detecting...") self.portsUpdated.emit(self._service.enumerate_ports()) self._append_log("Scanning all ports for wafer ...") @@ -295,7 +305,7 @@ class DeviceController(QObject): """Main-thread completion handler for detectWafer.""" if info is not None: self._selected_port = port or "" - self._connection_status = "Connected" + self._set_connection_status("Connected") self._append_log( f"Detected: {info.serial_number} (family={info.family_code}, " f"port={port}, runtime={info.runtime}s, cycles={info.cycle_count})" @@ -306,7 +316,7 @@ class DeviceController(QObject): self._save_status() else: self._selected_port = "" - self._connection_status = "Disconnected" + self._set_connection_status("Disconnected") self._append_log("No wafer detected on any port") self._last_wafer_info = {} self.detectResult.emit(None) @@ -334,7 +344,7 @@ class DeviceController(QObject): family_code = self._last_wafer_info.get("familyCode", "") self._set_operation_progress(True) - self._connection_status = "Reading..." + self._set_connection_status("Reading...") self.portsUpdated.emit(self._service.enumerate_ports()) self._append_log( f"Reading memory on {port} (family={family_code or 'auto'}) ..." @@ -362,7 +372,7 @@ class DeviceController(QObject): ) -> None: """Main-thread completion handler for readMemoryAsync.""" if data is not None: - self._connection_status = "Connected" + self._set_connection_status("Connected") self._append_log(f"Read {len(data)} bytes") self._raw_bytes = data self.readResult.emit({"success": True, "bytes": len(data)}) @@ -370,7 +380,7 @@ class DeviceController(QObject): self._append_log("Memory read complete - choose save directory to save CSV") self._save_status() return - self._connection_status = "Disconnected" + self._set_connection_status("Disconnected") self._append_log("Read returned no data") self._raw_bytes = None self.readResult.emit({"error": "Read returned no data"}) @@ -382,16 +392,16 @@ class DeviceController(QObject): def eraseMemory(self, port: str) -> None: """Send p1 erase command.""" self._set_operation_progress(True) - self._connection_status = "Erasing..." + self._set_connection_status("Erasing...") self.portsUpdated.emit(self._service.enumerate_ports()) self._append_log(f"Erase command sent to {port} (wafer takes ~15s) ...") ok = self._service.erase_wafer(port) if ok: - self._connection_status = "Connected" + self._set_connection_status("Connected") self._append_log("Erase command accepted — wait ~15 seconds") else: - self._connection_status = "Disconnected" + self._set_connection_status("Disconnected") self._append_log("Erase command failed") self.eraseResult.emit({"success": ok}) self._set_operation_progress(False) @@ -404,14 +414,14 @@ class DeviceController(QObject): Emits debugResult with counts on success. """ self._set_operation_progress(True) - self._connection_status = "Reading debug..." + self._set_connection_status("Reading debug...") self.portsUpdated.emit(self._service.enumerate_ports()) self._append_log(f"Reading debug data on {port} ...") # Step 1: D1 sensor data sensor_data = self._service.read_wafer_data(port, family_code="") if sensor_data is None: - self._connection_status = "Disconnected" + self._set_connection_status("Disconnected") self._append_log("D1 read failed — aborting debug read") self.debugResult.emit({"error": "D1 read failed"}) self._set_operation_progress(False) @@ -422,13 +432,13 @@ class DeviceController(QObject): # Step 2: F1 debug data debug_data = self._service.read_wafer_data(port, family_code="", cmd="F1") if debug_data is None: - self._connection_status = "Disconnected" + self._set_connection_status("Disconnected") self._append_log("F1 read failed") self.debugResult.emit({"error": "F1 read failed"}) self._set_operation_progress(False) return - self._connection_status = "Connected" + self._set_connection_status("Connected") self._append_log( f"Debug read complete: {len(sensor_data)} sensor bytes, " f"{len(debug_data)} debug bytes" diff --git a/src/pygui/backend/controllers/session_controller.py b/src/pygui/backend/controllers/session_controller.py index 4191ce7..8d54907 100644 --- a/src/pygui/backend/controllers/session_controller.py +++ b/src/pygui/backend/controllers/session_controller.py @@ -87,12 +87,7 @@ class SessionController(QObject): def _load_settings(self, settings: dict) -> None: """Restore session state from persisted settings dict.""" - last_file = settings.get("session_last_file", "") - if last_file: - try: - self.loadFile(last_file) - except Exception: - log.warning("Could not restore last session file: %s", last_file) + # Do NOT auto-restore the last session file — always start fresh. def collect_settings(self) -> dict: """Return a dict of session state to persist.""" diff --git a/src/pygui/backend/models/replay_stats.py b/src/pygui/backend/models/replay_stats.py index 7794b6e..09c7f97 100644 --- a/src/pygui/backend/models/replay_stats.py +++ b/src/pygui/backend/models/replay_stats.py @@ -2,14 +2,14 @@ import numpy as np class ReplayStatsTracker: - """Accumulates per-frames statistic acrossa replay session.""" + """Accumulates per-frame statistics across a replay session.""" def __init__(self) -> None: self.history: list[dict] = [] self.running_avg: float = 0.0 def track(self, frame_index: int, temperatures: list[float]) -> dict: - arr = np.array(temperatures, dtype = np.float32) + arr = np.array(temperatures, dtype=np.float32) clean_arr = arr[np.isfinite(arr)] if clean_arr.size == 0: @@ -24,12 +24,11 @@ class ReplayStatsTracker: } self.history.append(stats) - if self.history: - self.running_avg = float(np.mean([s["avg"] for s in self.history])) + self.running_avg = float(np.mean([s["avg"] for s in self.history])) return stats def get_trend(self) -> list[float]: - """Return list of per-frame average for trend chat""" + """Return list of per-frame averages for the trend chart.""" return [s["avg"] for s in self.history] def reset(self) -> None: