Rings and wave propagation, skip features for flex images, etc

This commit is contained in:
RyanOnTheInside
2024-08-31 10:01:55 -04:00
parent fdca299c6e
commit d02bb5b1ba
9 changed files with 248 additions and 219 deletions
+16 -10
View File
@@ -70,15 +70,7 @@ from .nodes.flex.midi_feature_extractor import(
MIDILoadAndExtract,
)
from .nodes.masks.flex_masks import (
FlexMaskMorph,
FlexMaskWarp,
FlexMaskTransform,
FlexMaskMath,
FlexMaskOpacity,
FlexMaskVoronoiScheduled,
FlexMaskBinary,
)
@@ -117,6 +109,19 @@ from .nodes.masks.utility_nodes import (
from .nodes.images.image_utility_nodes import (
DyeImage,
)
from .nodes.masks.flex_masks import (
FlexMaskMorph,
FlexMaskWarp,
FlexMaskTransform,
FlexMaskMath,
FlexMaskOpacity,
FlexMaskVoronoiScheduled,
FlexMaskBinary,
FlexMaskWavePropagation,
FlexMaskEmanatingRings,
)
from .nodes.images.flex_images import (
FlexImageEdgeDetect,
FlexImagePosterize,
@@ -192,7 +197,8 @@ NODE_CLASS_MAPPINGS = {
"FlexMaskBinary": FlexMaskBinary,
"FlexMaskOpacity": FlexMaskOpacity,
"FlexMaskVoronoiScheduled": FlexMaskVoronoiScheduled,
"FlexMaskWavePropagation": FlexMaskWavePropagation,
"FlexMaskEmanatingRings": FlexMaskEmanatingRings,
#audio
"AudioSeparator": AudioSeparator,
+36 -8
View File
@@ -315,7 +315,6 @@ add_node_config("ParticleColorModulation", {
add_node_config("FlexMaskBase", {
"BASE_DESCRIPTION": """
## Common Parameters
- `feature`: The feature used to modulate the mask operation (FEATURE type)
- `feature_pipe`: The feature pipe containing frame information (FEATURE_PIPE type)
- `feature_threshold`: Threshold for feature activation (0.0 to 1.0)
@@ -392,8 +391,8 @@ add_node_config("FlexMaskVoronoiScheduled", {
- `seed`: Random seed for reproducible results (0 to 2^64 - 1).
- `x_offset`: Horizontal offset of the Voronoi pattern (-1000.0 to 1000.0).
- `y_offset`: Vertical offset of the Voronoi pattern (-1000.0 to 1000.0).
- `control_parameter`: Which parameter to modulate based on the feature ("scale", "detail", "randomness", "seed", "x_offset", "y_offset").
- `formula`: Mathematical formula used to map the feature value to the control parameter. Options include "Linear", "Quadratic", "Cubic", "Sinusoidal", and "Exponential".
- `feature_param`: Which parameter to modulate based on the feature ("scale", "detail", "randomness", "seed", "x_offset", "y_offset").
- `formula`: Mathematical formula used to map the feature value to the feature parameter. Options include "Linear", "Quadratic", "Cubic", "Sinusoidal", and "Exponential".
- `a` and `b`: Parameters for fine-tuning the chosen formula (0.1 to 10.0).
Credit for the heavy lifting for this node goes to https://github.com/alanhuang67/
@@ -415,11 +414,42 @@ The binary mask operation is applied with strength determined by the selected fe
"""
})
add_node_config("FlexMaskEmanatingRings", {
"TOP_DESCRIPTION": "Creates dynamic, expanding ring patterns that emanate from the edges of the input mask.",
"ADDITIONAL_INFO": """
- `num_rings`: Number of concentric rings to generate (1 to 50). More rings create a denser, more complex pattern.
- `max_ring_width`: Maximum width of each ring as a fraction of the total distance (0.01 to 0.9). Larger values create wider, more prominent rings that overlap more.
- `wave_speed`: Speed at which the rings expand outward (0.01 to 0.5). Higher values create faster-moving, more dynamic patterns.
- `feature_param`: Determines which aspect of the effect is modulated by the input feature.
- Options: "num_rings" (varies ring count), "ring_width" (adjusts ring thickness), "wave_speed" (changes expansion rate), "all" (modulates all parameters)
This node creates a mesmerizing effect of rings expanding from the edges of the input mask. The rings start thick at the mask boundary and thin out as they move outward, creating a pulsating, wave-like appearance. The effect can be subtle and smooth or bold and dynamic depending on the parameter settings.
The feature input can be used to dynamically adjust the effect over time, allowing for rhythmic pulsing (e.g., synced to audio) or gradual evolution of the pattern. When the feature value is below the threshold, the animation continues but no new rings are generated, creating a smooth transition effect.
"""
})
add_node_config("FlexMaskWavePropagation", {
"TOP_DESCRIPTION": "Good luck with this one...Simulates wave-like abstract distortions propagating from the edges of the input mask.",
"ADDITIONAL_INFO": """
- `wave_speed`: Controls the rate of wave propagation (0.1 to 100.0). Higher values create faster-moving, more rapidly evolving patterns.
- `wave_amplitude`: Determines the intensity of the wave effect (0.1 to 2.0). Larger values create more pronounced, exaggerated distortions.
- `wave_decay`: Rate at which waves fade out over time (0.9 to 10.0). Lower values cause waves to dissipate quickly, while higher values allow waves to persist and interact more.
- `wave_frequency`: Frequency of the wave oscillations (0.01 to 10.0). Higher values create more rapid, ripple-like effects, while lower values produce smoother, more gradual undulations.
- `max_wave_field`: Maximum allowed intensity for the wave field (10.0 to 10000.0). This parameter prevents the effect from becoming too extreme over time.
This node creates a dynamic, fluid-like effect where waves seem to emanate from the edges of the input mask. The waves propagate outward, interacting with each other and creating complex, evolving patterns. The effect can range from subtle, water-like ripples to intense, psychedelic distortions depending on the parameter settings.
The wave propagation is particularly sensitive to the interplay between `wave_speed`, `wave_amplitude`, and `wave_decay`. High speed with low decay can create a turbulent, chaotic effect, while lower speed with higher decay produces a more serene, flowing appearance.
The feature input modulates the intensity of new waves being generated, allowing for dynamic control over the effect's strength. This can be used to create pulsating effects synchronized with audio or other time-varying inputs.
"""
})
add_node_config("BaseFeatureNode", {
"BASE_DESCRIPTION": """
Features are used to modulate mask operations in FlexMask nodes.
## Common Parameters
- `frame_rate`: Frame rate of the video
- `frame_count`: Total number of frames
"""
@@ -719,15 +749,13 @@ This node is particularly useful for creating dynamic, feature-driven animations
NODE_CONFIGS["FlexImageBase"] = {
"BASE_DESCRIPTION": """
## Common Parameters
- `images`: Input image sequence (IMAGE type)
- `feature`: Feature used to modulate the effect (FEATURE type)
- `feature_pipe`: Feature pipe containing frame information (FEATURE_PIPE type)
- `strength`: Overall strength of the effect (0.0 to 1.0)
- `feature_threshold`: Minimum feature value to apply the effect (0.0 to 1.0)
- `modulate_param`: Parameter to be modulated by the feature
- `modulation_mode`: How the feature modulates the parameter ("relative" or "absolute")
- `feature_param`: Parameter to be modulated by the feature
- `feature_mode`: How the feature modulates the parameter ("relative" or "absolute"). Relative mode adjusts the parameter based on its current value, while absolute mode directly sets the parameter to the feature value.
"""
}
+8 -8
View File
@@ -19,7 +19,7 @@ class FlexImageEdgeDetect(FlexImageBase):
@classmethod
def get_modifiable_params(cls):
return ["low_threshold", "high_threshold"]
return ["low_threshold", "high_threshold", "None"]
def apply_effect_internal(self, image: np.ndarray, low_threshold: float, high_threshold: float, **kwargs) -> np.ndarray:
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
@@ -47,7 +47,7 @@ class FlexImagePosterize(FlexImageBase):
@classmethod
def get_modifiable_params(cls):
return ["max_levels", "dither_strength", "channel_separation", "gamma"]
return ["max_levels", "dither_strength", "channel_separation", "gamma", "None"]
def apply_effect_internal(self, image: np.ndarray, max_levels: int, dither_strength: float,
channel_separation: float, gamma: float, **kwargs) -> np.ndarray:
@@ -105,7 +105,7 @@ class FlexImageKaleidoscope(FlexImageBase):
@classmethod
def get_modifiable_params(cls):
return ["segments", "zoom", "rotation", "precession", "speed"]
return ["segments", "zoom", "rotation", "precession", "speed", "None"]
def apply_effect_internal(self, image: np.ndarray, segments: int, center_x: float, center_y: float,
zoom: float, rotation: float, precession: float, speed: float, **kwargs) -> np.ndarray:
@@ -204,7 +204,7 @@ class FlexImageGlitch(FlexImageBase):
@classmethod
def get_modifiable_params(cls):
return ["shift_amount", "scan_lines", "color_shift"]
return ["shift_amount", "scan_lines", "color_shift", "None"]
def apply_effect_internal(self, image: np.ndarray, shift_amount: float, scan_lines: int, color_shift: float, **kwargs) -> np.ndarray:
h, w = image.shape[:2]
@@ -241,7 +241,7 @@ class FlexImageChromaticAberration(FlexImageBase):
@classmethod
def get_modifiable_params(cls):
return ["shift_amount", "angle"]
return ["shift_amount", "angle", "None"]
def apply_effect_internal(self, image: np.ndarray, shift_amount: float, angle: float, **kwargs) -> np.ndarray:
h, w = image.shape[:2]
@@ -272,7 +272,7 @@ class FlexImagePixelate(FlexImageBase):
@classmethod
def get_modifiable_params(cls):
return ["pixel_size"]
return ["pixel_size", "None"]
def apply_effect_internal(self, image: np.ndarray, pixel_size: int, **kwargs) -> np.ndarray:
h, w = image.shape[:2]
@@ -309,7 +309,7 @@ class FlexImageBloom(FlexImageBase):
@classmethod
def get_modifiable_params(cls):
return ["threshold", "blur_amount", "intensity"]
return ["threshold", "blur_amount", "intensity", "None"]
def apply_effect_internal(self, image: np.ndarray, threshold: float, blur_amount: float,
intensity: float, **kwargs) -> np.ndarray:
@@ -342,7 +342,7 @@ class FlexImageTiltShift(FlexImageBase):
@classmethod
def get_modifiable_params(cls):
return ["blur_amount", "focus_position_x", "focus_position_y", "focus_width", "focus_height"]
return ["blur_amount", "focus_position_x", "focus_position_y", "focus_width", "focus_height", "None"]
def apply_effect_internal(self, image: np.ndarray, blur_amount: float, focus_position_x: float,
focus_position_y: float, focus_width: float, focus_height: float,
+10 -185
View File
@@ -1,181 +1,3 @@
# import torch
# import numpy as np
# from abc import ABC, abstractmethod
# from tqdm import tqdm
# from comfy.utils import ProgressBar
# from .image_utils import preserve_original_colors, apply_blur
# from ... import RyanOnTheInside
# class ImageBase(RyanOnTheInside, ABC):
# @classmethod
# def INPUT_TYPES(cls):
# return {
# "required": {
# "images": ("IMAGE",),
# "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
# "blend_mode": (["normal", "multiply", "screen", "overlay", "soft_light"],),
# "preserve_colors": ("BOOLEAN", {"default": False}),
# "apply_to_alpha": ("BOOLEAN", {"default": False}),
# }
# }
# CATEGORY = "RyanOnTheInside/Images"
# def __init__(self):
# self.pre_processors = []
# self.post_processors = []
# self.progress_bar = None
# self.tqdm_bar = None
# self.current_progress = 0
# self.total_steps = 0
# def add_pre_processor(self, func):
# self.pre_processors.append(func)
# return self
# def add_post_processor(self, func):
# self.post_processors.append(func)
# return self
# def pre_process(self, image):
# for processor in self.pre_processors:
# image = processor(image)
# return image
# def post_process(self, image):
# for processor in self.post_processors:
# image = processor(image)
# return image
# def start_progress(self, total_steps, desc="Processing"):
# self.progress_bar = ProgressBar(total_steps)
# self.tqdm_bar = tqdm(total=total_steps, desc=desc, leave=False)
# self.current_progress = 0
# self.total_steps = total_steps
# def update_progress(self, step=1):
# self.current_progress += step
# if self.progress_bar:
# self.progress_bar.update(step)
# if self.tqdm_bar:
# self.tqdm_bar.update(step)
# def end_progress(self):
# if self.tqdm_bar:
# self.tqdm_bar.close()
# self.progress_bar = None
# self.tqdm_bar = None
# self.current_progress = 0
# self.total_steps = 0
# @abstractmethod
# def process_image(self, image: np.ndarray, strength: float, **kwargs) -> np.ndarray:
# """
# Process a single image. This method must be implemented by child classes.
# """
# pass
# def apply_image_operation(self, processed_images: torch.Tensor, original_images: torch.Tensor, strength: float, blend_mode: str, preserve_colors: bool, apply_to_alpha: bool, **kwargs) -> torch.Tensor:
# processed_images_np = processed_images.cpu().numpy() if isinstance(processed_images, torch.Tensor) else processed_images
# original_images_np = original_images.cpu().numpy() if isinstance(original_images, torch.Tensor) else original_images
# num_frames = processed_images_np.shape[0]
# self.start_progress(num_frames, desc="Applying image operation")
# result = []
# for processed_image, original_image in zip(processed_images_np, original_images_np):
# # Pre-processing
# processed_image = self.pre_process(processed_image)
# if preserve_colors:
# processed_image = preserve_original_colors(original_image, processed_image)
# if not apply_to_alpha and original_image.shape[-1] == 4:
# processed_image[..., -1] = original_image[..., -1]
# # Post-processing
# processed_image = self.post_process(processed_image)
# # Ensure the final image is clipped between 0 and 1
# processed_image = np.clip(processed_image, 0, 1)
# result.append(processed_image)
# self.update_progress()
# self.end_progress()
# return torch.from_numpy(np.stack(result)).float()
# @abstractmethod
# def main_function(self, *args, **kwargs) -> torch.Tensor:
# """
# Main entry point for the node. This method must be implemented by child classes.
# It should call apply_image_operation with the appropriate arguments.
# """
# pass
# import torch
# import numpy as np
# from abc import abstractmethod
# from .image_base import ImageBase
# class FlexImageBase(ImageBase):
# BLEND_MODES = ["multiply", "add", "subtract", "override"]
# @classmethod
# def INPUT_TYPES(cls):
# return {
# "required": {
# **super().INPUT_TYPES()["required"],
# "feature": ("FEATURE",),
# "feature_pipe": ("FEATURE_PIPE",),
# "feature_threshold": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
# "feature_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
# "feature_blend_mode": (cls.BLEND_MODES,),
# }
# }
# CATEGORY = "RyanOnTheInside/FlexImages"
# RETURN_TYPES = ("IMAGE",)
# FUNCTION = "main_function"
# @abstractmethod
# def process_image(self, image: np.ndarray, feature_value: float, strength: float, **kwargs) -> np.ndarray:
# pass
# def apply_image_operation(self, images, feature, feature_pipe, strength, blend_mode, preserve_colors, apply_to_alpha, feature_threshold, feature_strength, feature_blend_mode, **kwargs):
# num_frames = feature_pipe.frame_count
# original_images = images.clone()
# self.start_progress(num_frames, desc="Applying flex image operation")
# result = []
# for i in range(num_frames):
# image = images[i].numpy()
# feature_value = feature.get_value_at_frame(i)
# if feature_value >= feature_threshold:
# # Apply the effect with the feature value directly controlling the intensity
# processed_image = self.process_image(image, feature_value * feature_strength, strength, **kwargs)
# else:
# processed_image = image
# result.append(processed_image)
# self.update_progress()
# self.end_progress()
# processed_images = torch.from_numpy(np.stack(result)).float()
# return super().apply_image_operation(processed_images, original_images, strength, blend_mode, preserve_colors, apply_to_alpha, **kwargs)
# @abstractmethod
# def main_function(self, images, feature, feature_pipe, strength, blend_mode, preserve_colors, apply_to_alpha, feature_threshold, feature_strength, feature_blend_mode, **kwargs):
# pass
import torch
import numpy as np
from abc import ABC, abstractmethod
@@ -193,8 +15,8 @@ class FlexImageBase(RyanOnTheInside, ABC):
"feature_pipe": ("FEATURE_PIPE",),
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"feature_threshold": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"modulate_param": (cls.get_modifiable_params(), {"default": cls.get_modifiable_params()[0]}),
"modulation_mode": (["relative", "absolute"], {"default": "relative"}),
"feature_param": (cls.get_modifiable_params(), {"default": cls.get_modifiable_params()[0]}),
"feature_mode": (["relative", "absolute"], {"default": "relative"}),
}
}
@@ -227,7 +49,7 @@ class FlexImageBase(RyanOnTheInside, ABC):
else: # absolute
return param_value * feature_value * strength
def apply_effect(self, images, feature, feature_pipe, strength, feature_threshold, **kwargs):
def apply_effect(self, images, feature, feature_pipe, strength, feature_threshold, feature_param, feature_mode, **kwargs):
num_frames = feature_pipe.frame_count
images_np = images.cpu().numpy()
@@ -239,7 +61,10 @@ class FlexImageBase(RyanOnTheInside, ABC):
feature_value = feature.get_value_at_frame(i)
if feature_value >= feature_threshold:
processed_image = self.process_image(image, feature_value, strength, **kwargs)
processed_image = self.process_image(image, feature_value, strength,
feature_param=feature_param,
feature_mode=feature_mode,
**kwargs)
else:
processed_image = image
@@ -261,13 +86,13 @@ class FlexImageBase(RyanOnTheInside, ABC):
return (result_tensor,)
def process_image(self, image: np.ndarray, feature_value: float, strength: float,
modulate_param: str, modulation_mode: str, **kwargs) -> np.ndarray:
feature_param: str, feature_mode: str, **kwargs) -> np.ndarray:
# Modulate the selected parameter
for param_name in self.get_modifiable_params():
if param_name in kwargs:
if param_name == modulate_param:
if param_name == feature_param:
kwargs[param_name] = self.modulate_param(param_name, kwargs[param_name],
feature_value, strength, modulation_mode)
feature_value, strength, feature_mode)
# Call the child class's implementation
return self.apply_effect_internal(image, **kwargs)
+156 -1
View File
@@ -1,10 +1,12 @@
from .mask_base import FlexMaskBase
from .mask_base import FlexMaskBase, MaskBase
from .mask_utils import morph_mask, warp_mask, transform_mask, combine_masks
import math
import numpy as np
from .voronoi_noise import VoronoiNoise #NOTE credit for Voronoi goes to Alan Huang https://github.com/alanhuang67/
from comfy.model_management import get_torch_device
import cv2
from .mask_base import FlexMaskBase
from scipy.ndimage import distance_transform_edt
class FlexMaskMorph(FlexMaskBase):
@classmethod
@@ -303,6 +305,159 @@ class FlexMaskBinary(FlexMaskBase):
class FlexMaskWavePropagation(FlexMaskBase):
@classmethod
def INPUT_TYPES(cls):
cls.feature_threshold_default = 0.25
return {
**super().INPUT_TYPES(),
"required": {
**super().INPUT_TYPES()["required"],
"wave_speed": ("FLOAT", {"default": 50.0, "min": 0.1, "max": 100.0, "step": 0.5}),
"wave_amplitude": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 2.0, "step": 0.05}),
"wave_decay": ("FLOAT", {"default": 5.0, "min": 0.9, "max": 10.0, "step": 0.001}),
"wave_frequency": ("FLOAT", {"default": 0.1, "min": 0.01, "max": 10.0, "step": 0.01}),
"max_wave_field": ("FLOAT", {"default": 750.0, "min": 10.0, "max": 10000.0, "step": 10.0}),
}
}
def __init__(self):
super().__init__()
self.wave_field = None
self.frame_count = 0
def process_mask_below_threshold(self, mask, feature_value, strength, **kwargs):
self.wave_field = None
self.frame_count = 0
return mask
def process_mask(self, mask: np.ndarray, feature_value: float, strength: float,
wave_speed: float, wave_amplitude: float, wave_decay: float,
wave_frequency: float, max_wave_field: float, **kwargs) -> np.ndarray:
height, width = mask.shape
if self.wave_field is None:
self.wave_field = np.zeros((height, width), dtype=np.float32)
# Find mask boundary
kernel = np.ones((3,3), np.uint8)
boundary = cv2.dilate(mask.astype(np.uint8), kernel, iterations=1) - mask.astype(np.uint8)
# Reset wave field where the mask is not present
self.wave_field[mask == 0] *= wave_decay
# Emit wave from boundary and propagate
self.wave_field += boundary * feature_value * wave_amplitude
self.wave_field = cv2.GaussianBlur(self.wave_field, (0, 0), sigmaX=wave_speed)
# Apply decay
self.wave_field *= wave_decay
# Normalize wave field if it exceeds max_wave_field
max_value = np.max(np.abs(self.wave_field))
if max_value > max_wave_field:
self.wave_field *= (max_wave_field / max_value)
time_factor = self.frame_count * wave_frequency
wave_pattern = np.sin(self.wave_field + time_factor) * 0.5 + 0.5
# Combine with original mask
result_mask = np.clip(mask + wave_pattern * strength, 0, 1)
# Print debug information
print(f"Frame: {self.frame_count}")
print(f"Wave field min/max: {self.wave_field.min():.4f} / {self.wave_field.max():.4f}")
print(f"Wave pattern min/max: {wave_pattern.min():.4f} / {wave_pattern.max():.4f}")
print(f"Result mask min/max: {result_mask.min():.4f} / {result_mask.max():.4f}")
print("---")
self.frame_count += 1
return result_mask.astype(np.float32)
def main_function(self, masks, feature, feature_pipe, strength, feature_threshold,
invert, subtract_original, grow_with_blur, **kwargs):
# Reset wave_field and frame_count for each new feature input
self.wave_field = None
self.frame_count = 0
return (self.apply_mask_operation(masks, feature, feature_pipe, strength,
feature_threshold, invert, subtract_original,
grow_with_blur, **kwargs),)
class FlexMaskEmanatingRings(FlexMaskBase):
@classmethod
def INPUT_TYPES(cls):
cls.feature_threshold_default = 0.25
return {
**super().INPUT_TYPES(),
"required": {
**super().INPUT_TYPES()["required"],
"num_rings": ("INT", {"default": 4, "min": 1, "max": 50, "step": 1}),
"max_ring_width": ("FLOAT", {"default": 0.5, "min": 0.01, "max": 0.9, "step": 0.01}),
"wave_speed": ("FLOAT", {"default": 0.05, "min": 0.01, "max": 0.5, "step": 0.01}),
"feature_param": (["num_rings", "ring_width", "wave_speed", "all"],),
}
}
def __init__(self):
super().__init__()
self.time = 0
def process_mask(self, mask: np.ndarray, feature_value: float, strength: float,
num_rings: int, max_ring_width: float, wave_speed: float,
feature_param: str, **kwargs) -> np.ndarray:
# Adjust parameters based on feature_value and feature_param
if feature_param in ["num_rings", "all"]:
adjusted_num_rings = max(1, int(num_rings * feature_value * strength))
else:
adjusted_num_rings = num_rings
if feature_param in ["ring_width", "all"]:
adjusted_max_ring_width = max_ring_width * feature_value * strength
else:
adjusted_max_ring_width = max_ring_width
if feature_param in ["wave_speed", "all"]:
adjusted_wave_speed = wave_speed * feature_value * strength
else:
adjusted_wave_speed = wave_speed
distance = distance_transform_edt(1 - mask)
max_distance = np.max(distance)
normalized_distance = distance / max_distance
# Create emanating rings
rings = np.zeros_like(mask)
for i in range(adjusted_num_rings):
ring_progress = (self.time * adjusted_wave_speed + i / adjusted_num_rings) % 1
ring_width = adjusted_max_ring_width * (1 - ring_progress) # Rings get thinner as they move out
ring_outer = normalized_distance < ring_progress
ring_inner = normalized_distance < (ring_progress - ring_width)
rings = np.logical_or(rings, np.logical_xor(ring_outer, ring_inner))
# Combine with original mask
result = np.logical_or(mask, rings).astype(np.float32)
self.time += 1
return result
def process_mask_below_threshold(self, mask: np.ndarray, feature_value: float, strength: float, **kwargs) -> np.ndarray:
# Continue the animation don't create new rings
return self.process_mask(mask, 0, strength, **kwargs)
def main_function(self, masks, feature, feature_pipe, strength, feature_threshold,
invert, subtract_original, grow_with_blur, num_rings,
max_ring_width, wave_speed, feature_param, **kwargs):
self.time = 0 # Reset time for each new feature input
return (self.apply_mask_operation(masks, feature, feature_pipe, strength,
feature_threshold, invert, subtract_original,
grow_with_blur, num_rings=num_rings,
max_ring_width=max_ring_width, wave_speed=wave_speed,
feature_param=feature_param, **kwargs),)
#TODO
# class FlexMaskVoronoiShape(FlexMaskBase):
# @classmethod
+8 -3
View File
@@ -917,14 +917,15 @@ class OpticalFlowMaskBase(MaskBase, ABC):
#TODO check if input mask is blank and just skip, then check children
class FlexMaskBase(MaskBase):
feature_threshold_default=0.0
@classmethod
def INPUT_TYPES(cls):
def INPUT_TYPES(cls):
return {
"required": {
**super().INPUT_TYPES()["required"],
"feature": ("FEATURE",),
"feature_pipe": ("FEATURE_PIPE",),
"feature_threshold": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"feature_threshold": ("FLOAT", {"default": cls.feature_threshold_default, "min": 0.0, "max": 1.0, "step": 0.01}),
}
}
@@ -950,7 +951,11 @@ class FlexMaskBase(MaskBase):
if feature_value >= feature_threshold:
processed_mask = self.process_mask(mask, feature_value, strength, **kwargs)
else:
processed_mask = mask
if hasattr(self, 'process_mask_below_threshold'):
processed_mask = self.process_mask_below_threshold(mask, feature_value, strength, **kwargs)
else:
processed_mask = mask
result.append(processed_mask)
self.update_progress()
+7 -3
View File
@@ -247,8 +247,8 @@ class _mfc:
"threshold": ("INT", { "default": 0, "min": 0, "max": 127, "step": 1, }),
}
}
#TODO add image
RETURN_TYPES = ("MASK",)
RETURN_TYPES = ("MASK", "IMAGE")
FUNCTION = "execute"
CATEGORY = "/RyanOnTheInside/masks/"
@@ -262,4 +262,8 @@ class _mfc:
mask = (temp >= lower_bound) & (temp <= upper_bound)
mask = mask.all(dim=-1)
mask = mask.float()
return (mask, )
# Create an image of the mask
mask_image = mask.unsqueeze(-1).repeat(1, 1, 1, 3)
return (mask, mask_image)
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui_ryanonyheinside"
description = "Custom nodes introducing particle simulations, optical flow, audio manipulation & reactivity, and temporal masks"
version = "1.4.7"
version = "1.4.8"
license = {file = "LICENSE"}
dependencies = ["opencv-python==4.10.0","scipy","pillow","librosa","pymunk==6.8.1","matplotlib","openunmix","mido"]
+6
View File
@@ -28,6 +28,12 @@ RyanOnTheInside node pack introduces dynamic features to ComfyUI:
*Examples showcasing various effects using particle emitters, vortices, and other node features*
##### 🆕 Recent Updates
- 8/30/24 - **FlexMaskEmanatingRings**: Create dynamic, expanding ring patterns emanating from mask edges.
- 8/30/24 - **FlexMaskWavePropagation**: Simulate wave-like distortions propagating from mask boundaries.
- 8/29/24 - **Added feature reactivity to paricle simulations**
## 🚀 Quick Start
Getting started with the RyanOnTheInside node pack is easy: