feat/tracking and save in redis finished
This commit is contained in:
parent
3a4a27ca68
commit
5873945115
8 changed files with 393 additions and 245 deletions
|
@ -13,6 +13,7 @@ import concurrent.futures
|
|||
from ultralytics import YOLO
|
||||
from urllib.parse import urlparse
|
||||
from .database import DatabaseManager
|
||||
from datetime import datetime
|
||||
|
||||
# Create a logger specifically for this module
|
||||
logger = logging.getLogger("detector_worker.pympta")
|
||||
|
@ -108,6 +109,7 @@ def load_pipeline_node(node_config: dict, mpta_dir: str, redis_client, db_manage
|
|||
"modelFile": node_config["modelFile"],
|
||||
"triggerClasses": trigger_classes,
|
||||
"triggerClassIndices": trigger_class_indices,
|
||||
"classMapping": node_config.get("classMapping", {}),
|
||||
"crop": node_config.get("crop", False),
|
||||
"cropClass": node_config.get("cropClass"),
|
||||
"minConfidence": node_config.get("minConfidence", None),
|
||||
|
@ -608,8 +610,7 @@ def run_detection_with_tracking(frame, node, context=None):
|
|||
)[0]
|
||||
|
||||
# Process detection results
|
||||
all_detections = []
|
||||
regions_dict = {}
|
||||
candidate_detections = []
|
||||
min_confidence = node.get("minConfidence", 0.0)
|
||||
|
||||
if res.boxes is None or len(res.boxes) == 0:
|
||||
|
@ -618,6 +619,7 @@ def run_detection_with_tracking(frame, node, context=None):
|
|||
|
||||
logger.debug(f"Processing {len(res.boxes)} raw detections")
|
||||
|
||||
# First pass: collect all valid detections
|
||||
for i, box in enumerate(res.boxes):
|
||||
# Extract detection data
|
||||
conf = float(box.cpu().conf[0])
|
||||
|
@ -658,17 +660,39 @@ def run_detection_with_tracking(frame, node, context=None):
|
|||
"class_id": cls_id
|
||||
}
|
||||
|
||||
all_detections.append(detection)
|
||||
logger.debug(f"Detection {i} accepted: {class_name} (conf={conf:.3f}, id={track_id}, bbox={bbox})")
|
||||
|
||||
# Update regions_dict with highest confidence detection per class
|
||||
if class_name not in regions_dict or conf > regions_dict[class_name]["confidence"]:
|
||||
regions_dict[class_name] = {
|
||||
"bbox": bbox,
|
||||
"confidence": conf,
|
||||
"detection": detection,
|
||||
"track_id": track_id
|
||||
}
|
||||
candidate_detections.append(detection)
|
||||
logger.debug(f"Detection {i} candidate: {class_name} (conf={conf:.3f}, id={track_id}, bbox={bbox})")
|
||||
|
||||
# Second pass: select only the highest confidence detection overall
|
||||
if not candidate_detections:
|
||||
logger.debug("No valid candidate detections found")
|
||||
return [], {}
|
||||
|
||||
# Find the single highest confidence detection across all detected classes
|
||||
best_detection = max(candidate_detections, key=lambda x: x["confidence"])
|
||||
original_class = best_detection["class"]
|
||||
logger.info(f"Selected highest confidence detection: {original_class} (conf={best_detection['confidence']:.3f})")
|
||||
|
||||
# Apply class mapping if configured
|
||||
mapped_class = original_class
|
||||
class_mapping = node.get("classMapping", {})
|
||||
if original_class in class_mapping:
|
||||
mapped_class = class_mapping[original_class]
|
||||
logger.info(f"Class mapping applied: {original_class} → {mapped_class}")
|
||||
# Update the detection object with mapped class
|
||||
best_detection["class"] = mapped_class
|
||||
best_detection["original_class"] = original_class # Keep original for reference
|
||||
|
||||
# Keep only the best detection with mapped class
|
||||
all_detections = [best_detection]
|
||||
regions_dict = {
|
||||
mapped_class: {
|
||||
"bbox": best_detection["bbox"],
|
||||
"confidence": best_detection["confidence"],
|
||||
"detection": best_detection,
|
||||
"track_id": best_detection["id"]
|
||||
}
|
||||
}
|
||||
|
||||
# Multi-class validation
|
||||
if node.get("multiClass", False) and node.get("expectedClasses"):
|
||||
|
@ -964,7 +988,7 @@ def run_pipeline(frame, node: dict, return_bbox: bool=False, context=None):
|
|||
elif "color" in model_id:
|
||||
det["color"] = class_name
|
||||
|
||||
execute_actions(node, frame, det)
|
||||
execute_actions(node, frame, det, context.get("regions_dict") if context else None)
|
||||
return (det, None) if return_bbox else det
|
||||
|
||||
# ─── Session management check ───────────────────────────────────────
|
||||
|
@ -1019,13 +1043,14 @@ def run_pipeline(frame, node: dict, return_bbox: bool=False, context=None):
|
|||
**(context or {})
|
||||
}
|
||||
|
||||
# ─── Create initial database record when Car+Frontal detected ────
|
||||
if node.get("db_manager") and node.get("multiClass", False):
|
||||
# Only create database record if we have both Car and Frontal
|
||||
has_car = "Car" in regions_dict
|
||||
has_frontal = "Frontal" in regions_dict
|
||||
# ─── Create initial database record when valid detection found ────
|
||||
if node.get("db_manager") and regions_dict:
|
||||
# Create database record if we have any valid detection (after class mapping and filtering)
|
||||
detected_classes = list(regions_dict.keys())
|
||||
logger.debug(f"Valid detections found for database record: {detected_classes}")
|
||||
|
||||
if has_car and has_frontal:
|
||||
# Always create record if we have valid detections that passed all filters
|
||||
if detected_classes:
|
||||
# Generate UUID session_id since client session is None for now
|
||||
import uuid as uuid_lib
|
||||
from datetime import datetime
|
||||
|
@ -1047,9 +1072,12 @@ def run_pipeline(frame, node: dict, return_bbox: bool=False, context=None):
|
|||
detection_result["timestamp"] = timestamp # Update with proper timestamp
|
||||
logger.info(f"Created initial database record with session_id: {inserted_session_id}")
|
||||
else:
|
||||
logger.debug(f"Database record not created - missing required classes. Has Car: {has_car}, Has Frontal: {has_frontal}")
|
||||
logger.debug("Database record not created - no valid detections found after filtering")
|
||||
|
||||
execute_actions(node, frame, detection_result, regions_dict)
|
||||
# Execute actions for root node only if it doesn't have branches
|
||||
# Branch nodes with actions will execute them after branch processing
|
||||
if not node.get("branches") or node.get("modelId") == "yolo11n":
|
||||
execute_actions(node, frame, detection_result, regions_dict)
|
||||
|
||||
# ─── Branch processing (no stability check here) ─────────────────────────────
|
||||
if node["branches"]:
|
||||
|
@ -1089,21 +1117,28 @@ def run_pipeline(frame, node: dict, return_bbox: bool=False, context=None):
|
|||
futures = {}
|
||||
|
||||
for br in active_branches:
|
||||
crop_class = br.get("cropClass", br.get("triggerClasses", [])[0] if br.get("triggerClasses") else None)
|
||||
sub_frame = frame
|
||||
crop_class = br.get("cropClass")
|
||||
|
||||
logger.info(f"Starting parallel branch: {br['modelId']}, crop_class: {crop_class}")
|
||||
logger.info(f"Starting parallel branch: {br['modelId']}, cropClass: {crop_class}")
|
||||
|
||||
if br.get("crop", False) and crop_class:
|
||||
cropped = crop_region_by_class(frame, regions_dict, crop_class)
|
||||
if cropped is not None:
|
||||
sub_frame = cv2.resize(cropped, (224, 224))
|
||||
logger.debug(f"Successfully cropped {crop_class} region for {br['modelId']}")
|
||||
if crop_class in regions_dict:
|
||||
cropped = crop_region_by_class(frame, regions_dict, crop_class)
|
||||
if cropped is not None:
|
||||
sub_frame = cropped # Use cropped image without manual resizing
|
||||
logger.debug(f"Successfully cropped {crop_class} region for {br['modelId']} - model will handle resizing")
|
||||
else:
|
||||
logger.warning(f"Failed to crop {crop_class} region for {br['modelId']}, skipping branch")
|
||||
continue
|
||||
else:
|
||||
logger.warning(f"Failed to crop {crop_class} region for {br['modelId']}, skipping branch")
|
||||
logger.warning(f"Crop class {crop_class} not found in detected regions for {br['modelId']}, skipping branch")
|
||||
continue
|
||||
|
||||
future = executor.submit(run_pipeline, sub_frame, br, True, context)
|
||||
# Add regions_dict to context for child branches
|
||||
branch_context = dict(context) if context else {}
|
||||
branch_context["regions_dict"] = regions_dict
|
||||
future = executor.submit(run_pipeline, sub_frame, br, True, branch_context)
|
||||
futures[future] = br
|
||||
|
||||
# Collect results
|
||||
|
@ -1119,22 +1154,29 @@ def run_pipeline(frame, node: dict, return_bbox: bool=False, context=None):
|
|||
else:
|
||||
# Run branches sequentially
|
||||
for br in active_branches:
|
||||
crop_class = br.get("cropClass", br.get("triggerClasses", [])[0] if br.get("triggerClasses") else None)
|
||||
sub_frame = frame
|
||||
crop_class = br.get("cropClass")
|
||||
|
||||
logger.info(f"Starting sequential branch: {br['modelId']}, crop_class: {crop_class}")
|
||||
logger.info(f"Starting sequential branch: {br['modelId']}, cropClass: {crop_class}")
|
||||
|
||||
if br.get("crop", False) and crop_class:
|
||||
cropped = crop_region_by_class(frame, regions_dict, crop_class)
|
||||
if cropped is not None:
|
||||
sub_frame = cv2.resize(cropped, (224, 224))
|
||||
logger.debug(f"Successfully cropped {crop_class} region for {br['modelId']}")
|
||||
if crop_class in regions_dict:
|
||||
cropped = crop_region_by_class(frame, regions_dict, crop_class)
|
||||
if cropped is not None:
|
||||
sub_frame = cropped # Use cropped image without manual resizing
|
||||
logger.debug(f"Successfully cropped {crop_class} region for {br['modelId']} - model will handle resizing")
|
||||
else:
|
||||
logger.warning(f"Failed to crop {crop_class} region for {br['modelId']}, skipping branch")
|
||||
continue
|
||||
else:
|
||||
logger.warning(f"Failed to crop {crop_class} region for {br['modelId']}, skipping branch")
|
||||
logger.warning(f"Crop class {crop_class} not found in detected regions for {br['modelId']}, skipping branch")
|
||||
continue
|
||||
|
||||
try:
|
||||
result, _ = run_pipeline(sub_frame, br, True, context)
|
||||
# Add regions_dict to context for child branches
|
||||
branch_context = dict(context) if context else {}
|
||||
branch_context["regions_dict"] = regions_dict
|
||||
result, _ = run_pipeline(sub_frame, br, True, branch_context)
|
||||
if result:
|
||||
branch_results[br["modelId"]] = result
|
||||
logger.info(f"Branch {br['modelId']} completed: {result}")
|
||||
|
@ -1156,6 +1198,14 @@ def run_pipeline(frame, node: dict, return_bbox: bool=False, context=None):
|
|||
start_cooldown_timer(camera_id, model_id)
|
||||
logger.info(f"Camera {camera_id}: Pipeline completed successfully, starting 30s cooldown")
|
||||
|
||||
# ─── Execute actions after successful detection AND branch processing ──────────
|
||||
# This ensures detection nodes (like frontal_detection_v1) execute their actions
|
||||
# after completing both detection and branch processing
|
||||
if node.get("actions") and regions_dict and node.get("modelId") != "yolo11n":
|
||||
# Execute actions for branch detection nodes, skip root to avoid duplication
|
||||
logger.debug(f"Executing post-detection actions for branch node {node.get('modelId')}")
|
||||
execute_actions(node, frame, detection_result, regions_dict)
|
||||
|
||||
# ─── Return detection result ────────────────────────────────
|
||||
primary_detection = max(all_detections, key=lambda x: x["confidence"])
|
||||
primary_bbox = primary_detection["bbox"]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue