feature interpolator and peak detection

This commit is contained in:
ryanontheinstide
2025-01-19 12:15:59 -05:00
parent 808c0960f0
commit 6faed403bb
4 changed files with 178 additions and 2 deletions
+5
View File
@@ -261,6 +261,8 @@ from .nodes.flex.feature_modulation import (
FeatureAccumulate,
FeatureContiguousInterpolate,
FeatureRenormalize,
FeatureInterpolator,
FeaturePeakDetector,
)
from .nodes.audio.flex_audio import (
@@ -351,6 +353,9 @@ NODE_CLASS_MAPPINGS = {
#"PoseInterpolator": PoseInterpolator,
# "FlexFeatureAttentionControl": FlexFeatureAttentionControl,
"FeatureInterpolator": FeatureInterpolator,
"FeaturePeakDetector": FeaturePeakDetector,
"Doom": Doom,
"WhisperToPromptTravel": WhisperToPromptTravel,
"ManualFeaturePipe": ManualFeaturePipe,
+1 -1
View File
@@ -169,7 +169,7 @@ class ManualFeatureFromPipe(ManualFeatureNode):
@apply_tooltips
class DrawableFeatureNode(FeatureExtractorBase):
#TODO: add logic to truncate on frame count change
@classmethod
def feature_type(cls) -> type[BaseFeature]:
return DrawableFeature
+137
View File
@@ -4,6 +4,8 @@ import torch
import random
from ..node_utilities import apply_easing
from ...tooltips import apply_tooltips
from scipy.interpolate import interp1d
from scipy.signal import find_peaks
@apply_tooltips
class FeatureModulationBase(RyanOnTheInside):
@@ -628,3 +630,138 @@ class FeatureContiguousInterpolate(FeatureModulationBase):
processed_feature = self.create_processed_feature(feature, interpolated, "Interpolated", invert_output)
return (processed_feature,)
@apply_tooltips
class FeatureInterpolator(FeatureModulationBase):
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"feature": ("FEATURE",),
"interpolation_method": (["zero", "linear", "cubic", "nearest", "previous", "next", "quadratic"],),
"threshold": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"min_difference": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"min_distance": ("INT", {"default": 1, "min": 1, "max": 100, "step": 1}),
"extrapolate": ("BOOLEAN", {"default": False}),
**super().INPUT_TYPES()["required"],
}
}
RETURN_TYPES = ("FEATURE",)
RETURN_NAMES = ("FEATURE",)
FUNCTION = "modulate"
def modulate(self, feature, interpolation_method, threshold, min_difference, min_distance, extrapolate, invert_output):
# Get feature values
values = [feature.get_value_at_frame(i) for i in range(feature.frame_count)]
# Find significant points based on threshold and difference
significant_indices = []
last_value = None
last_index = None
for i, v in enumerate(values):
# Check threshold
if v >= threshold:
# Check minimum difference from last point
if last_value is None or abs(v - last_value) >= min_difference:
# Check minimum distance from last point
if last_index is None or (i - last_index) >= min_distance:
significant_indices.append(i)
last_value = v
last_index = i
if not significant_indices:
# If no significant points found, return original feature
return (feature,)
significant_values = [values[i] for i in significant_indices]
# Create interpolation function
x = np.array(significant_indices)
y = np.array(significant_values)
# Handle extrapolation
fill_value = "extrapolate" if extrapolate else (significant_values[0], significant_values[-1])
# Create interpolator based on method
if interpolation_method == "zero":
# Zero-order hold (step function)
f = interp1d(x, y, kind='zero', bounds_error=False, fill_value=fill_value)
elif interpolation_method == "linear":
f = interp1d(x, y, kind='linear', bounds_error=False, fill_value=fill_value)
elif interpolation_method == "cubic":
# Need at least 4 points for cubic, fallback to quadratic if not enough points
if len(x) >= 4:
f = interp1d(x, y, kind='cubic', bounds_error=False, fill_value=fill_value)
else:
f = interp1d(x, y, kind='quadratic', bounds_error=False, fill_value=fill_value)
elif interpolation_method == "nearest":
f = interp1d(x, y, kind='nearest', bounds_error=False, fill_value=fill_value)
elif interpolation_method == "previous":
f = interp1d(x, y, kind='previous', bounds_error=False, fill_value=fill_value)
elif interpolation_method == "next":
f = interp1d(x, y, kind='next', bounds_error=False, fill_value=fill_value)
elif interpolation_method == "quadratic":
# Need at least 3 points for quadratic, fallback to linear if not enough points
if len(x) >= 3:
f = interp1d(x, y, kind='quadratic', bounds_error=False, fill_value=fill_value)
else:
f = interp1d(x, y, kind='linear', bounds_error=False, fill_value=fill_value)
# Generate interpolated values for all frames
x_new = np.arange(feature.frame_count)
interpolated = f(x_new)
# Create processed feature
processed_feature = self.create_processed_feature(feature, interpolated, "Interpolated", invert_output)
return (processed_feature,)
@apply_tooltips
class FeaturePeakDetector(FeatureModulationBase):
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"feature": ("FEATURE",),
"prominence": ("FLOAT", {"default": 0.1, "min": 0.0, "max": 1.0, "step": 0.01}),
"distance": ("INT", {"default": 1, "min": 1, "max": 100, "step": 1}),
"width": ("INT", {"default": 1, "min": 1, "max": 100, "step": 1}),
"plateau_size": ("INT", {"default": 1, "min": 1, "max": 100, "step": 1}),
"detect_valleys": ("BOOLEAN", {"default": False}),
**super().INPUT_TYPES()["required"],
}
}
RETURN_TYPES = ("FEATURE",)
RETURN_NAMES = ("FEATURE",)
FUNCTION = "modulate"
def modulate(self, feature, prominence, distance, width, plateau_size, detect_valleys, invert_output):
# Get feature values
values = [feature.get_value_at_frame(i) for i in range(feature.frame_count)]
# Convert to numpy array for processing
signal = np.array(values)
# If detecting valleys, invert the signal temporarily
if detect_valleys:
signal = -signal
# Find peaks with given parameters
peaks, properties = find_peaks(
signal,
prominence=prominence, # Minimum prominence of peaks
distance=distance, # Minimum distance between peaks
width=width, # Minimum width of peaks
plateau_size=plateau_size # Minimum size of flat peaks
)
# Create output signal where peaks are 1.0 and everything else is 0.0
peak_signal = np.zeros_like(signal)
peak_signal[peaks] = 1.0
# Create processed feature
name_prefix = "Valleys" if detect_valleys else "Peaks"
processed_feature = self.create_processed_feature(feature, peak_signal, name_prefix, invert_output)
return (processed_feature,)
+35 -1
View File
@@ -615,4 +615,38 @@ The feature's maximum value will be mapped to this threshold."""
}, inherits_from='SchedulerNode', description="""Converts a feature into a sequence of floating-point values.
Ideal for controlling parameters that accept decimal values, like strengths or ratios.
The output will automatically adjust to match the constraints of the target parameter.""")
The output will automatically adjust to match the constraints of the target parameter.""")
# FeatureInterpolator tooltips (inherits from: FeatureModulationBase)
TooltipManager.register_tooltips("FeatureInterpolator", {
"feature": "Input feature to interpolate",
"interpolation_method": """Choose interpolation method:
- zero: Step function between points
- linear: Straight lines between points
- cubic: Smooth curves (needs 4+ points)
- nearest: Jump to nearest point value
- previous: Hold previous value
- next: Jump to next value early
- quadratic: Smooth curves (needs 3+ points)""",
"threshold": "Only consider points above this value (0.0 to 1.0)",
"min_difference": "Minimum value change required between points (0.0 to 1.0)",
"min_distance": "Minimum frames between points (1 to 100)",
"extrapolate": "Whether to extend values beyond the first/last points"
}, inherits_from='FeatureModulationBase', description="""Interpolate between significant points in a feature.
Combine with different interpolation methods and point selection criteria to create custom curves.
Great for smoothing, step functions, or custom animation curves.""")
# FeaturePeakDetector tooltips (inherits from: FeatureModulationBase)
TooltipManager.register_tooltips("FeaturePeakDetector", {
"feature": "Input feature to detect peaks from",
"prominence": "How much a peak needs to stand out from surrounding values (0.0 to 1.0)",
"distance": "Minimum number of frames between peaks (1 to 100)",
"width": "Minimum width of peaks in frames (1 to 100)",
"plateau_size": "Minimum size of flat peaks in frames (1 to 100)",
"detect_valleys": "When enabled, detects troughs instead of peaks"
}, inherits_from='FeatureModulationBase', description="""Detect significant peaks or valleys in any feature.
Works with any type of feature data (audio, motion, etc).
Outputs a binary signal (1.0 at peaks, 0.0 elsewhere).
Great for detecting significant moments or transitions.""")