wait update RX cam sub

This commit is contained in:
Pongsatorn 2025-08-28 16:46:18 +07:00
parent 5bf2d49e6b
commit 80d9c925de
3 changed files with 530 additions and 60 deletions

192
app.py
View file

@ -308,12 +308,14 @@ def get_or_init_session_pipeline_state(camera_id):
"""Get or initialize session pipeline state for a camera"""
if camera_id not in session_pipeline_states:
session_pipeline_states[camera_id] = {
"mode": "validation_detecting", # "validation_detecting", "send_detections", "waiting_for_session_id", "full_pipeline", "lightweight"
"mode": "validation_detecting", # "validation_detecting", "send_detections", "waiting_for_session_id", "full_pipeline", "lightweight", "car_gone_waiting"
"session_id_received": False,
"full_pipeline_completed": False,
"absence_counter": 0,
"max_absence_frames": 3
# Removed validation_counter and validation_threshold - now using only track-based validation
"max_absence_frames": 3,
"yolo_inference_enabled": True, # Controls whether to run YOLO inference
"cached_detection_dict": None, # Cached detection dict for lightweight mode
"stable_track_id": None # The stable track ID we're monitoring
}
return session_pipeline_states[camera_id]
@ -597,56 +599,145 @@ async def detect(websocket: WebSocket):
"result": detection_result.copy(),
"timestamp": time.time()
}
# Cache the detection dict for lightweight mode reuse
branch_results = detection_result.get("branch_results", {})
cached_dict = {
"carModel": branch_results.get("car_brand_cls_v1", {}).get("model"),
"carBrand": branch_results.get("car_brand_cls_v1", {}).get("brand"),
"carYear": None,
"bodyType": branch_results.get("car_bodytype_cls_v1", {}).get("body_type"),
"licensePlateText": None,
"licensePlateConfidence": None
}
pipeline_state["cached_detection_dict"] = cached_dict
# Log what was cached for debugging
logger.info(f"💾 Camera {camera_id}: CACHING DETECTION DICT:")
logger.info(f"💾 Camera {camera_id}: - Full branch_results: {branch_results}")
logger.info(f"💾 Camera {camera_id}: - Cached dict: {cached_dict}")
# Store the stable track ID for lightweight monitoring
track_id = detection_result.get("track_id") or detection_result.get("id")
if track_id is not None:
pipeline_state["stable_track_id"] = track_id
logger.info(f"💾 Camera {camera_id}: Cached stable track_id={track_id}")
else:
logger.warning(f"⚠️ Camera {camera_id}: No track_id found in detection_result: {detection_result.keys()}")
# Switch to lightweight mode
update_session_pipeline_mode(camera_id, "lightweight")
logger.info(f"✅ Camera {camera_id}: Full pipeline completed - switching to LIGHTWEIGHT mode")
elif current_mode == "lightweight":
# ═══ LIGHTWEIGHT MODE ═══
# Use tracking to check for stable car presence
from siwatsystem.pympta import run_detection_with_tracking
all_detections, regions_dict, track_validation_result = run_detection_with_tracking(cropped_frame, model_tree, pipeline_context)
# ═══ ENHANCED LIGHTWEIGHT MODE ═══
# Only run YOLO11n.pt to check stable track presence, use cached detection dict
stable_tracks = track_validation_result.get("stable_tracks", [])
current_tracks = track_validation_result.get("current_tracks", [])
stable_tracks_present = bool(set(stable_tracks) & set(current_tracks))
stable_track_id = pipeline_state.get("stable_track_id")
cached_detection_dict = pipeline_state.get("cached_detection_dict")
if stable_tracks_present:
# Stable tracked car still present - use cached result
pipeline_state["absence_counter"] = 0
if camera_id in cached_full_pipeline_results:
detection_result = cached_full_pipeline_results[camera_id]["result"]
logger.debug(f"🔄 Camera {camera_id}: Stable tracked car still present - using cached detection")
else:
logger.warning(f"⚠️ Camera {camera_id}: Stable tracked car detected but no cached result available")
detection_result = None
logger.debug(f"🪶 Camera {camera_id}: LIGHTWEIGHT MODE - monitoring stable track_id={stable_track_id}")
if not pipeline_state.get("yolo_inference_enabled", True):
# YOLO inference disabled - car considered gone, wait for reset
logger.debug(f"🛑 Camera {camera_id}: YOLO inference disabled - waiting for reset")
detection_result = None # Don't send anything
else:
# No stable tracked cars - increment absence counter
pipeline_state["absence_counter"] += 1
absence_count = pipeline_state["absence_counter"]
max_absence = pipeline_state["max_absence_frames"]
# Run lightweight YOLO inference to check track presence only (no full pipeline)
from siwatsystem.pympta import run_detection_with_tracking
all_detections, regions_dict, track_validation_result = run_detection_with_tracking(cropped_frame, model_tree, pipeline_context)
logger.debug(f"👻 Camera {camera_id}: No stable tracked cars - absence {absence_count}/{max_absence}")
# OPTION A: Car presence only (track ID kept for internal use)
any_car_detected = len(all_detections) > 0
current_tracks = track_validation_result.get("current_tracks", [])
if absence_count >= max_absence:
# Send "none" detection and reset to validation mode
detection_result = {
"class": "none",
"confidence": 1.0,
"bbox": [0, 0, 0, 0],
"branch_results": {}
}
cached_full_pipeline_results.pop(camera_id, None) # Clear cache
update_session_pipeline_mode(camera_id, "validation_detecting")
logger.info(f"📤 Camera {camera_id}: Stable tracked cars absent for {absence_count} frames - sending 'none' and resetting to track validation")
else:
# Still within absence tolerance - use cached result
if camera_id in cached_full_pipeline_results:
detection_result = cached_full_pipeline_results[camera_id]["result"]
logger.debug(f"⏳ Camera {camera_id}: Stable tracked cars absent {absence_count}/{max_absence} - still using cached detection")
logger.debug(f"🪶 Camera {camera_id}: LIGHTWEIGHT - any_cars={any_car_detected} (main decision), current_tracks={current_tracks} (internal only)")
if not any_car_detected:
# NO cars detected at all - increment absence counter
pipeline_state["absence_counter"] += 1
absence_count = pipeline_state["absence_counter"]
max_absence = 2 # Changed from 3 to 2 consecutive frames
logger.info(f"👻 Camera {camera_id}: NO CARS detected - absence {absence_count}/{max_absence}")
# Check robust AND condition: backend confirmed AND detection confirmed
backend_confirmed_gone = (backend_session_id is None)
detection_confirmed_gone = (absence_count >= max_absence)
logger.debug(f"🔍 Camera {camera_id}: Reset conditions - backend_null={backend_confirmed_gone}, absence_2frames={detection_confirmed_gone}")
if backend_confirmed_gone and detection_confirmed_gone:
# BOTH conditions met - RESET TO VALIDATION PHASE
logger.info(f"🔄 Camera {camera_id}: ROBUST RESET - both conditions met (backend=null AND absence>=2)")
# Clear all state and prepare for next car
cached_full_pipeline_results.pop(camera_id, None)
pipeline_state["cached_detection_dict"] = None
pipeline_state["stable_track_id"] = None
pipeline_state["absence_counter"] = 0
pipeline_state["yolo_inference_enabled"] = True # Re-enable for next car
# Clear stability tracking data for this camera
from siwatsystem.pympta import reset_camera_stability_tracking
reset_camera_stability_tracking(camera_id, model_tree.get("modelId", "unknown"))
# Switch back to validation phase - ready for next car
update_session_pipeline_mode(camera_id, "detection_dict")
logger.info(f"🔄 Camera {camera_id}: RESET TO VALIDATION - model ready for next car")
detection_result = None # Stop sending data during reset
else:
# One or both conditions not met - keep sending cached detection dict
if cached_detection_dict:
detection_result = cached_detection_dict # Always send cached data
logger.info(f"⏳ Camera {camera_id}: NO CARS absence {absence_count}/2, backend_null={backend_confirmed_gone} - sending cached detection dict")
else:
logger.warning(f"⚠️ Camera {camera_id}: NO CARS but no cached detection dict available")
detection_result = None
else:
# Cars detected - reset absence counter, send cached detection dict
pipeline_state["absence_counter"] = 0 # Reset absence since cars are present
if cached_detection_dict:
detection_result = cached_detection_dict # Always send cached data
logger.info(f"🪶 Camera {camera_id}: CARS DETECTED - sending cached detection dict:")
logger.info(f"🪶 Camera {camera_id}: - Cached dict: {cached_detection_dict}")
logger.info(f"🪶 Camera {camera_id}: - Track info (internal): {current_tracks}")
else:
logger.warning(f"⚠️ Camera {camera_id}: Cars detected but no cached detection dict available")
detection_result = None
elif current_mode == "car_gone_waiting":
# ═══ CAR GONE WAITING STATE ═══
# Car is gone (both conditions met), YOLO inference disabled, waiting for new session
logger.debug(f"🛑 Camera {camera_id}: CAR GONE WAITING - YOLO inference stopped")
# Check if backend has started a new session (indicates new car scenario)
if backend_session_id is not None:
# Backend started new session - re-enable YOLO and reset to validation
pipeline_state["yolo_inference_enabled"] = True
pipeline_state["absence_counter"] = 0
pipeline_state["stable_track_id"] = None
pipeline_state["cached_detection_dict"] = None
# Clear stability tracking data for this camera
from siwatsystem.pympta import reset_camera_stability_tracking
reset_camera_stability_tracking(camera_id, model_tree.get("modelId", "unknown"))
update_session_pipeline_mode(camera_id, "validation_detecting")
logger.info(f"🔄 Camera {camera_id}: New session detected (id={backend_session_id}) - re-enabling YOLO inference")
logger.info(f"✅ Camera {camera_id}: Reset to validation mode - cleared all tracking, ready for new car detection")
# Don't run detection this frame - let next frame start fresh
detection_result = {"class": "none", "confidence": 1.0, "bbox": [0, 0, 0, 0]}
else:
# Still waiting - no sessionId, no detection to send
logger.debug(f"🛑 Camera {camera_id}: Car gone waiting - no YOLO inference, no data sent")
detection_result = None
process_time = (time.time() - start_time) * 1000
logger.debug(f"Detection for camera {camera_id} completed in {process_time:.2f}ms (mode: {current_mode})")
@ -698,8 +789,23 @@ async def detect(websocket: WebSocket):
# "None" detection in other modes (lightweight) - car left or absent for 3 frames
detection_dict = None
logger.info(f"📤 SENDING 'NONE' (detection: null) - Car absent, expecting backend to clear session for camera {camera_id}")
elif detection_result.get("cached_mode", False):
# Cached mode in lightweight - use cached detection dict directly
cached_dict = detection_result.get("branch_results", {})
detection_dict = cached_dict if cached_dict else {
"carModel": None,
"carBrand": None,
"carYear": None,
"bodyType": None,
"licensePlateText": None,
"licensePlateConfidence": None
}
logger.info(f"💾 Camera {camera_id}: SENDING CACHED DETECTION_DICT to backend:")
logger.info(f"💾 Camera {camera_id}: - Cached branch_results: {cached_dict}")
logger.info(f"💾 Camera {camera_id}: - Final detection_dict: {detection_dict}")
logger.info(f"💾 Camera {camera_id}: - Track ID: {detection_result.get('track_id')} (lightweight mode)")
else:
# Valid detection - convert to backend format
# Valid detection - convert to backend format (will be populated by branch processing)
detection_dict = {
"carModel": None,
"carBrand": None,
@ -709,8 +815,8 @@ async def detect(websocket: WebSocket):
"licensePlateConfidence": None
}
# Extract and process branch results from parallel classification (only for valid detections)
if detection_result.get("class") != "none" and "branch_results" in detection_result:
# Extract and process branch results from parallel classification (only for valid detections, skip cached mode)
if detection_result.get("class") != "none" and "branch_results" in detection_result and not detection_result.get("cached_mode", False):
def process_branch_results(branch_results, depth=0):
"""Recursively process branch results including nested branches."""
if not isinstance(branch_results, dict):