diff --git a/__init__.py b/__init__.py index 8410cf7..1beab6a 100644 --- a/__init__.py +++ b/__init__.py @@ -1,8 +1,8 @@ -from .custom_nodes.audio_nodes import LoadAudio, PreviewAudio, MergeAudioNode, DownloadAudio, AudioBatchValueNode +from .custom_nodes.audio_nodes import NODE_CLASS_MAPPINGS as audio_nodes, NODE_DISPLAY_NAME_MAPPINGS as audio_nodes_name from .custom_nodes.stt import AudioTranscriptionNode, BatchedTranscriptionEncoderNode, LoadWhisperModelNode from .custom_nodes.uvr import UVR5Node from .custom_nodes.rvc import LoadHubertModel, LoadPitchExtractionParams, LoadRVCModelNode, RVCNode -from .custom_nodes.utils import Any2ListNode, List2AnyNode, MergeImageBatches, MergeLatentBatches, ImageRepeatInterleavedNode, LatentRepeatInterleavedNode, SimpleMathNode, SliceNode, SortImagesNode, ZipImagesNode +from .custom_nodes.utility_nodes import NODE_CLASS_MAPPINGS as utility_nodes, NODE_DISPLAY_NAME_MAPPINGS as utility_nodes_name from .custom_nodes.musetalk_nodes import NODE_CLASS_MAPPINGS as musetalk_nodes, NODE_DISPLAY_NAME_MAPPINGS as musetalk_nodes_name # Set the web directory, any .js file in that directory will be loaded by the frontend as a frontend extension WEB_DIRECTORY = "./web" @@ -11,55 +11,29 @@ WEB_DIRECTORY = "./web" # NOTE: names should be globally unique NODE_CLASS_MAPPINGS = { "UVR5Node": UVR5Node, - "RVC-Studio.LoadAudio": LoadAudio, - "RVC-Studio.PreviewAudio": PreviewAudio, - "MergeAudioNode": MergeAudioNode, "AudioTranscriptionNode": AudioTranscriptionNode, "LoadWhisperModelNode": LoadWhisperModelNode, "LoadRVCModelNode": LoadRVCModelNode, "RVCNode": RVCNode, "LoadHubertModel": LoadHubertModel, "LoadPitchExtractionParams": LoadPitchExtractionParams, - "AudioBatchValueNode": AudioBatchValueNode, - "MergeImageBatches": MergeImageBatches, - "MergeLatentBatches": MergeLatentBatches, - "ImageRepeatInterleavedNode": ImageRepeatInterleavedNode, - "LatentRepeatInterleavedNode": LatentRepeatInterleavedNode, - "DownloadAudio": DownloadAudio, "BatchedTranscriptionEncoderNode": BatchedTranscriptionEncoderNode, - "SimpleMathNode": SimpleMathNode, - "SliceNode": SliceNode, - "ZipNode": ZipImagesNode, - "Any2ListNode": Any2ListNode, - "List2AnyNode": List2AnyNode, - "SortImagesNode": SortImagesNode, - **musetalk_nodes + **audio_nodes, + **musetalk_nodes, + **utility_nodes } # A dictionary that contains the friendly/humanly readable titles for the nodes NODE_DISPLAY_NAME_MAPPINGS = { "UVR5Node": "🌺Vocal Removal", - "RVC-Studio.LoadAudio": "🌺Load Audio", - "DownloadAudio": "🌺Youtube Downloader", - "RVC-Studio.PreviewAudio": "🌺Preview Audio", "AudioTranscriptionNode": "🌺Transcribe Audio", "LoadWhisperModelNode": "🌺Load Whisper Model", "LoadRVCModelNode": "🌺Load RVC Model", "RVCNode": "🌺Voice Changer", "LoadHubertModel": "🌺Load Hubert Model", "LoadPitchExtractionParams": "🌺Load Pitch Extraction Params", - "MergeAudioNode": "🌺Merge Audio", - "AudioBatchValueNode": "🌺Audio RMS Batch Values", - "MergeImageBatches": "🌺Merge Image Batches", - "MergeLatentBatches": "🌺Merge Latent Batches", - "ImageRepeatInterleavedNode": "🌺Image Repeat Interleaved", - "LatentRepeatInterleavedNode": "🌺Latent Repeat Interleaved", "BatchedTranscriptionEncoderNode": "🌺Batched CLIP Transcription Encode (Prompt)", - "SimpleMathNode": "🌺Simple Math Operations", - "SliceNode": "🌺Slice Array", - "ZipNode": "🌺Zip Images", - "Any2ListNode": "🌺Any to List", - "List2AnyNode": "🌺List to Any", - "SortImagesNode": "🌺Sort Images", - **musetalk_nodes_name + **audio_nodes_name, + **musetalk_nodes_name, + **utility_nodes_name } \ No newline at end of file diff --git a/custom_nodes/audio_nodes.py b/custom_nodes/audio_nodes.py index 471d3ac..6340f5e 100644 --- a/custom_nodes/audio_nodes.py +++ b/custom_nodes/audio_nodes.py @@ -284,4 +284,20 @@ class AudioBatchValueNode: else: x_norm_int = map(int,x_norm) - return (list(x_norm),list(x_norm_int),num_values) \ No newline at end of file + return (list(x_norm),list(x_norm_int),num_values) + +NODE_CLASS_MAPPINGS = { + "RVC-Studio.LoadAudio": LoadAudio, + "RVC-Studio.PreviewAudio": PreviewAudio, + "MergeAudioNode": MergeAudioNode, + "AudioBatchValueNode": AudioBatchValueNode, + "DownloadAudio": DownloadAudio, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "RVC-Studio.LoadAudio": "🌺Load Audio", + "DownloadAudio": "🌺Youtube Downloader", + "RVC-Studio.PreviewAudio": "🌺Preview Audio", + "MergeAudioNode": "🌺Merge Audio", + "AudioBatchValueNode": "🌺Audio RMS Batch Values", +} \ No newline at end of file diff --git a/custom_nodes/utility_nodes.py b/custom_nodes/utility_nodes.py new file mode 100644 index 0000000..3d6596f --- /dev/null +++ b/custom_nodes/utility_nodes.py @@ -0,0 +1,446 @@ +import itertools +import math +import os +import torch +from .utils import AlwaysEqualProxy, MultipleTypeProxy +import folder_paths +from ..lib.utils import gc_collect +import numpy as np + +CATEGORY = "🌺RVC-Studio/utils" +temp_path = folder_paths.get_temp_directory() + +class MergeImageBatches: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "images": ("IMAGE",), + "iterate": ("BOOLEAN",{"default": True}) + }, + } + + INPUT_IS_LIST = (True, False) + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "merge" + + CATEGORY = CATEGORY + + __archive__ = None + + @staticmethod + def iter_images(fname,images): + size = len(images) + if size==0: return images + shape = images[0].shape + if len(shape)>3: shape=shape[1:] + + memmap = np.memmap(fname, mode='w+', dtype=np.float32, shape=(size,*shape)) + + for i,img in enumerate(itertools.chain(*images)): + if i >= len(memmap): #need to expand memmap + print(f"index {i} is larger than {len(memmap)}. expanding memmap to {size+i}...") + memmap.flush() + memmap = np.memmap(fname, dtype=np.float32, mode='r+',shape=(size+i,*shape), order='F') + memmap[i]=img + # del img + # gc_collect() + memmap.flush() + + print(f"{fname=} size={i} shape={memmap.shape}") + + # del images + images = torch.from_numpy(memmap[:i]) + + return images + + def __del__(self): + try: + if os.path.exists(self.__archive__): os.remove(self.__archive__) + gc_collect() + except Exception as error: + print(f"Failed to delete {self.__archive__}: {error=}") + + def merge(self, images, iterate): + + if len(images) <= 1: + return (images[0],) + else: + if hasattr(iterate,"pop"): iterate=iterate.pop() + print(f"{iterate=}") + + if iterate: + image = images[0] + for new_img in images[1:]: + image = torch.cat([image,new_img]) + gc_collect() + + # if self.__archive__ is None: + # with tempfile.NamedTemporaryFile(delete=False) as ntf: + # self.__archive__ = ntf.name + # images = self.iter_images(self.__archive__,images) + del images + images = image + gc_collect() + else: + images = torch.cat(images) + + gc_collect() + print(f"Merged images: {len(images)=} {images.shape=}") + + return (images,) + +class MergeLatentBatches: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "latents": ("LATENT",), + } + } + + INPUT_IS_LIST = True + + RETURN_TYPES = ("LATENT",) + FUNCTION = "merge" + + CATEGORY = CATEGORY + + @staticmethod + def merge_tensor(tensors): + try: + if len(tensors): return torch.cat(tensors, dim=0) + else: print(tensors) + except Exception as error: + print(f"{error=}") + return [] + + def merge(self, latents): + merged_latent = {} + gc_collect() + merged_latent["samples"] = self.merge_tensor([latent["samples"] for latent in latents]) + merged_latent["noise_mask"] = self.merge_tensor([latent["noise_mask"] for latent in latents if "noise_mask" in latent]) + # if len(merged_latent["noise_mask"])>0: merged_latent["noise_mask"] = torch.mean(merged_latent["noise_mask"],dim=0) + # merged_latent["batch_index"] = self.merge_tensor([latent["batch_index"] for latent in latents if "batch_index" in latent]) + merged_latent["batch_index"] = range(len(merged_latent["samples"])) + + # remove empty fields + if merged_latent["noise_mask"]==[]: del merged_latent["noise_mask"] + + print(f'Merged latents: {len(merged_latent["samples"])=} {merged_latent["batch_index"]=}') + return (merged_latent,) + +class ImageRepeatInterleavedNode: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "images": ("IMAGE",), + "repeats": ("INT", {"default": 1, "min": 1, "max": 4096}), + }, + "optional": { + "fps": ("INT", {"default": 1, "min": 1}) + } + } + RETURN_TYPES = ("IMAGE",) + INPUT_IS_LIST = True + OUTPUT_IS_LIST = (True, ) + + FUNCTION = "rebatch" + + CATEGORY = CATEGORY + + def rebatch(self, images, repeats, fps=1): + repeats = np.array(repeats).flatten() + fps = np.array(fps)[0] + print(f"{repeats=} {fps=}") + all_images = [] + for img in images: + for i in range(img.shape[0]): + all_images.append(img[i:i+1]) + + if len(repeats)==1: + all_images*=repeats[0] + repeats = np.ones(len(all_images)) + elif len(repeats)1: all_images[i] = img.expand(repeat, *img.shape).flatten(0,1) + else: all_images[i] = img + + print(f"{len(images)=} {len(all_images)=}") + gc_collect() + + return (all_images,) + +class LatentRepeatInterleavedNode: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "latents": ("LATENT",), + "repeats": ("INT", {"default": 1, "min": 1, "max": 4096}), + }, + "optional": { + "fps": ("INT", {"default": 1, "min": 1}) + } + } + RETURN_NAMES = ("latents","num_latents") + RETURN_TYPES = ("LATENT","INT") + INPUT_IS_LIST = True + OUTPUT_IS_LIST = (True, False) + + FUNCTION = "merge" + + CATEGORY = CATEGORY + + @staticmethod + def repeat(latents, repeat): + if latents.ndim>3: #batched + expanded_latents=(latents.expand(repeat, *latents.shape).flatten(0,1)) + else: + expanded_latents=(latents.expand(repeat, *latents.shape)) + + print(f"{len(latents)}*{repeat}={len(expanded_latents)} ({latents.shape=} => {expanded_latents.shape=})") + return expanded_latents + + @staticmethod + def rebatch(samples, repeats): + offset = 0 + num_latents = 0 + for i in range(len(samples)): + repeat = int(repeats[i]) + samples[i]["batch_index"] = [i+offset for i in range(repeat)] + offset += repeats[i] + + samples[i]["samples"] = LatentRepeatInterleavedNode.repeat(samples[i]["samples"],repeat) + num_latents+=len(samples[i]["samples"]) + + if "noise_mask" in samples[i]: + samples[i]["noise_mask"] = LatentRepeatInterleavedNode.repeat(samples[i]["noise_mask"],repeat) + + return samples,num_latents + + def merge(self, latents, repeats, fps=1): + repeats = np.array(repeats).flatten() + fps = np.array(fps)[0] + print(f"{repeats=} {fps=}") + + # combine all latents + new_latents = [] + for latent in latents: + if latent["samples"].ndim==3: #unbatched + new_latents.append(latent) + else: + for i in range(len(latent["samples"])): + new_latent = { + "samples": latent["samples"][i], + } + if "noise_mask" in latent: + print(f"{latent['noise_mask'].shape=}") + new_latent["noise_mask"] = latent["noise_mask"] + new_latents.append(new_latent) + + if len(repeats)==1: + new_latents *= repeats[0] + repeats = np.ones(len(new_latents)) + elif len(repeats)1: # handles list inputs + return (list(map(num_to_int,number)), list(map(float,number)), list(map(str,number))) + else: return (num_to_int(number[0]), float(number[0]), str(number[0])) + +class SliceNode: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "optional": { + "start": ("INT", {"default": 0, "min": 0}), + "end": ("INT", {"default": -1}), + }, + "required": { + "array": (AlwaysEqualProxy("*"),), + }, + } + + RETURN_TYPES = (AlwaysEqualProxy("*"),) + RETURN_NAMES = ("array",) + FUNCTION = "slice" + CATEGORY = CATEGORY + + def slice(self, array, start=0, end=-1): + if end==-1: end=len(array) + return (array[start:end],) + +class ZipImagesNode: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "images1": ("IMAGE",), + "images2": ("IMAGE",), + }, + } + + RETURN_TYPES = ("IMAGE",) + OUTPUT_IS_LIST = (True, ) + FUNCTION = "dozip" + CATEGORY = CATEGORY + + def dozip(self, images1, images2): + return (list(map(torch.stack,zip(images1,images2))),) + +class Any2ListNode: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "any": (AlwaysEqualProxy("*"),), + }, + } + + RETURN_TYPES = (AlwaysEqualProxy("*"),) + OUTPUT_IS_LIST = (True, ) + FUNCTION = "to" + CATEGORY = CATEGORY + + def to(self, any): + return (list(any),) + +class List2AnyNode: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "any": (AlwaysEqualProxy("*"),), + }, + } + + RETURN_TYPES = (AlwaysEqualProxy("*"),) + INPUT_IS_LIST = (True, ) + FUNCTION = "to" + CATEGORY = CATEGORY + + def to(self, any): + return (any,) + +class SortImagesNode: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "images": ("IMAGE",), + }, + "optional": { + "indices": ("INT", {"forceInput": True}), + "reverse": ("BOOLEAN", {"default": False}), + "sort_by": (["sum","mean","median","min","max"],{"default": "sum"}) + } + } + + RETURN_TYPES = ("IMAGE", "INT") + RETURN_NAMES = ("images", "indices") + FUNCTION = "execute" + CATEGORY = CATEGORY + + def execute(self, images, indices=None, reverse=False, sort_by="sum"): + if sort_by=="mean": func=np.mean + elif sort_by=="median": func=np.median + elif sort_by=="min": func=np.amin + elif sort_by=="max": func=np.amax + else: func=np.sum + values = list(map(lambda x: func(x.numpy()),images)) + if indices is None: + indices = np.argsort(values) + if reverse: indices=indices[::-1] + indices = list(indices) + return (images[indices],indices) + +NODE_CLASS_MAPPINGS = { + "MergeImageBatches": MergeImageBatches, + "MergeLatentBatches": MergeLatentBatches, + "ImageRepeatInterleavedNode": ImageRepeatInterleavedNode, + "LatentRepeatInterleavedNode": LatentRepeatInterleavedNode, + "SimpleMathNode": SimpleMathNode, + "SliceNode": SliceNode, + "ZipNode": ZipImagesNode, + "Any2ListNode": Any2ListNode, + "List2AnyNode": List2AnyNode, + "SortImagesNode": SortImagesNode, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "MergeImageBatches": "🌺Merge Image Batches", + "MergeLatentBatches": "🌺Merge Latent Batches", + "ImageRepeatInterleavedNode": "🌺Image Repeat Interleaved", + "LatentRepeatInterleavedNode": "🌺Latent Repeat Interleaved", + "SimpleMathNode": "🌺Simple Math Operations", + "SliceNode": "🌺Slice Array", + "ZipNode": "🌺Zip Images", + "Any2ListNode": "🌺Any to List", + "List2AnyNode": "🌺List to Any", + "SortImagesNode": "🌺Sort Images", +} \ No newline at end of file diff --git a/custom_nodes/utils.py b/custom_nodes/utils.py index eb155e6..2ffba1f 100644 --- a/custom_nodes/utils.py +++ b/custom_nodes/utils.py @@ -1,15 +1,9 @@ -import itertools -import math import os import re -import torch import folder_paths -from ..lib.utils import gc_collect from .settings.downloader import RVC_DOWNLOAD_LINK, download_file from ..lib import BASE_MODELS_DIR -import numpy as np -CATEGORY = "🌺RVC-Studio/utils" temp_path = folder_paths.get_temp_directory() def model_downloader(model): @@ -52,413 +46,4 @@ class AlwaysEqualProxy(str): return True def __ne__(self, _): - return False - -class MergeImageBatches: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "images": ("IMAGE",), - "iterate": ("BOOLEAN",{"default": True}) - }, - } - - INPUT_IS_LIST = (True, False) - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "merge" - - CATEGORY = CATEGORY - - __archive__ = None - - @staticmethod - def iter_images(fname,images): - size = len(images) - if size==0: return images - shape = images[0].shape - if len(shape)>3: shape=shape[1:] - - memmap = np.memmap(fname, mode='w+', dtype=np.float32, shape=(size,*shape)) - - for i,img in enumerate(itertools.chain(*images)): - if i >= len(memmap): #need to expand memmap - print(f"index {i} is larger than {len(memmap)}. expanding memmap to {size+i}...") - memmap.flush() - memmap = np.memmap(fname, dtype=np.float32, mode='r+',shape=(size+i,*shape), order='F') - memmap[i]=img - # del img - # gc_collect() - memmap.flush() - - print(f"{fname=} size={i} shape={memmap.shape}") - - # del images - images = torch.from_numpy(memmap[:i]) - - return images - - def __del__(self): - try: - if os.path.exists(self.__archive__): os.remove(self.__archive__) - gc_collect() - except Exception as error: - print(f"Failed to delete {self.__archive__}: {error=}") - - def merge(self, images, iterate): - - if len(images) <= 1: - return (images[0],) - else: - if hasattr(iterate,"pop"): iterate=iterate.pop() - print(f"{iterate=}") - - if iterate: - image = images[0] - for new_img in images[1:]: - image = torch.cat([image,new_img]) - gc_collect() - - # if self.__archive__ is None: - # with tempfile.NamedTemporaryFile(delete=False) as ntf: - # self.__archive__ = ntf.name - # images = self.iter_images(self.__archive__,images) - del images - images = image - gc_collect() - else: - images = torch.cat(images) - - gc_collect() - print(f"Merged images: {len(images)=} {images.shape=}") - - return (images,) - -class MergeLatentBatches: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "latents": ("LATENT",), - } - } - - INPUT_IS_LIST = True - - RETURN_TYPES = ("LATENT",) - FUNCTION = "merge" - - CATEGORY = CATEGORY - - @staticmethod - def merge_tensor(tensors): - try: - if len(tensors): return torch.cat(tensors, dim=0) - else: print(tensors) - except Exception as error: - print(f"{error=}") - return [] - - def merge(self, latents): - merged_latent = {} - gc_collect() - merged_latent["samples"] = self.merge_tensor([latent["samples"] for latent in latents]) - merged_latent["noise_mask"] = self.merge_tensor([latent["noise_mask"] for latent in latents if "noise_mask" in latent]) - # if len(merged_latent["noise_mask"])>0: merged_latent["noise_mask"] = torch.mean(merged_latent["noise_mask"],dim=0) - # merged_latent["batch_index"] = self.merge_tensor([latent["batch_index"] for latent in latents if "batch_index" in latent]) - merged_latent["batch_index"] = range(len(merged_latent["samples"])) - - # remove empty fields - if merged_latent["noise_mask"]==[]: del merged_latent["noise_mask"] - - print(f'Merged latents: {len(merged_latent["samples"])=} {merged_latent["batch_index"]=}') - return (merged_latent,) - -class ImageRepeatInterleavedNode: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "images": ("IMAGE",), - "repeats": ("INT", {"default": 1, "min": 1, "max": 4096}), - }, - "optional": { - "fps": ("INT", {"default": 1, "min": 1}) - } - } - RETURN_TYPES = ("IMAGE",) - INPUT_IS_LIST = True - OUTPUT_IS_LIST = (True, ) - - FUNCTION = "rebatch" - - CATEGORY = CATEGORY - - def rebatch(self, images, repeats, fps=1): - repeats = np.array(repeats).flatten() - fps = np.array(fps)[0] - print(f"{repeats=} {fps=}") - all_images = [] - for img in images: - for i in range(img.shape[0]): - all_images.append(img[i:i+1]) - - if len(repeats)==1: - all_images*=repeats[0] - repeats = np.ones(len(all_images)) - elif len(repeats)1: all_images[i] = img.expand(repeat, *img.shape).flatten(0,1) - else: all_images[i] = img - - print(f"{len(images)=} {len(all_images)=}") - gc_collect() - - return (all_images,) - -class LatentRepeatInterleavedNode: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "latents": ("LATENT",), - "repeats": ("INT", {"default": 1, "min": 1, "max": 4096}), - }, - "optional": { - "fps": ("INT", {"default": 1, "min": 1}) - } - } - RETURN_NAMES = ("latents","num_latents") - RETURN_TYPES = ("LATENT","INT") - INPUT_IS_LIST = True - OUTPUT_IS_LIST = (True, False) - - FUNCTION = "merge" - - CATEGORY = CATEGORY - - @staticmethod - def repeat(latents, repeat): - if latents.ndim>3: #batched - expanded_latents=(latents.expand(repeat, *latents.shape).flatten(0,1)) - else: - expanded_latents=(latents.expand(repeat, *latents.shape)) - - print(f"{len(latents)}*{repeat}={len(expanded_latents)} ({latents.shape=} => {expanded_latents.shape=})") - return expanded_latents - - @staticmethod - def rebatch(samples, repeats): - offset = 0 - num_latents = 0 - for i in range(len(samples)): - repeat = int(repeats[i]) - samples[i]["batch_index"] = [i+offset for i in range(repeat)] - offset += repeats[i] - - samples[i]["samples"] = LatentRepeatInterleavedNode.repeat(samples[i]["samples"],repeat) - num_latents+=len(samples[i]["samples"]) - - if "noise_mask" in samples[i]: - samples[i]["noise_mask"] = LatentRepeatInterleavedNode.repeat(samples[i]["noise_mask"],repeat) - - return samples,num_latents - - def merge(self, latents, repeats, fps=1): - repeats = np.array(repeats).flatten() - fps = np.array(fps)[0] - print(f"{repeats=} {fps=}") - - # combine all latents - new_latents = [] - for latent in latents: - if latent["samples"].ndim==3: #unbatched - new_latents.append(latent) - else: - for i in range(len(latent["samples"])): - new_latent = { - "samples": latent["samples"][i], - } - if "noise_mask" in latent: - print(f"{latent['noise_mask'].shape=}") - new_latent["noise_mask"] = latent["noise_mask"] - new_latents.append(new_latent) - - if len(repeats)==1: - new_latents *= repeats[0] - repeats = np.ones(len(new_latents)) - elif len(repeats)1: # handles list inputs - return (list(map(num_to_int,number)), list(map(float,number)), list(map(str,number))) - else: return (num_to_int(number[0]), float(number[0]), str(number[0])) - -class SliceNode: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - return { - "optional": { - "start": ("INT", {"default": 0, "min": 0}), - "end": ("INT", {"default": -1}), - }, - "required": { - "array": (AlwaysEqualProxy("*"),), - }, - } - - RETURN_TYPES = (AlwaysEqualProxy("*"),) - RETURN_NAMES = ("array",) - FUNCTION = "slice" - CATEGORY = CATEGORY - - def slice(self, array, start=0, end=-1): - if end==-1: end=len(array) - return (array[start:end],) - -class ZipImagesNode: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "images1": ("IMAGE",), - "images2": ("IMAGE",), - }, - } - - RETURN_TYPES = ("IMAGE",) - OUTPUT_IS_LIST = (True, ) - FUNCTION = "dozip" - CATEGORY = CATEGORY - - def dozip(self, images1, images2): - return (list(map(torch.stack,zip(images1,images2))),) - -class Any2ListNode: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "any": (AlwaysEqualProxy("*"),), - }, - } - - RETURN_TYPES = (AlwaysEqualProxy("*"),) - OUTPUT_IS_LIST = (True, ) - FUNCTION = "to" - CATEGORY = CATEGORY - - def to(self, any): - return (list(any),) - -class List2AnyNode: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "any": (AlwaysEqualProxy("*"),), - }, - } - - RETURN_TYPES = (AlwaysEqualProxy("*"),) - INPUT_IS_LIST = (True, ) - FUNCTION = "to" - CATEGORY = CATEGORY - - def to(self, any): - return (any,) - -class SortImagesNode: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "images": ("IMAGE",), - }, - "optional": { - "indices": ("INT", {"forceInput": True}), - "reverse": ("BOOLEAN", {"default": False}), - "sort_by": (["sum","mean","median","min","max"],{"default": "sum"}) - } - } - - RETURN_TYPES = ("IMAGE", "INT") - RETURN_NAMES = ("images", "indices") - FUNCTION = "execute" - CATEGORY = CATEGORY - - def execute(self, images, indices=None, reverse=False, sort_by="sum"): - if sort_by=="mean": func=np.mean - elif sort_by=="median": func=np.median - elif sort_by=="min": func=np.amin - elif sort_by=="max": func=np.amax - else: func=np.sum - values = list(map(lambda x: func(x.numpy()),images)) - if indices is None: - indices = np.argsort(values) - if reverse: indices=indices[::-1] - indices = list(indices) - return (images[indices],indices) \ No newline at end of file + return False \ No newline at end of file