10 Commits
Author SHA1 Message Date
NitishTRI3D 5ee4d01da6 Merge pull request #60 from TRI3D-LC/saveflattened
flattened
2025-06-19 14:14:57 +05:30
Ubuntu 21ee6a4f71 flattened 2025-06-19 08:44:27 +00:00
NitishTRI3D 48024f3901 Merge pull request #59 from TRI3D-LC/lipmask_merge
Lipmask merge
2025-05-28 10:56:13 +05:30
Ubuntu 2a4e6731d2 version 5.0.0 2025-05-27 12:08:19 +00:00
Ubuntu 42cc6d54bd Merge branch 'lipmask' of github.com:TRI3D-LC/tri3d-comfyui-nodes into lipmask 2025-05-27 12:04:50 +00:00
Ubuntu 75ba9bfb25 percentage and cutbymask changes 2025-05-23 05:14:55 +00:00
Ubuntu c0817a1882 lipmask 2025-05-21 15:28:03 +00:00
Ubuntu 18b8c18c5e cut by mask aspect ratio 2025-05-21 11:56:54 +00:00
NitishTRI3D c9ba9c9c59 Merge pull request #58 from TRI3D-LC/lipmask
lipmask node added
2025-05-20 13:57:26 +05:30
Ubuntu 67aee87652 lipmask node added 2025-05-20 08:26:42 +00:00
7 changed files with 650 additions and 3 deletions
+22 -3
View File
@@ -20,6 +20,8 @@ from utility_nodes import TRI3D_extract_facer_mask
from .AEMatter import (load_AEMatter_Model, run_AEMatter_inference)
from .light_layer import main_light_layer
from .remove_small_mask_islands import TRI3D_RemoveSmallMaskIslands
from .mask_area_percentage import TRI3D_MaskAreaPercentage
from .image_stack import (
@@ -1930,7 +1932,7 @@ class TRI3DDWPose_Preprocessor:
cur_file_dir = os.path.dirname(os.path.realpath(__file__))
save_file_path = os.path.join(cur_file_dir,
filename_path)
json.dump(pose_dict, open(save_file_path, 'w'))
json.dump(pose_dict, open(save_file_path, 'w'), indent=4)
np_result = cv2.resize(np_result, (W, H),
interpolation=cv2.INTER_AREA)
out_image_list.append(
@@ -3694,8 +3696,11 @@ class TRI3D_BGREMOVE_MEGA():
from photoroom import TRI3D_photoroom_bgremove_api
from smart_box import TRI3D_SmartBox, TRI3D_Skip_HeadMask, TRI3D_Skip_HeadMask_AddNeck, TRI3D_Image_extend, TRI3D_Smart_Depth, TRI3D_NarrowfyImage
from smart_box import TRI3D_SmartBox, TRI3D_Skip_HeadMask, TRI3D_Skip_HeadMask_AddNeck, TRI3D_Image_extend, TRI3D_Smart_Depth, TRI3D_NarrowfyImage, TRI3D_Skip_LipMask
from nsfw import TRI3DNSFWFilter
from cut_by_mask_aspect_ratio import TRI3D_CutByMaskAspectRatio
from string_check import TRI3D_StringContains
from .dwpose_conversion import SaveFlattenedPoseKpsAsJsonFile
# A dictionary that contains all nodes you want to export with their names
# NOTE: names should be globally unique
@@ -3759,14 +3764,22 @@ NODE_CLASS_MAPPINGS = {
"tri3d_SmartBox": TRI3D_SmartBox,
"tri3d_Skip_HeadMask": TRI3D_Skip_HeadMask,
"tri3d_Skip_HeadMask_AddNeck": TRI3D_Skip_HeadMask_AddNeck,
"tri3d_Skip_LipMask": TRI3D_Skip_LipMask,
"tri3d_Image_extend": TRI3D_Image_extend,
"tri3d_Smart_Depth": TRI3D_Smart_Depth,
"tri3d_NSFWFilter": TRI3DNSFWFilter,
"tri3d_NarrowfyImage": TRI3D_NarrowfyImage,
"tri3d_Skip_LipMask": TRI3D_Skip_LipMask,
"tri3d_Remove_Small_Mask_Islands": TRI3D_RemoveSmallMaskIslands,
"tri3d_CutByMaskAspectRatio": TRI3D_CutByMaskAspectRatio,
"tri3d_StringContains": TRI3D_StringContains,
"tri3d_MaskAreaPercentage": TRI3D_MaskAreaPercentage,
"tri3d_SaveFlattenedPoseKpsAsJsonFile": SaveFlattenedPoseKpsAsJsonFile,
}
VERSION = "4.9.0"
VERSION = "5.1.0"
# A dictionary that contains the friendly/humanly readable titles for the nodes
NODE_DISPLAY_NAME_MAPPINGS = {
"tri3d-photoroom-bgremove-api": "Photoroom BG Remove" + " v" + VERSION,
@@ -3830,8 +3843,14 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"tri3d_SmartBox": "Smart Box" + " v" + VERSION,
"tri3d_Skip_HeadMask": "Skip Head Mask" + " v" + VERSION,
"tri3d_Skip_HeadMask_AddNeck": "Skip Head Mask and add neck" + " v" + VERSION,
"tri3d_Skip_LipMask": "Skip Lip Mask" + " v" + VERSION,
"tri3d_NSFWFilter": "TRI3D NSFW Filter" + " v" + VERSION,
"tri3d_Image_extend": "Image extend" + " v" + VERSION,
"tri3d_Smart_Depth": "Smart Depth" + " v" + VERSION,
"tri3d_NarrowfyImage": "Narrowfy Image" + " v" + VERSION,
"tri3d_Remove_Small_Mask_Islands": "Remove Small Mask Islands" + " v" + VERSION,
"tri3d_CutByMaskAspectRatio": "Cut by mask aspect ratio" + " v" + VERSION,
"tri3d_StringContains": "String contains" + " v" + VERSION,
"tri3d_MaskAreaPercentage": "Mask Area Percentage" + " v" + VERSION,
"tri3d_SaveFlattenedPoseKpsAsJsonFile": "Save Flattened Pose Keypoints as JSON File" + " v" + VERSION,
}
+183
View File
@@ -0,0 +1,183 @@
import os
import cv2
import numpy as np
import torch
class TRI3D_CutByMaskAspectRatio:
"""
ComfyUI node that crops an image based on a mask's bounding box,
adjusts the aspect ratio, and resizes to specified dimensions.
"""
def from_torch_image(self, image):
"""Convert a torch tensor image to numpy array for OpenCV processing"""
image = image.cpu().numpy() * 255.0
image = np.clip(image, 0, 255).astype(np.uint8)
return image
def to_torch_image(self, image):
"""Convert numpy array back to torch tensor format"""
image = image.astype(dtype=np.float32)
image /= 255.0
image = torch.from_numpy(image)
return image
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",),
"mask": ("IMAGE",),
"margin": ("INT", {"default": 10, "min": 0, "max": 100, "step": 1}),
"target_width": ("INT", {"default": 512, "min": 64, "max": 4096, "step": 8}),
"target_height": ("INT", {"default": 512, "min": 64, "max": 4096, "step": 8}),
"padding_color": ("INT", {"default": 255, "min": 0, "max": 255, "step": 1}),
},
}
FUNCTION = "run"
RETURN_TYPES = ("IMAGE",)
CATEGORY = "TRI3D"
def run(self, image, mask, margin, target_width, target_height, padding_color=255):
# Convert Torch images to OpenCV format
cv_image = self.from_torch_image(image)
cv_mask = self.from_torch_image(mask)
# Remove batch dimension if present
if len(cv_image.shape) == 4:
cv_image = cv_image[0]
if len(cv_mask.shape) == 4:
cv_mask = cv_mask[0]
# Convert mask to grayscale if it's not already
if len(cv_mask.shape) == 3 and cv_mask.shape[2] > 1:
mask_gray = cv2.cvtColor(cv_mask, cv2.COLOR_RGB2GRAY)
else:
mask_gray = cv_mask[:, :, 0]
# Create binary mask
_, binary_mask = cv2.threshold(mask_gray, 127, 255, cv2.THRESH_BINARY)
# Find contours in the binary mask
contours, _ = cv2.findContours(binary_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if not contours:
# If no contours found, return the original image
print("No contours found in mask. Returning original image.")
return (image,)
# Find bounding box around all contours
x_min, y_min = float('inf'), float('inf')
x_max, y_max = 0, 0
for contour in contours:
x, y, w, h = cv2.boundingRect(contour)
x_min = min(x_min, x)
y_min = min(y_min, y)
x_max = max(x_max, x + w)
y_max = max(y_max, y + h)
# Add margin to bounding box
x_min = max(0, x_min - margin)
y_min = max(0, y_min - margin)
x_max = min(cv_image.shape[1], x_max + margin)
y_max = min(cv_image.shape[0], y_max + margin)
# Current dimensions of the bounding box
height = y_max - y_min
width = x_max - x_min
# Calculate the target aspect ratio (width/height)
target_aspect_ratio = target_width / target_height
# Calculate current aspect ratio
current_aspect_ratio = width / height
# Adjust width to match the target aspect ratio while keeping height constant
if current_aspect_ratio < target_aspect_ratio:
# Current width is too narrow - need to extend it
# Calculate the required width for the target aspect ratio
required_width = int(height * target_aspect_ratio)
width_difference = required_width - width
# Calculate how much to extend on each side
left_extend = width_difference // 2
right_extend = width_difference - left_extend
# Calculate new potential boundaries
new_x_min = x_min - left_extend
new_x_max = x_max + right_extend
# Check if the new boundaries are within the original image
left_padding_needed = abs(min(0, new_x_min))
right_padding_needed = max(0, new_x_max - cv_image.shape[1])
# Adjust boundaries to be within the original image
new_x_min = max(0, new_x_min)
new_x_max = min(cv_image.shape[1], new_x_max)
# Get the portion of the original image within valid boundaries
extended_image = cv_image[y_min:y_max, new_x_min:new_x_max]
# If we need padding (i.e., extension goes beyond image boundaries)
if left_padding_needed > 0 or right_padding_needed > 0:
# Create canvas with padding color
num_channels = extended_image.shape[2] if len(extended_image.shape) == 3 else 1
if num_channels == 1:
canvas = np.full((height, required_width), padding_color, dtype=np.uint8)
else:
canvas = np.full((height, required_width, num_channels), padding_color, dtype=np.uint8)
# Calculate the position to place the extended image
place_x = left_padding_needed
# Place the extended image on the canvas
if num_channels == 1:
canvas[:, place_x:place_x+extended_image.shape[1]] = extended_image
else:
canvas[:, place_x:place_x+extended_image.shape[1], :] = extended_image
# Use the canvas as our cropped image
cropped_image = canvas
else:
# No padding needed, use the extended image
cropped_image = extended_image
elif current_aspect_ratio > target_aspect_ratio:
# Current width is too wide, crop it
new_width = int(height * target_aspect_ratio)
width_difference = width - new_width
# Crop equally from both sides if possible
left_crop = width_difference // 2
right_crop = width_difference - left_crop
# Apply the crop
cropped_image = cv_image[y_min:y_max, x_min+left_crop:x_max-right_crop]
else:
# Aspect ratio is already correct
cropped_image = cv_image[y_min:y_max, x_min:x_max]
# Resize the cropped/padded image to the target dimensions using Lanczos interpolation
resized_image = cv2.resize(cropped_image, (target_width, target_height), interpolation=cv2.INTER_LANCZOS4)
# Convert back to torch format
torch_image = self.to_torch_image(resized_image)
# Add batch dimension back
torch_image = torch_image.unsqueeze(0)
return (torch_image,)
# Node registration for ComfyUI
NODE_CLASS_MAPPINGS = {
"TRI3D_CutByMaskAspectRatio": TRI3D_CutByMaskAspectRatio
}
NODE_DISPLAY_NAME_MAPPINGS = {
"TRI3D_CutByMaskAspectRatio": "TRI3D Cut By Mask Aspect Ratio"
}
+111
View File
@@ -0,0 +1,111 @@
import os
import json
import torch
import numpy as np
import folder_paths
print("Loading TRI3D_SavePoseKeypointsJSON module")
class SaveFlattenedPoseKpsAsJsonFile:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"pose_kps": ("POSE_KEYPOINT",),
"file_path": ("STRING", {"default": "dwpose/keypoints/input.json"})
}
}
RETURN_TYPES = (
"STRING",
)
FUNCTION = "save_flattened_pose_kps"
OUTPUT_NODE = True
CATEGORY = "ControlNet Preprocessors/Pose Keypoint Postprocess"
def __init__(self):
self.output_dir = folder_paths.get_output_directory()
self.type = "output"
self.prefix_append = ""
def _flatten_openpose_dict(self, pose_dict: dict) -> dict:
"""
Converts a single OpenPose dictionary into flattened format.
"""
# Get canvas dimensions from the input dictionary
H = pose_dict.get('canvas_height', 512)
W = pose_dict.get('canvas_width', 512)
flat_keypoints = []
# Check if any person was detected
if not pose_dict.get('people'):
# If no people, return a list of 130 invalid keypoints
flat_keypoints.extend([[-1, -1]] * 130)
return {"height": H, "width": W, "keypoints": flat_keypoints}
person = pose_dict['people'][0] # Process the first person found
# Helper function to process each body part
def process_part(keypoints_data, expected_length):
processed_kps = []
if keypoints_data:
# Iterate in steps of 3 (x, y, confidence)
for i in range(0, len(keypoints_data), 3):
x, y, conf = keypoints_data[i], keypoints_data[i+1], keypoints_data[i+2]
# Use confidence score to check for validity. If 0, it's a missing point.
if conf > 0:
processed_kps.append([x, y])
else:
processed_kps.append([-1, -1])
# Ensure the list has the exact expected length
while len(processed_kps) < expected_length:
processed_kps.append([-1, -1])
return processed_kps
# Process parts in order: body -> face -> left hand -> right hand
body_kps = process_part(person.get('pose_keypoints_2d'), 18)
face_kps = process_part(person.get('face_keypoints_2d'), 70)
left_hand_kps = process_part(person.get('hand_left_keypoints_2d'), 21)
right_hand_kps = process_part(person.get('hand_right_keypoints_2d'), 21)
# Combine all parts into the final flat list
flat_keypoints.extend(body_kps)
flat_keypoints.extend(face_kps)
flat_keypoints.extend(left_hand_kps)
flat_keypoints.extend(right_hand_kps)
return {"height": H, "width": W, "keypoints": flat_keypoints}
def save_flattened_pose_kps(self, pose_kps, file_path):
# filename_prefix += self.prefix_append
# # Get the save path using the first pose keypoint's dimensions
# full_output_folder, filename, counter, subfolder, filename_prefix = \
# folder_paths.get_save_image_path(filename_prefix, self.output_dir,
# pose_kps[0]["canvas_width"],
# pose_kps[0]["canvas_height"])
# Process each pose keypoint in the batch
flattened_poses = []
for pose_dict in pose_kps:
flattened_data = self._flatten_openpose_dict(pose_dict)
flattened_poses.append(flattened_data)
# # Save the flattened data
# file = f"{filename}_{counter:05}.json"
# save_path = os.path.join(full_output_folder, file)
cur_file_dir = os.path.dirname(os.path.realpath(__file__))
save_path = os.path.join(cur_file_dir,
file_path)
with open(save_path, 'w') as f:
if len(flattened_poses) == 1:
json.dump(flattened_poses[0], f, indent=4) # Save single pose directly
else:
json.dump(flattened_poses, f, indent=4) # Save batch as array
print(f"Saved flattened pose keypoints to: {save_path}")
return (save_path,)
+68
View File
@@ -0,0 +1,68 @@
import os
import cv2
import numpy as np
import torch
class TRI3D_MaskAreaPercentage:
"""
ComfyUI node that calculates the percentage of white pixels in an image
relative to the total image area.
"""
def from_torch_image(self, image):
"""Convert a torch tensor image to numpy array for OpenCV processing"""
image = image.cpu().numpy() * 255.0
image = np.clip(image, 0, 255).astype(np.uint8)
return image
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",),
"threshold": ("INT", {"default": 127, "min": 0, "max": 255, "step": 1}),
},
}
FUNCTION = "run"
RETURN_TYPES = ("FLOAT", "INT", "INT",)
RETURN_NAMES = ("percentage", "white_pixels", "total_pixels",)
CATEGORY = "TRI3D"
def run(self, image, threshold=127):
# Convert Torch image to OpenCV format
cv_image = self.from_torch_image(image)
# Remove batch dimension if present
if len(cv_image.shape) == 4:
cv_image = cv_image[0]
# Convert to grayscale if it's a color image
if len(cv_image.shape) == 3 and cv_image.shape[2] > 1:
gray_image = cv2.cvtColor(cv_image, cv2.COLOR_RGB2GRAY)
else:
gray_image = cv_image[:, :, 0]
# Calculate total number of pixels
total_pixels = gray_image.shape[0] * gray_image.shape[1]
# Count white pixels (pixels with values above threshold)
_, binary_image = cv2.threshold(gray_image, threshold, 255, cv2.THRESH_BINARY)
white_pixels = cv2.countNonZero(binary_image)
# Calculate percentage of white pixels
percentage = (white_pixels / total_pixels) * 100.0
return (percentage, white_pixels, total_pixels,)
# # Node registration for ComfyUI
# NODE_CLASS_MAPPINGS = {
# "TRI3D_MaskAreaPercentage": TRI3D_MaskAreaPercentage
# }
# NODE_DISPLAY_NAME_MAPPINGS = {
# "TRI3D_MaskAreaPercentage": "TRI3D Mask Area Percentage"
# }
+117
View File
@@ -0,0 +1,117 @@
import os
import cv2
import numpy as np
import torch
class TRI3D_RemoveSmallMaskIslands:
"""
ComfyUI node that removes small islands of white pixels from a mask image
based on a specified area threshold.
"""
def from_torch_image(self, image):
"""Convert a torch tensor image to numpy array for OpenCV processing"""
image = image.cpu().numpy() * 255.0
image = np.clip(image, 0, 255).astype(np.uint8)
return image
def to_torch_image(self, image):
"""Convert numpy array back to torch tensor format"""
image = image.astype(dtype=np.float32)
image /= 255.0
image = torch.from_numpy(image)
return image
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE", ),
"min_island_area": ("INT", {"default": 100, "min": 1, "max": 10000, "step": 10}),
"invert": ("BOOLEAN", {"default": False}),
},
}
FUNCTION = "run"
RETURN_TYPES = ("IMAGE", )
CATEGORY = "TRI3D"
def run(self, image, min_island_area, invert):
# Convert Torch image to OpenCV format
cv_image = self.from_torch_image(image)
# Remove batch dimension if present
if len(cv_image.shape) == 4:
cv_image = cv_image[0]
# Make a copy to work with
result_image = cv_image.copy()
# Process each channel (if grayscale, it will just be one iteration)
height, width = cv_image.shape[:2]
# If the image has 3 channels (RGB), convert to grayscale for contour detection
if len(cv_image.shape) == 3 and cv_image.shape[2] == 3:
# Convert to grayscale for processing
gray = cv2.cvtColor(cv_image, cv2.COLOR_RGB2GRAY)
else:
# Use the first channel if it's already grayscale or has alpha
gray = cv_image[:, :, 0]
# Invert if needed (to work with black islands instead of white)
if invert:
gray = 255 - gray
# Create binary image
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# Find contours in the binary image
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Create a blank mask for the cleaned image
clean_mask = np.zeros((height, width), dtype=np.uint8)
# Draw only contours with area greater than the threshold
for contour in contours:
area = cv2.contourArea(contour)
if area >= min_island_area:
cv2.drawContours(clean_mask, [contour], 0, 255, -1)
# Invert back if needed
if invert:
clean_mask = 255 - clean_mask
# Apply the clean mask to each channel of the original image
if len(cv_image.shape) == 3 and cv_image.shape[2] == 3:
# RGB image
for i in range(3):
result_image[:, :, i] = cv2.bitwise_and(cv_image[:, :, i], clean_mask)
elif len(cv_image.shape) == 3 and cv_image.shape[2] == 4:
# RGBA image
for i in range(4):
result_image[:, :, i] = cv2.bitwise_and(cv_image[:, :, i], clean_mask)
else:
# Single channel image
result_image = cv2.bitwise_and(cv_image, clean_mask)
# Reshape to match expected dimensions
result_image = result_image.reshape(height, width, 1)
# Convert back to torch format
torch_image = self.to_torch_image(result_image)
# Add batch dimension back
torch_image = torch_image.unsqueeze(0)
return (torch_image,)
# # Node registration for ComfyUI
# NODE_CLASS_MAPPINGS = {
# "TRI3D_RemoveSmallMaskIslands": TRI3D_RemoveSmallMaskIslands
# }
# NODE_DISPLAY_NAME_MAPPINGS = {
# "TRI3D_RemoveSmallMaskIslands": "TRI3D Remove Small Mask Islands"
# }
+107
View File
@@ -818,3 +818,110 @@ class TRI3D_CropAndExtend:
torch_human_mask = self.to_torch_image(cropped_human_mask).unsqueeze(0)
return (torch_garment, torch_garment_mask, torch_human, torch_human_mask, cropped_width, cropped_height)
class TRI3D_Skip_LipMask:
def adjust_keypoints(self, keypoints, image_shape, original_height, original_width):
image_height, image_width = image_shape[:2]
scale_x = image_width / original_width
scale_y = image_height / original_height
adjusted_keypoints = [
(int(x * scale_x), int(y * scale_y)) for x, y in keypoints
]
return adjusted_keypoints
def from_torch_image(self, image):
image = image.cpu().numpy() * 255.0
image = np.clip(image, 0, 255).astype(np.uint8)
return image
def to_torch_image(self, image):
image = image.astype(dtype=np.float32)
image /= 255.0
image = torch.from_numpy(image)
return image
def extract_lip_keypoints(self, keypoints):
# In DWPose, lips are typically keypoints in face area
# Assuming standard face keypoint format where lips are around indices 61-68
# This may need adjustment based on your specific keypoint format
lip_indices = range(61, 69) # Adjust these indices based on your keypoint format
# Filter out invalid keypoints (those with negative confidence or coordinates)
lip_keypoints = []
for idx in lip_indices:
if idx < len(keypoints):
x, y = keypoints[idx]
if x >= 0 and y >= 0: # Check for valid coordinates
lip_keypoints.append((x, y))
return lip_keypoints
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE", ),
"keypoints_json": ("STRING", {"multiline": True}),
},
}
FUNCTION = "run"
RETURN_TYPES = ("IMAGE", )
CATEGORY = "TRI3D"
def run(self, image, keypoints_json):
# Convert Torch image to OpenCV format
cv_image = self.from_torch_image(image)
# Remove the batch dimension if present
if len(cv_image.shape) == 4:
cv_image = cv_image[0]
# Make a copy of the original image
result_image = cv_image.copy()
# Parse keypoints JSON
try:
kp_data = json.loads(open(keypoints_json, 'r').read())
original_height, original_width = kp_data['height'], kp_data['width']
keypoints = kp_data['keypoints']
# Extract lip keypoints
lip_keypoints = self.extract_lip_keypoints(keypoints)
# If no valid lip keypoints found, use a fallback approach
if not lip_keypoints:
# Fallback: use the nose point (index 0) as reference
nose_point = keypoints[0]
if nose_point[1] > 0: # If y-coordinate is valid
# Estimate lip position slightly below nose
lip_y = int(nose_point[1] + 0.15 * cv_image.shape[0])
lowest_y = lip_y
else:
# If no valid reference point, use 1/3 of the image height
lowest_y = cv_image.shape[0] // 3
else:
# Find the lowest y-coordinate among lip keypoints
adjusted_lip_keypoints = self.adjust_keypoints(lip_keypoints, cv_image.shape, original_height, original_width)
lowest_y = max([kp[1] for kp in adjusted_lip_keypoints])
# Black out everything above the lowest lip point
result_image[:lowest_y, :] = 0
except Exception as e:
print(f"Error processing keypoints JSON: {e}")
# In case of error, return the original image
result_image = cv_image
# Convert back to Torch format
torch_image = self.to_torch_image(result_image)
# Add the batch dimension back
torch_image = torch_image.unsqueeze(0)
return (torch_image,)
+42
View File
@@ -0,0 +1,42 @@
import os
class TRI3D_StringContains:
"""
ComfyUI node that checks if a specified string exists within another string.
Performs case-insensitive comparison by converting all text to lowercase.
"""
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"input_string": ("STRING", {"multiline": True}),
"search_string": ("STRING", {"default": "", "multiline": False}),
},
}
FUNCTION = "run"
RETURN_TYPES = ("BOOLEAN",)
CATEGORY = "TRI3D"
def run(self, input_string, search_string):
# Convert both strings to lowercase for case-insensitive comparison
input_lower = input_string.lower()
search_lower = search_string.lower()
# Check if search string exists in input string
contains = search_lower in input_lower
return (contains,)
# # Node registration for ComfyUI
# NODE_CLASS_MAPPINGS = {
# "TRI3D_StringContains": TRI3D_StringContains
# }
# NODE_DISPLAY_NAME_MAPPINGS = {
# "TRI3D_StringContains": "TRI3D String Contains"
# }