File size: 2,891 Bytes
11fab85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
"""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