Source code for nml_hand_exo.calibration.rom

"""Pure calculations and output naming for range-of-motion assessments."""

from __future__ import annotations

from pathlib import Path

from nml_hand_exo._paths import ROM_OUTPUT_DIR


[docs] def build_motor_orientation( profile: dict | None, motor_names: list[str] ) -> dict[str, dict[str, float | bool]]: """Return home/flip metadata for every requested motor.""" orientation = {} profile_motors = (profile or {}).get("motors", {}) for name in motor_names: motor = profile_motors.get(name, {}) orientation[name] = { "home": float(motor.get("home", 0.0)), "flip": bool(motor.get("flip", False)), } return orientation
[docs] def normalize_angle(absolute: float, home: float, flip: bool) -> float: """Normalize an encoder angle to zero-at-home, positive-flexion space.""" return home - absolute if flip else absolute - home
[docs] def determine_run_number( participant: str, date_str: str, output_dir: str | Path = ROM_OUTPUT_DIR, ) -> int: """Return the next run number for a participant and date.""" directory = Path(output_dir) prefix = f"{participant}_rom_{date_str}_" run = 1 if not directory.is_dir(): return run for path in directory.iterdir(): if not path.is_file() or not path.name.startswith(prefix): continue if path.suffix.lower() != ".csv": continue run_text = path.name.removeprefix(prefix).removesuffix(path.suffix) try: run = max(run, int(run_text) + 1) except ValueError: continue return run