TrafficSentinelAI / violations /wrong_side.py
cricketgeek's picture
Sync from GitHub via hub-sync (part 3)
1b30630 verified
Raw
History Blame Contribute Delete
4.95 kB
"""
Wrong-Side Driving Detector.
Detects vehicles travelling against the expected flow of traffic in their assigned lane.
"""
import logging
from typing import List, Optional
import numpy as np
from config.settings import ViolationType, VIOLATION_DISPLAY_NAMES, TWO_WHEELER_CLASSES, FOUR_WHEELER_CLASSES
from config.settings import EntityClass
from core.violation_engine import ViolationRecord
from core.scene_graph import SceneGraph
from violations.base import BaseViolationDetector
logger = logging.getLogger(__name__)
class WrongSideViolationDetector(BaseViolationDetector):
"""
Detector for wrong-side driving violations.
"""
violation_type = ViolationType.WRONG_SIDE_DRIVING
def detect(
self,
scene_graph: SceneGraph,
frame: Optional[np.ndarray] = None,
) -> List[ViolationRecord]:
"""
Scan the scene graph for vehicles driving in the wrong direction.
Args:
scene_graph: Structured representation of the current frame.
frame: Optional raw BGR image.
Returns:
List of Wrong-Side driving violations.
"""
violations: List[ViolationRecord] = []
import datetime
# Expected directions by lane half (left / right of frame).
# In India, traffic flows left (Left-Hand Driving). We assume:
# - Left lanes (indices < 2) should move NORTH (away from camera).
# - Right lanes (indices >= 2) should move SOUTH (oncoming/towards camera).
expected_left_half = {"NORTH", "NORTHWEST", "NORTHEAST"}
expected_right_half = {"SOUTH", "SOUTHWEST", "SOUTHEAST"}
all_vehicle_classes = (
TWO_WHEELER_CLASSES | FOUR_WHEELER_CLASSES | {EntityClass.AUTO_RICKSHAW}
)
for node in scene_graph.nodes.values():
if node.entity_class not in all_vehicle_classes:
continue
direction = scene_graph.get_direction(node.node_id)
if direction is None or direction == "STATIONARY":
continue
# Skip nodes with negligible movement to avoid false flags from tracking jitter
import math
vector = node.attributes.get("direction_vector")
if vector is not None:
dx, dy = vector
magnitude = math.hypot(dx, dy)
if magnitude < 2.0:
continue
lane_index = node.attributes.get("lane_index")
if lane_index is None:
continue
is_left_half = lane_index < 2
wrong_side = False
if is_left_half and direction in expected_right_half:
wrong_side = True
elif not is_left_half and direction in expected_left_half:
wrong_side = True
if not wrong_side:
continue
involved = [node.node_id]
plate_node = scene_graph.get_plate_of(node.node_id)
plate_text = ""
if plate_node is not None:
involved.append(plate_node.node_id)
plate_text = plate_node.attributes.get("plate_text", "")
display_name = VIOLATION_DISPLAY_NAMES.get(
self.violation_type, "Wrong-Side Driving"
)
timestamp = datetime.datetime.now(datetime.timezone.utc).isoformat()
unique_suffix = f"{node.node_id[-3:]}-{direction}"
temp_id = f"VIO-WRONGSIDE-{unique_suffix}"
expected_dir = "SOUTH-bound" if is_left_half else "NORTH-bound"
chain = [
f"Detected {node.entity_class.value}_{node.node_id} (conf={node.confidence:.2f}) with trajectory data",
f"Computed direction: {direction} in lane {lane_index}",
f"Lane {lane_index} expected {expected_dir} flow, vehicle moving {direction} (against flow)",
f"VIOLATION: {display_name}{node.entity_class.value}_{node.node_id}",
]
violations.append(
ViolationRecord(
violation_id=temp_id,
violation_type=self.violation_type,
confidence=node.confidence * 0.8, # Heuristic penalty
involved_nodes=involved,
description=(
f"{display_name}: {node.entity_class.value} moving "
f"{direction} in lane {lane_index} (expected opposite flow)."
),
bbox=node.bbox,
timestamp=timestamp,
metadata={
"direction": direction,
"lane_index": lane_index,
"plate_text": plate_text,
"vehicle_type": node.entity_class.value,
},
reasoning_chain=chain,
)
)
return violations