Image and mask batching and JS to handle the node inputs dynamically. Readme updates

This commit is contained in:
SuperBeastsAI
2024-04-30 18:49:38 +10:00
parent 14c5dad4a4
commit 41a64ba58b
5 changed files with 157 additions and 98 deletions
+68 -2
View File
@@ -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.
![Batch Management](examples/BatchManagers.jpg)
## 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
View File
@@ -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

+77
View File
@@ -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
View File
@@ -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)'
}