File size: 4,138 Bytes
1e05592
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
"""Phase G.0a β€” Build 8-way hazard category labels for the v3 cache.

Heuristic mapping from (source, category) β†’ hazard index, using the taxonomy
from `lkalert/models/adaptive_window.py:49-58`:
    0 = HAZARD_PEDESTRIAN
    1 = HAZARD_VRURIDER
    2 = HAZARD_VEHICLE_CROSS
    3 = HAZARD_VEHICLE_ONCOMING
    4 = HAZARD_VEHICLE_LEAD
    5 = HAZARD_WEATHER
    6 = HAZARD_INFRASTRUCTURE
    7 = HAZARD_NONE

This is an auxiliary-loss label set β€” it doesn't need to be ground truth.
The AdaptiveWindow uses hazard logits to bias window choice; even a noisy
3-way effective mapping (non_ego β†’ cross, ego_positive β†’ lead, safe β†’ none)
gives the model a meaningful inductive bias for window selection.

Output: data/policy_labels/hazard_categories_{train_9k,multisrc_val}.json
"""
from __future__ import annotations

import argparse
import json
from collections import Counter
from pathlib import Path

import torch

ROOT = Path(__file__).resolve().parents[1]


# (source, category) β†’ hazard index
# Fallback HAZARD_VEHICLE_LEAD (4) for ambiguous accident cases
def map_to_hazard(source: str, category: str) -> int:
    src = (source or "").lower()
    cat = (category or "").lower()

    # Negative / safe β†’ NONE
    if cat == "safe_neg" or cat.endswith("silent"):
        return 7

    # Non-ego cross-traffic
    if "non_ego" in cat or "cross" in cat:
        return 2  # VEHICLE_CROSS

    # Ego-involved accidents
    if "ego" in cat or cat in ("ego_alert", "ego_observe"):
        if src in ("dota",):
            return 4  # default DoTA ego = lead vehicle
        if src in ("dada",):
            return 3  # DADA ego often oncoming
        if src in ("nexar",):
            return 4  # Nexar ego mostly rear-end / lead
        return 4

    # ego_positive (Nexar / DADA) β†’ lead vehicle
    if "positive" in cat:
        return 4

    # Source-only fallbacks
    if src == "dota":
        return 4  # most DoTA cases are ego-related vehicle
    if src == "dada":
        return 3
    if src == "nexar":
        return 4
    if src == "dad":
        return 4
    return 4   # generic fallback


def build_for_cache(cache_path: Path, out_path: Path):
    cache = torch.load(cache_path, weights_only=False, map_location="cpu")
    ids = cache["ids"]
    sources = cache["source"]
    cats = cache["category"]
    n = len(ids)
    print(f"[load] {cache_path}: N={n}")

    hazard_idx = []
    for i in range(n):
        h = map_to_hazard(sources[i], cats[i])
        hazard_idx.append(h)

    dist = Counter(hazard_idx)
    print(f"  hazard dist: {dict(sorted(dist.items()))}")
    src_dist = Counter(sources)
    cat_dist = Counter(cats)
    print(f"  source dist: {dict(src_dist.most_common(8))}")
    print(f"  category dist: {dict(cat_dist.most_common(8))}")

    out = {
        "schema": "v3_hazard_labels_v1",
        "cache_path": str(cache_path),
        "n_samples": n,
        "taxonomy": {
            0: "PEDESTRIAN", 1: "VRURIDER", 2: "VEHICLE_CROSS",
            3: "VEHICLE_ONCOMING", 4: "VEHICLE_LEAD", 5: "WEATHER",
            6: "INFRASTRUCTURE", 7: "NONE",
        },
        "rule_source": "heuristic (source Γ— category) β€” auxiliary supervision",
        "labels": hazard_idx,    # parallel to cache["ids"]
        "ids": ids,
        "dist": dict(dist),
    }
    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(json.dumps(out, indent=None))
    print(f"[save] {out_path}")


def main():
    ap = argparse.ArgumentParser(description=__doc__)
    ap.add_argument("--train_cache", type=Path,
                    default=ROOT / "data/belief_cache_v3/sft_x_v3__train_9k.pt")
    ap.add_argument("--val_cache", type=Path,
                    default=ROOT / "data/belief_cache_v3/sft_x_v3__multisrc_val.pt")
    ap.add_argument("--out_dir", type=Path,
                    default=ROOT / "data/policy_labels")
    args = ap.parse_args()

    build_for_cache(
        args.train_cache, args.out_dir / "hazard_categories_train_9k.json")
    build_for_cache(
        args.val_cache, args.out_dir / "hazard_categories_multisrc_val.json")


if __name__ == "__main__":
    main()