Files
orisys-sdk/examples/pipeline/csv_recorder.py
T
2026-07-15 14:25:03 +08:00

115 lines
3.0 KiB
Python

"""CSV recorder for DD02 contact / force logging."""
from __future__ import annotations
import csv
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
CSV_COLUMNS = (
"frame_index",
"timestamp_s",
"contact_x_mm",
"contact_y_mm",
"contact_z_mm",
"normal_x",
"normal_y",
"normal_z",
"force_local_fn",
"force_local_fx",
"force_local_fy",
)
@dataclass
class CsvMeasurement:
frame_index: int
timestamp_s: float
contact_x: float
contact_y: float
contact_z: float
normal_x: float
normal_y: float
normal_z: float
force_local_fn: float
force_local_fx: float
force_local_fy: float
def measurement_to_csv_row(row: CsvMeasurement) -> dict[str, float | int]:
return {
"frame_index": row.frame_index,
"timestamp_s": row.timestamp_s,
"contact_x_mm": row.contact_x,
"contact_y_mm": row.contact_y,
"contact_z_mm": row.contact_z,
"normal_x": row.normal_x,
"normal_y": row.normal_y,
"normal_z": row.normal_z,
"force_local_fn": row.force_local_fn,
"force_local_fx": row.force_local_fx,
"force_local_fy": row.force_local_fy,
}
class CsvRecorder:
"""Write one contact/force measurement row per frame."""
def __init__(self, save_dir: str | Path = "./outputs/csv") -> None:
self.save_dir = Path(save_dir)
self._file = None
self._writer: csv.DictWriter | None = None
self._path: Path | None = None
self._row_count = 0
@property
def is_recording(self) -> bool:
return self._writer is not None
@property
def path(self) -> Path | None:
return self._path
@property
def row_count(self) -> int:
return self._row_count
def start(self, save_dir: str | Path | None = None) -> Path:
if self.is_recording:
self.stop()
if save_dir is not None:
self.save_dir = Path(save_dir)
self.save_dir.mkdir(parents=True, exist_ok=True)
stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
self._path = self.save_dir / f"dd02_record_{stamp}.csv"
self._file = self._path.open("w", newline="", encoding="utf-8")
self._writer = csv.DictWriter(self._file, fieldnames=CSV_COLUMNS)
self._writer.writeheader()
self._file.flush()
self._row_count = 0
return self._path
def write(self, row: CsvMeasurement) -> None:
if self._writer is None:
return
self._writer.writerow(measurement_to_csv_row(row))
self._row_count += 1
if self._file is not None and self._row_count % 30 == 0:
self._file.flush()
def stop(self) -> tuple[Path | None, int]:
count = self._row_count
saved_path = self._path
if self._file is not None:
self._file.flush()
self._file.close()
self._file = None
self._writer = None
self._path = None
self._row_count = 0
return saved_path, count