"""Append live frames""" from __future__ import annotations from datetime import datetime from pathlib import Path from typing import Optional, TextIO from pygui.backend.models.frame import Frame from pygui.backend.wafer.zwafer_models import Sensor class CsvRecorder: def __init__(self) -> None: self._file_handle: Optional[TextIO] = None self._path: Optional[str] = None @property def is_recording(self) -> bool: return self._file_handle is not None def start(self, path: str, sensors: list[Sensor], serial: str = "") -> None: Path(path).parent.mkdir(parents=True, exist_ok=True) file_handle = open(path, "w", encoding="utf-8", newline="") file_handle.write(f"Wafer ID={serial}\n") file_handle.write(f"Acquisition Date={datetime.now():%m/%d/%Y}\n") file_handle.write("Label," + ",".join(s.label for s in sensors) + "\n") file_handle.write("X (mm)," + ",".join(str(s.x) for s in sensors) + "\n") file_handle.write("Y (mm)," + ",".join(str(s.y) for s in sensors) + "\n") file_handle.write("data\n") file_handle.flush() self._file_handle, self._path = file_handle, path def write(self, frame: Frame) -> None: if self._file_handle is None: return row = [f"{frame.time:.3f}"] + [f"{v:.3f}" for v in frame.values] self._file_handle.write(",".join(row) + "\n") self._file_handle.flush() def stop(self) -> Optional[str]: if self._file_handle is None: return None self._file_handle.close() path, self._file_handle, self._path = self._path, None, None return path @staticmethod def write_segment(file_path: str, source_path: str, start_frame: int, end_frame: int) -> bool: """Copy a range of frames from source CSV into a new file. Preserves the source's header structure so the segment re-imports through FileBrowser unchanged: official CSVs keep their sensor-name row, Z-wafer CSVs keep every metadata line through the "data" sentinel. Frame indices count only valid data rows, matching how the readers in data_records.py number frames. Args: file_path: Output CSV path. source_path: Source CSV path with full wafer data start_frame: First frame index (inclusive). end_frame: Last frame index (inclusive) Returns: True on success (at least one frame written). """ from pygui.backend.data.data_records import is_data_row, is_official_csv if start_frame < 0 or end_frame < start_frame: return False with open(source_path, "r", encoding="utf-8") as f: lines = f.readlines() out: list[str] = [] written = 0 frame_idx = -1 in_data = is_official_csv(source_path) for i, line in enumerate(lines): stripped = line.rstrip().rstrip(",").strip() if not in_data: out.append(line) if stripped and stripped.split(",")[0].lower() == "data": in_data = True continue if i == 0: # official format: row 0 is the sensor-name header out.append(line) continue if not stripped or not is_data_row(stripped): continue frame_idx += 1 if start_frame <= frame_idx <= end_frame: out.append(line if line.endswith("\n") else line + "\n") written += 1 if written == 0: return False Path(file_path).parent.mkdir(parents=True, exist_ok=True) with open(file_path, "w", encoding="utf-8", newline="") as f: f.writelines(out) return True