"""URFD 標註 CSV → ground-truth 跌倒事件區間。 URFD 的 ``urfall-cam0-falls.csv`` / ``urfall-cam0-adls.csv`` 無標頭, 欄位依序為:sequenceName, frameNumber, label, 之後為深度特徵欄(本專案不用)。 label 語意(官方):-1 = 未躺、0 = 跌落中(過渡姿態)、1 = 躺地。 GT 事件慣例(文獻對正類定義分歧,本專案明文寫死,README 同步聲明): GT 事件區間 = [第一個 label=0 的幀, 緊接其後連續 label=1 區段的最後一幀] 理由:告警依設計在「躺地持續」後觸發;若 GT 只含 label=0 的短暫過渡段, 設計正確的告警反而會被判為 miss。 """ from __future__ import annotations from pathlib import Path import pandas as pd def load_annotations(csv_path: str | Path) -> pd.DataFrame: """讀取 URFD 標註 CSV,回傳欄位 ``sequence / frame / label`` 的 DataFrame。""" df = pd.read_csv(csv_path, header=None) df = df.iloc[:, :3] df.columns = ["sequence", "frame", "label"] df["frame"] = df["frame"].astype(int) df["label"] = df["label"].astype(int) return df def gt_interval(labels: pd.DataFrame) -> tuple[int, int] | None: """單一序列的 GT 事件區間(幀號);無跌倒標記(純 ADL)回傳 None。 邊界處理: - 若 0 之後找不到任何 1(理論上不會發生):以連續 0 區段末幀收尾; - 若完全沒有 0 但有 1(不合規格的標註):以 1 區段起訖充當。 """ seq = labels.sort_values("frame") frames = seq["frame"].to_numpy() lab = seq["label"].to_numpy() zero_idx = [i for i, v in enumerate(lab) if v == 0] if not zero_idx: one_idx = [i for i, v in enumerate(lab) if v == 1] if not one_idx: return None start = one_idx[0] end = start while end + 1 < len(lab) and lab[end + 1] == 1: end += 1 return int(frames[start]), int(frames[end]) start = zero_idx[0] # 找 0 之後第一個 1,再走到該連續 1 區段結束 i = start first_one = None for j in range(start, len(lab)): if lab[j] == 1: first_one = j break if first_one is None: end = start while end + 1 < len(lab) and lab[end + 1] == 0: end += 1 return int(frames[start]), int(frames[end]) end = first_one while end + 1 < len(lab) and lab[end + 1] == 1: end += 1 return int(frames[start]), int(frames[end]) def load_gt_events(csv_path: str | Path) -> dict[str, tuple[int, int]]: """整份標註 CSV → {sequence 名: (起幀, 訖幀)};無事件的序列不在字典中。""" df = load_annotations(csv_path) out: dict[str, tuple[int, int]] = {} for name, grp in df.groupby("sequence", sort=True): interval = gt_interval(grp) if interval is not None: out[str(name)] = interval return out