new visualizer

This commit is contained in:
RyanOnTheInside
2025-05-31 17:17:15 -04:00
parent 8370a6bad4
commit 3dd0ea53dd
2 changed files with 234 additions and 1 deletions
+2
View File
@@ -165,6 +165,7 @@ from .nodes.flex.visualizers import(
EffectVisualizer,
PitchVisualizer,
PreviewFeature,
AnimatedFeaturePreview,
)
@@ -534,6 +535,7 @@ NODE_CLASS_MAPPINGS = {
"FeatureFade": FeatureFade,
"FeatureMath": FeatureMath,
"PreviewFeature": PreviewFeature,
"AnimatedFeaturePreview": AnimatedFeaturePreview,
"FeatureRebase": FeatureRebase,
"FeatureTruncateOrExtend": FeatureTruncateOrExtend,
"FeatureAccumulate": FeatureAccumulate,
+232 -1
View File
@@ -303,4 +303,235 @@ class PreviewFeature(RyanOnTheInside):
"type": type
}]
return ({"ui": {"images": results}})
return ({"ui": {"images": results}})
class AnimatedFeaturePreview(RyanOnTheInside):
CATEGORY = "RyanOnTheInside/FlexFeatures/Utilities/Previews"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"feature": ("FEATURE",),
"window_size": ("INT", {"default": 60, "min": 10, "max": 3000, "step": 1}),
"top_label": ("STRING", {"default": "Max"}),
"bottom_label": ("STRING", {"default": "Min"}),
"width": ("INT", {"default": 960, "min": 480, "max": 1920, "step": 1}),
"height": ("INT", {"default": 540, "min": 270, "max": 1080, "step": 1}),
"low_color": ("STRING", {"default": "(255,100,100)"}), # Red for low values
"high_color": ("STRING", {"default": "(100,255,100)"}), # Green for high values
"title_override": ("STRING", {"default": ""}), # Override for feature name in title
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "animate_feature"
def parse_color(self, color_string):
"""Parse color string to tuple"""
return tuple(map(int, color_string.strip("()").split(",")))
def interpolate_color(self, color1, color2, factor):
"""Interpolate between two colors based on factor (0.0 to 1.0)"""
return tuple(int(c1 + (c2 - c1) * factor) for c1, c2 in zip(color1, color2))
def draw_text_with_background(self, frame, text, position, font, font_scale, text_color, thickness=2, bg_color=(0, 0, 0), padding=8):
"""Draw text with a background rectangle for better visibility"""
text_size = cv2.getTextSize(text, font, font_scale, thickness)[0]
x, y = position
# Draw background rectangle with some padding
bg_rect_start = (x - padding, y - text_size[1] - padding)
bg_rect_end = (x + text_size[0] + padding, y + padding)
# Add subtle transparency effect by blending
overlay = frame.copy()
cv2.rectangle(overlay, bg_rect_start, bg_rect_end, bg_color, -1)
cv2.addWeighted(overlay, 0.7, frame, 0.3, 0, frame)
# Draw border
cv2.rectangle(frame, bg_rect_start, bg_rect_end, (64, 64, 64), 1)
# Draw text
cv2.putText(frame, text, position, font, font_scale, text_color, thickness)
def draw_gradient_background(self, frame, start_color, end_color):
"""Draw a subtle gradient background"""
height, width = frame.shape[:2]
for y in range(height):
factor = y / height
color = self.interpolate_color(start_color, end_color, factor)
frame[y, :] = color
def animate_feature(self, feature, window_size, top_label, bottom_label, width, height, low_color, high_color, title_override):
low_color = self.parse_color(low_color)
high_color = self.parse_color(high_color)
values = [feature.get_value_at_frame(i) for i in range(feature.frame_count)]
# Calculate actual min and max from the values
actual_min = min(values)
actual_max = max(values)
# Handle constant value case
if actual_max == actual_min:
actual_max = actual_min + 1.0
y_range = actual_max - actual_min
padding = 0.05 * y_range
y_min = actual_min - padding
y_max = actual_max + padding
# Graph area parameters with better proportions for labels above/below
margin_x = 80
margin_y = 120 # More space for labels above/below
graph_width = width - 2 * margin_x
graph_height = height - 2 * margin_y
graph_x = margin_x
graph_y = margin_y
output_frames = []
for frame_index in range(feature.frame_count):
# Create frame with gradient background
frame = np.zeros((height, width, 3), dtype=np.uint8)
self.draw_gradient_background(frame, (15, 15, 25), (25, 25, 35))
# Determine the window for this frame
window_start = max(0, frame_index - window_size // 2)
window_end = min(feature.frame_count, window_start + window_size)
# Adjust window_start if we're near the end
if window_end - window_start < window_size and window_start > 0:
window_start = max(0, window_end - window_size)
# Draw enhanced grid
grid_color = (45, 45, 55) # Subtle grid
major_grid_color = (65, 65, 75) # Slightly more visible for major lines
# Horizontal grid lines
for i in range(9): # More grid lines for precision
y = graph_y + (i * graph_height // 8)
color = major_grid_color if i % 2 == 0 else grid_color
cv2.line(frame, (graph_x, y), (graph_x + graph_width, y), color, 1)
# Vertical grid lines
for i in range(13): # More vertical lines
x = graph_x + (i * graph_width // 12)
color = major_grid_color if i % 3 == 0 else grid_color
cv2.line(frame, (x, graph_y), (x, graph_y + graph_height), color, 1)
# Draw graph border with gradient effect
border_color = (120, 120, 140)
cv2.rectangle(frame, (graph_x-2, graph_y-2), (graph_x + graph_width+2, graph_y + graph_height+2), border_color, 3)
cv2.rectangle(frame, (graph_x-1, graph_y-1), (graph_x + graph_width+1, graph_y + graph_height+1), (200, 200, 220), 1)
# Get the values for the current window
window_values = values[window_start:window_end]
window_frames = list(range(window_start, window_end))
# Convert values to screen coordinates and draw enhanced line with smoother interpolation
if len(window_values) > 1:
points = []
point_values = []
for i, (f, v) in enumerate(zip(window_frames, window_values)):
# X coordinate based on frame position in window
x_ratio = (f - window_start) / window_size
x = int(graph_x + x_ratio * graph_width)
# Y coordinate based on value (inverted because screen Y increases downward)
y_ratio = (v - y_min) / (y_max - y_min)
y = int(graph_y + graph_height - y_ratio * graph_height)
points.append((x, y))
point_values.append(v)
# Draw the line with smooth gradient colors
for i in range(len(points) - 1):
# Create multiple segments for smoother color transition
start_point = points[i]
end_point = points[i + 1]
start_value = point_values[i]
end_value = point_values[i + 1]
# Number of segments for smooth color transition
num_segments = max(5, abs(end_point[0] - start_point[0]) // 2)
for seg in range(num_segments):
seg_ratio = seg / num_segments
next_seg_ratio = (seg + 1) / num_segments
# Interpolate position
seg_x = int(start_point[0] + (end_point[0] - start_point[0]) * seg_ratio)
seg_y = int(start_point[1] + (end_point[1] - start_point[1]) * seg_ratio)
next_x = int(start_point[0] + (end_point[0] - start_point[0]) * next_seg_ratio)
next_y = int(start_point[1] + (end_point[1] - start_point[1]) * next_seg_ratio)
# Interpolate value for color
seg_value = start_value + (end_value - start_value) * (seg_ratio + next_seg_ratio) / 2
color_factor = (seg_value - actual_min) / (actual_max - actual_min) if actual_max != actual_min else 0.5
line_color = self.interpolate_color(low_color, high_color, color_factor)
# Draw segment with glow effect
cv2.line(frame, (seg_x, seg_y), (next_x, next_y), (line_color[0]//3, line_color[1]//3, line_color[2]//3), 5) # Glow
cv2.line(frame, (seg_x, seg_y), (next_x, next_y), line_color, 3) # Main line
# Draw the current position dot with enhanced styling
current_value = values[frame_index]
x_ratio = (frame_index - window_start) / window_size
x = int(graph_x + x_ratio * graph_width)
y_ratio = (current_value - y_min) / (y_max - y_min)
y = int(graph_y + graph_height - y_ratio * graph_height)
# Calculate dot color based on current value
color_factor = (current_value - actual_min) / (actual_max - actual_min) if actual_max != actual_min else 0.5
dot_color = self.interpolate_color(low_color, high_color, color_factor)
# Draw dot with glow effect
cv2.circle(frame, (x, y), 12, (dot_color[0]//2, dot_color[1]//2, dot_color[2]//2), -1) # Outer glow
cv2.circle(frame, (x, y), 8, (255, 255, 255), 2) # White outline
cv2.circle(frame, (x, y), 6, dot_color, -1) # Colored fill
# Enhanced text rendering
font = cv2.FONT_HERSHEY_SIMPLEX
text_color = (255, 255, 255)
# Labels positioned next to the y-axis
# Top label (left side, aligned with top of graph)
self.draw_text_with_background(frame, top_label, (15, graph_y + 20), font, 0.9, text_color, 2, (20, 20, 40), 8)
# Bottom label (left side, aligned with bottom of graph)
self.draw_text_with_background(frame, bottom_label, (15, graph_y + graph_height - 10), font, 0.9, text_color, 2, (20, 20, 40), 8)
# Enhanced title (centered at top)
title = title_override if title_override.strip() else feature.name
title_size = cv2.getTextSize(title, font, 1.2, 3)[0]
title_x = (width - title_size[0]) // 2
self.draw_text_with_background(frame, title, (title_x, 40), font, 1.2, text_color, 3, (20, 20, 40), 12)
# Current value (top right, no crowding)
current_value = values[frame_index]
value_text = f'Value: {current_value:.4f}'
value_size = cv2.getTextSize(value_text, font, 0.8, 2)[0]
value_pos = (width - value_size[0] - 20, 70) # Moved down to avoid title
# Color the value text based on the current value
color_factor = (current_value - actual_min) / (actual_max - actual_min) if actual_max != actual_min else 0.5
value_color = self.interpolate_color(low_color, high_color, color_factor)
self.draw_text_with_background(frame, value_text, value_pos, font, 0.8, value_color, 2, (20, 20, 40), 8)
# Frame info (bottom left - no crowding)
frame_info = f'Frame: {frame_index + 1}/{feature.frame_count}'
self.draw_text_with_background(frame, frame_info, (20, height - 30), font, 0.7, text_color, 2, (20, 20, 40), 6)
# Window range info (bottom right, properly spaced)
range_text = f'Window: {window_start}-{window_end-1}'
range_size = cv2.getTextSize(range_text, font, 0.6, 1)[0]
range_pos = (width - range_size[0] - 20, height - 30) # Same height as frame info
self.draw_text_with_background(frame, range_text, range_pos, font, 0.6, (180, 180, 180), 1, (20, 20, 40), 4)
output_frames.append(frame)
# Convert to tensor
output_tensor = torch.from_numpy(np.stack(output_frames)).float() / 255.0
return (output_tensor,)