104 lines
3.8 KiB
Python
104 lines
3.8 KiB
Python
"""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
|