version 1.0

This commit is contained in:
Pongsatorn 2025-08-30 01:39:54 +07:00
parent 72eb7d55ea
commit cee856f59a
2 changed files with 765 additions and 41 deletions

205
app.py
View file

@ -652,7 +652,8 @@ def get_or_init_session_pipeline_state(camera_id):
"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
"validated_detection": None # Stored detection result from validation phase for full_pipeline reuse
"validated_detection": None, # Stored detection result from validation phase for full_pipeline reuse
"progression_stage": None # Tracks current progression stage (welcome, car_wait_staff, car_fueling, car_waitpayment)
}
return session_pipeline_states[camera_id]
@ -829,11 +830,25 @@ async def detect(websocket: WebSocket):
# ═══ SESSION ID-BASED PROCESSING MODE ═══
if not backend_session_id:
# No session ID - keep current mode if it's validation_detecting or send_detections
if current_mode not in ["validation_detecting", "send_detections", "waiting_for_session_id"]:
# No session ID - handle different modes appropriately
if current_mode == "lightweight":
# Check if we're in car_waitpayment stage - if so, don't reset immediately
current_progression = pipeline_state.get("progression_stage")
if current_progression == "car_waitpayment":
# Stay in lightweight mode - let absence counter + sessionId null logic handle reset
logger.debug(f"🔍 Camera {camera_id}: No session ID but in car_waitpayment - staying in lightweight mode for dual reset condition")
else:
# Not in car_waitpayment - reset immediately (situation 1)
update_session_pipeline_mode(camera_id, "validation_detecting")
current_mode = "validation_detecting"
logger.debug(f"🔍 Camera {camera_id}: No session ID - reset to validation_detecting (not in car_waitpayment)")
elif current_mode not in ["validation_detecting", "send_detections", "waiting_for_session_id"]:
# Other modes - reset to validation_detecting
update_session_pipeline_mode(camera_id, "validation_detecting")
current_mode = "validation_detecting"
logger.debug(f"🔍 Camera {camera_id}: No session ID - in {current_mode} mode")
logger.debug(f"🔍 Camera {camera_id}: No session ID - reset to validation_detecting from {current_mode}")
else:
logger.debug(f"🔍 Camera {camera_id}: No session ID - staying in {current_mode} mode")
else:
# Session ID available - switch to full pipeline mode
if current_mode in ["send_detections", "waiting_for_session_id"]:
@ -1041,16 +1056,21 @@ async def detect(websocket: WebSocket):
logger.debug(f"🪶 Camera {camera_id}: LIGHTWEIGHT MODE - stable_track_id={stable_track_id}")
if not pipeline_state.get("yolo_inference_enabled", True):
# YOLO inference disabled - waiting for reset
logger.debug(f"🛑 Camera {camera_id}: YOLO inference disabled - waiting for reset")
detection_result = None # Don't send anything
# YOLO inference disabled during car_fueling - continue sending cached detection dict
logger.debug(f"🛑 Camera {camera_id}: YOLO inference disabled during car_fueling - continue sending cached detection dict")
if cached_detection_dict:
detection_result = cached_detection_dict # Continue sending cached data
logger.info(f"⛽ Camera {camera_id}: YOLO disabled during car_fueling but sending cached detection dict")
else:
logger.warning(f"⚠️ Camera {camera_id}: YOLO disabled but no cached detection dict available")
detection_result = None
else:
# Run YOLO inference to check car presence for reset logic
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)
# Run lightweight YOLO inference to check car presence for reset logic (no tracking validation needed)
from siwatsystem.pympta import run_lightweight_detection
basic_detection = run_lightweight_detection(cropped_frame, model_tree)
any_car_detected = len(all_detections) > 0
current_tracks = track_validation_result.get("current_tracks", [])
any_car_detected = basic_detection and basic_detection.get("car_detected", False)
logger.debug(f"🔍 Camera {camera_id}: LIGHTWEIGHT - simple car presence check: {any_car_detected}")
if any_car_detected:
# Car detected - reset absence counter, continue sending cached detection dict
@ -1070,33 +1090,77 @@ async def detect(websocket: WebSocket):
logger.info(f"👻 Camera {camera_id}: LIGHTWEIGHT - no car detected (absence {absence_count}/{max_absence})")
# Check if we should reset: Need BOTH 3 consecutive absence frames AND sessionId: null
current_progression = pipeline_state.get("progression_stage")
should_check_session_null = current_progression == "car_waitpayment"
if absence_count >= max_absence:
# SIMPLE RESET CONDITION: 2 consecutive empty frames
logger.info(f"🔄 Camera {camera_id}: RESET CONDITION MET - {max_absence} consecutive empty frames")
# 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["validated_detection"] = None
old_absence_counter = pipeline_state["absence_counter"]
old_validation_counter = pipeline_state.get("validation_counter", 0)
pipeline_state["absence_counter"] = 0
pipeline_state["validation_counter"] = 0 # Clear validation counter
pipeline_state["yolo_inference_enabled"] = True
logger.info(f"🧹 Camera {camera_id}: CLEARING ALL COUNTERS - absence_counter: {old_absence_counter}→0, validation_counter: {old_validation_counter}→0")
# 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
update_session_pipeline_mode(camera_id, "validation_detecting")
logger.info(f"✅ Camera {camera_id}: RESET TO VALIDATION COMPLETE - ready for new car")
# Now in validation mode - send what YOLO detection finds (will be null since no car)
detection_result = {"class": "none", "confidence": 1.0, "bbox": [0, 0, 0, 0]}
if should_check_session_null:
# In car_waitpayment stage - require BOTH conditions
if backend_session_id is None:
# Both conditions met: 3 absence frames + sessionId: null
logger.info(f"🔄 Camera {camera_id}: DUAL RESET CONDITIONS MET - {max_absence} consecutive absence frames + sessionId: null")
# 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["validated_detection"] = None
pipeline_state["progression_stage"] = None
old_absence_counter = pipeline_state["absence_counter"]
old_validation_counter = pipeline_state.get("validation_counter", 0)
pipeline_state["absence_counter"] = 0
pipeline_state["validation_counter"] = 0
pipeline_state["yolo_inference_enabled"] = True
logger.info(f"🧹 Camera {camera_id}: DUAL RESET - absence_counter: {old_absence_counter}→0, validation_counter: {old_validation_counter}→0, progression_stage: {current_progression}→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"))
# Switch back to validation phase
update_session_pipeline_mode(camera_id, "validation_detecting")
logger.info(f"✅ Camera {camera_id}: DUAL RESET TO VALIDATION COMPLETE - ready for new car")
# Now in validation mode - send what YOLO detection finds (will be null since no car)
detection_result = {"class": "none", "confidence": 1.0, "bbox": [0, 0, 0, 0]}
else:
# Only absence frames met, but sessionId is not null - continue sending cached detection
logger.info(f"⏳ Camera {camera_id}: {max_absence} absence frames reached but sessionId={backend_session_id} (not null) - continuing with cached detection")
if cached_detection_dict:
detection_result = cached_detection_dict
else:
logger.warning(f"⚠️ Camera {camera_id}: No cached detection dict available")
detection_result = None
else:
# Not in car_waitpayment - use original simple reset condition (situation 1)
logger.info(f"🔄 Camera {camera_id}: SIMPLE RESET CONDITION MET - {max_absence} consecutive empty frames (not in car_waitpayment)")
# 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["validated_detection"] = None
pipeline_state["progression_stage"] = None
old_absence_counter = pipeline_state["absence_counter"]
old_validation_counter = pipeline_state.get("validation_counter", 0)
pipeline_state["absence_counter"] = 0
pipeline_state["validation_counter"] = 0
pipeline_state["yolo_inference_enabled"] = True
logger.info(f"🧹 Camera {camera_id}: SIMPLE RESET - absence_counter: {old_absence_counter}→0, validation_counter: {old_validation_counter}→0")
# 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
update_session_pipeline_mode(camera_id, "validation_detecting")
logger.info(f"✅ Camera {camera_id}: SIMPLE RESET TO VALIDATION COMPLETE - ready for new car")
# Now in validation mode - send what YOLO detection finds (will be null since no car)
detection_result = {"class": "none", "confidence": 1.0, "bbox": [0, 0, 0, 0]}
else:
# Still within absence threshold - continue sending cached detection dict
if cached_detection_dict:
@ -1181,9 +1245,16 @@ async def detect(websocket: WebSocket):
detection_dict = None # Send detection: null
logger.info(f"🚫 LIGHTWEIGHT - no car detected, sending detection=null")
elif isinstance(detection_result, dict) and ("carBrand" in detection_result or "carModel" in detection_result):
# This is a cached detection dict - send it
detection_dict = detection_result
logger.info(f"💾 LIGHTWEIGHT - sending cached detection dict")
# Check if we're waiting for dual reset condition
current_progression = pipeline_state.get("progression_stage")
if current_progression == "car_waitpayment" and backend_session_id is None:
# In car_waitpayment + sessionId: null - STOP sending cached detection to prevent new session creation
detection_dict = None
logger.info(f"🛑 LIGHTWEIGHT - in car_waitpayment with sessionId: null, NOT sending cached detection (waiting for dual reset)")
else:
# Normal lightweight mode - send cached detection dict
detection_dict = detection_result
logger.info(f"💾 LIGHTWEIGHT - sending cached detection dict")
else:
logger.warning(f"⚠️ LIGHTWEIGHT - unexpected detection_result type: {type(detection_result)}")
detection_dict = None
@ -2311,6 +2382,58 @@ async def detect(websocket: WebSocket):
await websocket.send_json(response)
logger.info(f"Acknowledged patch for session {session_id}")
elif msg_type == "setProgressionStage":
payload = data.get("payload", {})
display_identifier = payload.get("displayIdentifier")
progression_stage = payload.get("progressionStage")
logger.info(f"🏁 PROGRESSION STAGE RECEIVED: displayId={display_identifier}, stage={progression_stage}")
if display_identifier:
# Find all cameras associated with this display
with streams_lock:
affected_cameras = []
for camera_id, stream in streams.items():
if stream["subscriptionIdentifier"].startswith(display_identifier + ";"):
affected_cameras.append(camera_id)
logger.debug(f"🎯 Found {len(affected_cameras)} cameras for display {display_identifier}: {affected_cameras}")
# Handle different progression stages
for camera_id in affected_cameras:
pipeline_state = get_or_init_session_pipeline_state(camera_id)
current_mode = pipeline_state.get("mode", "validation_detecting")
if progression_stage == "car_fueling":
# Situation 2: Stop YOLO inference, continue sending cached detection dict
if current_mode == "lightweight":
pipeline_state["yolo_inference_enabled"] = False
pipeline_state["progression_stage"] = "car_fueling"
logger.info(f"⏸️ Camera {camera_id}: YOLO inference DISABLED for car_fueling stage (still sending cached detection dict)")
else:
logger.debug(f"📊 Camera {camera_id}: car_fueling received but not in lightweight mode (mode: {current_mode})")
elif progression_stage == "car_waitpayment":
# Resume YOLO inference for absence counter
pipeline_state["yolo_inference_enabled"] = True
pipeline_state["progression_stage"] = "car_waitpayment"
logger.info(f"▶️ Camera {camera_id}: YOLO inference RE-ENABLED for car_waitpayment stage")
elif progression_stage == "welcome":
# Ignore welcome messages during car_waitpayment as per requirement
current_progression = pipeline_state.get("progression_stage")
if current_progression == "car_waitpayment":
logger.info(f"🚫 Camera {camera_id}: IGNORING welcome stage (currently in car_waitpayment)")
else:
pipeline_state["progression_stage"] = "welcome"
logger.info(f"🎉 Camera {camera_id}: Progression stage set to welcome")
elif progression_stage in ["car_wait_staff"]:
pipeline_state["progression_stage"] = progression_stage
logger.info(f"📋 Camera {camera_id}: Progression stage set to {progression_stage}")
else:
logger.warning(f"🚨 Invalid setProgressionStage message: missing displayIdentifier in payload")
else:
logger.error(f"Unknown message type: {msg_type}")
except json.JSONDecodeError: