Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97320545f9 | ||
|
|
d7826218bf | ||
|
|
5b417c03cb | ||
|
|
01fb6ea503 | ||
|
|
f20879d8c6 | ||
|
|
8a1ab4acdd | ||
|
|
f2a674c653 | ||
|
|
a1ba600ca4 | ||
|
|
ad03811aa9 | ||
|
|
4851d400f9 | ||
|
|
b1953bed74 | ||
|
|
1bf1797c8b | ||
|
|
848a8bce00 | ||
|
|
b5bd1fbe3e | ||
|
|
d16d4f85d6 | ||
|
|
b3f6344976 | ||
|
|
05e0889248 | ||
|
|
728020c4a4 | ||
|
|
c230b28a97 | ||
|
|
1e1ae6d21c | ||
|
|
52f6e88de7 | ||
|
|
acb81d0532 | ||
|
|
b2ea696d67 | ||
|
|
586346940d | ||
|
|
cd84ea85de | ||
|
|
b8dd1b62b8 | ||
|
|
3903e7243d | ||
|
|
6238a618f7 | ||
|
|
327f15b9a0 |
+22
-1
@@ -3,11 +3,18 @@ from custom_nodes.Comfy_KepListStuff.nodes.images import (
|
||||
ImageLabelOverlay,
|
||||
EmptyImages,
|
||||
XYImage,
|
||||
ImageListLoader,
|
||||
VariableImageBuilder,
|
||||
)
|
||||
from custom_nodes.Comfy_KepListStuff.nodes.list_utils import (
|
||||
ListLengthNode,
|
||||
JoinFloatLists,
|
||||
JoinImageLists,
|
||||
StringList,
|
||||
ReverseList,
|
||||
RepeatList,
|
||||
JoinListAny,
|
||||
StringListFromNewline,
|
||||
)
|
||||
from custom_nodes.Comfy_KepListStuff.nodes.range_nodes import (
|
||||
IntRangeNode,
|
||||
@@ -29,9 +36,23 @@ NODE_CLASS_MAPPINGS = {
|
||||
"Join Image Lists": JoinImageLists,
|
||||
"Join Float Lists": JoinFloatLists,
|
||||
"XYAny": UnzippedProductAny,
|
||||
"XYImage": XYImage
|
||||
"XYImage": XYImage,
|
||||
"ImageListLoader": ImageListLoader,
|
||||
"KepStringList": StringList,
|
||||
"KepStringListFromNewline": StringListFromNewline,
|
||||
"Kep_VariableImageBuilder": VariableImageBuilder,
|
||||
"Kep_ReverseList": ReverseList,
|
||||
"Kep_RepeatList": RepeatList,
|
||||
"Kep_JoinListAny": JoinListAny
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Stack Images": "Stack Images(Deprecated)",
|
||||
"ImageListLoader": "Image List Loader",
|
||||
"KepStringList": "String List",
|
||||
"Kep_VariableImageBuilder": "Variable Image Builder",
|
||||
"Kep_ReverseList": "Reverse List",
|
||||
"Kep_RepeatList": "Repeat List",
|
||||
"Kep_JoinListAny": "Join List Any",
|
||||
"KepStringListFromNewline": "String List From Newline",
|
||||
}
|
||||
|
||||
+297
-56
@@ -1,4 +1,6 @@
|
||||
from typing import Any, Dict, List, Tuple, Union, Optional
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Tuple, Union, Optional, Callable, TYPE_CHECKING
|
||||
|
||||
import torch
|
||||
from PIL import ImageFont, ImageDraw, Image
|
||||
@@ -10,6 +12,9 @@ from custom_nodes.Comfy_KepListStuff.utils import (
|
||||
tensor2pil,
|
||||
pil2tensor,
|
||||
)
|
||||
if TYPE_CHECKING:
|
||||
from mypy.typeshed.stdlib._typeshed import SupportsDunderGT, SupportsDunderLT
|
||||
|
||||
|
||||
class ImageLabelOverlay:
|
||||
def __init__(self) -> None:
|
||||
@@ -60,24 +65,24 @@ class ImageLabelOverlay:
|
||||
f"Non-matching input sizes got {len(batches)} Image Batches, {len(labels)} Labels for label type {l_type}"
|
||||
)
|
||||
|
||||
image_h, image_w, _ = batches[0][0].size()
|
||||
image_h, _, _ = batches[0][0].size()
|
||||
|
||||
font = ImageFont.truetype(fm.findfont(fm.FontProperties()), 60)
|
||||
|
||||
ret_images = []
|
||||
ret_images: List[Tensor]= []
|
||||
loop_gen = zip_with_fill(batches, float_labels, int_labels, str_labels)
|
||||
for b_idx, (img_batch, float_lbl, int_lbl, str_lbl) in enumerate(loop_gen):
|
||||
batch = []
|
||||
batch: List[Tensor] = []
|
||||
for i_idx, img in enumerate(img_batch):
|
||||
pil_img = tensor2pil(img)
|
||||
print(f"Batch: {b_idx} | img: {i_idx}")
|
||||
print(img.size())
|
||||
# print(f"Batch: {b_idx} | img: {i_idx}")
|
||||
# print(img.size())
|
||||
draw = ImageDraw.Draw(pil_img)
|
||||
|
||||
draw.text((0, image_h - 60), f"B: {b_idx} | I: {i_idx}", fill="red", font=font)
|
||||
|
||||
y_offset = 0
|
||||
for lbl_type, lbl in zip(["float", "int", "str"], [float_lbl, int_lbl, str_lbl]):
|
||||
for _, lbl in zip(["float", "int", "str"], [float_lbl, int_lbl, str_lbl]):
|
||||
if lbl is None:
|
||||
continue
|
||||
draw.rectangle((0, 0 + y_offset, 512, 60 + y_offset), fill="#ffff33")
|
||||
@@ -111,10 +116,15 @@ class XYImage:
|
||||
"splits": ("INT", {"forceInput": True, "min": 1}),
|
||||
"flip_axis": (["False", "True"], {"default": "False"}),
|
||||
"batch_stack_mode": (["horizontal", "vertical"], {"default": "horizontal"}),
|
||||
"z_enabled": (["False", "True"], {"default": "False"}),
|
||||
},
|
||||
"optional": {
|
||||
"x_main_label": ("STRING", {}),
|
||||
"y_main_label": ("STRING", {}),
|
||||
"z_main_label": ("STRING", {}),
|
||||
"x_labels": (ANY,{}),
|
||||
"y_labels": (ANY,{}),
|
||||
"z_labels": (ANY,{}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,30 +132,72 @@ class XYImage:
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("Image",)
|
||||
INPUT_IS_LIST = (True,)
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
OUTPUT_NODE = True
|
||||
FUNCTION = "xy_image"
|
||||
|
||||
CATEGORY = "List Stuff"
|
||||
|
||||
|
||||
MAIN_LABEL_SIZE = 60
|
||||
LABEL_SIZE = 60
|
||||
Z_LABEL_SIZE = 60
|
||||
LABEL_COLOR = "#000"
|
||||
def xy_image(
|
||||
self,
|
||||
images: List[Tensor],
|
||||
splits: List[int],
|
||||
flip_axis: List[str],
|
||||
batch_stack_mode: List[str],
|
||||
z_enabled: List[str],
|
||||
x_main_label: Optional[List[str]] = None,
|
||||
y_main_label: Optional[List[str]] = None,
|
||||
z_main_label: Optional[List[str]] = None,
|
||||
x_labels: Optional[List[str]] = None,
|
||||
y_labels: Optional[List[str]] = None,
|
||||
) -> Tuple[Tensor]:
|
||||
z_labels: Optional[List[str]] = None,
|
||||
) -> Tuple[List[Tensor]]:
|
||||
if len(flip_axis) != 1:
|
||||
raise Exception("Only single flip_axis value supported.")
|
||||
if len(batch_stack_mode) != 1:
|
||||
raise Exception("Only single batch stack mode supported.")
|
||||
if len(z_enabled) != 1:
|
||||
raise Exception("Only single z_enabled value supported.")
|
||||
if x_main_label is not None and len(x_main_label) != 1:
|
||||
raise Exception("Only single x_main_label value supported.")
|
||||
if y_main_label is not None and len(y_main_label) != 1:
|
||||
raise Exception("Only single y_main_label value supported.")
|
||||
if z_main_label is not None and len(z_main_label) != 1:
|
||||
raise Exception("Only single z_main_label value supported.")
|
||||
|
||||
if x_main_label is not None and not isinstance(x_main_label[0], str):
|
||||
try:
|
||||
x_main_label[0] = str(x_main_label[0])
|
||||
except:
|
||||
raise Exception("x_main_label must be a string or convertible to a string.")
|
||||
if y_main_label is not None and not isinstance(y_main_label[0], str):
|
||||
try:
|
||||
y_main_label[0] = str(y_main_label[0])
|
||||
except:
|
||||
raise Exception("y_main_label must be a string or convertible to a string.")
|
||||
if z_main_label is not None and not isinstance(z_main_label[0], str):
|
||||
try:
|
||||
z_main_label[0] = str(z_main_label[0])
|
||||
except:
|
||||
raise Exception("z_main_label must be a string or convertible to a string.")
|
||||
|
||||
if x_main_label is not None and x_main_label[0] == '':
|
||||
x_main_label = None
|
||||
if y_main_label is not None and y_main_label[0] == '':
|
||||
y_main_label = None
|
||||
if z_main_label is not None and z_main_label[0] == '':
|
||||
z_main_label = None
|
||||
|
||||
stack_direction = "horizontal"
|
||||
if flip_axis[0] == "True":
|
||||
stack_direction = "vertical"
|
||||
x_labels, y_labels = y_labels, x_labels
|
||||
x_main_label, y_main_label = y_main_label, x_main_label
|
||||
|
||||
batch_stack_direction = batch_stack_mode[0]
|
||||
|
||||
@@ -160,6 +212,24 @@ class XYImage:
|
||||
batches = images
|
||||
batch_size = len(batches[0])
|
||||
|
||||
# TODO: Some better way...
|
||||
# Currently chops splits to match x_labels/y_labels and then loops over the split set over and over
|
||||
num_z = 1
|
||||
splits_per_z = len(splits)
|
||||
images_per_z = len(images)
|
||||
if z_enabled[0] == "True":
|
||||
if y_labels is None or x_labels is None:
|
||||
raise Exception("Must provide x_labels and y_labels when z_enabled is True.")
|
||||
|
||||
if stack_direction == "horizontal":
|
||||
splits_per_z = len(x_labels)
|
||||
else:
|
||||
splits_per_z = len(y_labels)
|
||||
|
||||
num_z = int(len(splits) / splits_per_z)
|
||||
splits = splits[:splits_per_z]
|
||||
images_per_z = sum(splits)
|
||||
|
||||
image_h, image_w, _ = batches[0][0].size()
|
||||
if batch_stack_direction == "horizontal":
|
||||
batch_h = image_h
|
||||
@@ -176,6 +246,8 @@ class XYImage:
|
||||
else:
|
||||
full_w = batch_w * max(splits)
|
||||
full_h = batch_h * len(splits)
|
||||
grid_w = full_w
|
||||
_ = full_h
|
||||
|
||||
y_label_offset = 0
|
||||
has_horizontal_labels = False
|
||||
@@ -187,8 +259,8 @@ class XYImage:
|
||||
else:
|
||||
if len(x_labels) != max(splits):
|
||||
raise Exception("Number of horizontal labels must match maximum split size.")
|
||||
full_h += 60
|
||||
y_label_offset = 60
|
||||
full_h += self.LABEL_SIZE
|
||||
y_label_offset = self.LABEL_SIZE
|
||||
has_horizontal_labels = True
|
||||
|
||||
x_label_offset = 0
|
||||
@@ -201,56 +273,156 @@ class XYImage:
|
||||
else:
|
||||
if len(y_labels) != len(splits):
|
||||
raise Exception(f"Number of vertical labels must match number of splits. Got {len(y_labels)} labels for {len(splits)} splits.")
|
||||
full_w += 60
|
||||
x_label_offset = 60
|
||||
full_w += self.LABEL_SIZE
|
||||
x_label_offset = self.LABEL_SIZE
|
||||
has_vertical_labels = True
|
||||
|
||||
has_z_labels = False
|
||||
if z_labels is not None:
|
||||
has_z_labels = True
|
||||
z_labels = [str(lbl) for lbl in z_labels]
|
||||
if z_main_label is not None:
|
||||
z_labels = [f"{z_main_label[0]}: {lbl}" for lbl in z_labels]
|
||||
full_h += self.Z_LABEL_SIZE
|
||||
y_label_offset += self.Z_LABEL_SIZE
|
||||
if len(z_labels) != num_z:
|
||||
raise Exception(f"Number of z_labels must match number of z splits. Got {len(z_labels)} labels for {num_z} splits.")
|
||||
|
||||
full_image = Image.new("RGB", (full_w, full_h))
|
||||
has_main_x_label = False
|
||||
if x_main_label is not None:
|
||||
full_h += self.MAIN_LABEL_SIZE
|
||||
y_label_offset += self.MAIN_LABEL_SIZE
|
||||
has_main_x_label = True
|
||||
|
||||
batch_idx = 0
|
||||
has_main_y_label = False
|
||||
if y_main_label is not None:
|
||||
full_w += self.MAIN_LABEL_SIZE
|
||||
x_label_offset += self.MAIN_LABEL_SIZE
|
||||
has_main_y_label = True
|
||||
|
||||
if has_horizontal_labels:
|
||||
assert x_labels is not None
|
||||
font = ImageFont.truetype(fm.findfont(fm.FontProperties()), 60)
|
||||
for label_idx, label in enumerate(x_labels):
|
||||
x_offset = (batch_w * label_idx) + x_label_offset
|
||||
draw = ImageDraw.Draw(full_image)
|
||||
draw.rectangle((x_offset, 0, x_offset + batch_w, 60), fill="#ffffff")
|
||||
draw.text((x_offset + (batch_w / 2), 0), label, fill="red", font=font)
|
||||
images = []
|
||||
for z_idx in range(num_z):
|
||||
full_image = Image.new("RGB", (full_w, full_h))
|
||||
full_draw = ImageDraw.Draw(full_image)
|
||||
|
||||
if has_vertical_labels:
|
||||
assert y_labels is not None
|
||||
font = ImageFont.truetype(fm.findfont(fm.FontProperties()), 60)
|
||||
for label_idx, label in enumerate(y_labels):
|
||||
y_offset = (batch_h * label_idx) + y_label_offset
|
||||
draw = ImageDraw.Draw(full_image)
|
||||
draw.rectangle((0, y_offset, 60, y_offset + batch_h), fill="#ffffff")
|
||||
draw.text((0, y_offset + (batch_h / 2)), label, fill="red", font=font)
|
||||
full_draw.rectangle((0, 0, full_w, full_h), fill="#ffffff")
|
||||
|
||||
for split_idx, split in enumerate(splits):
|
||||
for idx_in_split in range(split):
|
||||
batch_img = Image.new("RGB", (batch_w, batch_h))
|
||||
batch = batches[batch_idx + idx_in_split]
|
||||
if batch_stack_direction == "horizontal":
|
||||
for img_idx, img in enumerate(batch):
|
||||
x_offset = image_w * img_idx
|
||||
batch_img.paste(tensor2pil(img), (x_offset, 0))
|
||||
else:
|
||||
for img_idx, img in enumerate(batch):
|
||||
y_offset = image_h * img_idx
|
||||
batch_img.paste(tensor2pil(img), (0, y_offset))
|
||||
batch_idx = 0
|
||||
active_y_offset = 0
|
||||
active_x_offset = 0
|
||||
if has_z_labels:
|
||||
font = ImageFont.truetype(fm.findfont(fm.FontProperties()), self.Z_LABEL_SIZE)
|
||||
full_draw.rectangle((0, 0, full_w, self.Z_LABEL_SIZE), fill="#ffffff")
|
||||
full_draw.text((grid_w//2 + x_label_offset, 0), z_labels[z_idx], anchor='ma', fill=self.LABEL_COLOR, font=font)
|
||||
active_y_offset += self.Z_LABEL_SIZE
|
||||
|
||||
if stack_direction == "horizontal":
|
||||
x_offset = batch_w * split_idx + x_label_offset
|
||||
y_offset = batch_h * idx_in_split + y_label_offset
|
||||
else:
|
||||
x_offset = batch_w * idx_in_split + x_label_offset
|
||||
y_offset = batch_h * split_idx + y_label_offset
|
||||
full_image.paste(batch_img, (x_offset, y_offset))
|
||||
if has_main_x_label:
|
||||
assert x_main_label is not None
|
||||
font = ImageFont.truetype(fm.findfont(fm.FontProperties()), self.MAIN_LABEL_SIZE)
|
||||
full_draw.rectangle((0, active_y_offset, full_w, self.MAIN_LABEL_SIZE + active_y_offset), fill="#ffffff")
|
||||
full_draw.text((grid_w//2 + x_label_offset, 0 + active_y_offset), x_main_label[0], anchor='ma', fill=self.LABEL_COLOR, font=font)
|
||||
active_y_offset += self.MAIN_LABEL_SIZE
|
||||
|
||||
if has_horizontal_labels:
|
||||
assert x_labels is not None
|
||||
font = ImageFont.truetype(fm.findfont(fm.FontProperties()), self.LABEL_SIZE)
|
||||
for label_idx, label in enumerate(x_labels):
|
||||
x_offset = (batch_w * label_idx) + x_label_offset
|
||||
full_draw.rectangle((x_offset, 0 + active_y_offset, x_offset + batch_w, self.LABEL_SIZE + active_y_offset), fill="#ffffff")
|
||||
full_draw.text((x_offset + (batch_w / 2), 0 + active_y_offset), label, anchor='ma', fill=self.LABEL_COLOR, font=font)
|
||||
|
||||
if has_main_y_label:
|
||||
assert y_main_label is not None
|
||||
font = ImageFont.truetype(fm.findfont(fm.FontProperties()), self.MAIN_LABEL_SIZE)
|
||||
|
||||
img_txt = Image.new('RGB', (full_h - active_y_offset, self.MAIN_LABEL_SIZE))
|
||||
draw_txt = ImageDraw.Draw(img_txt)
|
||||
draw_txt.rectangle((0, 0, full_h - active_y_offset, self.MAIN_LABEL_SIZE), fill="#ffffff")
|
||||
draw_txt.text(((full_h - active_y_offset)//2, 0), y_main_label[0], anchor='ma', fill=self.LABEL_COLOR, font=font)
|
||||
img_txt = img_txt.rotate(90, expand=True)
|
||||
full_image.paste(img_txt, (active_x_offset, active_y_offset))
|
||||
active_x_offset += self.MAIN_LABEL_SIZE
|
||||
|
||||
if has_vertical_labels:
|
||||
assert y_labels is not None
|
||||
font = ImageFont.truetype(fm.findfont(fm.FontProperties()), self.LABEL_SIZE)
|
||||
for label_idx, label in enumerate(y_labels):
|
||||
y_offset = (batch_h * label_idx) + y_label_offset
|
||||
|
||||
img_txt = Image.new('RGB', (batch_h, self.LABEL_SIZE))
|
||||
draw_txt = ImageDraw.Draw(img_txt)
|
||||
draw_txt.rectangle((0, 0, batch_h, self.LABEL_SIZE), fill="#ffffff")
|
||||
draw_txt.text((batch_h//2, 0), label, anchor='ma', fill=self.LABEL_COLOR, font=font)
|
||||
img_txt = img_txt.rotate(90, expand=True)
|
||||
full_image.paste(img_txt, (active_x_offset, y_offset))
|
||||
|
||||
for split_idx, split in enumerate(splits):
|
||||
for idx_in_split in range(split):
|
||||
batch_img = Image.new("RGB", (batch_w, batch_h))
|
||||
batch = batches[batch_idx + idx_in_split + images_per_z * z_idx]
|
||||
if batch_stack_direction == "horizontal":
|
||||
for img_idx, img in enumerate(batch):
|
||||
x_offset = image_w * img_idx
|
||||
batch_img.paste(tensor2pil(img), (x_offset, 0))
|
||||
else:
|
||||
for img_idx, img in enumerate(batch):
|
||||
y_offset = image_h * img_idx
|
||||
batch_img.paste(tensor2pil(img), (0, y_offset))
|
||||
|
||||
if stack_direction == "horizontal":
|
||||
x_offset = batch_w * split_idx + x_label_offset
|
||||
y_offset = batch_h * idx_in_split + y_label_offset
|
||||
else:
|
||||
x_offset = batch_w * idx_in_split + x_label_offset
|
||||
y_offset = batch_h * split_idx + y_label_offset
|
||||
full_image.paste(batch_img, (x_offset, y_offset))
|
||||
|
||||
batch_idx += split
|
||||
images.append(pil2tensor(full_image))
|
||||
return (images,)
|
||||
|
||||
class VariableImageBuilder:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s) -> Dict[str, Dict[str, Any]]:
|
||||
return {
|
||||
"required": {
|
||||
"r": ("INT", {"defaultInput": True, "min": 0, "max": 255}),
|
||||
"g": ("INT", {"defaultInput": True, "min": 0, "max": 255}),
|
||||
"b": ("INT", {"defaultInput": True, "min": 0, "max": 255}),
|
||||
"a": ("INT", {"defaultInput": True, "min": 0, "max": 255}),
|
||||
"width": ("INT", {"defaultInput": False, "default": 512}),
|
||||
"height": ("INT", {"defaultInput": False, "default": 512}),
|
||||
"batch_size": ("INT", {"default": 1, "min": 1}),
|
||||
},
|
||||
}
|
||||
|
||||
RELOAD_INST = True
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("Image",)
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
FUNCTION = "generate_images"
|
||||
|
||||
CATEGORY = "List Stuff"
|
||||
|
||||
def generate_images(
|
||||
self,
|
||||
r: int,
|
||||
g: int,
|
||||
b: int,
|
||||
a: int,
|
||||
width: int,
|
||||
height: int,
|
||||
batch_size: int,
|
||||
) -> Tuple[Tensor]:
|
||||
batch_tensors: List[Tensor] = []
|
||||
for _ in range(batch_size):
|
||||
image = Image.new("RGB", (width, height), color=(r, g, b, a))
|
||||
batch_tensors.append(pil2tensor(image))
|
||||
return (torch.cat(batch_tensors),)
|
||||
|
||||
batch_idx += split
|
||||
return (pil2tensor(full_image),)
|
||||
|
||||
class EmptyImages:
|
||||
def __init__(self) -> None:
|
||||
@@ -314,15 +486,84 @@ class EmptyImages:
|
||||
if sum(splits) != num_images[0]:
|
||||
raise Exception("Sum of splits must match number of images.")
|
||||
|
||||
ret_images = []
|
||||
for split in splits:
|
||||
if splits is None:
|
||||
raise ValueError("Unexpected error: Splits is None")
|
||||
|
||||
ret_images: List[Tensor] = []
|
||||
for split_idx, split in enumerate(splits):
|
||||
# Rotate between fully dynamic range of colors
|
||||
color = (split * 10, split * 20, split * 30)
|
||||
base_color = (
|
||||
50 + (split_idx * 45) % 200, # Cycle between 50 and 250
|
||||
30 + (split_idx * 75) % 200,
|
||||
10 + (split_idx * 105) % 200,
|
||||
)
|
||||
print(f"Splits: {split} | Base Color: {base_color}")
|
||||
|
||||
for _ in range(split):
|
||||
batch_tensor = torch.zeros(batch_size[0], 512, 512, 3)
|
||||
for batch_idx in range(batch_size[0]):
|
||||
batch_color = (color[0] + 75 * batch_idx, color[1], color[2])
|
||||
batch_color = (
|
||||
(base_color[0] + int(((255 - base_color[0]) / batch_size[0]) * batch_idx)),
|
||||
(base_color[1] + int(((255 - base_color[1]) / batch_size[0]) * batch_idx)),
|
||||
(base_color[2] + int(((255 - base_color[2]) / batch_size[0]) * batch_idx)),
|
||||
)
|
||||
image = Image.new("RGB", (512, 512), color=batch_color)
|
||||
batch_tensor[batch_idx] = pil2tensor(image)
|
||||
ret_images.append(batch_tensor)
|
||||
return (ret_images,)
|
||||
|
||||
class ImageListLoader:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s) -> Dict[str, Dict[str, Any]]:
|
||||
return {
|
||||
"required": {
|
||||
"folder_path": ("STRING", {}),
|
||||
"file_filter": ("STRING", {"default": "*.png"}),
|
||||
"sort_method": (["numerical", "alphabetical"], {"default": "numerical"}),
|
||||
},
|
||||
}
|
||||
|
||||
RELOAD_INST = True
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("Images",)
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
FUNCTION = "load_images"
|
||||
|
||||
CATEGORY = "List Stuff"
|
||||
|
||||
@staticmethod
|
||||
def numerical_sort(file_name: Path) -> int:
|
||||
subbed = re.sub("\D", "", str(file_name))
|
||||
if subbed == "":
|
||||
return 0
|
||||
return int(subbed)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def alphabetical_sort(file_name: Path) -> str:
|
||||
return str(file_name)
|
||||
|
||||
def load_images(
|
||||
self, folder_path: str, file_filter: str, sort_method: str
|
||||
) -> Tuple[List[Tensor]]:
|
||||
folder = Path(folder_path)
|
||||
|
||||
if not folder.is_dir():
|
||||
raise Exception(f"Folder path {folder_path} does not exist.")
|
||||
|
||||
sort_method_impl: Callable[[str], Union[SupportsDunderGT, SupportsDunderLT]]
|
||||
if sort_method == "numerical":
|
||||
sort_method_impl = self.numerical_sort
|
||||
elif sort_method == "alphabetical":
|
||||
sort_method_impl = self.alphabetical_sort
|
||||
else:
|
||||
raise ValueError(f"Unknown sort method {sort_method}")
|
||||
|
||||
files = sorted(folder.glob(file_filter), key=sort_method_impl)
|
||||
images = [pil2tensor(Image.open(file)) for file in files]
|
||||
|
||||
return (images,)
|
||||
|
||||
+178
-4
@@ -28,6 +28,96 @@ class ListLengthNode:
|
||||
def get_len(self, In: List[Any]) -> Tuple[int]:
|
||||
return (len(In),)
|
||||
|
||||
class RepeatList:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(self) -> Dict[str, Dict[str, Any]]:
|
||||
return {
|
||||
"required": {
|
||||
"In": (any_type, {}),
|
||||
"Count": ("INT", {"default": 0, "min": 0, "max": 99999, "step": 1}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_type,)
|
||||
RETURN_NAMES = ("Extended",)
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
FUNCTION = "repeat_list"
|
||||
|
||||
CATEGORY = "List Stuff"
|
||||
|
||||
def repeat_list(self, In: List[Any], Count: List[int]) -> Tuple[List[Any]]:
|
||||
if len(Count) != 1:
|
||||
raise ValueError("Count does not support multiple values")
|
||||
return (In * Count[0],)
|
||||
|
||||
class JoinListAny:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(self) -> Dict[str, Dict[str, Any]]:
|
||||
return {
|
||||
"required": {
|
||||
"In1": (any_type, {}),
|
||||
"In2": (any_type, {}),
|
||||
},
|
||||
"optional": {
|
||||
"In3": (any_type, {}),
|
||||
"In4": (any_type, {}),
|
||||
"In5": (any_type, {}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_type,)
|
||||
RETURN_NAMES = ("Joined", "Sizes")
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
FUNCTION = "join_lists"
|
||||
|
||||
CATEGORY = "List Stuff"
|
||||
|
||||
|
||||
def join_lists(
|
||||
self,
|
||||
*args: List[Tensor],
|
||||
**kwargs: List[Tensor],
|
||||
) -> Tuple[List[Tensor], List[int]]:
|
||||
sizes = []
|
||||
joined = []
|
||||
for arg in args:
|
||||
sizes.append(len(arg))
|
||||
joined.extend(arg)
|
||||
for arg in kwargs.values():
|
||||
if arg is not None:
|
||||
sizes.append(len(arg))
|
||||
joined.extend(arg)
|
||||
|
||||
return joined, sizes
|
||||
|
||||
class ReverseList:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(self) -> Dict[str, Dict[str, Any]]:
|
||||
return {
|
||||
"required": {"In": (any_type, {})},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_type,)
|
||||
RETURN_NAMES = ("Reversed",)
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
FUNCTION = "reverse_list"
|
||||
|
||||
CATEGORY = "List Stuff"
|
||||
|
||||
def reverse_list(self, In: List[Any]) -> Tuple[List[Any]]:
|
||||
return (In[::-1],)
|
||||
|
||||
class JoinFloatLists:
|
||||
def __init__(self) -> None:
|
||||
@@ -64,16 +154,100 @@ class JoinImageLists:
|
||||
"In1": ("IMAGE",),
|
||||
"In2": ("IMAGE",),
|
||||
},
|
||||
"optional": {
|
||||
"In3": ("IMAGE",),
|
||||
"In4": ("IMAGE",),
|
||||
"In5": ("IMAGE",),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "INT")
|
||||
RETURN_NAMES = ("Joined", "Sizes")
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
OUTPUT_IS_LIST = (True, True)
|
||||
FUNCTION = "join_lists"
|
||||
|
||||
CATEGORY = "List Stuff"
|
||||
|
||||
def join_lists(self, In1: List[Tensor], In2: List[Tensor]) -> Tuple[List[Tensor], List[int]]:
|
||||
sizes = [len(In1), len(In2)]
|
||||
return In1 + In2, sizes
|
||||
def join_lists(
|
||||
self,
|
||||
*args: List[Tensor],
|
||||
**kwargs: List[Tensor],
|
||||
) -> Tuple[List[Tensor], List[int]]:
|
||||
sizes = []
|
||||
joined = []
|
||||
for arg in args:
|
||||
sizes.append(len(arg))
|
||||
joined.extend(arg)
|
||||
for arg in kwargs.values():
|
||||
if arg is not None:
|
||||
sizes.append(len(arg))
|
||||
joined.extend(arg)
|
||||
|
||||
return joined, sizes
|
||||
|
||||
|
||||
class StringList:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(self) -> Dict[str, Dict[str, Any]]:
|
||||
return {
|
||||
"required": {
|
||||
"Text1": ("STRING", {}),
|
||||
"Text2": ("STRING", {}),
|
||||
},
|
||||
"optional": {
|
||||
"Text3": ("STRING", {}),
|
||||
"Text4": ("STRING", {}),
|
||||
"Text5": ("STRING", {}),
|
||||
"Text6": ("STRING", {}),
|
||||
"Text7": ("STRING", {}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "INT")
|
||||
RETURN_NAMES = ("Strings", "Num Strings")
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
FUNCTION = "to_string_list"
|
||||
|
||||
CATEGORY = "List Stuff"
|
||||
|
||||
def to_string_list(
|
||||
self,
|
||||
*args: str,
|
||||
**kwargs: str,
|
||||
) -> Tuple[List[str], List[int]]:
|
||||
ret = []
|
||||
for arg in args:
|
||||
ret.append(arg)
|
||||
for arg in kwargs.values():
|
||||
if arg != "":
|
||||
ret.append(arg)
|
||||
|
||||
return ret, [len(ret)]
|
||||
|
||||
class StringListFromNewline:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(self) -> Dict[str, Dict[str, Any]]:
|
||||
return {
|
||||
"required": {
|
||||
"Text": ("STRING", {"multiline": True}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "INT")
|
||||
RETURN_NAMES = ("Strings", "Num Strings")
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
FUNCTION = "to_string_list"
|
||||
|
||||
CATEGORY = "List Stuff"
|
||||
|
||||
def to_string_list(self, Text: str) -> Tuple[List[str], List[int]]:
|
||||
return Text.split("\n"), [len(Text.split("\n"))]
|
||||
|
||||
+72
-19
@@ -1,5 +1,5 @@
|
||||
import itertools
|
||||
from typing import List, Any
|
||||
from typing import List, Any, Optional, Callable, Tuple, Dict
|
||||
|
||||
|
||||
class AnyType(str):
|
||||
@@ -16,52 +16,105 @@ class UnzippedProductAny:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
def INPUT_TYPES(s) -> Dict[str, Dict[str, Any]]:
|
||||
return {
|
||||
"required": {
|
||||
"X": (ANY, {}),
|
||||
"Y": (ANY, {}),
|
||||
"X_Label_Fallback": (["str()", "Numbers"], {"default": "str()", "name": "X Label Fallback"}),
|
||||
"Y_Label_Fallback": (["str()", "Numbers"], {"default": "str()", "name": "Y Label Fallback"})
|
||||
"X_Label_Fallback": (["str()", "Numbers"], {"default": "str()"}),
|
||||
"Y_Label_Fallback": (["str()", "Numbers"], {"default": "str()"}),
|
||||
"Z_Label_Fallback": (["str()", "Numbers"], {"default": "str()"}),
|
||||
},
|
||||
"optional": {
|
||||
"Z": (ANY, {}),
|
||||
"X_Labels": (ANY, {}),
|
||||
"Y_Labels": (ANY, {}),
|
||||
"Z_Labels": (ANY, {}),
|
||||
},
|
||||
"optional": {"X_Labels": (ANY, {"name": "X Labels"}), "Y_Labels": (ANY, {"name": "Y Labels"})},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (ANY, "STRING", ANY, "STRING", "INT", "INT")
|
||||
RETURN_NAMES = ("X Values", "X Labels", "Y Values", "Y Labels", "Total Images", "Split Every")
|
||||
OUTPUT_IS_LIST = (True, True, True, True, False, False)
|
||||
RETURN_NAMES, RETURN_TYPES = zip(*{
|
||||
"X Values": ANY,
|
||||
"X Labels": "STRING",
|
||||
"Y Values": ANY,
|
||||
"Y Labels": "STRING",
|
||||
"Z Values": ANY,
|
||||
"Z Labels": "STRING",
|
||||
"Total Images": "INT",
|
||||
"Split Every": "INT",
|
||||
}.items())
|
||||
|
||||
OUTPUT_IS_LIST = (True, True, True, True, True, True, False, False)
|
||||
INPUT_IS_LIST = True
|
||||
FUNCTION = "to_xy"
|
||||
|
||||
CATEGORY = "List Stuff"
|
||||
|
||||
def to_xy(self, X: List[Any], Y: List[Any], X_Label_Fallback: List[str], Y_Label_Fallback: List[str], X_Labels: List[Any] = None, Y_Labels: List[Any] = None):
|
||||
#region Validation
|
||||
def to_xy(
|
||||
self,
|
||||
X: List[Any],
|
||||
Y: List[Any],
|
||||
X_Label_Fallback: List[str],
|
||||
Y_Label_Fallback: List[str],
|
||||
Z_Label_Fallback: List[str],
|
||||
Z: Optional[List[Any]] = None,
|
||||
X_Labels: Optional[List[Any]] = None,
|
||||
Y_Labels: Optional[List[Any]] = None,
|
||||
Z_Labels: Optional[List[Any]] = None,
|
||||
) -> Tuple[List[Any], List[str], List[Any], List[str], List[Any], List[str], int, int]:
|
||||
# region Validation
|
||||
if len(X_Label_Fallback) != 1:
|
||||
raise Exception("X_Label_Fallback must be a single value")
|
||||
if len(Y_Label_Fallback) != 1:
|
||||
raise Exception("Y_Label_Fallback must be a single value")
|
||||
if len(Z_Label_Fallback) != 1:
|
||||
raise Exception("Z_Label_Fallback must be a single value")
|
||||
|
||||
#region Labels
|
||||
if Z_Labels is not None and Z is None:
|
||||
raise Exception("Z_Labels must be None if Z is None")
|
||||
|
||||
# region Labels
|
||||
get_x_fallback_labels: Callable[[Any], List[str]]
|
||||
if X_Label_Fallback[0] == "str()":
|
||||
get_x_fallback_labels = lambda x: [str(i) for i in x]
|
||||
else:
|
||||
get_x_fallback_labels = lambda x: list(range(len(x)))
|
||||
get_x_fallback_labels = lambda x: [str(i) for i in range(len(x))]
|
||||
|
||||
get_y_fallback_labels: Callable[[Any], List[str]]
|
||||
if Y_Label_Fallback[0] == "str()":
|
||||
get_y_fallback_labels = lambda x: [str(i) for i in x]
|
||||
else:
|
||||
get_y_fallback_labels = lambda x: list(range(len(x)))
|
||||
get_y_fallback_labels = lambda x: [str(i) for i in range(len(x))]
|
||||
|
||||
get_z_fallback_labels: Callable[[Any], List[str]]
|
||||
if Z_Label_Fallback[0] == "str()":
|
||||
get_z_fallback_labels = lambda x: [str(i) for i in x]
|
||||
else:
|
||||
get_z_fallback_labels = lambda x: [str(i) for i in range(len(x))]
|
||||
|
||||
if X_Labels is None:
|
||||
X_Labels = get_x_fallback_labels(X)
|
||||
if Y_Labels is None:
|
||||
Y_Labels = get_y_fallback_labels(Y)
|
||||
#endregion
|
||||
if Z_Labels is None and Z is not None:
|
||||
Z_Labels = get_z_fallback_labels(Z)
|
||||
# endregion
|
||||
|
||||
product = itertools.product(X, Y)
|
||||
X_out, Y_out = zip(*product)
|
||||
X_out = list(X_out)
|
||||
Y_out = list(Y_out)
|
||||
xy_product = itertools.product(X, Y)
|
||||
X_out_tuple, Y_out_tuple = zip(*xy_product)
|
||||
X_out: List[str] = list(X_out_tuple)
|
||||
Y_out: List[str] = list(Y_out_tuple)
|
||||
|
||||
return (X_out, X_Labels, Y_out, Y_Labels, len(X_out), len(Y))
|
||||
Z_out = []
|
||||
if Z is not None:
|
||||
original_len = len(X_out)
|
||||
X_out = X_out * len(Z)
|
||||
Y_out = Y_out * len(Z)
|
||||
for z in Z:
|
||||
Z_out.extend([z] * original_len)
|
||||
|
||||
|
||||
if Z_Labels is None:
|
||||
Z_Labels = []
|
||||
|
||||
return X_out, X_Labels, Y_out, Y_Labels, Z_out, Z_Labels, len(X_out), len(Y)
|
||||
|
||||
Reference in New Issue
Block a user