29 Commits
Author SHA1 Message Date
Michael Poutre 97320545f9 Merge pull request #1 from M1kep/z-2
Update
2023-10-10 18:40:18 -07:00
Michael Poutre d7826218bf feat(nodes): Add String List From Newline 2023-10-10 18:28:55 -07:00
Michael Poutre 5b417c03cb feat(Nodes): Add Join List Any 2023-09-20 21:43:43 -07:00
Michael Poutre 01fb6ea503 feat(Node): Add Repeat List 2023-09-20 21:38:48 -07:00
Michael Poutre f20879d8c6 feat(Node): Add Reverse List 2023-09-20 20:32:37 -07:00
Michael Poutre 8a1ab4acdd Revert "refactor(Grid): flip_axis -> split_layout"
This reverts commit b1953bed74.
2023-09-19 23:13:12 -07:00
Michael Poutre f2a674c653 fix(Grid): Z Label Size 2023-09-19 23:04:35 -07:00
Michael Poutre a1ba600ca4 Revert "refactor: X/Y -> Splits/Items"
This reverts commit 1bf1797c8b.
2023-09-19 23:00:29 -07:00
Michael Poutre ad03811aa9 feat(Node): Add Variable Image Builder 2023-09-19 22:59:37 -07:00
Michael Poutre 4851d400f9 fix(Grid): Correct Y offset due to Z labels 2023-09-19 22:59:37 -07:00
Michael Poutre b1953bed74 refactor(Grid): flip_axis -> split_layout 2023-09-19 22:59:03 -07:00
Michael Poutre 1bf1797c8b refactor: X/Y -> Splits/Items 2023-09-19 22:58:43 -07:00
Michael Poutre 848a8bce00 refactor(Grid): Use constant for label color 2023-09-18 23:15:48 -07:00
Michael Poutre b5bd1fbe3e feat(Nodes): Add ImageListLoader and StringList 2023-09-18 23:15:11 -07:00
Michael Poutre d16d4f85d6 refactor(JoinImageLists): Add more inputs 2023-09-18 23:14:12 -07:00
Michael Poutre b3f6344976 refactor(Nodes): Assist MyPy some more 2023-09-18 18:23:28 -07:00
Michael Poutre 05e0889248 refactor(Grid): Use constants for label sizes 2023-09-17 23:06:31 -07:00
Michael Poutre 728020c4a4 refactor(XY): More type hints 2023-09-17 22:43:12 -07:00
Michael Poutre c230b28a97 fix(xy): Return the correct number of z values 2023-09-16 22:15:26 -07:00
Michael Poutre 1e1ae6d21c fix(XY): If no z_labels, set to empty list 2023-09-16 21:18:19 -07:00
Michael Poutre 52f6e88de7 fix(XYImage): If no z, default to len(images) for images_per_z 2023-09-16 21:18:01 -07:00
Michael Poutre acb81d0532 fix(grid): Fill whole grid with white 2023-09-16 20:48:34 -07:00
Michael Poutre b2ea696d67 refactor(grid): Use single instance of Image.Draw 2023-09-16 20:48:08 -07:00
Michael Poutre 586346940d fix(grid): Center vertical labels 2023-09-16 20:46:13 -07:00
Michael Poutre cd84ea85de refactor(grid): Use "ma" for text anchor 2023-09-16 20:45:49 -07:00
Michael Poutre b8dd1b62b8 feat(xy): Initial support for "main labels" 2023-09-16 20:38:37 -07:00
Michael Poutre 3903e7243d feat: Initial Z support 2023-09-16 19:08:21 -07:00
Michael Poutre 6238a618f7 refactor(overlay images): Disable debug logging 2023-09-16 19:08:05 -07:00
Michael Poutre 327f15b9a0 refactor(Empty Images): More consistent color handling 2023-09-16 19:07:30 -07:00
4 changed files with 569 additions and 80 deletions
+22 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)