Added TT_ImageLoaderResizerNode
This commit is contained in:
@@ -35,6 +35,7 @@ try:
|
||||
from .nodes.image.image_preview_save import TT_ImagePreviewSave
|
||||
from .nodes.image.image_preview_upscale_save import TT_ImagePreviewUpscaleSave
|
||||
from .nodes.image.guider_image_reference import TT_GuiderImageReference
|
||||
from .nodes_image import *
|
||||
|
||||
NODES_COUNT += len(set(dir()) - before)
|
||||
except ImportError:
|
||||
@@ -153,6 +154,8 @@ NODE_CLASS_MAPPINGS = {
|
||||
"TT_ImagePreviewSave": TT_ImagePreviewSave,
|
||||
"TT_ImagePreviewUpscaleSave": TT_ImagePreviewUpscaleSave,
|
||||
"TT_GuiderImageReference": TT_GuiderImageReference,
|
||||
# Image V3 Nodes
|
||||
"TT_ImageLoaderResizerNode": TT_ImageLoaderResizerNode,
|
||||
# Latent
|
||||
"TT_LatentFactory": TT_LatentFactory,
|
||||
# Latent V3 Nodes
|
||||
@@ -229,6 +232,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"TT_ImagePreviewSave": "TT Image Preview / Save",
|
||||
"TT_ImagePreviewUpscaleSave": "TT Image Preview / Upscale / Save",
|
||||
"TT_GuiderImageReference": "TT Guider Image Reference",
|
||||
# Image V3 Nodes
|
||||
"TT_ImageLoaderResizerNode": "TT Image Loader / Resizer",
|
||||
# Latent
|
||||
"TT_LatentFactory": "TT Latent Factory (Deprecated)",
|
||||
# Latent V3 Nodes
|
||||
|
||||
+3
-3
@@ -315,7 +315,7 @@ class TT_ContextExtractImageNode(ContextNode):
|
||||
# V3 entrypoint — registers context nodes with ComfyUI
|
||||
# ==============================================================================
|
||||
|
||||
class ContextExtension(ComfyExtension):
|
||||
class ContextNodesExtension(ComfyExtension):
|
||||
@override
|
||||
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
||||
return [
|
||||
@@ -334,8 +334,8 @@ class ContextExtension(ComfyExtension):
|
||||
]
|
||||
|
||||
|
||||
async def comfy_entrypoint() -> ContextExtension:
|
||||
return ContextExtension()
|
||||
async def comfy_entrypoint() -> ContextNodesExtension:
|
||||
return ContextNodesExtension()
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
|
||||
+199
@@ -1 +1,200 @@
|
||||
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import override
|
||||
|
||||
import numpy
|
||||
import torch
|
||||
from PIL import Image, ImageOps, ImageSequence
|
||||
|
||||
import folder_paths
|
||||
from comfy import model_management, utils
|
||||
from comfy_api.latest import ComfyExtension, IO, ui
|
||||
from comfy_api.latest._io import UploadType
|
||||
from node_helpers import pillow
|
||||
from .nodes_latent import MEGAPIXELS
|
||||
|
||||
CATEGORY = "TenserTensor/Image"
|
||||
RESIZE_METHODS = ["nearest-exact", "bilinear", "area", "bicubic", "bislerp"]
|
||||
|
||||
|
||||
def align_to_step(size, scale_factor, dimension_step):
|
||||
return round(size * scale_factor / dimension_step) * dimension_step
|
||||
|
||||
|
||||
# crop = "center" / "disabled"
|
||||
def resize_image(timage, to_width, to_height, upscale_method, dimension_step=1, crop="disabled"):
|
||||
samples = timage.movedim(-1, 1)
|
||||
orig_height, orig_width = samples.shape[2:]
|
||||
scale_factor = max(to_width / orig_width, to_height / orig_height)
|
||||
to_width = align_to_step(orig_width, scale_factor, dimension_step)
|
||||
to_height = align_to_step(orig_height, scale_factor, dimension_step)
|
||||
|
||||
return utils.common_upscale(samples, int(to_width), int(to_height), upscale_method, crop).movedim(1, -1)
|
||||
|
||||
|
||||
def resize_image_to_megapixels(timage, resize_method, megapixels, dimension_step=1):
|
||||
mp_value = float(megapixels.split()[0])
|
||||
total_pixels = int(mp_value * 1_000_000)
|
||||
samples = timage.movedim(-1, 1)
|
||||
orig_height, orig_width = samples.shape[2:]
|
||||
scale_factor = math.sqrt(total_pixels / (orig_width * orig_height))
|
||||
to_width = align_to_step(orig_width, scale_factor, dimension_step)
|
||||
to_height = align_to_step(orig_height, scale_factor, dimension_step)
|
||||
|
||||
return utils.common_upscale(samples, int(to_width), int(to_height), resize_method, "disabled").movedim(1, -1)
|
||||
|
||||
|
||||
def load_image(filename, create_mask, device=None):
|
||||
tdevice = torch.device(device) if device is not None else model_management.get_torch_device()
|
||||
path = folder_paths.get_annotated_filepath(filename)
|
||||
pil_image = pillow(Image.open, path)
|
||||
|
||||
images, masks = [], []
|
||||
width, height = None, None
|
||||
|
||||
for frame in ImageSequence.Iterator(pil_image):
|
||||
pil_frame = pillow(ImageOps.exif_transpose, frame)
|
||||
|
||||
if pil_frame.mode == "I":
|
||||
pil_frame = pil_frame.point(lambda i: i * (1 / 255))
|
||||
|
||||
if len(images) == 0:
|
||||
width, height = pil_frame.size
|
||||
|
||||
if pil_frame.size != (width, height,):
|
||||
continue
|
||||
|
||||
if create_mask:
|
||||
alpha = numpy.zeros((height, width,), dtype=numpy.float32)
|
||||
if 'A' in pil_frame.getbands():
|
||||
alpha = numpy.array(pil_frame.getchannel('A'), dtype=numpy.float32) / 255.0
|
||||
elif pil_frame.mode == 'P' and 'transparency' in pil_frame.info:
|
||||
alpha = numpy.array(pil_frame.convert('RGBA').getchannel('A'), dtype=numpy.float32) / 255.0
|
||||
tmask = torch.from_numpy(1.0 - alpha).to(tdevice)
|
||||
masks.append(tmask)
|
||||
|
||||
rgb_frame = pil_frame.convert("RGB")
|
||||
np_image = numpy.array(rgb_frame, dtype=numpy.float32) / 255.0
|
||||
timage = torch.from_numpy(np_image)[None].to(tdevice)
|
||||
|
||||
images.append(timage)
|
||||
|
||||
if pil_image.format == "MPO":
|
||||
break
|
||||
|
||||
if len(images) == 0:
|
||||
raise ValueError(f"ERROR: No frames found in {path}")
|
||||
|
||||
o_image = torch.cat(images, dim=0) if len(images) > 1 else images[0]
|
||||
|
||||
match len(masks):
|
||||
case 0:
|
||||
o_mask = torch.zeros((1, height, width), dtype=torch.float32)
|
||||
case 1:
|
||||
o_mask = masks[0]
|
||||
case _:
|
||||
o_mask = torch.cat(masks, dim=0)
|
||||
|
||||
return o_image, o_mask
|
||||
|
||||
|
||||
class TT_ImageLoaderResizerNode(IO.ComfyNode):
|
||||
@classmethod
|
||||
def get_image_files(cls):
|
||||
return sorted([
|
||||
f.name for f in os.scandir(folder_paths.get_input_directory())
|
||||
if f.is_file() and Path(f.name).suffix.lower() in (".png", ".jpg", ".webp", ".bmp")
|
||||
])
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> IO.Schema:
|
||||
return IO.Schema(
|
||||
node_id="TT_ImageLoaderResizerNode",
|
||||
display_name="TT Image Loader / Resizer",
|
||||
category=CATEGORY,
|
||||
description="",
|
||||
inputs=[
|
||||
IO.Boolean.Input("resize_image", default=False, label_on="Scale", label_off="Keep"),
|
||||
IO.Combo.Input("resize_method", options=RESIZE_METHODS, advanced=True),
|
||||
IO.Int.Input("dimension_step", default=1, min=1, max=256, advanced=True),
|
||||
IO.Combo.Input("megapixels", options=MEGAPIXELS, advanced=True),
|
||||
IO.Boolean.Input("create_mask", default=False, label_on="Mask", label_off="None"),
|
||||
IO.Combo.Input("image", options=cls.get_image_files(), upload=UploadType.image)
|
||||
],
|
||||
outputs=[
|
||||
IO.Image.Output(display_name="IMAGE"),
|
||||
IO.Mask.Output(display_name="MASK"),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def fingerprint_inputs(cls, **kwargs):
|
||||
path = folder_paths.get_annotated_filepath(kwargs.get("image"))
|
||||
resize = kwargs.get("resize_image")
|
||||
result = {
|
||||
"image": path + str(os.path.getmtime(path)),
|
||||
"resize_image": resize,
|
||||
"resize_method": kwargs.get("resize_method"),
|
||||
"dimension_step": kwargs.get("dimension_step"),
|
||||
"create_mask": kwargs.get("create_mask"),
|
||||
}
|
||||
|
||||
if resize:
|
||||
result["megapixels"] = kwargs.get("megapixels")
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def validate_inputs(cls, **kwargs) -> bool | str:
|
||||
path = folder_paths.get_annotated_filepath(kwargs.get("image"))
|
||||
if not os.path.isfile(path):
|
||||
return f"Image file not found: {path}"
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> IO.NodeOutput:
|
||||
timage, tmask = load_image(kwargs.get("image"), kwargs.get("create_mask"))
|
||||
if kwargs.get("resize_image"):
|
||||
resize_method, megapixels, dimension_step = kwargs.get("resize_method"), kwargs.get("megapixels"), kwargs.get("dimension_step")
|
||||
timage = resize_image_to_megapixels(
|
||||
timage,
|
||||
resize_method,
|
||||
megapixels,
|
||||
dimension_step
|
||||
)
|
||||
tmask = resize_image_to_megapixels(
|
||||
tmask,
|
||||
resize_method,
|
||||
megapixels,
|
||||
dimension_step
|
||||
)
|
||||
|
||||
return IO.NodeOutput(timage, tmask, ui=ui.PreviewImage(timage, cls=cls))
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# V3 entrypoint — registers context nodes with ComfyUI
|
||||
# ==============================================================================
|
||||
|
||||
class ImageNodesExtension(ComfyExtension):
|
||||
@override
|
||||
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
||||
return [
|
||||
TT_ImageLoaderResizerNode,
|
||||
]
|
||||
|
||||
|
||||
async def comfy_entrypoint() -> ImageNodesExtension:
|
||||
return ImageNodesExtension()
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Re-exports for backward compatibility
|
||||
# ==============================================================================
|
||||
|
||||
__all__ = [
|
||||
"TT_ImageLoaderResizerNode",
|
||||
]
|
||||
|
||||
+6
-6
@@ -7,10 +7,11 @@ import torch
|
||||
|
||||
import comfy.sample
|
||||
from comfy_api.latest import IO, ComfyExtension
|
||||
from .lib.common import CommonTypes
|
||||
|
||||
CATEGORY = "TenserTensor/Latent"
|
||||
|
||||
ASPECT_RATIOS = ["1:1", "4:3", "3:2", "16:9", "21:9"]
|
||||
MEGAPIXELS = ["0.25 MP", "0.5 MP", "1 MP", "2 MP", "4 MP", "8 MP"]
|
||||
ORIENTATIONS = ["landscape", "portrait"]
|
||||
MODEL_TYPES = ["FLUX1.D", "FLUX2.D", "SDXL"]
|
||||
CLIP_MULTIPLIERS = ["1x", "2x", "4x"]
|
||||
@@ -26,7 +27,6 @@ class RandomNoise:
|
||||
return comfy.sample.prepare_noise(latent_image, self.seed, batch_idx)
|
||||
|
||||
|
||||
|
||||
class TT_LatentFactoryNode(IO.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> IO.Schema:
|
||||
@@ -39,7 +39,7 @@ class TT_LatentFactoryNode(IO.ComfyNode):
|
||||
IO.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
|
||||
IO.Int.Input("noise_seed", default=0, min=0, max=0xffffffffffffffff, control_after_generate=True),
|
||||
IO.Combo.Input("aspect_ratio", options=ASPECT_RATIOS),
|
||||
IO.Combo.Input("megapixels", options=CommonTypes.MEGAPIXELS),
|
||||
IO.Combo.Input("megapixels", options=MEGAPIXELS),
|
||||
IO.Combo.Input("orientation", options=ORIENTATIONS),
|
||||
IO.Combo.Input("model_type", options=MODEL_TYPES),
|
||||
IO.Int.Input("batch_size", default=1, min=1, max=64, advanced=True),
|
||||
@@ -133,7 +133,7 @@ class TT_LatentFactoryNode(IO.ComfyNode):
|
||||
# V3 entrypoint — registers context nodes with ComfyUI
|
||||
# ==============================================================================
|
||||
|
||||
class ContextExtension(ComfyExtension):
|
||||
class LatentNodesExtension(ComfyExtension):
|
||||
@override
|
||||
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
||||
return [
|
||||
@@ -141,8 +141,8 @@ class ContextExtension(ComfyExtension):
|
||||
]
|
||||
|
||||
|
||||
async def comfy_entrypoint() -> ContextExtension:
|
||||
return ContextExtension()
|
||||
async def comfy_entrypoint() -> LatentNodesExtension:
|
||||
return LatentNodesExtension()
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
|
||||
@@ -1 +1,29 @@
|
||||
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
|
||||
from typing import override
|
||||
|
||||
from comfy_api.latest import ComfyExtension, IO
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# V3 entrypoint — registers context nodes with ComfyUI
|
||||
# ==============================================================================
|
||||
|
||||
class LoaderNodesExtension(ComfyExtension):
|
||||
@override
|
||||
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
||||
return [
|
||||
# TT_Node,
|
||||
]
|
||||
|
||||
|
||||
async def comfy_entrypoint() -> LoaderNodesExtension:
|
||||
return LoaderNodesExtension()
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Re-exports for backward compatibility
|
||||
# ==============================================================================
|
||||
|
||||
__all__ = [
|
||||
# "TT_Node",
|
||||
]
|
||||
|
||||
@@ -1 +1,30 @@
|
||||
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
from typing import override
|
||||
|
||||
from comfy_api.latest import ComfyExtension, IO
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# V3 entrypoint — registers context nodes with ComfyUI
|
||||
# ==============================================================================
|
||||
|
||||
class VaeNodesExtension(ComfyExtension):
|
||||
@override
|
||||
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
||||
return [
|
||||
# TT_Node,
|
||||
]
|
||||
|
||||
|
||||
async def comfy_entrypoint() -> VaeNodesExtension:
|
||||
return VaeNodesExtension()
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Re-exports for backward compatibility
|
||||
# ==============================================================================
|
||||
|
||||
__all__ = [
|
||||
# "TT_Node",
|
||||
]
|
||||
|
||||
@@ -1 +1,30 @@
|
||||
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
from typing import override
|
||||
|
||||
from comfy_api.latest import ComfyExtension, IO
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# V3 entrypoint — registers context nodes with ComfyUI
|
||||
# ==============================================================================
|
||||
|
||||
class LatentNodesExtension(ComfyExtension):
|
||||
@override
|
||||
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
||||
return [
|
||||
# TT_Node,
|
||||
]
|
||||
|
||||
|
||||
async def comfy_entrypoint() -> LatentNodesExtension:
|
||||
return LatentNodesExtension()
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Re-exports for backward compatibility
|
||||
# ==============================================================================
|
||||
|
||||
__all__ = [
|
||||
# "TT_Node",
|
||||
]
|
||||
|
||||
@@ -1 +1,29 @@
|
||||
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
|
||||
from typing import override
|
||||
|
||||
from comfy_api.latest import IO, ComfyExtension
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# V3 entrypoint — registers context nodes with ComfyUI
|
||||
# ==============================================================================
|
||||
|
||||
class VaeNodesExtension(ComfyExtension):
|
||||
@override
|
||||
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
||||
return [
|
||||
# TT_Node,
|
||||
]
|
||||
|
||||
|
||||
async def comfy_entrypoint() -> VaeNodesExtension:
|
||||
return VaeNodesExtension()
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Re-exports for backward compatibility
|
||||
# ==============================================================================
|
||||
|
||||
__all__ = [
|
||||
# "TT_Node",
|
||||
]
|
||||
|
||||
+4
-7
@@ -220,10 +220,7 @@ class TT_Flux2WorkflowSettingsAdvancedNode(IO.ComfyNode):
|
||||
@classmethod
|
||||
def execute(cls, **kwargs) -> IO.NodeOutput:
|
||||
kwargs["sampler"], kwargs["sigmas"] = build_sampler_sigmas(
|
||||
kwargs.get("sampler_name"),
|
||||
kwargs.get("steps"),
|
||||
kwargs.get("width"),
|
||||
kwargs.get("height"),
|
||||
kwargs.get("sampler_name"), kwargs.get("steps"), kwargs.get("width"), kwargs.get("height"),
|
||||
)
|
||||
|
||||
workflow_config = TTWorkflowSettings.create(**kwargs)
|
||||
@@ -248,7 +245,7 @@ class TT_Flux2WorkflowSettingsAdvancedNode(IO.ComfyNode):
|
||||
# V3 entrypoint — registers context nodes with ComfyUI
|
||||
# ==============================================================================
|
||||
|
||||
class ContextExtension(ComfyExtension):
|
||||
class WorkflowNodesExtension(ComfyExtension):
|
||||
@override
|
||||
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
||||
return [
|
||||
@@ -257,8 +254,8 @@ class ContextExtension(ComfyExtension):
|
||||
]
|
||||
|
||||
|
||||
async def comfy_entrypoint() -> ContextExtension:
|
||||
return ContextExtension()
|
||||
async def comfy_entrypoint() -> WorkflowNodesExtension:
|
||||
return WorkflowNodesExtension()
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
|
||||
Reference in New Issue
Block a user