Spaces:
Paused
Paused
Download sensorflow/rotr/taxonomy.py from whassan/SensorflowStudio: direct link, hf CLI and curl.
- Browser
- Download file 8.02 kB
-
https://huggingface.co/spaces/whassan/SensorflowStudio/resolve/main/sensorflow/rotr/taxonomy.py
- Command line
-
hf download hf://spaces/whassan/SensorflowStudio/sensorflow/rotr/taxonomy.py
-
curl -L -o taxonomy.py https://huggingface.co/spaces/whassan/SensorflowStudio/resolve/main/sensorflow/rotr/taxonomy.py
8.02 kB
| """Six-axis violation taxonomy, structured query, and clustering. | |
| Axes: Actor / Vulnerability / Legality / Environment / Interaction / | |
| Behavior, plus extensions RoadGeometry / TrafficControl / Visibility. | |
| The structured query turns a natural-language request such as "failed to | |
| yield to pedestrian at uncontrolled intersection during low visibility" | |
| into a filterable query object via a DETERMINISTIC keyword map (no LLM in | |
| the retrieval path), then filters violation signatures exactly. | |
| Clustering mirrors raremine's mining semantics conceptually (signature -> | |
| dedup -> diversity-aware exemplar) over ROTR-native structured signatures — | |
| own implementation, raremine's store is untouched. Exact structured | |
| matching over ~10^3 records needs a dict, not a vector DB (see the | |
| DO-NOT-BUILD decision in the architecture doc). | |
| """ | |
| from __future__ import annotations | |
| import hashlib | |
| from typing import Dict, List, Optional | |
| from pydantic import BaseModel | |
| from sensorflow.rotr.models import ROTRScenario, ROTRViolation | |
| TAXONOMY_VERSION = "rotr-taxonomy-1.0.0" | |
| AXES = ["actor", "vulnerability", "legality", "environment", "interaction", | |
| "behavior", "road_geometry", "traffic_control", "visibility"] | |
| RULE_TO_LEGALITY = { | |
| "R-YIELD-PED-01": "YIELD", | |
| "R-PATH-RESTRICT-02": "RESTRICTED_PATH", | |
| "R-LANE-MANEUVER-03": "LANE_ASSOCIATION", | |
| "R-INT-CONFLICT-04": "SIGNAL", | |
| "R-MERGE-GAP-05": "MERGE", | |
| "R-STOP-06": "STOP", | |
| } | |
| RULE_TO_BEHAVIOR = { | |
| "R-YIELD-PED-01": "proceed_without_yield", | |
| "R-PATH-RESTRICT-02": "enter_restricted_path", | |
| "R-LANE-MANEUVER-03": "unpermitted_maneuver", | |
| "R-INT-CONFLICT-04": "run_red_signal", | |
| "R-MERGE-GAP-05": "insufficient_gap_merge", | |
| "R-STOP-06": "rolling_stop", | |
| } | |
| def signature(violation: ROTRViolation, scenario: ROTRScenario) -> Dict[str, str]: | |
| material = [a for a in scenario.actors | |
| if a.actor_id in set(violation.actor_ids)] | |
| actor_cls = material[0].class_name if material else "none" | |
| vru = any(a.class_name in ("pedestrian", "cyclist") for a in material) | |
| interaction = "NONE" | |
| if material: | |
| intent = material[0].intent | |
| interaction = {"CROSSING": "CROSSING", "MERGING": "MERGING", | |
| "PROCEEDING": "FOLLOWING"}.get(intent, "OTHER") | |
| env = scenario.environment | |
| ctx = scenario.actual_context | |
| return { | |
| "actor": actor_cls, | |
| "vulnerability": "VRU" if vru else "NON_VRU", | |
| "legality": RULE_TO_LEGALITY.get(violation.rule_id, "OTHER"), | |
| "environment": f"{env.lighting}/{env.weather}", | |
| "interaction": interaction, | |
| "behavior": RULE_TO_BEHAVIOR.get(violation.rule_id, "other"), | |
| "road_geometry": ctx.intersection_type, | |
| "traffic_control": ctx.control, | |
| "visibility": env.visibility, | |
| } | |
| # ------------------------------------------------------------ structured query | |
| class ROTRQuery(BaseModel): | |
| """Filterable query object over the taxonomy axes (all optional).""" | |
| actor: Optional[str] = None | |
| vulnerability: Optional[str] = None | |
| legality: Optional[str] = None | |
| environment: Optional[str] = None | |
| interaction: Optional[str] = None | |
| behavior: Optional[str] = None | |
| road_geometry: Optional[str] = None | |
| traffic_control: Optional[str] = None | |
| visibility: Optional[str] = None | |
| lighting: Optional[str] = None | |
| weather: Optional[str] = None | |
| consequence_class: Optional[str] = None | |
| primary_layer: Optional[str] = None | |
| text: Optional[str] = None # original text, for provenance | |
| # Deterministic keyword -> axis map (retrieval path is LLM-free). | |
| _KEYWORDS = [ | |
| ("pedestrian", "actor", "pedestrian"), | |
| ("cyclist", "actor", "cyclist"), | |
| ("vulnerable", "vulnerability", "VRU"), | |
| ("yield", "legality", "YIELD"), | |
| ("restricted", "legality", "RESTRICTED_PATH"), | |
| ("bus lane", "legality", "RESTRICTED_PATH"), | |
| ("wrong lane", "legality", "LANE_ASSOCIATION"), | |
| ("lane association", "legality", "LANE_ASSOCIATION"), | |
| ("red light", "legality", "SIGNAL"), | |
| ("signal", "legality", "SIGNAL"), | |
| ("merge", "legality", "MERGE"), | |
| ("stop sign", "legality", "STOP"), | |
| ("rolling stop", "legality", "STOP"), | |
| ("uncontrolled intersection", "road_geometry", "uncontrolled"), | |
| ("controlled intersection", "road_geometry", "controlled"), | |
| ("intersection", "road_geometry", None), # weak; only if none set | |
| ("low visibility", "visibility", "low"), | |
| ("night", "lighting", "night"), | |
| ("dusk", "lighting", "dusk"), | |
| ("rain", "weather", "rain"), | |
| ("crossing", "interaction", "CROSSING"), | |
| ("safety critical", "consequence_class", "SAFETY_CRITICAL"), | |
| ] | |
| def parse_query(text: str) -> ROTRQuery: | |
| low = text.lower() | |
| fields: Dict[str, str] = {} | |
| for kw, axis, value in _KEYWORDS: | |
| if kw in low and value is not None and axis not in fields: | |
| fields[axis] = value | |
| return ROTRQuery(text=text, **fields) | |
| def matches(query: ROTRQuery, sig: Dict[str, str], | |
| consequence_class: Optional[str], | |
| primary_layer: Optional[str], | |
| environment: Dict[str, str]) -> bool: | |
| for axis in AXES: | |
| want = getattr(query, axis) | |
| if want is not None and sig.get(axis) != want: | |
| return False | |
| if query.lighting is not None and environment.get("lighting") != query.lighting: | |
| return False | |
| if query.weather is not None and environment.get("weather") != query.weather: | |
| return False | |
| if query.consequence_class is not None and \ | |
| consequence_class != query.consequence_class: | |
| return False | |
| if query.primary_layer is not None and primary_layer != query.primary_layer: | |
| return False | |
| return True | |
| # ------------------------------------------------------------ clustering | |
| CLUSTER_KEY_AXES = ["legality", "actor", "traffic_control", "behavior"] | |
| def cluster_key(sig: Dict[str, str], primary_layer: Optional[str]) -> str: | |
| parts = [sig.get(a, "?") for a in CLUSTER_KEY_AXES] + [primary_layer or "?"] | |
| return "|".join(parts) | |
| def cluster_id_for(key: str) -> str: | |
| return "cl-" + hashlib.sha256( | |
| (TAXONOMY_VERSION + "|" + key).encode()).hexdigest()[:8] | |
| def build_clusters(items: List[Dict]) -> List[Dict]: | |
| """items: [{violation_id, signature, primary_layer, consequence_class, | |
| environment}] -> recurring-pattern clusters with a diversity-aware | |
| exemplar (max environment spread, raremine-style selection concept).""" | |
| groups: Dict[str, List[Dict]] = {} | |
| for it in items: | |
| key = cluster_key(it["signature"], it.get("primary_layer")) | |
| groups.setdefault(key, []).append(it) | |
| clusters = [] | |
| for key, members in sorted(groups.items()): | |
| envs = sorted({m["signature"].get("environment", "") + | |
| "/" + m["signature"].get("visibility", "") | |
| for m in members}) | |
| cons: Dict[str, int] = {} | |
| for m in members: | |
| c = m.get("consequence_class") or "UNCLASSIFIED" | |
| cons[c] = cons.get(c, 0) + 1 | |
| # Exemplar: the member in the least-represented environment cell | |
| # (diversity-aware pick, not just the first). | |
| env_counts: Dict[str, int] = {} | |
| for m in members: | |
| e = m["signature"].get("environment", "") | |
| env_counts[e] = env_counts.get(e, 0) + 1 | |
| exemplar = min(members, | |
| key=lambda m: (env_counts[m["signature"].get("environment", "")], | |
| m["violation_id"])) | |
| clusters.append({ | |
| "cluster_id": cluster_id_for(key), | |
| "key": key, | |
| "count": len(members), | |
| "member_violation_ids": [m["violation_id"] for m in members], | |
| "exemplar_violation_id": exemplar["violation_id"], | |
| "environment_spread": envs, | |
| "consequence_distribution": cons, | |
| "taxonomy_version": TAXONOMY_VERSION, | |
| }) | |
| clusters.sort(key=lambda c: -c["count"]) | |
| return clusters | |