Image and mask batching and JS to handle the node inputs dynamically. Readme updates
This commit is contained in:
@@ -2,6 +2,16 @@
|
||||
|
||||
This repository contains custom nodes for ComfyUI created and used by SuperBeasts.AI (@SuperBeasts.AI on Instagram)
|
||||
|
||||
##Updates
|
||||
- 30/04/24:
|
||||
-- Updated readme with documentation
|
||||
-- Added Deflicker and PixelDeflicker nodes for reducing flickering artifacts in image sequences.
|
||||
-- Introduced CrossFadeImageBatches node for smooth cross-fade transitions between image batches
|
||||
-- Enhanced ImageBatchManagement and MaskBatchManagement nodes with resizing, cropping, and reordering capabilities
|
||||
- 27/03/24: Released and also pushed an update to resolve batched images for videos etc
|
||||
|
||||
# Image Effects
|
||||
|
||||
## HDR Effects (SuperBeasts.AI)
|
||||
|
||||
The HDR Effects is an image processing application that enhances the dynamic range and visual appeal of input images. It provides a set of adjustable parameters to fine-tune the HDR effect according to user preferences.
|
||||
@@ -51,5 +61,61 @@ The application provides the following adjustable parameters:
|
||||
- Enhances the color saturation of the image
|
||||
- Higher values result in more vibrant colors
|
||||
|
||||
##Updates
|
||||
- 27/03/24: Released and also pushed an update to resolve batched images for videos etc
|
||||
|
||||
#Video tools
|
||||
|
||||
## Deflicker
|
||||
|
||||
The Deflicker node is designed to reduce flickering artifacts in a sequence of images. It adjusts the brightness of each frame based on the average brightness of its surrounding context frames.
|
||||
|
||||
### Features
|
||||
- Adjusts brightness of each frame based on the context frames
|
||||
- Applies noise reduction and gradient smoothing to the adjusted images
|
||||
- Blends the adjusted images with the original images using adaptive blending strength
|
||||
- Supports batch processing for efficient computation
|
||||
|
||||
## PixelDeflicker
|
||||
|
||||
The PixelDeflicker node reduces flickering artifacts in a sequence of images by applying temporal smoothing at the pixel level. It blends the smoothed frames with the original frames to achieve a more stable output.
|
||||
|
||||
### Features
|
||||
- Applies temporal smoothing to reduce flickering
|
||||
- Blends the smoothed frames with the original frames
|
||||
- Supports batch processing for efficient computation
|
||||
|
||||
# Image & Mask Batch Management
|
||||
|
||||
Keep your image and masks sized, cropped and ordered how ever you like without having to recreate the masks or mess with connections.
|
||||
Note the resizing capability doesn't provide any settings, we simply crop and resize to maximise the image size with your provided width/height.
|
||||
|
||||

|
||||
|
||||
|
||||
## ImageBatchManagement
|
||||
|
||||
The ImageBatchManagement node provides functionality to resize, crop, and reorder a batch of images. It ensures that all images in the batch have the same specified dimensions.
|
||||
|
||||
### Features
|
||||
- Resizes and crops images to the specified width and height
|
||||
- Allows reordering the images in the batch based on a provided order
|
||||
- Supports unlimited input images
|
||||
|
||||
## MaskBatchManagement
|
||||
|
||||
The MaskBatchManagement node is similar to the ImageBatchManagement node but works with mask inputs. It resizes, crops, and reorders a batch of masks to match the specified dimensions.
|
||||
|
||||
### Features
|
||||
- Resizes and crops masks to the specified width and height
|
||||
- Allows reordering the masks in the batch based on a provided order
|
||||
- Supports unlimited input masks
|
||||
|
||||
|
||||
# Deprecated / Removed
|
||||
|
||||
## MakeResizedMaskBatch (Deprecated please use MaskBatchManagement)
|
||||
The MakeResizedMaskBatch node creates a batch of masks from multiple individual masks or batches.
|
||||
It resizes and crops the input masks to match the specified width and height.
|
||||
|
||||
## Cross Fade Image Batches (SuperBeasts.AI)
|
||||
There is another preexisting node that does this and more so I've removed this function.
|
||||
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
WEB_DIRECTORY = "js"
|
||||
from .superbeastsai_nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 182 KiB |
@@ -0,0 +1,77 @@
|
||||
import { ComfyApp, app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "Comfy.superbeastsai_nodes",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name == "Image Batch Manager (SuperBeasts.AI)" ||
|
||||
nodeData.name == "Mask Batch Manager (SuperBeasts.AI)") {
|
||||
|
||||
var input_name = "input";
|
||||
|
||||
switch(nodeData.name) {
|
||||
|
||||
case 'Image Batch Manager (SuperBeasts.AI)':
|
||||
input_name = "image";
|
||||
break;
|
||||
|
||||
case 'Mask Batch Manager (SuperBeasts.AI)':
|
||||
input_name = "mask";
|
||||
break;
|
||||
}
|
||||
|
||||
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
|
||||
nodeType.prototype.onConnectionsChange = function (type, index, connected, link_info) {
|
||||
if (!link_info) return;
|
||||
|
||||
const node = app.graph.getNodeById(link_info.origin_id);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
let slot_type = node.outputs[link_info.origin_slot].type;
|
||||
|
||||
|
||||
|
||||
let select_slot = this.inputs.find(x => x.name == "select");
|
||||
let mode_slot = this.inputs.find(x => x.name == "sel_mode");
|
||||
|
||||
let converted_count = 0;
|
||||
converted_count += select_slot?1:0;
|
||||
converted_count += mode_slot?1:0;
|
||||
|
||||
if (!connected && (this.inputs.length > 1+converted_count)) {
|
||||
const stackTrace = new Error().stack;
|
||||
|
||||
if(
|
||||
!stackTrace.includes('LGraphNode.prototype.connect') && // for touch device
|
||||
!stackTrace.includes('LGraphNode.connect') && // for mouse device
|
||||
!stackTrace.includes('loadGraphData') &&
|
||||
this.inputs[index].name != 'select') {
|
||||
this.removeInput(index);
|
||||
}
|
||||
}
|
||||
|
||||
let slot_i = 1;
|
||||
|
||||
for (let i = 0; i < this.inputs.length; i++) {
|
||||
let input_i = this.inputs[i];
|
||||
if(input_i.name != 'select'&& input_i.name != 'sel_mode') {
|
||||
input_i.name = `${input_name}${slot_i}`
|
||||
slot_i++;
|
||||
}
|
||||
}
|
||||
|
||||
let last_slot = this.inputs[this.inputs.length - 1];
|
||||
|
||||
if (
|
||||
(last_slot.name == 'select' && last_slot.name != 'sel_mode' && this.inputs[this.inputs.length - 2].link != undefined)
|
||||
|| (last_slot.name != 'select' && last_slot.name != 'sel_mode' && last_slot.link != undefined)) {
|
||||
this.addInput(`${input_name}${slot_i}`, this.outputs[0].type);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
+10
-94
@@ -410,57 +410,6 @@ class PixelDeflicker:
|
||||
|
||||
return (blended_tensor,)
|
||||
|
||||
class CrossFadeImageBatches:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"batch1": ("IMAGE",),
|
||||
"batch2": ("IMAGE",),
|
||||
"fade_frames": ("INT", {"default": 10, "min": 1, "max": 100, "step": 1}),
|
||||
"fade_strength": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"ease_type": (["linear", "ease_in", "ease_out", "ease_in_out"], {"default": "linear"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "crossfade"
|
||||
CATEGORY = "SuperBeastsAI/Animation"
|
||||
|
||||
def crossfade(self, batch1, batch2, fade_frames=10, fade_strength=0.5, ease_type="linear"):
|
||||
num_frames_batch1 = batch1.shape[0]
|
||||
num_frames_batch2 = batch2.shape[0]
|
||||
|
||||
if num_frames_batch1 < fade_frames or num_frames_batch2 < fade_frames:
|
||||
raise ValueError("The number of fade frames must be less than or equal to the number of frames in each batch.")
|
||||
|
||||
if batch1.shape[2:] != batch2.shape[2:]:
|
||||
raise ValueError("The spatial dimensions of batch1 and batch2 must match. Please resize the batches to the same size before using this node.")
|
||||
|
||||
# Extract the frames to be faded from each batch
|
||||
fade_frames_batch1 = batch1[-fade_frames:]
|
||||
fade_frames_batch2 = batch2[:fade_frames]
|
||||
|
||||
# Generate the interpolation weights based on the selected easing function
|
||||
if ease_type == "linear":
|
||||
weights = torch.linspace(0, 1, fade_frames)
|
||||
elif ease_type == "ease_in":
|
||||
weights = torch.pow(torch.linspace(0, 1, fade_frames), 2)
|
||||
elif ease_type == "ease_out":
|
||||
weights = 1 - torch.pow(torch.linspace(1, 0, fade_frames), 2)
|
||||
elif ease_type == "ease_in_out":
|
||||
weights = 0.5 * (1 - torch.cos(torch.linspace(0, torch.pi, fade_frames)))
|
||||
|
||||
weights = weights.view(-1, 1, 1, 1) # Reshape weights to match the dimensions of the frames
|
||||
|
||||
# Perform the cross-fade between the fade frames of each batch
|
||||
faded_frames = fade_frames_batch1 * (1 - weights * fade_strength) + fade_frames_batch2 * (weights * fade_strength)
|
||||
|
||||
# Combine the non-faded frames from batch1, the faded frames, and the remaining frames from batch2
|
||||
output_batch = torch.cat((batch1[:-fade_frames], faded_frames, batch2[fade_frames:]), dim=0)
|
||||
|
||||
return (output_batch,)
|
||||
|
||||
def resize_and_crop(pil_img, target_width, target_height):
|
||||
"""Resize and crop an image to fit exactly the specified dimensions."""
|
||||
original_width, original_height = pil_img.size
|
||||
@@ -503,11 +452,11 @@ class ImageBatchManagement:
|
||||
"required": {
|
||||
"width": ("INT", {"default": 512}),
|
||||
"height": ("INT", {"default": 768}),
|
||||
"ordering_enabled": (["disabled", "enabled"], {"default": "disabled"})
|
||||
"ordering_enabled": (["disabled", "enabled"], {"default": "disabled"}),
|
||||
"image1": ("IMAGE",) # Ensure at least one image is required
|
||||
},
|
||||
"optional": {
|
||||
"new_order": ("STRING", {"default": ""}),
|
||||
**{f"image{i}": ("IMAGE",) for i in range(1, 13)}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -516,8 +465,12 @@ class ImageBatchManagement:
|
||||
CATEGORY = "SuperBeastsAI/Image"
|
||||
|
||||
def reorder(self, width, height, ordering_enabled, new_order, **kwargs):
|
||||
image_keys = [f'image{i}' for i in range(1, 13)]
|
||||
images = [kwargs.get(key) for key in image_keys if kwargs.get(key) is not None]
|
||||
images = [kwargs["image1"]] # Start with the required image1 input
|
||||
|
||||
i = 2
|
||||
while f"image{i}" in kwargs:
|
||||
images.append(kwargs[f"image{i}"])
|
||||
i += 1
|
||||
|
||||
if ordering_enabled == "enabled" and new_order:
|
||||
order_indices = [int(idx) - 1 for idx in new_order.split(',') if idx.strip()]
|
||||
@@ -578,49 +531,15 @@ class MaskBatchManagement:
|
||||
|
||||
return (result,)
|
||||
|
||||
class ConcatConditionings2:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"conditioning1": ("CONDITIONING", ),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("CONDITIONING", )
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, **kwargs):
|
||||
conditioning_to = list(kwargs.values())[0]
|
||||
|
||||
for k, conditioning_from in list(kwargs.items())[1:]:
|
||||
out = []
|
||||
if len(conditioning_from) > 1:
|
||||
print("Warning: ConcatConditionings {k} contains more than 1 cond, only the first one will actually be applied to conditioning1.")
|
||||
|
||||
cond_from = conditioning_from[0][0]
|
||||
|
||||
for i in range(len(conditioning_to)):
|
||||
t1 = conditioning_to[i][0]
|
||||
tw = torch.cat((t1, cond_from), 1)
|
||||
n = [tw, conditioning_to[i][1].copy()]
|
||||
out.append(n)
|
||||
|
||||
conditioning_to = out
|
||||
|
||||
return (out, )
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
'HDR Effects (SuperBeasts.AI)': HDREffects,
|
||||
'Make Resized Mask Batch (SuperBeasts.AI)': MakeResizedMaskBatch,
|
||||
'Deflicker (SuperBeasts.AI)': Deflicker,
|
||||
'Pixel Deflicker (SuperBeasts.AI)': PixelDeflicker,
|
||||
'Cross Fade Image Batches (SuperBeasts.AI)': CrossFadeImageBatches,
|
||||
'Mask Batch Manager (SuperBeasts.AI)': MaskBatchManagement,
|
||||
'Image Batch Manager (SuperBeasts.AI)': ImageBatchManagement,
|
||||
'Batch Manager Test (SuperBeasts.AI)': ConcatConditionings2
|
||||
|
||||
'Image Batch Manager (SuperBeasts.AI)': ImageBatchManagement
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
@@ -628,9 +547,6 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
'MakeResizedMaskBatch': 'Make Resized Mask Batch (SuperBeasts.AI)',
|
||||
'Deflicker': 'Deflicker (SuperBeasts.AI)',
|
||||
'PixelDeflicker': 'Pixel Deflicker (SuperBeasts.AI)',
|
||||
'CrossFadeImageBatches': 'Cross Fade Image Batches (SuperBeasts.AI)',
|
||||
'MaskBatchManagement':'Mask Batch Manager (SuperBeasts.AI)',
|
||||
'ImageBatchManagement':'Image Batch Manager (SuperBeasts.AI)',
|
||||
'ConcatConditionings2':'Batch Manager Test (SuperBeasts.AI)'
|
||||
|
||||
'ImageBatchManagement':'Image Batch Manager (SuperBeasts.AI)'
|
||||
}
|
||||
Reference in New Issue
Block a user