Refactor: done phase 5
This commit is contained in:
parent
5176f99ba7
commit
476f19cabe
3 changed files with 740 additions and 109 deletions
|
@ -520,6 +520,58 @@ class BranchProcessor:
|
|||
else:
|
||||
logger.warning(f"[NO RESULTS] {branch_id}: No detections found")
|
||||
|
||||
# Execute branch actions if this branch found valid detections
|
||||
actions_executed = []
|
||||
branch_actions = getattr(branch_config, 'actions', [])
|
||||
if branch_actions and branch_detections:
|
||||
logger.info(f"[BRANCH ACTIONS] {branch_id}: Executing {len(branch_actions)} actions")
|
||||
|
||||
# Create detected_regions from THIS branch's detections for actions
|
||||
branch_detected_regions = {}
|
||||
for detection in branch_detections:
|
||||
branch_detected_regions[detection['class_name']] = {
|
||||
'bbox': detection['bbox'],
|
||||
'confidence': detection['confidence']
|
||||
}
|
||||
|
||||
for action in branch_actions:
|
||||
try:
|
||||
action_type = action.type.value # Access the enum value
|
||||
logger.info(f"[ACTION EXECUTE] {branch_id}: Executing action '{action_type}'")
|
||||
|
||||
if action_type == 'redis_save_image':
|
||||
action_result = self._execute_redis_save_image_sync(
|
||||
action, input_frame, branch_detected_regions, detection_context
|
||||
)
|
||||
elif action_type == 'redis_publish':
|
||||
action_result = self._execute_redis_publish_sync(
|
||||
action, detection_context
|
||||
)
|
||||
else:
|
||||
logger.warning(f"[ACTION UNKNOWN] {branch_id}: Unknown action type '{action_type}'")
|
||||
action_result = {'status': 'error', 'message': f'Unknown action type: {action_type}'}
|
||||
|
||||
actions_executed.append({
|
||||
'action_type': action_type,
|
||||
'result': action_result
|
||||
})
|
||||
|
||||
logger.info(f"[ACTION COMPLETE] {branch_id}: Action '{action_type}' result: {action_result.get('status')}")
|
||||
|
||||
except Exception as e:
|
||||
action_type = getattr(action, 'type', None)
|
||||
if action_type:
|
||||
action_type = action_type.value if hasattr(action_type, 'value') else str(action_type)
|
||||
logger.error(f"[ACTION ERROR] {branch_id}: Error executing action '{action_type}': {e}", exc_info=True)
|
||||
actions_executed.append({
|
||||
'action_type': action_type,
|
||||
'result': {'status': 'error', 'message': str(e)}
|
||||
})
|
||||
|
||||
# Add actions executed to result
|
||||
if actions_executed:
|
||||
result['actions_executed'] = actions_executed
|
||||
|
||||
# Handle nested branches ONLY if parent found valid detections
|
||||
nested_branches = getattr(branch_config, 'branches', [])
|
||||
if nested_branches:
|
||||
|
@ -566,6 +618,164 @@ class BranchProcessor:
|
|||
|
||||
return result
|
||||
|
||||
def _execute_redis_save_image_sync(self,
|
||||
action: Dict,
|
||||
frame: np.ndarray,
|
||||
detected_regions: Dict[str, Any],
|
||||
context: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Execute redis_save_image action synchronously."""
|
||||
if not self.redis_manager:
|
||||
return {'status': 'error', 'message': 'Redis not available'}
|
||||
|
||||
try:
|
||||
# Get image to save (cropped or full frame)
|
||||
image_to_save = frame
|
||||
region_name = action.params.get('region')
|
||||
|
||||
bbox = None
|
||||
if region_name and region_name in detected_regions:
|
||||
# Crop the specified region
|
||||
bbox = detected_regions[region_name]['bbox']
|
||||
elif region_name and region_name.lower() == 'frontal' and 'front_rear' in detected_regions:
|
||||
# Special case: "frontal" region maps to "front_rear" detection
|
||||
bbox = detected_regions['front_rear']['bbox']
|
||||
|
||||
if bbox is not None:
|
||||
x1, y1, x2, y2 = [int(coord) for coord in bbox]
|
||||
cropped = frame[y1:y2, x1:x2]
|
||||
if cropped.size > 0:
|
||||
image_to_save = cropped
|
||||
logger.debug(f"Cropped region '{region_name}' for redis_save_image")
|
||||
else:
|
||||
logger.warning(f"Empty crop for region '{region_name}', using full frame")
|
||||
|
||||
# Format key with context
|
||||
key = action.params['key'].format(**context)
|
||||
|
||||
# Convert image to bytes
|
||||
import cv2
|
||||
image_format = action.params.get('format', 'jpeg')
|
||||
quality = action.params.get('quality', 90)
|
||||
|
||||
if image_format.lower() == 'jpeg':
|
||||
encode_param = [cv2.IMWRITE_JPEG_QUALITY, quality]
|
||||
_, image_bytes = cv2.imencode('.jpg', image_to_save, encode_param)
|
||||
else:
|
||||
_, image_bytes = cv2.imencode('.png', image_to_save)
|
||||
|
||||
# Save to Redis synchronously using a sync Redis client
|
||||
try:
|
||||
import redis
|
||||
import cv2
|
||||
|
||||
# Create a synchronous Redis client with same connection details
|
||||
sync_redis = redis.Redis(
|
||||
host=self.redis_manager.host,
|
||||
port=self.redis_manager.port,
|
||||
password=self.redis_manager.password,
|
||||
db=self.redis_manager.db,
|
||||
decode_responses=False, # We're storing binary data
|
||||
socket_timeout=self.redis_manager.socket_timeout,
|
||||
socket_connect_timeout=self.redis_manager.socket_connect_timeout
|
||||
)
|
||||
|
||||
# Encode the image
|
||||
if image_format.lower() == 'jpeg':
|
||||
encode_param = [cv2.IMWRITE_JPEG_QUALITY, quality]
|
||||
success, encoded_image = cv2.imencode('.jpg', image_to_save, encode_param)
|
||||
else:
|
||||
success, encoded_image = cv2.imencode('.png', image_to_save)
|
||||
|
||||
if not success:
|
||||
return {'status': 'error', 'message': 'Failed to encode image'}
|
||||
|
||||
# Save to Redis with expiration
|
||||
expire_seconds = action.params.get('expire_seconds', 600)
|
||||
result = sync_redis.setex(key, expire_seconds, encoded_image.tobytes())
|
||||
|
||||
sync_redis.close() # Clean up connection
|
||||
|
||||
if result:
|
||||
# Add image_key to context for subsequent actions
|
||||
context['image_key'] = key
|
||||
return {'status': 'success', 'key': key}
|
||||
else:
|
||||
return {'status': 'error', 'message': 'Failed to save image to Redis'}
|
||||
|
||||
except Exception as redis_error:
|
||||
logger.error(f"Error calling Redis from sync context: {redis_error}")
|
||||
return {'status': 'error', 'message': f'Redis operation failed: {redis_error}'}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in redis_save_image action: {e}", exc_info=True)
|
||||
return {'status': 'error', 'message': str(e)}
|
||||
|
||||
def _execute_redis_publish_sync(self, action: Dict, context: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Execute redis_publish action synchronously."""
|
||||
if not self.redis_manager:
|
||||
return {'status': 'error', 'message': 'Redis not available'}
|
||||
|
||||
try:
|
||||
channel = action.params['channel']
|
||||
message_template = action.params['message']
|
||||
|
||||
# Debug the message template
|
||||
logger.debug(f"Message template: {repr(message_template)}")
|
||||
logger.debug(f"Context keys: {list(context.keys())}")
|
||||
|
||||
# Format message with context - handle JSON string formatting carefully
|
||||
# The message template contains JSON which causes issues with .format()
|
||||
# Use string replacement instead of format to avoid JSON brace conflicts
|
||||
try:
|
||||
# Ensure image_key is available for message formatting
|
||||
if 'image_key' not in context:
|
||||
context['image_key'] = '' # Default empty value if redis_save_image failed
|
||||
|
||||
# Use string replacement to avoid JSON formatting issues
|
||||
message = message_template
|
||||
for key, value in context.items():
|
||||
placeholder = '{' + key + '}'
|
||||
message = message.replace(placeholder, str(value))
|
||||
|
||||
logger.debug(f"Formatted message using replacement: {message}")
|
||||
except Exception as e:
|
||||
logger.error(f"Message formatting failed: {e}")
|
||||
logger.error(f"Template: {repr(message_template)}")
|
||||
logger.error(f"Context: {context}")
|
||||
return {'status': 'error', 'message': f'Message formatting failed: {e}'}
|
||||
|
||||
# Publish message synchronously using a sync Redis client
|
||||
try:
|
||||
import redis
|
||||
|
||||
# Create a synchronous Redis client with same connection details
|
||||
sync_redis = redis.Redis(
|
||||
host=self.redis_manager.host,
|
||||
port=self.redis_manager.port,
|
||||
password=self.redis_manager.password,
|
||||
db=self.redis_manager.db,
|
||||
decode_responses=True, # For publishing text messages
|
||||
socket_timeout=self.redis_manager.socket_timeout,
|
||||
socket_connect_timeout=self.redis_manager.socket_connect_timeout
|
||||
)
|
||||
|
||||
# Publish message
|
||||
result = sync_redis.publish(channel, message)
|
||||
sync_redis.close() # Clean up connection
|
||||
|
||||
if result >= 0: # Redis publish returns number of subscribers
|
||||
return {'status': 'success', 'subscribers': result, 'channel': channel}
|
||||
else:
|
||||
return {'status': 'error', 'message': 'Failed to publish message to Redis'}
|
||||
|
||||
except Exception as redis_error:
|
||||
logger.error(f"Error calling Redis from sync context: {redis_error}")
|
||||
return {'status': 'error', 'message': f'Redis operation failed: {redis_error}'}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in redis_publish action: {e}", exc_info=True)
|
||||
return {'status': 'error', 'message': str(e)}
|
||||
|
||||
def get_statistics(self) -> Dict[str, Any]:
|
||||
"""Get branch processor statistics."""
|
||||
return {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue