fix: validator
This commit is contained in:
parent
791f611f7d
commit
61ac39b4f3
3 changed files with 106 additions and 95 deletions
|
@ -297,31 +297,31 @@ class WebSocketHandler:
|
||||||
async def _reconcile_subscriptions_with_tracking(self, target_subscriptions) -> dict:
|
async def _reconcile_subscriptions_with_tracking(self, target_subscriptions) -> dict:
|
||||||
"""Reconcile subscriptions with tracking integration."""
|
"""Reconcile subscriptions with tracking integration."""
|
||||||
try:
|
try:
|
||||||
# First, we need to create tracking integrations for each unique model
|
# Create separate tracking integrations for each subscription (camera isolation)
|
||||||
tracking_integrations = {}
|
tracking_integrations = {}
|
||||||
|
|
||||||
for subscription_payload in target_subscriptions:
|
for subscription_payload in target_subscriptions:
|
||||||
|
subscription_id = subscription_payload['subscriptionIdentifier']
|
||||||
model_id = subscription_payload['modelId']
|
model_id = subscription_payload['modelId']
|
||||||
|
|
||||||
# Create tracking integration if not already created
|
# Create separate tracking integration per subscription for camera isolation
|
||||||
if model_id not in tracking_integrations:
|
# Get pipeline configuration for this model
|
||||||
# Get pipeline configuration for this model
|
pipeline_parser = model_manager.get_pipeline_config(model_id)
|
||||||
pipeline_parser = model_manager.get_pipeline_config(model_id)
|
if pipeline_parser:
|
||||||
if pipeline_parser:
|
# Create tracking integration with message sender (separate instance per camera)
|
||||||
# Create tracking integration with message sender
|
tracking_integration = TrackingPipelineIntegration(
|
||||||
tracking_integration = TrackingPipelineIntegration(
|
pipeline_parser, model_manager, model_id, self._send_message
|
||||||
pipeline_parser, model_manager, model_id, self._send_message
|
)
|
||||||
)
|
|
||||||
|
|
||||||
# Initialize tracking model
|
# Initialize tracking model
|
||||||
success = await tracking_integration.initialize_tracking_model()
|
success = await tracking_integration.initialize_tracking_model()
|
||||||
if success:
|
if success:
|
||||||
tracking_integrations[model_id] = tracking_integration
|
tracking_integrations[subscription_id] = tracking_integration
|
||||||
logger.info(f"[Tracking] Created tracking integration for model {model_id}")
|
logger.info(f"[Tracking] Created isolated tracking integration for subscription {subscription_id} (model {model_id})")
|
||||||
else:
|
|
||||||
logger.warning(f"[Tracking] Failed to initialize tracking for model {model_id}")
|
|
||||||
else:
|
else:
|
||||||
logger.warning(f"[Tracking] No pipeline config found for model {model_id}")
|
logger.warning(f"[Tracking] Failed to initialize tracking for subscription {subscription_id} (model {model_id})")
|
||||||
|
else:
|
||||||
|
logger.warning(f"[Tracking] No pipeline config found for model {model_id} in subscription {subscription_id}")
|
||||||
|
|
||||||
# Now reconcile with StreamManager, adding tracking integrations
|
# Now reconcile with StreamManager, adding tracking integrations
|
||||||
current_subscription_ids = set()
|
current_subscription_ids = set()
|
||||||
|
@ -379,8 +379,8 @@ class WebSocketHandler:
|
||||||
|
|
||||||
logger.info(f"[SUBSCRIPTION_MAPPING] subscription_id='{subscription_id}' → camera_id='{camera_id}'")
|
logger.info(f"[SUBSCRIPTION_MAPPING] subscription_id='{subscription_id}' → camera_id='{camera_id}'")
|
||||||
|
|
||||||
# Get tracking integration for this model
|
# Get tracking integration for this subscription (camera-isolated)
|
||||||
tracking_integration = tracking_integrations.get(model_id)
|
tracking_integration = tracking_integrations.get(subscription_id)
|
||||||
|
|
||||||
# Extract crop coordinates if present
|
# Extract crop coordinates if present
|
||||||
crop_coords = None
|
crop_coords = None
|
||||||
|
@ -412,7 +412,7 @@ class WebSocketHandler:
|
||||||
)
|
)
|
||||||
|
|
||||||
if success and tracking_integration:
|
if success and tracking_integration:
|
||||||
logger.info(f"[Tracking] Subscription {subscription_id} configured with tracking for model {model_id}")
|
logger.info(f"[Tracking] Subscription {subscription_id} configured with isolated tracking for model {model_id}")
|
||||||
|
|
||||||
return success
|
return success
|
||||||
|
|
||||||
|
|
|
@ -389,20 +389,51 @@ class StreamManager:
|
||||||
logger.debug(f"Set session {session_id} for display {display_id}")
|
logger.debug(f"Set session {session_id} for display {display_id}")
|
||||||
|
|
||||||
def clear_session_id(self, session_id: str):
|
def clear_session_id(self, session_id: str):
|
||||||
"""Clear session ID from tracking integrations."""
|
"""Clear session ID from the specific tracking integration handling this session."""
|
||||||
with self._lock:
|
with self._lock:
|
||||||
|
# Find the subscription that's handling this session
|
||||||
|
session_subscription = None
|
||||||
for subscription_info in self._subscriptions.values():
|
for subscription_info in self._subscriptions.values():
|
||||||
if subscription_info.tracking_integration:
|
if subscription_info.tracking_integration:
|
||||||
subscription_info.tracking_integration.clear_session_id(session_id)
|
# Check if this integration is handling the given session_id
|
||||||
logger.debug(f"Cleared session {session_id}")
|
integration = subscription_info.tracking_integration
|
||||||
|
if session_id in integration.session_vehicles:
|
||||||
|
session_subscription = subscription_info
|
||||||
|
break
|
||||||
|
|
||||||
|
if session_subscription and session_subscription.tracking_integration:
|
||||||
|
session_subscription.tracking_integration.clear_session_id(session_id)
|
||||||
|
logger.debug(f"Cleared session {session_id} from subscription {session_subscription.subscription_id}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"No tracking integration found for session {session_id}, broadcasting to all subscriptions")
|
||||||
|
# Fallback: broadcast to all (original behavior)
|
||||||
|
for subscription_info in self._subscriptions.values():
|
||||||
|
if subscription_info.tracking_integration:
|
||||||
|
subscription_info.tracking_integration.clear_session_id(session_id)
|
||||||
|
|
||||||
def set_progression_stage(self, session_id: str, stage: str):
|
def set_progression_stage(self, session_id: str, stage: str):
|
||||||
"""Set progression stage for tracking integrations."""
|
"""Set progression stage for the specific tracking integration handling this session."""
|
||||||
with self._lock:
|
with self._lock:
|
||||||
|
# Find the subscription that's handling this session
|
||||||
|
session_subscription = None
|
||||||
for subscription_info in self._subscriptions.values():
|
for subscription_info in self._subscriptions.values():
|
||||||
if subscription_info.tracking_integration:
|
if subscription_info.tracking_integration:
|
||||||
subscription_info.tracking_integration.set_progression_stage(session_id, stage)
|
# Check if this integration is handling the given session_id
|
||||||
logger.debug(f"Set progression stage for session {session_id}: {stage}")
|
# We need to check the integration's active sessions
|
||||||
|
integration = subscription_info.tracking_integration
|
||||||
|
if session_id in integration.session_vehicles:
|
||||||
|
session_subscription = subscription_info
|
||||||
|
break
|
||||||
|
|
||||||
|
if session_subscription and session_subscription.tracking_integration:
|
||||||
|
session_subscription.tracking_integration.set_progression_stage(session_id, stage)
|
||||||
|
logger.debug(f"Set progression stage for session {session_id}: {stage} on subscription {session_subscription.subscription_id}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"No tracking integration found for session {session_id}, broadcasting to all subscriptions")
|
||||||
|
# Fallback: broadcast to all (original behavior)
|
||||||
|
for subscription_info in self._subscriptions.values():
|
||||||
|
if subscription_info.tracking_integration:
|
||||||
|
subscription_info.tracking_integration.set_progression_stage(session_id, stage)
|
||||||
|
|
||||||
def get_tracking_stats(self) -> Dict[str, Any]:
|
def get_tracking_stats(self) -> Dict[str, Any]:
|
||||||
"""Get tracking statistics from all subscriptions."""
|
"""Get tracking statistics from all subscriptions."""
|
||||||
|
|
|
@ -36,8 +36,14 @@ class ValidationResult:
|
||||||
|
|
||||||
class StableCarValidator:
|
class StableCarValidator:
|
||||||
"""
|
"""
|
||||||
Validates whether a tracked vehicle is stable (fueling) or just passing by.
|
Validates whether a tracked vehicle should be processed through the pipeline.
|
||||||
Uses multiple criteria including position stability, duration, and movement patterns.
|
|
||||||
|
Updated for BoT-SORT integration: Trusts the sophisticated BoT-SORT tracking algorithm
|
||||||
|
for stability determination and focuses on business logic validation:
|
||||||
|
- Duration requirements for processing
|
||||||
|
- Confidence thresholds
|
||||||
|
- Session management and cooldowns
|
||||||
|
- Camera isolation with composite keys
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config: Optional[Dict] = None):
|
def __init__(self, config: Optional[Dict] = None):
|
||||||
|
@ -169,7 +175,10 @@ class StableCarValidator:
|
||||||
|
|
||||||
def _determine_vehicle_state(self, vehicle: TrackedVehicle) -> VehicleState:
|
def _determine_vehicle_state(self, vehicle: TrackedVehicle) -> VehicleState:
|
||||||
"""
|
"""
|
||||||
Determine the current state of the vehicle based on movement patterns.
|
Determine the current state of the vehicle based on BoT-SORT tracking results.
|
||||||
|
|
||||||
|
BoT-SORT provides sophisticated tracking, so we trust its stability determination
|
||||||
|
and focus on business logic validation.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
vehicle: The tracked vehicle
|
vehicle: The tracked vehicle
|
||||||
|
@ -177,53 +186,44 @@ class StableCarValidator:
|
||||||
Returns:
|
Returns:
|
||||||
Current vehicle state
|
Current vehicle state
|
||||||
"""
|
"""
|
||||||
# Not enough data
|
# Trust BoT-SORT's stability determination
|
||||||
if len(vehicle.last_position_history) < 3:
|
if vehicle.is_stable:
|
||||||
return VehicleState.UNKNOWN
|
# Check if it's been stable long enough for processing
|
||||||
|
|
||||||
# Calculate velocity
|
|
||||||
velocity = self._calculate_velocity(vehicle)
|
|
||||||
|
|
||||||
# Get position zones
|
|
||||||
x_position = vehicle.center[0] / self.frame_width
|
|
||||||
y_position = vehicle.center[1] / self.frame_height
|
|
||||||
|
|
||||||
# Check if vehicle is stable
|
|
||||||
stability = vehicle.calculate_stability()
|
|
||||||
if stability > 0.7 and velocity < self.velocity_threshold:
|
|
||||||
# Check if it's been stable long enough
|
|
||||||
duration = time.time() - vehicle.first_seen
|
duration = time.time() - vehicle.first_seen
|
||||||
if duration > self.min_stable_duration and vehicle.stable_frames >= self.min_stable_frames:
|
if duration >= self.min_stable_duration:
|
||||||
return VehicleState.STABLE
|
return VehicleState.STABLE
|
||||||
else:
|
else:
|
||||||
return VehicleState.ENTERING
|
return VehicleState.ENTERING
|
||||||
|
|
||||||
# Check if vehicle is entering or leaving
|
# For non-stable vehicles, use simplified state determination
|
||||||
|
if len(vehicle.last_position_history) < 2:
|
||||||
|
return VehicleState.UNKNOWN
|
||||||
|
|
||||||
|
# Calculate velocity for movement classification
|
||||||
|
velocity = self._calculate_velocity(vehicle)
|
||||||
|
|
||||||
|
# Basic movement classification
|
||||||
if velocity > self.velocity_threshold:
|
if velocity > self.velocity_threshold:
|
||||||
# Determine direction based on position history
|
# Vehicle is moving - classify as passing by or entering/leaving
|
||||||
positions = np.array(vehicle.last_position_history)
|
x_position = vehicle.center[0] / self.frame_width
|
||||||
if len(positions) >= 2:
|
|
||||||
direction = positions[-1] - positions[0]
|
|
||||||
|
|
||||||
# Entering: moving towards center
|
# Simple heuristic: vehicles near edges are entering/leaving, center vehicles are passing
|
||||||
if x_position < self.entering_zone_ratio or x_position > (1 - self.entering_zone_ratio):
|
if x_position < 0.2 or x_position > 0.8:
|
||||||
if abs(direction[0]) > abs(direction[1]): # Horizontal movement
|
return VehicleState.ENTERING
|
||||||
if (x_position < 0.5 and direction[0] > 0) or (x_position > 0.5 and direction[0] < 0):
|
else:
|
||||||
return VehicleState.ENTERING
|
return VehicleState.PASSING_BY
|
||||||
|
|
||||||
# Leaving: moving away from center
|
# Low velocity but not marked stable by tracker - likely entering
|
||||||
if 0.3 < x_position < 0.7: # In center zone
|
return VehicleState.ENTERING
|
||||||
if abs(direction[0]) > abs(direction[1]): # Horizontal movement
|
|
||||||
if abs(direction[0]) > 10: # Significant movement
|
|
||||||
return VehicleState.LEAVING
|
|
||||||
|
|
||||||
return VehicleState.PASSING_BY
|
|
||||||
|
|
||||||
return VehicleState.UNKNOWN
|
|
||||||
|
|
||||||
def _validate_stable_vehicle(self, vehicle: TrackedVehicle) -> ValidationResult:
|
def _validate_stable_vehicle(self, vehicle: TrackedVehicle) -> ValidationResult:
|
||||||
"""
|
"""
|
||||||
Perform detailed validation of a stable vehicle.
|
Perform business logic validation of a stable vehicle.
|
||||||
|
|
||||||
|
Since BoT-SORT already determined the vehicle is stable, we focus on:
|
||||||
|
- Duration requirements for processing
|
||||||
|
- Confidence thresholds
|
||||||
|
- Business logic constraints
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
vehicle: The stable vehicle to validate
|
vehicle: The stable vehicle to validate
|
||||||
|
@ -231,7 +231,7 @@ class StableCarValidator:
|
||||||
Returns:
|
Returns:
|
||||||
Detailed validation result
|
Detailed validation result
|
||||||
"""
|
"""
|
||||||
# Check duration
|
# Check duration (business requirement)
|
||||||
duration = time.time() - vehicle.first_seen
|
duration = time.time() - vehicle.first_seen
|
||||||
if duration < self.min_stable_duration:
|
if duration < self.min_stable_duration:
|
||||||
return ValidationResult(
|
return ValidationResult(
|
||||||
|
@ -243,18 +243,7 @@ class StableCarValidator:
|
||||||
track_id=vehicle.track_id
|
track_id=vehicle.track_id
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check frame count
|
# Check confidence (business requirement)
|
||||||
if vehicle.stable_frames < self.min_stable_frames:
|
|
||||||
return ValidationResult(
|
|
||||||
is_valid=False,
|
|
||||||
state=VehicleState.STABLE,
|
|
||||||
confidence=0.6,
|
|
||||||
reason=f"Not enough stable frames ({vehicle.stable_frames} < {self.min_stable_frames})",
|
|
||||||
should_process=False,
|
|
||||||
track_id=vehicle.track_id
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check confidence
|
|
||||||
if vehicle.avg_confidence < self.min_confidence:
|
if vehicle.avg_confidence < self.min_confidence:
|
||||||
return ValidationResult(
|
return ValidationResult(
|
||||||
is_valid=False,
|
is_valid=False,
|
||||||
|
@ -265,28 +254,19 @@ class StableCarValidator:
|
||||||
track_id=vehicle.track_id
|
track_id=vehicle.track_id
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check position variance
|
# Trust BoT-SORT's stability determination - skip position variance check
|
||||||
variance = self._calculate_position_variance(vehicle)
|
# BoT-SORT's sophisticated tracking already ensures consistent positioning
|
||||||
if variance > self.position_variance_threshold:
|
|
||||||
return ValidationResult(
|
|
||||||
is_valid=False,
|
|
||||||
state=VehicleState.STABLE,
|
|
||||||
confidence=0.7,
|
|
||||||
reason=f"Position variance too high ({variance:.1f} > {self.position_variance_threshold})",
|
|
||||||
should_process=False,
|
|
||||||
track_id=vehicle.track_id
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check state history consistency
|
# Simplified state history check - just ensure recent stability
|
||||||
if vehicle.track_id in self.validation_history:
|
if vehicle.track_id in self.validation_history:
|
||||||
history = self.validation_history[vehicle.track_id][-5:] # Last 5 states
|
history = self.validation_history[vehicle.track_id][-3:] # Last 3 states
|
||||||
stable_count = sum(1 for s in history if s == VehicleState.STABLE)
|
stable_count = sum(1 for s in history if s == VehicleState.STABLE)
|
||||||
if stable_count < 3:
|
if len(history) >= 2 and stable_count == 0: # Only fail if clear instability
|
||||||
return ValidationResult(
|
return ValidationResult(
|
||||||
is_valid=False,
|
is_valid=False,
|
||||||
state=VehicleState.STABLE,
|
state=VehicleState.STABLE,
|
||||||
confidence=0.7,
|
confidence=0.7,
|
||||||
reason="Inconsistent state history",
|
reason="Recent state history shows instability",
|
||||||
should_process=False,
|
should_process=False,
|
||||||
track_id=vehicle.track_id
|
track_id=vehicle.track_id
|
||||||
)
|
)
|
||||||
|
@ -298,7 +278,7 @@ class StableCarValidator:
|
||||||
is_valid=True,
|
is_valid=True,
|
||||||
state=VehicleState.STABLE,
|
state=VehicleState.STABLE,
|
||||||
confidence=vehicle.avg_confidence,
|
confidence=vehicle.avg_confidence,
|
||||||
reason="Vehicle is stable and ready for processing",
|
reason="Vehicle is stable and ready for processing (BoT-SORT validated)",
|
||||||
should_process=True,
|
should_process=True,
|
||||||
track_id=vehicle.track_id
|
track_id=vehicle.track_id
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue