Files
ryanontheinside-ComfyUI_Rea…/controls/detector_controls.py
2024-12-30 16:43:20 +00:00

295 lines
11 KiB
Python

from ..base.control_base import ControlNodeBase
from ..base.detector_base import ROIAction, SharedProcessing
import time
import numpy as np
import torch
class DetectionControlBase(ControlNodeBase):
"""Base class for all detection-based controllers"""
@classmethod
def INPUT_TYPES(cls):
inputs = super().INPUT_TYPES() # Get base inputs including always_execute
inputs["required"].update({
"image": ("IMAGE",),
"roi_chain": ("ROI",),
})
return inputs
RETURN_TYPES = ("MASK",)
CATEGORY = "real-time/control/detection"
def update(self, *args, **kwargs):
"""Implement abstract method from ControlNodeBase"""
return self.process_detection_base(*args, **kwargs)
def _process_action(self, state, action, detection, value, roi_state, min_val, max_val):
"""Process ROI action based on detection"""
detected = detection > 0.5 # Basic threshold
if detected and not roi_state["active"]:
roi_state["active"] = True
if action == ROIAction.ADD.value:
state["current_value"] = min(max_val, state["current_value"] + value)
elif action == ROIAction.SUBTRACT.value:
state["current_value"] = max(min_val, state["current_value"] - value)
elif action == ROIAction.MULTIPLY.value:
state["current_value"] = min(max_val, state["current_value"] * value)
elif action == ROIAction.DIVIDE.value:
if value != 0:
state["current_value"] = max(min_val, state["current_value"] / value)
elif action == ROIAction.SET.value:
state["current_value"] = max(min_val, min(max_val, value))
elif action == ROIAction.TOGGLE.value:
state["current_value"] = max_val if state["current_value"] == min_val else min_val
elif action == ROIAction.TRIGGER.value:
state["current_value"] = max_val
elif action == ROIAction.COUNTER.value:
roi_state["count"] += 1
state["current_value"] = min_val + (
roi_state["count"] % (int((max_val - min_val) + 1))
)
elif action == ROIAction.MOMENTARY.value:
state["current_value"] = max_val if detected else min_val
elif not detected:
roi_state["active"] = False
if action == ROIAction.TRIGGER.value:
state["current_value"] = min_val
def process_detection_base(self, image, roi_chain, minimum_value, maximum_value, starting_value, always_execute=True):
state = self.get_state({
"current_value": starting_value,
"detector_states": {},
"roi_states": {},
"last_cleanup": time.time()
})
# Convert image tensor to numpy
current_frame = (image[0] * 255).cpu().numpy().astype(np.uint8)
detection_mask = np.zeros_like(current_frame[:,:,0], dtype=np.float32)
# Generate shared preprocessed data once
shared_data = SharedProcessing.get_shared_data(current_frame)
# Collect all unique detector types from ROI chain
detector_types = set()
current_roi = roi_chain
while current_roi is not None:
detector = current_roi["detector"]
detector_types.add(detector.__class__)
current_roi = current_roi["next"]
# Run preprocessing for each detector type
preprocessed_data = {}
for detector_type in detector_types:
if hasattr(detector_type, 'preprocess'):
preprocessed_data[detector_type] = detector_type.preprocess(current_frame, shared_data)
# Process ROI chain
current_roi = roi_chain
while current_roi is not None:
bounds = current_roi["bounds"]
y_min, x_min, y_max, x_max = bounds
roi_id = str(bounds)
# Get detector state
detector_state = state["detector_states"].setdefault(roi_id, {})
detector_state.update({
'y_offset': y_min,
'x_offset': x_min,
'shared': shared_data # Add shared data
})
# Add preprocessed data if available
detector_type = current_roi["detector"].__class__
if detector_type in preprocessed_data:
detector_state["preprocessed"] = preprocessed_data[detector_type]
# Get ROI state
roi_state = state["roi_states"].setdefault(roi_id, {
"active": False,
"count": 0
})
# Run detection
detection_value, viz_mask = current_roi["detector"].detect(
current_frame[y_min:y_max+1, x_min:x_max+1],
current_roi["mask"][y_min:y_max+1, x_min:x_max+1],
detector_state
)
# Update visualization mask
detection_mask[y_min:y_max+1, x_min:x_max+1] = np.maximum(
detection_mask[y_min:y_max+1, x_min:x_max+1],
viz_mask
)
# Process action
self._process_action(
state, current_roi["action"],
detection_value, current_roi["value"],
roi_state, minimum_value, maximum_value
)
current_roi = current_roi["next"]
self.set_state(state)
return state["current_value"], torch.from_numpy(detection_mask).unsqueeze(0)
class FloatDetectionControl(DetectionControlBase):
"""Controls a floating point value based on detection events"""
DESCRIPTION = "Generates a floating point value based on detection events in regions of interest"
@classmethod
def INPUT_TYPES(cls):
inputs = super().INPUT_TYPES()
inputs["required"].update({
"maximum_value": ("FLOAT", {
"default": 1.0,
"min": -10000.0,
"max": 10000.0,
"step": 0.1,
"tooltip": "Maximum value that can be output"
}),
"minimum_value": ("FLOAT", {
"default": 0.0,
"min": -10000.0,
"max": 10000.0,
"step": 0.1,
"tooltip": "Minimum value that can be output"
}),
"starting_value": ("FLOAT", {
"default": 0.5,
"min": -10000.0,
"max": 10000.0,
"step": 0.1,
"tooltip": "Initial value when the node first executes"
})
})
return inputs
RETURN_TYPES = ("FLOAT", "MASK")
FUNCTION = "process_detection"
def process_detection(self, *args, **kwargs):
value, mask = self.process_detection_base(*args, **kwargs)
return (float(value), mask)
class IntDetectionControl(DetectionControlBase):
"""Controls an integer value based on detection events"""
DESCRIPTION = "Generates an integer value based on detection events in regions of interest"
@classmethod
def INPUT_TYPES(cls):
inputs = super().INPUT_TYPES()
inputs["required"].update({
"maximum_value": ("INT", {
"default": 100,
"min": -10000,
"max": 10000,
"step": 1,
"tooltip": "Maximum value that can be output"
}),
"minimum_value": ("INT", {
"default": 0,
"min": -10000,
"max": 10000,
"step": 1,
"tooltip": "Minimum value that can be output"
}),
"starting_value": ("INT", {
"default": 50,
"min": -10000,
"max": 10000,
"step": 1,
"tooltip": "Initial value when the node first executes"
})
})
return inputs
RETURN_TYPES = ("INT", "MASK")
FUNCTION = "process_detection"
def process_detection(self, *args, **kwargs):
value, mask = self.process_detection_base(*args, **kwargs)
return (int(round(value)), mask)
class StringDetectionControl(DetectionControlBase):
"""Controls string output based on detection events"""
DESCRIPTION = "Outputs strings based on detection events in regions of interest"
@classmethod
def INPUT_TYPES(cls):
inputs = super().INPUT_TYPES()
inputs["required"].update({
"strings": ("STRING", {
"multiline": True,
"default": "first string\nsecond string\nthird string",
"tooltip": "List of strings to cycle through (one per line)"
})
})
return inputs
RETURN_TYPES = ("STRING", "MASK")
FUNCTION = "process_detection"
def process_detection(self, image, roi_chain, strings, always_execute=True):
# Split strings into list
string_list = [s.strip() for s in strings.split('\n') if s.strip()]
if not string_list:
return ("", torch.zeros((1, image.shape[1], image.shape[2])))
# Process with normalized range
value, mask = self.process_detection_base(
image, roi_chain,
minimum_value=0,
maximum_value=len(string_list) - 1,
starting_value=0,
always_execute=always_execute
)
# Convert value to string index
index = int(round(value)) % len(string_list)
return (string_list[index], mask)
def _process_action(self, state, action, detection, value, roi_state, min_val, max_val):
"""Process ROI action based on detection"""
detected = detection > 0.5 # Basic threshold
if detected and not roi_state["active"]:
roi_state["active"] = True
if action == ROIAction.ADD.value:
state["current_value"] = min(max_val, state["current_value"] + value)
elif action == ROIAction.SUBTRACT.value:
state["current_value"] = max(min_val, state["current_value"] - value)
elif action == ROIAction.MULTIPLY.value:
state["current_value"] = min(max_val, state["current_value"] * value)
elif action == ROIAction.DIVIDE.value:
if value != 0:
state["current_value"] = max(min_val, state["current_value"] / value)
elif action == ROIAction.SET.value:
state["current_value"] = max(min_val, min(max_val, value))
elif action == ROIAction.TOGGLE.value:
state["current_value"] = max_val if state["current_value"] == min_val else min_val
elif action == ROIAction.TRIGGER.value:
state["current_value"] = max_val
elif action == ROIAction.COUNTER.value:
roi_state["count"] += 1
state["current_value"] = min_val + (
roi_state["count"] % (int((max_val - min_val) + 1))
)
elif action == ROIAction.MOMENTARY.value:
state["current_value"] = max_val if detected else min_val
elif not detected:
roi_state["active"] = False
if action == ROIAction.TRIGGER.value:
state["current_value"] = min_val