Spaces:
Paused
Paused
Download sensorflow/rotr/attribution.py from whassan/SensorflowStudio: direct link, hf CLI and curl.
- Browser
- Download file 15.4 kB
-
https://huggingface.co/spaces/whassan/SensorflowStudio/resolve/main/sensorflow/rotr/attribution.py
- Command line
-
hf download hf://spaces/whassan/SensorflowStudio/sensorflow/rotr/attribution.py
-
curl -L -o attribution.py https://huggingface.co/spaces/whassan/SensorflowStudio/resolve/main/sensorflow/rotr/attribution.py
15.4 kB
| """Per-violation causal-layer attribution. | |
| THE invariant of this module: a downstream failure NEVER auto-implies an | |
| upstream cause. Every causal layer gets an INDEPENDENT evidence test that | |
| returns SUPPORTED, RULED_OUT, or UNKNOWN; a layer is implicated only by its | |
| own positive evidence, never by another layer's silence. Missing evidence | |
| is UNKNOWN — an honest triage outcome, not a defect. | |
| The planning test is the keystone of perception/planning separation: the | |
| plan is re-judged by the SAME rule engine, but against the stack's OWN | |
| world view (mapped context, detections it actually made, intents it | |
| actually predicted, in its believed localization frame). A compliant plan | |
| rules planning out; a non-compliant plan given the stack's own information | |
| implicates planning regardless of what perception did. | |
| """ | |
| from __future__ import annotations | |
| from typing import Dict, List, Optional | |
| from sensorflow.rotr import SOFTWARE_VERSION | |
| from sensorflow.rotr.models import ( | |
| CAUSAL_LAYERS, Actor, FailureAttribution, LayerEvidence, Provenance, | |
| ROTRScenario, ROTRViolation, | |
| ) | |
| from sensorflow.rotr.rules import plan_violations | |
| ATTRIBUTION_VERSION = "rotr-attr-1.0.0" | |
| # ILLUSTRATIVE evidence thresholds (synthetic substrate). | |
| DETECTION_RATE_RULED_OUT = 0.8 | |
| DETECTION_RATE_SUPPORTED = 0.5 | |
| POSITION_ERROR_SUPPORTED_M = 1.0 | |
| LOCALIZATION_ERROR_SUPPORTED_M = 1.0 | |
| CONTROL_POS_DEV_SUPPORTED_M = 1.5 | |
| CONTROL_SPEED_DEV_SUPPORTED_MPS = 2.0 | |
| def _material_actors(scenario: ROTRScenario, | |
| violation: ROTRViolation) -> List[Actor]: | |
| ids = set(violation.actor_ids) | |
| return [a for a in scenario.actors if a.actor_id in ids] | |
| # ------------------------------------------------------------ layer tests | |
| def _test_perception(scenario: ROTRScenario, | |
| violation: ROTRViolation) -> LayerEvidence: | |
| actors = _material_actors(scenario, violation) | |
| if not actors: | |
| return LayerEvidence( | |
| layer="perception", status="RULED_OUT", confidence=0.7, | |
| evidence="rule trigger is ego/map-geometric; no actor whose " | |
| "perception could have caused it") | |
| # Judge detection quality in the window that mattered: from the actor | |
| # becoming relevant (within 60 m) up to the violation instant. | |
| worst: Optional[LayerEvidence] = None | |
| for actor in actors: | |
| n_frames = n_detected = n_absent = 0 | |
| pos_err_sum = 0.0 | |
| for i, s in enumerate(actor.states): | |
| e = scenario.ego[min(i, len(scenario.ego) - 1)] | |
| if s.t > violation.t_end + 0.5: | |
| break | |
| if abs(s.x - e.x) > 60 or abs(s.y - e.y) > 30: | |
| continue | |
| n_frames += 1 | |
| if s.observation is None: | |
| n_absent += 1 | |
| elif s.observation.detected: | |
| n_detected += 1 | |
| if s.observation.x is not None: | |
| pos_err_sum += abs(s.observation.x - s.x) + \ | |
| abs((s.observation.y or s.y) - s.y) | |
| if n_frames == 0: | |
| continue | |
| if n_absent / n_frames > 0.5: | |
| cand = LayerEvidence( | |
| layer="perception", status="UNKNOWN", confidence=0.0, | |
| evidence=f"{actor.actor_id}: perception telemetry missing for " | |
| f"{n_absent}/{n_frames} relevant frames") | |
| else: | |
| assessable = n_frames - n_absent | |
| rate = n_detected / assessable if assessable else 0.0 | |
| mean_err = pos_err_sum / n_detected if n_detected else 0.0 | |
| if rate < DETECTION_RATE_SUPPORTED: | |
| cand = LayerEvidence( | |
| layer="perception", status="SUPPORTED", confidence=0.9, | |
| evidence=f"{actor.actor_id}: missed detection — GT visible " | |
| f"{assessable} frames, detected {n_detected} " | |
| f"(rate {rate:.2f})") | |
| elif mean_err > POSITION_ERROR_SUPPORTED_M: | |
| cand = LayerEvidence( | |
| layer="perception", status="SUPPORTED", confidence=0.75, | |
| evidence=f"{actor.actor_id}: detected but mislocalized " | |
| f"(mean |err| {mean_err:.2f} m)") | |
| elif rate >= DETECTION_RATE_RULED_OUT: | |
| cand = LayerEvidence( | |
| layer="perception", status="RULED_OUT", confidence=0.9, | |
| evidence=f"{actor.actor_id}: detected {n_detected}/" | |
| f"{assessable} relevant frames (rate {rate:.2f}, " | |
| f"mean |err| {mean_err:.2f} m) — GT-vs-detected " | |
| f"diff is clean") | |
| else: | |
| cand = LayerEvidence( | |
| layer="perception", status="SUPPORTED", confidence=0.6, | |
| evidence=f"{actor.actor_id}: degraded detection " | |
| f"(rate {rate:.2f})") | |
| worst = _worse(worst, cand) | |
| return worst or LayerEvidence(layer="perception", status="UNKNOWN", | |
| evidence="no relevant frames to assess") | |
| def _worse(a: Optional[LayerEvidence], b: LayerEvidence) -> LayerEvidence: | |
| """Prefer SUPPORTED > UNKNOWN > RULED_OUT when merging actor evidence.""" | |
| if a is None: | |
| return b | |
| order = {"SUPPORTED": 2, "UNKNOWN": 1, "RULED_OUT": 0} | |
| if order[b.status] > order[a.status]: | |
| return b | |
| if order[b.status] == order[a.status] and b.confidence > a.confidence: | |
| return b | |
| return a | |
| def _test_prediction(scenario: ROTRScenario, | |
| violation: ROTRViolation) -> LayerEvidence: | |
| actors = _material_actors(scenario, violation) | |
| if not actors: | |
| return LayerEvidence(layer="prediction", status="RULED_OUT", | |
| confidence=0.7, | |
| evidence="no actor whose intent needed predicting") | |
| worst: Optional[LayerEvidence] = None | |
| for actor in actors: | |
| if actor.predicted_intent is None: | |
| cand = LayerEvidence(layer="prediction", status="UNKNOWN", | |
| evidence=f"{actor.actor_id}: no recorded " | |
| "intent prediction") | |
| elif actor.predicted_intent != actor.intent: | |
| cand = LayerEvidence( | |
| layer="prediction", status="SUPPORTED", confidence=0.85, | |
| evidence=f"{actor.actor_id}: GT intent {actor.intent} but " | |
| f"stack predicted {actor.predicted_intent}") | |
| else: | |
| cand = LayerEvidence( | |
| layer="prediction", status="RULED_OUT", confidence=0.85, | |
| evidence=f"{actor.actor_id}: predicted intent matches GT " | |
| f"({actor.intent})") | |
| worst = _worse(worst, cand) | |
| return worst | |
| def _test_planning(scenario: ROTRScenario, | |
| violation: ROTRViolation) -> LayerEvidence: | |
| if not scenario.planned: | |
| return LayerEvidence(layer="planning", status="UNKNOWN", | |
| evidence="no planned trajectory recorded") | |
| hits = plan_violations(scenario) | |
| same_rule = [h for h in hits if h["rule_id"] == violation.rule_id] | |
| if same_rule: | |
| return LayerEvidence( | |
| layer="planning", status="SUPPORTED", confidence=0.85, | |
| evidence=f"plan violates {violation.rule_id} given the stack's " | |
| f"OWN world view (detections + predicted intents + " | |
| f"believed frame): {same_rule[0]['evidence']}") | |
| if hits: | |
| return LayerEvidence( | |
| layer="planning", status="SUPPORTED", confidence=0.6, | |
| evidence="plan violates a different rule given the stack's own " | |
| f"view: {hits[0]['rule_id']}") | |
| return LayerEvidence( | |
| layer="planning", status="RULED_OUT", confidence=0.85, | |
| evidence="plan is rule-compliant given the stack's own world view — " | |
| "the plan was reasonable for what the stack knew") | |
| def _test_localization(scenario: ROTRScenario, | |
| violation: ROTRViolation) -> LayerEvidence: | |
| errs = [] | |
| for e in scenario.ego: | |
| if e.believed_x is None or e.believed_y is None: | |
| return LayerEvidence(layer="localization", status="UNKNOWN", | |
| evidence="no believed-pose telemetry") | |
| errs.append(abs(e.believed_x - e.x) + abs(e.believed_y - e.y)) | |
| max_err = max(errs) if errs else 0.0 | |
| lane_mismatch = any(e.believed_lane_id != e.lane_id for e in scenario.ego | |
| if e.lane_id and e.believed_lane_id) | |
| if max_err > LOCALIZATION_ERROR_SUPPORTED_M or lane_mismatch: | |
| return LayerEvidence( | |
| layer="localization", status="SUPPORTED", confidence=0.9, | |
| evidence=f"believed-vs-true pose error up to {max_err:.2f} m" | |
| + ("; believed lane differs from true lane" | |
| if lane_mismatch else "")) | |
| return LayerEvidence(layer="localization", status="RULED_OUT", | |
| confidence=0.9, | |
| evidence=f"pose error <= {max_err:.2f} m; lane " | |
| "association consistent") | |
| def _test_map(scenario: ROTRScenario, | |
| violation: ROTRViolation) -> LayerEvidence: | |
| m, a = scenario.map_context, scenario.actual_context | |
| diffs = [] | |
| if m.control != a.control: | |
| diffs.append(f"control mapped={m.control!r} actual={a.control!r}") | |
| if m.signal_state_for_ego != a.signal_state_for_ego: | |
| diffs.append(f"signal mapped={m.signal_state_for_ego!r} " | |
| f"actual={a.signal_state_for_ego!r}") | |
| a_lanes = {l.lane_id: l for l in a.lanes} | |
| for lane in m.lanes: | |
| al = a_lanes.get(lane.lane_id) | |
| if al is None: | |
| diffs.append(f"lane {lane.lane_id} not in actual geometry") | |
| continue | |
| if lane.restricted_to != al.restricted_to: | |
| diffs.append(f"lane {lane.lane_id} restriction mapped=" | |
| f"{lane.restricted_to!r} actual={al.restricted_to!r}") | |
| if set(lane.permitted_maneuvers) != set(al.permitted_maneuvers): | |
| diffs.append(f"lane {lane.lane_id} permitted maneuvers differ") | |
| if diffs: | |
| return LayerEvidence(layer="map", status="SUPPORTED", confidence=0.9, | |
| evidence="map-vs-actual mismatch: " + "; ".join(diffs)) | |
| return LayerEvidence(layer="map", status="RULED_OUT", confidence=0.9, | |
| evidence="mapped context matches as-built geometry") | |
| def _test_control(scenario: ROTRScenario, | |
| violation: ROTRViolation) -> LayerEvidence: | |
| if not scenario.planned: | |
| return LayerEvidence(layer="control", status="UNKNOWN", | |
| evidence="no planned trajectory to compare " | |
| "execution against") | |
| n = min(len(scenario.planned), len(scenario.ego)) | |
| max_pos = max_spd = 0.0 | |
| for k in range(n): | |
| p, e = scenario.planned[k], scenario.ego[k] | |
| max_pos = max(max_pos, abs(p.x - e.x) + abs(p.y - e.y)) | |
| max_spd = max(max_spd, abs(p.v - e.speed)) | |
| if max_pos > CONTROL_POS_DEV_SUPPORTED_M or \ | |
| max_spd > CONTROL_SPEED_DEV_SUPPORTED_MPS: | |
| return LayerEvidence( | |
| layer="control", status="SUPPORTED", confidence=0.85, | |
| evidence=f"executed trajectory deviates from plan: max position " | |
| f"dev {max_pos:.2f} m, max speed dev {max_spd:.2f} m/s") | |
| return LayerEvidence( | |
| layer="control", status="RULED_OUT", confidence=0.85, | |
| evidence=f"execution tracks the plan (max pos dev {max_pos:.2f} m, " | |
| f"max speed dev {max_spd:.2f} m/s)") | |
| def _test_data_label(scenario: ROTRScenario, | |
| violation: ROTRViolation) -> LayerEvidence: | |
| problems = [] | |
| ts = [e.t for e in scenario.ego] | |
| if any(t2 <= t1 for t1, t2 in zip(ts, ts[1:])): | |
| problems.append("ego timestamps not strictly increasing") | |
| for a in scenario.actors: | |
| if len(a.states) != len(scenario.ego): | |
| problems.append(f"{a.actor_id}: state array length mismatch") | |
| if problems: | |
| return LayerEvidence(layer="data_label", status="SUPPORTED", | |
| confidence=0.95, | |
| evidence="record integrity failed: " | |
| + "; ".join(problems)) | |
| return LayerEvidence(layer="data_label", status="RULED_OUT", | |
| confidence=0.9, | |
| evidence="record integrity checks pass " | |
| "(monotone timestamps, consistent arrays)") | |
| def _test_policy_rule(others: Dict[str, LayerEvidence]) -> LayerEvidence: | |
| statuses = {le.status for le in others.values()} | |
| if statuses == {"RULED_OUT"}: | |
| return LayerEvidence( | |
| layer="policy_rule", status="SUPPORTED", confidence=0.4, | |
| evidence="every stack layer independently RULED_OUT — the rule " | |
| "itself needs HITL review (possible rule misfire)") | |
| if "SUPPORTED" in statuses: | |
| return LayerEvidence(layer="policy_rule", status="RULED_OUT", | |
| confidence=0.7, | |
| evidence="a stack layer has positive evidence; " | |
| "no indication the rule misfired") | |
| return LayerEvidence(layer="policy_rule", status="UNKNOWN", | |
| evidence="stack evidence incomplete; rule quality " | |
| "cannot be judged") | |
| # ------------------------------------------------------------ entry point | |
| def attribute(scenario: ROTRScenario, | |
| violation: ROTRViolation) -> FailureAttribution: | |
| layers: Dict[str, LayerEvidence] = { | |
| "perception": _test_perception(scenario, violation), | |
| "prediction": _test_prediction(scenario, violation), | |
| "planning": _test_planning(scenario, violation), | |
| "localization": _test_localization(scenario, violation), | |
| "map": _test_map(scenario, violation), | |
| "control": _test_control(scenario, violation), | |
| "data_label": _test_data_label(scenario, violation), | |
| } | |
| layers["policy_rule"] = _test_policy_rule(layers) | |
| supported = [(name, le) for name, le in layers.items() | |
| if le.status == "SUPPORTED"] | |
| primary = None | |
| if supported: | |
| supported.sort(key=lambda kv: (-kv[1].confidence, | |
| CAUSAL_LAYERS.index(kv[0]))) | |
| primary = supported[0][0] | |
| note = ("no layer has positive evidence — triaged to HITL, NOT " | |
| "defaulted to perception" if primary is None else | |
| f"primary={primary} by highest-confidence positive evidence; " | |
| f"{len(supported)} layer(s) SUPPORTED") | |
| return FailureAttribution( | |
| violation_id=violation.violation_id, | |
| scenario_id=scenario.scenario_id, | |
| layers=layers, primary_layer=primary, note=note, | |
| provenance=Provenance( | |
| scenario_id=scenario.scenario_id, | |
| dataset_version=scenario.provenance.dataset_version, | |
| model_version=scenario.provenance.model_version, | |
| software_version=f"{SOFTWARE_VERSION}/{ATTRIBUTION_VERSION}", | |
| source=scenario.provenance.source)) | |