update/car-in_no_fueling//next: car-in_fueling

This commit is contained in:
Pongsatorn 2025-08-29 19:05:10 +07:00
parent 5875b76d74
commit 72eb7d55ea
7 changed files with 4184 additions and 357 deletions

View file

@ -681,7 +681,7 @@ def run_detection_with_tracking(frame, node, context=None):
# Update stability tracking even when no detection (to reset counters)
camera_id = context.get("camera_id", "unknown") if context else "unknown"
model_id = node.get("modelId", "unknown")
track_validation_result = update_single_track_stability(node, None, camera_id, frame.shape, stability_threshold)
track_validation_result = update_single_track_stability(node, None, camera_id, frame.shape, stability_threshold, context)
# Store validation state in context for pipeline decisions
if context is not None:
@ -745,7 +745,7 @@ def run_detection_with_tracking(frame, node, context=None):
# Update stability tracking even when no detection (to reset counters)
camera_id = context.get("camera_id", "unknown") if context else "unknown"
model_id = node.get("modelId", "unknown")
track_validation_result = update_single_track_stability(node, None, camera_id, frame.shape, stability_threshold)
track_validation_result = update_single_track_stability(node, None, camera_id, frame.shape, stability_threshold, context)
# Store validation state in context for pipeline decisions
if context is not None:
@ -813,7 +813,7 @@ def run_detection_with_tracking(frame, node, context=None):
model_id = node.get("modelId", "unknown")
# Update stability tracking for the single best detection
track_validation_result = update_single_track_stability(node, best_detection, camera_id, frame.shape, stability_threshold)
track_validation_result = update_single_track_stability(node, best_detection, camera_id, frame.shape, stability_threshold, context)
# Store validation state in context for pipeline decisions
if context is not None:
@ -866,14 +866,8 @@ def get_camera_stability_data(camera_id, model_id):
"waiting_for_backend_session": False,
"wait_start_time": 0.0,
"reset_tracker_on_resume": False
},
"occupancy_state": {
"phase": "validation", # "validation", "waiting_for_session", or "occupancy"
"absence_counter": 0, # Count consecutive frames without stable tracks
"max_absence_frames": 3, # Trigger "none" after this many absent frames
"pipeline_completed": False # Track if full pipeline has run
}
# Removed detection_counter - using only track-based validation now
# Removed obsolete occupancy_state - app.py handles all mode transitions now
}
return _camera_stability_tracking[camera_id][model_id]
@ -886,6 +880,7 @@ def reset_camera_stability_tracking(camera_id, model_id):
# Clear all tracking data
track_counters = stability_data["track_stability_counters"]
stable_tracks = stability_data["stable_tracks"]
session_state = stability_data["session_state"]
old_counters = dict(track_counters)
old_stable = list(stable_tracks)
@ -893,17 +888,16 @@ def reset_camera_stability_tracking(camera_id, model_id):
track_counters.clear()
stable_tracks.clear()
# Reset occupancy state to validation
stability_data["occupancy_state"]["phase"] = "validation"
stability_data["occupancy_state"]["absence_counter"] = 0
stability_data["occupancy_state"]["pipeline_completed"] = False
# IMPORTANT: Set flag to reset YOLO tracker on next detection run
# This will ensure track IDs start fresh (1, 2, 3...) instead of continuing from old IDs
session_state["reset_tracker_on_resume"] = True
logger.info(f"🧹 Camera {camera_id}: CLEARED stability tracking - old_counters={old_counters}, old_stable={old_stable}")
# Occupancy state reset logging removed - not used in enhanced lightweight mode
logger.info(f"🔄 Camera {camera_id}: YOLO tracker will be reset on next detection - fresh track IDs will start from 1")
else:
logger.debug(f"🧹 Camera {camera_id}: No stability tracking data to clear for model {model_id}")
def update_single_track_stability(node, detection, camera_id, frame_shape=None, stability_threshold=4):
def update_single_track_stability(node, detection, camera_id, frame_shape=None, stability_threshold=4, context=None):
"""Update track stability validation for a single highest confidence car."""
model_id = node.get("modelId", "unknown")
@ -913,113 +907,102 @@ def update_single_track_stability(node, detection, camera_id, frame_shape=None,
logger.debug(f"⏭️ Camera {camera_id}: Skipping validation for branch node {model_id} - validation only done at main pipeline level")
return {"validation_complete": False, "branch_node": True, "stable_tracks": [], "current_tracks": []}
# Check current mode - VALIDATION COUNTERS should increment in both validation_detecting and full_pipeline modes
current_mode = context.get("current_mode", "unknown") if context else "unknown"
is_validation_mode = (current_mode in ["validation_detecting", "full_pipeline"])
# Get camera-specific stability data
stability_data = get_camera_stability_data(camera_id, model_id)
track_counters = stability_data["track_stability_counters"]
stable_tracks = stability_data["stable_tracks"]
occupancy_state = stability_data["occupancy_state"]
current_phase = occupancy_state["phase"]
current_track_id = detection.get("id") if detection else None
if current_phase == "validation":
# ═══ VALIDATION PHASE: Count consecutive frames for single track ═══
logger.debug(f"📋 Camera {camera_id}: === TRACK VALIDATION ANALYSIS ===")
logger.debug(f"📋 Camera {camera_id}: Current track_id: {current_track_id}")
logger.debug(f"📋 Camera {camera_id}: Existing counters: {dict(track_counters)}")
logger.debug(f"📋 Camera {camera_id}: Stable tracks: {list(stable_tracks)}")
# ═══ MODE-AWARE TRACK VALIDATION ═══
logger.debug(f"📋 Camera {camera_id}: === TRACK VALIDATION ANALYSIS ===")
logger.debug(f"📋 Camera {camera_id}: Current mode: {current_mode} (validation_mode={is_validation_mode})")
logger.debug(f"📋 Camera {camera_id}: Current track_id: {current_track_id} (assigned by YOLO tracking - not sequential)")
logger.debug(f"📋 Camera {camera_id}: Existing counters: {dict(track_counters)}")
logger.debug(f"📋 Camera {camera_id}: Stable tracks: {list(stable_tracks)}")
# IMPORTANT: Only modify validation counters during validation_detecting mode
if not is_validation_mode:
logger.debug(f"🚫 Camera {camera_id}: NOT in validation mode - skipping counter modifications")
return {
"validation_complete": False,
"stable_tracks": list(stable_tracks),
"current_tracks": [current_track_id] if current_track_id is not None else []
}
if current_track_id is not None:
# Check if this is a different track than we were tracking
previous_track_ids = list(track_counters.keys())
if current_track_id is not None:
# Check if this is a different track than we were tracking
previous_track_ids = list(track_counters.keys())
# ALWAYS reset counter if:
# 1. This is a different track ID than before
# 2. OR if we had no previous tracking (fresh start)
should_reset = (
len(previous_track_ids) == 0 or # No previous tracking
current_track_id not in previous_track_ids # Different track ID
)
logger.debug(f"📋 Camera {camera_id}: Previous track_ids: {previous_track_ids}")
logger.debug(f"📋 Camera {camera_id}: Should reset counters: {should_reset} (no_previous={len(previous_track_ids) == 0}, different_id={current_track_id not in previous_track_ids})")
if should_reset and previous_track_ids:
# Clear all previous tracking - different car detected
# VALIDATION MODE: Reset counter if different track OR if track was previously stable
should_reset = (
len(previous_track_ids) == 0 or # No previous tracking
current_track_id not in previous_track_ids or # Different track ID
current_track_id in stable_tracks # Track was stable - start fresh validation
)
logger.debug(f"📋 Camera {camera_id}: Previous track_ids: {previous_track_ids}")
logger.debug(f"📋 Camera {camera_id}: Track {current_track_id} was stable: {current_track_id in stable_tracks}")
logger.debug(f"📋 Camera {camera_id}: Should reset counters: {should_reset}")
if should_reset:
# Clear all previous tracking - fresh validation needed
if previous_track_ids:
for old_track_id in previous_track_ids:
old_count = track_counters.pop(old_track_id, 0)
stable_tracks.discard(old_track_id)
logger.info(f"🔄 Camera {camera_id}: Different car detected (track {current_track_id}) - RESET previous track {old_track_id} counter from {old_count} to 0")
logger.debug(f"🔄 Camera {camera_id}: Cleared track {old_track_id} from counters and stable_tracks")
logger.info(f"🔄 Camera {camera_id}: VALIDATION RESET - track {old_track_id} counter from {old_count} to 0 (reason: {'stable_track_restart' if current_track_id == old_track_id else 'different_track'})")
# Set counter to 1 for current track (fresh start each frame)
# Start fresh validation for this track
old_count = track_counters.get(current_track_id, 0) # Store old count for logging
track_counters[current_track_id] = 1
current_count = 1
logger.info(f"🆕 Camera {camera_id}: FRESH VALIDATION - Track {current_track_id} starting at 1/{stability_threshold}")
else:
# Continue validation for same track
old_count = track_counters.get(current_track_id, 0)
track_counters[current_track_id] = track_counters.get(current_track_id, 0) + 1
track_counters[current_track_id] = old_count + 1
current_count = track_counters[current_track_id]
logger.debug(f"🔢 Camera {camera_id}: Track {current_track_id} counter: {old_count}{current_count}")
logger.info(f"🔍 Camera {camera_id}: Track ID {current_track_id} validation {current_count}/{stability_threshold}")
# Check if track has reached stability threshold
logger.debug(f"📊 Camera {camera_id}: Checking stability: {current_count} >= {stability_threshold}? {current_count >= stability_threshold}")
logger.debug(f"📊 Camera {camera_id}: Already stable: {current_track_id in stable_tracks}")
if current_count >= stability_threshold and current_track_id not in stable_tracks:
stable_tracks.add(current_track_id)
occupancy_state["phase"] = "waiting_for_session"
occupancy_state["pipeline_completed"] = False
logger.info(f"✅ Camera {camera_id}: Track ID {current_track_id} STABLE after {current_count} consecutive frames")
logger.info(f"🎯 Camera {camera_id}: TRACK VALIDATION COMPLETE")
logger.debug(f"🎯 Camera {camera_id}: Phase changed to: waiting_for_session")
logger.debug(f"🎯 Camera {camera_id}: Stable tracks now: {list(stable_tracks)}")
return {
"validation_complete": True,
"send_none_detection": True,
"stable_tracks": [current_track_id],
"newly_stable_tracks": [current_track_id],
"current_tracks": [current_track_id]
}
elif current_count >= stability_threshold:
logger.debug(f"📊 Camera {camera_id}: Track {current_track_id} already stable - not re-adding")
else:
# No car detected - ALWAYS clear all tracking and reset counters
logger.debug(f"🚫 Camera {camera_id}: NO CAR DETECTED - clearing all tracking")
if track_counters:
logger.debug(f"🚫 Camera {camera_id}: Existing counters before reset: {dict(track_counters)}")
for track_id in list(track_counters.keys()):
old_count = track_counters.pop(track_id, 0)
stable_tracks.discard(track_id)
logger.info(f"🔄 Camera {camera_id}: No car detected - RESET track {track_id} counter from {old_count} to 0")
logger.debug(f"🚫 Camera {camera_id}: Cleared track {track_id} (was at {old_count}/{stability_threshold})")
track_counters.clear() # Ensure complete reset
stable_tracks.clear() # Clear all stable tracks
logger.debug(f"🚫 Camera {camera_id}: All counters and stable tracks cleared")
else:
logger.debug(f"🚫 Camera {camera_id}: No existing counters to clear")
logger.debug(f"Camera {camera_id}: VALIDATION - no car detected (all counters reset)")
elif current_phase == "waiting_for_session":
# ═══ WAITING PHASE: Maintain track stability ═══
logger.debug(f"⏳ Camera {camera_id}: WAITING FOR SESSION - monitoring stable track")
logger.debug(f"⏳ Camera {camera_id}: Current track_id: {current_track_id}, Stable tracks: {list(stable_tracks)}")
if current_track_id is None or current_track_id not in stable_tracks:
# Lost the stable track
logger.debug(f"⏳ Camera {camera_id}: Stable track lost - clearing all tracking")
stable_tracks.clear()
track_counters.clear()
logger.info(f"🔄 Camera {camera_id}: Lost stable track during waiting phase")
else:
logger.debug(f"⏳ Camera {camera_id}: Stable track {current_track_id} still present")
logger.debug(f"🔢 Camera {camera_id}: Track {current_track_id} counter: {old_count}{current_count}")
logger.info(f"🔍 Camera {camera_id}: Track ID {current_track_id} validation {current_count}/{stability_threshold}")
elif current_phase == "occupancy":
# ═══ OCCUPANCY PHASE: UNUSED in enhanced lightweight mode ═══
# This phase is bypassed by the new lightweight mode system
# Keeping minimal logic for backward compatibility but no CLI logging
if current_track_id is not None and current_track_id in stable_tracks:
occupancy_state["absence_counter"] = 0
# Check if track has reached stability threshold
logger.debug(f"📊 Camera {camera_id}: Checking stability: {current_count} >= {stability_threshold}? {current_count >= stability_threshold}")
logger.debug(f"📊 Camera {camera_id}: Already stable: {current_track_id in stable_tracks}")
if current_count >= stability_threshold and current_track_id not in stable_tracks:
stable_tracks.add(current_track_id)
logger.info(f"✅ Camera {camera_id}: Track ID {current_track_id} STABLE after {current_count} consecutive frames")
logger.info(f"🎯 Camera {camera_id}: TRACK VALIDATION COMPLETE")
logger.debug(f"🎯 Camera {camera_id}: Stable tracks now: {list(stable_tracks)}")
return {
"validation_complete": True,
"send_none_detection": True,
"stable_tracks": [current_track_id],
"newly_stable_tracks": [current_track_id],
"current_tracks": [current_track_id]
}
elif current_count >= stability_threshold:
logger.debug(f"📊 Camera {camera_id}: Track {current_track_id} already stable - not re-adding")
else:
# No car detected - ALWAYS clear all tracking and reset counters
logger.debug(f"🚫 Camera {camera_id}: NO CAR DETECTED - clearing all tracking")
if track_counters or stable_tracks:
logger.debug(f"🚫 Camera {camera_id}: Existing state before reset: counters={dict(track_counters)}, stable={list(stable_tracks)}")
for track_id in list(track_counters.keys()):
old_count = track_counters.pop(track_id, 0)
logger.info(f"🔄 Camera {camera_id}: No car detected - RESET track {track_id} counter from {old_count} to 0")
track_counters.clear() # Ensure complete reset
stable_tracks.clear() # Clear all stable tracks
logger.info(f"✅ Camera {camera_id}: RESET TO VALIDATION PHASE - All counters and stable tracks cleared")
else:
occupancy_state["absence_counter"] += 1
logger.debug(f"🚫 Camera {camera_id}: No existing counters to clear")
logger.debug(f"Camera {camera_id}: VALIDATION - no car detected (all counters reset)")
# Final return - validation not complete
result = {
@ -1040,9 +1023,9 @@ def update_track_stability_validation(node, detections, camera_id, frame_shape=N
logger.warning(f"update_track_stability_validation called for camera {camera_id} - this function is deprecated, use update_single_track_stability instead")
if detections:
best_detection = max(detections, key=lambda x: x.get("confidence", 0))
return update_single_track_stability(node, best_detection, camera_id, frame_shape, stability_threshold)
return update_single_track_stability(node, best_detection, camera_id, frame_shape, stability_threshold, None)
else:
return update_single_track_stability(node, None, camera_id, frame_shape, stability_threshold)
return update_single_track_stability(node, None, camera_id, frame_shape, stability_threshold, None)
def update_detection_stability(node, detections, camera_id, frame_shape=None):
"""Legacy detection-based stability counter - DEPRECATED."""
@ -1051,95 +1034,9 @@ def update_detection_stability(node, detections, camera_id, frame_shape=None):
return {"validation_complete": False, "valid_detections": 0, "deprecated": True}
def update_track_stability(node, detections, camera_id, frame_shape=None):
"""Update stability counters with two-phase detection system: validation → occupancy."""
stability_threshold = node.get("stabilityThreshold", 1)
model_id = node.get("modelId", "unknown")
min_bbox_area_ratio = node.get("minBboxAreaRatio", 0.0)
# Note: This function is deprecated - using detection-based stability now
# Get camera-specific stability data
stability_data = get_camera_stability_data(camera_id, model_id)
track_counters = stability_data["track_stability_counters"]
stable_tracks = stability_data["stable_tracks"]
occupancy_state = stability_data["occupancy_state"]
# Validate detections against confidence + area requirements
valid_detections = []
if frame_shape is not None:
frame_height, frame_width = frame_shape[:2]
frame_area = frame_width * frame_height
for detection in detections:
bbox = detection.get("bbox", [])
if len(bbox) >= 4:
x1, y1, x2, y2 = bbox
bbox_width = abs(x2 - x1)
bbox_height = abs(y2 - y1)
bbox_area = bbox_width * bbox_height
area_ratio = bbox_area / frame_area if frame_area > 0 else 0.0
if area_ratio >= min_bbox_area_ratio:
valid_detections.append(detection)
pass # Valid detection - no debug spam
else:
pass # Small detection - no debug spam
else:
valid_detections = detections
current_phase = occupancy_state["phase"]
if current_phase == "validation":
# ═══ VALIDATION PHASE: Count detections until stable ═══
detection_key = f"camera_{camera_id}_detections"
if valid_detections:
# Valid detection found - increment counter
track_counters[detection_key] = track_counters.get(detection_key, 0) + 1
current_count = track_counters[detection_key]
pass # Validation count - shown in main logs
# Check if we've reached the stability threshold
if current_count >= stability_threshold and detection_key not in stable_tracks:
stable_tracks.add(detection_key)
# Switch to waiting for backend session phase
occupancy_state["phase"] = "waiting_for_session"
occupancy_state["absence_counter"] = 0
occupancy_state["pipeline_completed"] = False
logger.info(f"✅ Camera {camera_id}: VALIDATION COMPLETE after {current_count} detections - READY FOR FULL PIPELINE")
else:
# No valid detections - reset validation counter for consecutive requirement
if detection_key in track_counters:
old_count = track_counters[detection_key]
track_counters.pop(detection_key, None)
stable_tracks.discard(detection_key)
logger.info(f"🔄 Camera {camera_id}: VALIDATION RESET - no valid detection, counter reset from {old_count} to 0 (requires consecutive detections)")
else:
logger.debug(f"Camera {camera_id}: VALIDATION - no valid detection, counter remains 0")
elif current_phase == "waiting_for_session":
# ═══ WAITING FOR BACKEND SESSION PHASE ═══
# Don't do occupancy monitoring yet, just maintain validation of current detections
# The main pipeline will handle sessionId detection and phase transition
pass # Waiting phase - no occupancy logic yet
elif current_phase == "occupancy":
# ═══ OCCUPANCY PHASE: Monitor car presence ═══
if valid_detections:
# Car still present - reset absence counter
if occupancy_state["absence_counter"] > 0:
pass # Car detected - counter reset (no debug spam)
occupancy_state["absence_counter"] = 0
else:
# No car detected - increment absence counter
occupancy_state["absence_counter"] += 1
pass # Absence count - will show in timeout log
pass # Phase summary - excessive debug
# Return occupancy state for pipeline decisions
return occupancy_state
"""DEPRECATED: This function is obsolete and should not be used."""
logger.warning(f"update_track_stability called for camera {camera_id} - this function is deprecated and obsolete")
return {"phase": "validation", "absence_counter": 0, "deprecated": True}
def check_stable_tracks(camera_id, model_id, regions_dict):
"""Check if any stable tracks match the detected classes for a specific camera."""
@ -1434,7 +1331,7 @@ def run_lightweight_detection(frame, node: dict):
logger.error(f"Error in lightweight detection: {str(e)}", exc_info=True)
return {"car_detected": False, "best_detection": None}
def run_pipeline(frame, node: dict, return_bbox: bool=False, context=None):
def run_pipeline(frame, node: dict, return_bbox: bool=False, context=None, validated_detection=None):
"""
Enhanced pipeline that supports:
- Multi-class detection (detecting multiple classes simultaneously)
@ -1501,8 +1398,31 @@ def run_pipeline(frame, node: dict, return_bbox: bool=False, context=None):
}
return (none_detection, [0, 0, 0, 0]) if return_bbox else none_detection
# ─── Detection stage - Using structured detection function ──────────────────
all_detections, regions_dict, track_validation_result = run_detection_with_tracking(frame, node, context)
# ─── Detection stage - Use validated detection if provided (full_pipeline mode) ───
if validated_detection:
track_id = validated_detection.get('track_id')
logger.info(f"🔄 PIPELINE: Using validated detection from validation phase - track_id={track_id}")
# Convert validated detection back to all_detections format for branch processing
all_detections = [validated_detection]
# Create regions_dict based on validated detection class with proper structure
class_name = validated_detection.get("class", "car")
regions_dict = {
class_name: {
"confidence": validated_detection.get("confidence"),
"bbox": validated_detection.get("bbox", [0, 0, 0, 0]),
"detection": validated_detection
}
}
# Bypass track validation completely - force pipeline execution
track_validation_result = {
"validation_complete": True,
"stable_tracks": ["cached"], # Use dummy stable track to force pipeline execution
"current_tracks": ["cached"],
"bypass_validation": True
}
else:
# Normal detection stage - Using structured detection function
all_detections, regions_dict, track_validation_result = run_detection_with_tracking(frame, node, context)
if not all_detections:
logger.debug("No detections from structured detection function - sending 'none' detection")
@ -1524,14 +1444,14 @@ def run_pipeline(frame, node: dict, return_bbox: bool=False, context=None):
camera_id = context.get("camera_id", "unknown") if context else "unknown"
if stability_threshold > 1 and tracking_config.get("enabled", True):
# Extract occupancy state from stability data (updated by track validation function)
# Note: Old occupancy state system removed - app.py handles all mode transitions now
# Track validation is handled by update_single_track_stability function
model_id = node.get("modelId", "unknown")
stability_data = get_camera_stability_data(camera_id, model_id)
occupancy_state = stability_data["occupancy_state"]
current_phase = occupancy_state.get("phase", "validation")
absence_counter = occupancy_state.get("absence_counter", 0)
max_absence_frames = occupancy_state.get("max_absence_frames", 3)
# Simplified: just check if we have stable tracks from track validation
current_phase = "validation" # Always validation phase in simplified system
absence_counter = 0
max_absence_frames = 3
if current_phase == "validation":
# ═══ TRACK VALIDATION PHASE ═══
@ -1562,78 +1482,8 @@ def run_pipeline(frame, node: dict, return_bbox: bool=False, context=None):
# We have stable tracks - validation is complete, proceed with pipeline
logger.info(f"🎯 Camera {camera_id}: STABLE TRACKS DETECTED - proceeding with full pipeline (tracks: {stable_tracks})")
elif current_phase == "waiting_for_session":
# ═══ WAITING FOR BACKEND SESSION PHASE ═══
if backend_session_id:
# Backend has responded with sessionId - NOW run the full pipeline for the first time
logger.info(f"🎯 Camera {camera_id}: BACKEND SESSION RECEIVED - RUNNING FULL PIPELINE (sessionId: {backend_session_id})")
occupancy_state["phase"] = "occupancy"
occupancy_state["absence_counter"] = 0
# Continue with normal pipeline processing now that we have sessionId
else:
# Still waiting for backend sessionId - send None detection dict to trigger sessionId generation
if not occupancy_state["pipeline_completed"]:
# First time in waiting phase - send empty detection to trigger sessionId
logger.info(f"⚙️ Camera {camera_id}: WAITING PHASE - sending empty detection {{}} for sessionId generation")
occupancy_state["pipeline_completed"] = True
# Return a special detection that signals app.py to send empty detection: {}
none_detection = {
"class": "validation_complete",
"confidence": 1.0,
"bbox": [0, 0, 0, 0],
"send_empty_detection": True
}
return (none_detection, [0, 0, 0, 0]) if return_bbox else none_detection
else:
# Already sent None detection - continue waiting for sessionId
logger.debug(f"⏳ Camera {camera_id}: WAITING FOR BACKEND SESSION - None detection already sent, waiting for sessionId")
waiting_detection = {
"class": "waiting_session_id",
"confidence": 1.0,
"bbox": [0, 0, 0, 0],
"waiting_for_session": True
}
return (waiting_detection, [0, 0, 0, 0]) if return_bbox else waiting_detection
elif current_phase == "occupancy":
# ═══ OCCUPANCY PHASE ═══
stable_tracks = track_validation_result.get("stable_tracks", [])
current_tracks = track_validation_result.get("current_tracks", [])
# Check if any stable tracks are still present
stable_tracks_present = bool(set(stable_tracks) & set(current_tracks))
if absence_counter >= max_absence_frames:
# Stable tracks have been absent for too long - trigger "none" detection and reset
# Occupancy timeout logging removed - not used in enhanced lightweight mode
# Reset occupancy state to validation phase
stability_data = get_camera_stability_data(camera_id, model_id)
stability_data["occupancy_state"]["phase"] = "validation"
stability_data["occupancy_state"]["absence_counter"] = 0
stability_data["track_stability_counters"].clear()
stability_data["stable_tracks"].clear()
logger.info(f"🔄 Camera {camera_id}: RESET TO VALIDATION PHASE - cleared track stability tracking (sessionId should become null)")
# Return "none" detection to trigger cache clearing in app.py
none_detection = {"class": "none", "confidence": 1.0, "bbox": [0, 0, 0, 0], "occupancy_triggered": True}
return (none_detection, [0, 0, 0, 0]) if return_bbox else none_detection
else:
# Still in occupancy phase - check if stable tracks are present
if stable_tracks_present:
# Stable tracks detected - continue with cached result or light processing
# Occupancy phase logging removed - not used in enhanced lightweight mode
pass
else:
# No stable tracks - absence counter was already incremented in track validation
# Occupancy phase logging removed - not used in enhanced lightweight mode
pass
# Continue with normal pipeline processing
pass
# Note: Old waiting_for_session and occupancy phases removed
# app.py lightweight mode handles all state transitions now
# ─── Pre-validate pipeline execution (only proceed if we have stable tracks for main pipeline) ────────────────────────
is_branch_node = node.get("cropClass") is not None or node.get("parallel") is True