Merge pull request #2 from tenser-tensor/feature/guided-sampler

v1.4.7: Add FLUX2 support, guided sampler, and reference image injection

- Add TT KSampler (Guided) with external guider/sigmas/sampler
- Add TT Guider Image Reference for reference-based generation
- Add TT FLUX2 GGUF loaders, CLIP encoders, workflow settings
- Add TT Image Preview / Upscale / Save all-in-one node
- Add TT Context Set Guider
- Add BasicGuider and CommonTypes to shared lib
- Fix missing f-strings in error prints
- Fix workflow settings module filenames (workfow → workflow)
This commit is contained in:
Tenser Tensor
2026-02-25 14:21:05 +03:00
committed by GitHub
56 changed files with 1365 additions and 368 deletions
+20
View File
@@ -0,0 +1,20 @@
## v1.4.7 (2026-02-25)
### New Nodes
- **TT KSampler (Guided)** — guided diffusion sampling with external guider, sigmas, and sampler
- **TT Guider Image Reference** — encodes reference image into latent space and injects it into Guider conditioning
- **TT Image Preview / Upscale / Save** — all-in-one preview, upscale, and save node
- **TT FLUX2 GGUF Models Loader** / **(Advanced)** — loaders for FLUX2 GGUF-quantized models
- **TT CLIP Text Encode FLUX2** / **(Context)** — text encoders for FLUX2 models
- **TT FLUX2 Workflow Settings** / **(Advanced)** — workflow configuration nodes for FLUX2
- **TT Context Set Guider** — injects guider into TT_CONTEXT object
### Improvements
- Added `BasicGuider` and `CommonTypes` to shared lib (`lib/common.py`)
- Fixed missing f-strings in all error print statements across node imports
- Fixed workflow settings module filenames (typo: `workfow` → `workflow`)
### Docs
- Added docs for all new nodes
- Updated NODES.md with descriptions for all new nodes
- Updated Latent Factory description
+54 -3
View File
@@ -10,6 +10,20 @@ Node for configuring Flux workflow generation parameters.
Advanced node for configuring Flux workflow generation parameters with separate CLIP-L and T5XXL prompts.
#### TT Flux2 Workflow Settings
Use this node as the single source of truth for FLUX2 workflow parameters. The `WORKFLOW_CONFIG` output is
designed for context-aware nodes that consume a TT_WORKFLOW_CONFIG directly. Individual outputs are available
for connecting to standard ComfyUI nodes. The sigma schedule (`SCHEDULER`) is computed automatically from
the seed and resolution via `get_schedule`.
#### TT Flux2 Workflow Settings (Advanced)
Advanced version of TT Flux2 Workflow Settings with `prompt` and `lora_triggers` fields included directly in the
node. Both are stored in the `WORKFLOW_CONFIG` output alongside all other sampling parameters, making this node
sufficient as the sole configuration source for full context-driven FLUX2 pipelines. Individual outputs remain
available for connecting to standard ComfyUI nodes.
#### TT SDXL Workflow Settings
Node for configuring SDXL workflow generation parameters.
@@ -22,9 +36,10 @@ Advanced SDXL workflow configuration with separate CLIP-L/G prompts and aestheti
#### TT Latent Factory
Generate empty latent images with precise dimension control based on aspect ratio and megapixel count.
Automatically calculates correct dimensions for FLUX and SDXL models, plus separate CLIP conditioning sizes.
Select aspect ratio, resolution, and orientation to automatically compute pixel dimensions suited for the chosen
model. FLUX1.D uses 16-channel latents, FLUX2.D uses 128-channel, SDXL uses 4-channel. `clip_multiplier` scales the TARGET
dimensions relative to the image dimensions — useful for multi-resolution CLIP conditioning. Connect `RANDOM_NOISE`
directly to a sampler node for reproducible noise tied to `noise_seed`.
### Loaders
@@ -36,6 +51,14 @@ All-in-one model loader for SDXL workflows with checkpoint merging support.
Extended all-in-one model loader for SDXL with checkpoint merging and LoRA support.
#### TT GGUF Models Loader
All-in-one loader for GGUF-quantized models. Place your `.gguf` diffusion model files in the `diffusion_models` or `unet` directory, CLIP files in `text_encoders` or `clip` directory. VAE can be any standard ComfyUI-compatible VAE file.
#### TT GGUF Models Loader (Advanced)
Extended version of GGUF Models Loader. Supports up to 4 LoRAs loaded simultaneously. Enable `apply_sampling` to apply flux sampling shift patch — useful for improving quality at non-standard resolutions. Advanced parameters are hidden by default and can be revealed via the node's show/hide controls.
#### TT FLUX Models Loader
All-in-one model loader for Flux workflows.
@@ -62,6 +85,19 @@ SDXL text encoder with separate CLIP-L and CLIP-G prompt inputs and conditioning
Context-based SDXL text encoder that extracts prompts and parameters from workflow config.
#### TT Clip Text Encode Flux2
Encodes text prompts specifically for FLUX2 models. Enter your main prompt in `prompt` and any LoRA-specific trigger
words separately in `lora_triggers` — the node combines them internally. Adjust `guidance` to control how strongly
the model follows the prompt (3.5 is a good starting point for FLUX2).
#### TT Clip Text Encode Flux2 (Context)
Context-aware version of the FLUX2 text encoder. Instead of individual inputs, it reads `model`, `clip`, and
`workflow_config` (containing `prompt`, `lora_triggers`, and `guidance`) from the passed `context`. The resulting
guider is both returned as an output and stored back into the context under the `guider` key. Raises an error if
any of the required fields are missing from the context.
### Samplers
#### TT KSampler
@@ -90,6 +126,12 @@ Automated two-stage sampling workflow. First performs a draft pass with partial
a second pass. Each stage can use different samplers, schedulers, and step counts. Useful for quick previews followed by
quality refinement, or combining different sampling strategies.
#### TT KSampler (Guided)
Runs guided diffusion sampling on the input latent. Connect a guider from a text encoder node, sigmas from a
scheduler, and a sampler from a workflow settings node. `random_noise` controls stochasticity — use a seeded
noise node for reproducible results.
### VAE
#### TT VAE Decode (Context)
@@ -164,6 +206,10 @@ saturation, sharpness, posterize, and solarize adjustments. All effects are opti
Dual-purpose node that can preview images in the UI and/or save them to disk with extensive formatting options.
#### TT Image Preview / Upscale / Save
All-in-one node that can preview, upscale, and save images in a single step. Upscaling is applied before saving — the upscaled result is both displayed in the UI and written to disk. When `upscale_image` is disabled, the original image is used. When `save_image` is disabled, the image is only previewed in the UI without writing to disk.
### Context
#### TT Base Context
@@ -199,6 +245,11 @@ Stores pixel-space image in context for use by downstream nodes. Overwrites exis
Stores latent representation in context for use by downstream nodes. Overwrites existing latent if present.
#### TT Context Set Guider
Sets the `guider` field inside a TT_CONTEXT object. Use this node when you have a guider produced outside the
context pipeline and need to inject it back in for use by subsequent context-aware nodes.
#### TT Context Passthrough
Use this node to pass context through your workflow while optionally modifying specific parameters without changing the
+4 -1
View File
@@ -6,9 +6,12 @@ WEB_DIRECTORY = "./web"
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY']
nodes_count = len(NODE_CLASS_MAPPINGS)
print()
SEP = f"{CYAN}{'=' * 60}{RESET}"
print(SEP)
print(f"{YELLOW}TenserTensor ComfyUI Nodes Pack (v1.3.1): {BRIGHT_GREEN}38 Nodes Loaded{RESET}")
print(f"{YELLOW}TenserTensor ComfyUI Nodes Pack (v1.4.7): {BRIGHT_GREEN}{nodes_count} Nodes Loaded{RESET}")
print(SEP)
print(f"{BRIGHT_CYAN}[V] Repository: https://github.com/tenser-tensor/ComfyUI-TenserTensor{RESET}")
print(f"{BRIGHT_CYAN}[V] ComfyUI Registry: https://registry.comfy.org/publishers/tenser-tensor{RESET}")
+13
View File
@@ -0,0 +1,13 @@
import comfy.samplers as S
class BasicGuider(S.CFGGuider):
def set_conds(self, conditioning):
self.inner_set_conds({"positive": conditioning})
def get_conds(self, key="positive"):
return [[c.get("cross_attn", None), c] for c in self.original_conds[key]]
class CommonTypes():
MEGAPIXELS = ["0.25 MP", "0.5 MP", "1 MP", "2 MP", "4 MP", "8 MP"]
+43 -19
View File
@@ -17,31 +17,34 @@ try:
from .nodes.context.context_passthrough import TT_ContextPassthrough
from .nodes.context.context_set_image import TT_ContextSetImage
from .nodes.context.context_set_latent import TT_ContextSetLatent
from .nodes.context.context_set_guider import TT_ContextSetGuider
from .nodes.context.even_larger_context import TT_EvenLargerContext
from .nodes.context.large_context_flux import TT_LargeContextFlux
from .nodes.context.large_context_sdxl import TT_LargeContextSdxl
except ImportError:
print("{YELLOW}TenserTensor: {RED}ERROR: Context nodes unavailable{RESET}")
print(f"{YELLOW}TenserTensor: {RED}ERROR: Context nodes unavailable{RESET}")
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
except ImportError:
print("{YELLOW}TenserTensor: {RED}ERROR: Image nodes unavailable{RESET}")
print(f"{YELLOW}TenserTensor: {RED}ERROR: Image nodes unavailable{RESET}")
try:
from .nodes.latent.latent_factory import TT_LatentFactory
except ImportError:
print("{YELLOW}TenserTensor: {RED}ERROR: Latent nodes unavailable{RESET}")
print(f"{YELLOW}TenserTensor: {RED}ERROR: Latent nodes unavailable{RESET}")
try:
from .nodes.loaders.flux_models_loader import TT_FluxModelsLoader
from .nodes.loaders.flux_models_loader_advanced import TT_FluxModelsLoaderAdvanced
from .nodes.loaders.gguf_models_loader import TT_GgufModelsLoader
from .nodes.loaders.gguf_models_loader_advanced import TT_GgufModelsLoaderAdvanced
from .nodes.loaders.gguf_models_loader import TT_Flux2GgufModelsLoader
from .nodes.loaders.gguf_models_loader_advanced import TT_Flux2GgufModelsLoaderAdvanced
from .nodes.loaders.sdxl_models_loader import TT_SdxlModelsLoader
from .nodes.loaders.sdxl_models_loader_advanced import TT_SdxlModelsLoaderAdvanced
except ImportError:
print("{YELLOW}TenserTensor: {RED}ERROR: Loader nodes unavailable{RESET}")
print(f"{YELLOW}TenserTensor: {RED}ERROR: Loader nodes unavailable{RESET}")
try:
from .nodes.postproduction.add_film_grain import TT_AddFilmGrain
@@ -51,23 +54,26 @@ try:
from .nodes.postproduction.postproduction_advanced import TT_PostproductionAdvanced
from .nodes.postproduction.quick_image_upscaler import TT_QuickImageUpscaler
except ImportError:
print("{YELLOW}TenserTensor: {RED}ERROR: Postproduction nodes unavailable{RESET}")
print(f"{YELLOW}TenserTensor: {RED}ERROR: Postproduction nodes unavailable{RESET}")
try:
from .nodes.sampling.ksampler import TT_KSampler
from .nodes.sampling.ksampler_advanced import TT_KSamplerAdvanced
from .nodes.sampling.ksampler_context import TT_KSamplerContext
from .nodes.sampling.ksampler_two_stage import TT_KSamplerTwoStage
from .nodes.sampling.ksampler_guided import TT_KSamplerGuided
except ImportError:
print("{YELLOW}TenserTensor: {RED}ERROR: Sampler nodes unavailable{RESET}")
print("f{YELLOW}TenserTensor: {RED}ERROR: Sampler nodes unavailable{RESET}")
try:
from .nodes.text_encoders.clip_text_encode_flux import TT_ClipTextEncodeFlux
from .nodes.text_encoders.clip_text_encode_flux_context import TT_ClipTextEncodeFluxContext
from .nodes.text_encoders.clip_text_encode_sdxl import TT_ClipTextEncodeSdxl
from .nodes.text_encoders.clip_text_encode_sdxl_context import TT_ClipTextEncodeSdxlContext
from .nodes.text_encoders.clip_text_encode_flux2 import TT_ClipTextEncodeFlux2
from .nodes.text_encoders.clip_text_encode_flux2_context import TT_ClipTextEncodeFlux2Context
except ImportError:
print("{YELLOW}TenserTensor: {RED}ERROR: Text Encoder nodes unavailable{RESET}")
print(f"{YELLOW}TenserTensor: {RED}ERROR: Text Encoder nodes unavailable{RESET}")
try:
from .nodes.vae.vae_decode_context import TT_VaeDecodeContext
@@ -75,15 +81,17 @@ try:
from .nodes.vae.vae_encode_context import TT_VaeEncodeContext
from .nodes.vae.vae_encode_tiled import TT_VaeEncodeTiled
except ImportError:
print("{YELLOW}TenserTensor: {RED}ERROR: VAE nodes unavailable{RESET}")
print(f"{YELLOW}TenserTensor: {RED}ERROR: VAE nodes unavailable{RESET}")
try:
from .nodes.workflow.flux_workfow_settings import TT_FluxWorkflowSettings
from .nodes.workflow.flux_workfow_settings_advanced import TT_FluxWorkflowSettingsAdvanced
from .nodes.workflow.sdxl_workfow_settings import TT_SdxlWorkflowSettings
from .nodes.workflow.sdxl_workfow_settings_advanced import TT_SdxlWorkflowSettingsAdvanced
from .nodes.workflow.flux_workflow_settings import TT_FluxWorkflowSettings
from .nodes.workflow.flux_workflow_settings_advanced import TT_FluxWorkflowSettingsAdvanced
from .nodes.workflow.sdxl_workflow_settings import TT_SdxlWorkflowSettings
from .nodes.workflow.sdxl_workflow_settings_advanced import TT_SdxlWorkflowSettingsAdvanced
from .nodes.workflow.flux2_workflow_settings import TT_Flux2WorkflowSettings
from .nodes.workflow.flux2_workflow_settings_advanced import TT_Flux2WorkflowSettingsAdvanced
except ImportError:
print("{YELLOW}TenserTensor: {RED}ERROR: Workflow nodes unavailable{RESET}")
print(f"{YELLOW}TenserTensor: {RED}ERROR: Workflow nodes unavailable{RESET}")
NODE_CLASS_MAPPINGS = {
# Context
@@ -94,17 +102,20 @@ NODE_CLASS_MAPPINGS = {
"TT_EvenLargerContext": TT_EvenLargerContext,
"TT_ContextSetImage": TT_ContextSetImage,
"TT_ContextSetLatent": TT_ContextSetLatent,
"TT_ContextSetGuider": TT_ContextSetGuider,
# Context Passthrough
"TT_ContextPassthrough": TT_ContextPassthrough,
# Image
"TT_ImagePreviewSave": TT_ImagePreviewSave,
"TT_ImagePreviewUpscaleSave": TT_ImagePreviewUpscaleSave,
"TT_GuiderImageReference": TT_GuiderImageReference,
# Latent
"TT_LatentFactory": TT_LatentFactory,
# Loaders
"TT_SdxlModelsLoader": TT_SdxlModelsLoader,
"TT_SdxlModelsLoaderAdvanced": TT_SdxlModelsLoaderAdvanced,
"TT_GgufModelsLoader": TT_GgufModelsLoader,
"TT_GgufModelsLoaderAdvanced": TT_GgufModelsLoaderAdvanced,
"TT_Flux2GgufModelsLoader": TT_Flux2GgufModelsLoader,
"TT_Flux2GgufModelsLoaderAdvanced": TT_Flux2GgufModelsLoaderAdvanced,
"TT_FluxModelsLoader": TT_FluxModelsLoader,
"TT_FluxModelsLoaderAdvanced": TT_FluxModelsLoaderAdvanced,
# Postproduction
@@ -119,11 +130,14 @@ NODE_CLASS_MAPPINGS = {
"TT_KSamplerAdvanced": TT_KSamplerAdvanced,
"TT_KSamplerContext": TT_KSamplerContext,
"TT_KSamplerTwoStage": TT_KSamplerTwoStage,
"TT_KSamplerGuided": TT_KSamplerGuided,
# Text Encoder
"TT_ClipTextEncodeFlux": TT_ClipTextEncodeFlux,
"TT_ClipTextEncodeFluxContext": TT_ClipTextEncodeFluxContext,
"TT_ClipTextEncodeSdxl": TT_ClipTextEncodeSdxl,
"TT_ClipTextEncodeSdxlContext": TT_ClipTextEncodeSdxlContext,
"TT_ClipTextEncodeFlux2": TT_ClipTextEncodeFlux2,
"TT_ClipTextEncodeFlux2Context": TT_ClipTextEncodeFlux2Context,
# VAE
"TT_VaeDecodeContext": TT_VaeDecodeContext,
"TT_VaeDecodeTiled": TT_VaeDecodeTiled,
@@ -134,6 +148,8 @@ NODE_CLASS_MAPPINGS = {
"TT_FluxWorkflowSettingsAdvanced": TT_FluxWorkflowSettingsAdvanced,
"TT_SdxlWorkflowSettings": TT_SdxlWorkflowSettings,
"TT_SdxlWorkflowSettingsAdvanced": TT_SdxlWorkflowSettingsAdvanced,
"TT_Flux2WorkflowSettings": TT_Flux2WorkflowSettings,
"TT_Flux2WorkflowSettingsAdvanced": TT_Flux2WorkflowSettingsAdvanced,
}
NODE_DISPLAY_NAME_MAPPINGS = {
@@ -145,17 +161,20 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"TT_EvenLargerContext": "TT Even Larger Context",
"TT_ContextSetImage": "TT Context Set Image",
"TT_ContextSetLatent": "TT Context Set Latent",
"TT_ContextSetGuider": "TT Context Set Guider",
# Context Passthrough
"TT_ContextPassthrough": "TT Context Passthrough",
# Image
"TT_ImagePreviewSave": "TT Image Preview / Save",
"TT_ImagePreviewUpscaleSave": "TT Image Preview / Upscale / Save",
"TT_GuiderImageReference": "TT Guider Image Reference",
# Latent
"TT_LatentFactory": "TT Latent Factory",
# Loaders
"TT_SdxlModelsLoader": "TT SDXL Models Loader",
"TT_SdxlModelsLoaderAdvanced": "TT SDXL Models Loader (Advanced)",
"TT_GgufModelsLoader": "TT GGUF Models Loader",
"TT_GgufModelsLoaderAdvanced": "TT GGUF Models Loader (Advanced)",
"TT_Flux2GgufModelsLoader": "TT FLUX2 GGUF Models Loader",
"TT_Flux2GgufModelsLoaderAdvanced": "TT FLUX2 GGUF Models Loader (Advanced)",
"TT_FluxModelsLoader": "TT FLUX Models Loader",
"TT_FluxModelsLoaderAdvanced": "TT FLUX Models Loader (Advanced)",
# Postproduction
@@ -170,11 +189,14 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"TT_KSamplerAdvanced": "TT KSampler (Advanced)",
"TT_KSamplerContext": "TT KSampler (Context)",
"TT_KSamplerTwoStage": "TT KSampler (Two Stages)",
"TT_KSamplerGuided": "TT KSampler (Guided)",
# Text Encoder
"TT_ClipTextEncodeFlux": "TT CLIP Text Encode FLUX",
"TT_ClipTextEncodeFluxContext": "TT CLIP Text Encode FLUX (Context)",
"TT_ClipTextEncodeSdxl": "TT CLIP Text Encode SDXL",
"TT_ClipTextEncodeSdxlContext": "TT CLIP Text Encode SDXL (Context)",
"TT_ClipTextEncodeFlux2": "TT CLIP Text Encode FLUX2",
"TT_ClipTextEncodeFlux2Context": "TT CLIP Text Encode FLUX2 (Context)",
# VAE
"TT_VaeDecodeContext": "TT VAE Decode (Context)",
"TT_VaeDecodeTiled": "TT VAE Decode (Tiled)",
@@ -185,4 +207,6 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"TT_FluxWorkflowSettingsAdvanced": "TT FLUX Workflow Settings (Advanced)",
"TT_SdxlWorkflowSettings": "TT SDXL Workflow Settings",
"TT_SdxlWorkflowSettingsAdvanced": "TT SDXL Workflow Settings (Advanced)",
"TT_Flux2WorkflowSettings": "TT FLUX2 Workflow Settings",
"TT_Flux2WorkflowSettingsAdvanced": "TT FLUX2 Workflow Settings (Advanced)",
}
+41 -5
View File
@@ -1,6 +1,6 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
import comfy.samplers
import comfy.samplers as S
CONTEXT_FIELDS = [
# input_name, return_name, field_type
@@ -13,14 +13,18 @@ CONTEXT_FIELDS = [
("negative", "NEGATIVE", "CONDITIONING"),
("image", "IMAGE", "IMAGE"),
("mask", "MASK", "MASK"),
("guider", "GUIDER", "GUIDER"),
("control_net", "CONTROL_NET", "CONTROL_NET"),
("workflow_config", "WORKFLOW_CONFIG", "TT_WORKFLOW_CONFIG",),
("seed", "SEED", "INT"),
("steps", "STEPS", "INT"),
("cfg", "CFG", "FLOAT"),
("sampler_name", "SAMPLER_NAME", comfy.samplers.KSampler.SAMPLERS),
("scheduler", "SCHEDULER", comfy.samplers.KSampler.SCHEDULERS),
("sampler_name", "SAMPLER_NAME", S.KSampler.SAMPLERS),
("sampler", "SAMPLER", "SAMPLER"),
("scheduler", "SCHEDULER", S.KSampler.SCHEDULERS),
("guidance", "GUIDANCE", "FLOAT"),
("prompt", "PROMPT", "STRING"),
("lora_triggers", "LORA_TRIGGERS", "STRING"),
("clip_l_positive", "CLIP_L_POSITIVE", "STRING"),
("clip_g_positive", "CLIP_L_POSITIVE", "STRING"),
("t5xxl_positive", "T5XXL_POSITIVE", "STRING"),
@@ -93,6 +97,28 @@ CONTEXTS = {
"height",
)
},
"big_flux2": {
"optional": (
"context",
"workflow_config",
"model",
"clip",
"vae",
"latent",
"image",
"seed",
"steps",
"cfg",
"sampler_name",
"sampler",
"scheduler",
"guidance",
"prompt",
"lora_triggers",
"width",
"height",
)
},
"big_sdxl": {
"optional": (
"context",
@@ -130,7 +156,7 @@ CONTEXTS = {
"context",
)
},
"set_image":{
"set_image": {
"required": (
"context",
"image",
@@ -139,7 +165,7 @@ CONTEXTS = {
"context",
)
},
"set_latent":{
"set_latent": {
"required": (
"context",
"latent",
@@ -148,6 +174,15 @@ CONTEXTS = {
"context",
)
},
"set_guider": {
"required": (
"context",
"guider",
),
"return": (
"context",
)
},
}
FORCE_INPUT_TYPES = {
@@ -162,6 +197,7 @@ WORKFOW_SETTINGS_KEYS = [
"steps",
"cfg",
"sampler_name",
"sampler",
"scheduler",
"pos_a_score",
"clip_l_positive",
+18
View File
@@ -0,0 +1,18 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
from .context_helpers import create_context_schema
class TT_ContextSetGuider:
INPUT_TYPES_DICT, RETURN_TYPES, RETURN_NAMES = create_context_schema("set_guider")
FUNCTION = "process_context"
CATEGORY = "TenserTensor/Context"
@classmethod
def INPUT_TYPES(cls):
return cls.INPUT_TYPES_DICT
def process_context(self, context, guider):
context["guider"] = guider
return (context,)
+58
View File
@@ -0,0 +1,58 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
import os
import folder_paths as FP
from node_helpers import conditioning_set_values
from ..image.image_helpers import load_image, resize_image_to_megapixels
from ...lib.common import CommonTypes
UPSCALE_METHODS = ["nearest-exact", "bilinear", "area", "bicubic", "lanczos"]
TILE_SIZE, OVERLAP = 512, 64
class TT_GuiderImageReference():
@classmethod
def INPUT_TYPES(cls):
input_dir = FP.get_input_directory()
files = FP.filter_files_content_types(
[f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))], ["image"]
)
return {
"required": {
"vae": ("VAE",),
"guider": ("GUIDER",),
"megapixels": (CommonTypes.MEGAPIXELS,),
"upscale_method": (UPSCALE_METHODS, {"default": "bicubic", "advanced": True}),
"dimension_step": ("INT", {"default": 1, "min": 1, "max": 10000, "advanced": True}),
"image": (sorted(files), {"image_upload": True}),
},
}
RETURN_TYPES = ("VAE", "GUIDER",)
RETURN_NAMES = ("VAE", "GUIDER",)
FUNCTION = "update_latent"
CATEGORY = "TenserTensor/Image"
def update_latent(self, vae, guider, megapixels, upscale_method, dimension_step, image):
timage, _ = load_image(image)
scaled = resize_image_to_megapixels(timage, upscale_method, megapixels, dimension_step)
samples = vae.encode_tiled(
scaled,
tile_x=TILE_SIZE,
tile_y=TILE_SIZE,
overlap=OVERLAP
)
conditioning = guider.get_conds()
if conditioning is not None:
conditioning = conditioning_set_values(conditioning, {"reference_latents": [samples]}, append=True)
guider.set_conds(conditioning)
else:
raise ValueError("ERROR: Guider has no conditioning")
return (vae, guider,)
+84 -1
View File
@@ -1,14 +1,20 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
import fnmatch
import math
import os
import random
import string as S
from datetime import datetime
import folder_paths as FP
import numpy
import numpy as NP
from PIL import Image
import torch
from PIL import Image, ImageOps, ImageSequence
from comfy import model_management as MM
from comfy import utils as U
from node_helpers import pillow
FORMAT_EXT = {
"PNG": ".png",
@@ -16,9 +22,11 @@ FORMAT_EXT = {
"WEBP": ".webp",
}
class InvalidSavePathError(Exception):
pass
def _build_filename(save_path, filename_mask):
count = sum(1 for f in os.scandir(save_path) if fnmatch.fnmatch(f.name, filename_mask)) + 1
@@ -112,3 +120,78 @@ def preview(image, filename_prefix):
filename_prefix=prefix,
output_path=temp_dir,
)
def load_image(image_filename, device=None):
tdevice = MM.get_torch_device() if device == None else torch.device("cpu")
image_path = FP.get_annotated_filepath(image_filename)
pil_image = pillow(Image.open, image_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
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)
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)
masks.append(tmask)
if pil_image.format == "MPO":
break
if len(images) == 0:
raise ValueError(f"ERROR: No frames found in {image_path}")
oimage = torch.cat(images, dim=0) if len(images) > 1 else images[0]
omask = torch.cat(masks, dim=0) if len(masks) > 1 else masks[0]
return oimage, omask
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 U.common_upscale(samples, int(to_width), int(to_height), upscale_method, crop).movedim(1, -1)
def resize_image_to_megapixels(timage, upscale_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 U.common_upscale(samples, int(to_width), int(to_height), upscale_method, "disabled").movedim(1, -1)
+4 -4
View File
@@ -14,11 +14,11 @@ class TT_ImagePreviewSave():
"image": ("IMAGE",),
"save_image": ("BOOLEAN", {"default": True, "label_on": "Enabled", "label_off": "Disabled"}),
"filename_prefix": ("STRING", {"default": "tenser-tensor"}),
"filename_format": (FILENAME_FORMATS, {"advanced": True, "default": "name-###"}),
"filename_format": (FILENAME_FORMATS, {"default": "name-###", "advanced": True}),
"subfolder": ("STRING", {"default": "", "advanced": True}),
"image_format": (IMAGE_FORMATS, {"advanced": True, "default": "PNG"}),
"image_quality": ("INT", {"default": 100, "min": 0, "max": 100}),
"compression_level": ("INT", {"default": 9, "min": 0, "max": 9}),
"image_format": (IMAGE_FORMATS, {"default": "PNG", "advanced": True}),
"image_quality": ("INT", {"default": 100, "min": 0, "max": 100, "advanced": True}),
"compression_level": ("INT", {"default": 9, "min": 0, "max": 9, "advanced": True}),
}
}
+71
View File
@@ -0,0 +1,71 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
import folder_paths as FP
from .image_helpers import store, preview
from ..postproduction.postproduction_helpers import upscale_image as upscale
class TT_ImagePreviewUpscaleSave():
@classmethod
def INPUT_TYPES(cls):
FILENAME_FORMATS = ["name-###", "date-name-###", "name-datetime"]
IMAGE_FORMATS = ["PNG", "JPEG", "WEBP"]
DEVICES = ["default", "cpu"]
UPSCALE_MODELS = FP.get_filename_list("upscale_models")
return {
"required": {
"image": ("IMAGE",),
"save_image": ("BOOLEAN", {"default": True, "label_on": "Enabled", "label_off": "Disabled"}),
"filename_prefix": ("STRING", {"default": "tenser-tensor"}),
"filename_format": (FILENAME_FORMATS, {"advanced": True, "default": "name-###"}),
"subfolder": ("STRING", {"default": "", "advanced": True}),
"image_format": (IMAGE_FORMATS, {"advanced": True, "default": "PNG"}),
"image_quality": ("INT", {"default": 100, "min": 0, "max": 100, "advanced": True}),
"compression_level": ("INT", {"default": 9, "min": 0, "max": 9, "advanced": True}),
"upscale_image": ("BOOLEAN", {"default": True, "label_on": "Enabled", "label_off": "Disabled"}),
"upscaler_device": (DEVICES, {"advanced": True}),
"upscale_model_name": (UPSCALE_MODELS, {"advanced": True}),
"tile": ("INT", {"default": 512, "min": 256, "max": 4096, "step": 64, "advanced": True}),
"overlap": ("INT", {"default": 64, "min": 0, "max": 256, "step": 8, "advanced": True}),
}
}
RETURN_TYPES = ()
RETURN_NAMES = ()
OUTPUT_NODE = True
FUNCTION = "preview_upscale_save_image"
CATEGORY = "TenserTensor/Image"
def preview_upscale_save_image(self, **kwargs):
image = kwargs.get("image")
timage = image.clone()
upscale_image = kwargs.get("upscale_image")
if upscale_image:
timage = upscale(
timage=timage,
device=kwargs.get("upscaler_device"),
upscale_model_name=kwargs.get("upscale_model_name"),
tile=kwargs.get("tile"),
overlap=kwargs.get("overlap")
)
save_image = kwargs.get("save_image")
if save_image:
timage = store(
image=timage,
filename_prefix=kwargs.get("filename_prefix"),
subfolder=kwargs.get("subfolder"),
filename_format=kwargs.get("filename_format"),
image_format=kwargs.get("image_format"),
image_quality=kwargs.get("image_quality"),
compress_level=kwargs.get("compression_level"),
)
else:
timage = preview(
image=timage,
filename_prefix=kwargs.get("filename_prefix"),
)
return {"ui": {"images": timage}}
+15 -97
View File
@@ -1,40 +1,12 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
import torch
from .latent_helpers import create_empty_latent
from ...lib.common import CommonTypes
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"
]
ASPECT_RATIOS = ["1:1", "4:3", "3:2", "16:9", "21:9"]
ORIENTATIONS = ["landscape", "portrait"]
MODEL_TYPES = ["FLUX1.D", "FLUX2.D", "SDXL"]
CLIP_MULTIPLIERS = ["1x", "2x", "4x"]
class TT_LatentFactory:
@@ -42,75 +14,21 @@ class TT_LatentFactory:
def INPUT_TYPES(cls):
return {
"required": {
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff},),
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff},),
"aspect_ratio": (ASPECT_RATIOS,),
"megapixels": (MEGAPIXELS,),
"megapixels": (CommonTypes.MEGAPIXELS,),
"orientation": (ORIENTATIONS,),
"model_type": (MODEL_TYPES,),
"batch_size": ("INT", {"default": 1, "min": 1, "max": 64}),
"clip_multiplier": (CLIP_MULTIPLIERS,)
"batch_size": ("INT", {"default": 1, "min": 1, "max": 64, "advanced": True},),
"clip_multiplier": (CLIP_MULTIPLIERS, {"advanced": True},)
}
}
RETURN_TYPES = ("LATENT", "INT", "INT", "INT", "INT", "INT")
RETURN_NAMES = ("LATENT", "SEED", "WIDTH", "HEIGHT", "TARGET_WIDTH", "TARGET_HEIGHT")
RETURN_TYPES = ("LATENT", "NOISE", "INT", "INT", CommonTypes.MEGAPIXELS, "INT", "INT", "INT", "INT")
RETURN_NAMES = ("LATENT", "RANDOM_NOISE", "SEED", "NOISE_SEED", "MEGAPIXELS", "WIDTH", "HEIGHT", "TARGET_WIDTH", "TARGET_HEIGHT")
FUNCTION = "create_latent"
CATEGORY = "TenserTensor/Latent"
def calculate_dimensions(self, total_pixels, ratio_w, ratio_h):
import math
height = math.sqrt(total_pixels * ratio_h / ratio_w)
width = height * ratio_w / ratio_h
width = int(width)
height = int(height)
width = round(width / 64) * 64
height = round(height / 64) * 64
return width, height
def create_latent(self, seed, aspect_ratio, megapixels, orientation, model_type, batch_size, clip_multiplier):
mp_value = float(megapixels.split()[0])
total_pixels = int(mp_value * 1_000_000)
ratio_parts = aspect_ratio.split(':')
ratio_w = int(ratio_parts[0])
ratio_h = int(ratio_parts[1])
if orientation == "portrait":
ratio_w, ratio_h = ratio_h, ratio_w
width, height = self.calculate_dimensions(total_pixels, ratio_w, ratio_h)
multiplier = int(clip_multiplier.replace('x', ''))
clip_width = width * multiplier
clip_height = height * multiplier
match model_type:
case "FLUX1.D":
scale_factor = 8
channels = 16
case "FLUX2.D":
scale_factor = 16
channels = 128
case "SDXL":
scale_factor = 8
channels = 4
latent_width = width // scale_factor
latent_height = height // scale_factor
generator = torch.Generator().manual_seed(seed)
latent = torch.randn(
batch_size,
channels,
latent_height,
latent_width,
generator=generator
)
latent_dict = {"samples": latent}
return (latent_dict, seed, width, height, clip_width, clip_height)
def create_latent(self, **kwargs):
return create_empty_latent(**kwargs)
+90
View File
@@ -0,0 +1,90 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
import comfy.sample as S
import torch
import math
class RandomNoise:
def __init__(self, seed):
self.seed = seed
def generate_noise(self, input_latent):
latent_image = input_latent["samples"]
batch_idx = input_latent["batch_index"] if "batch_index" in input_latent else None
return S.prepare_noise(latent_image, self.seed, batch_idx)
LATENT_DIMENSION_STEP = 64
def _calculate_dimensions(total_pixels, ratio_w, ratio_h):
height = math.sqrt(total_pixels * ratio_h / ratio_w)
width = height * ratio_w / ratio_h
width = int(width)
height = int(height)
width = round(width / LATENT_DIMENSION_STEP) * LATENT_DIMENSION_STEP
height = round(height / LATENT_DIMENSION_STEP) * LATENT_DIMENSION_STEP
return width, height
def create_empty_latent(seed, noise_seed, aspect_ratio, megapixels, orientation, model_type, batch_size, clip_multiplier):
mp_value = float(megapixels.split()[0])
total_pixels = int(mp_value * 1_000_000)
ratio_parts = aspect_ratio.split(':')
ratio_w = int(ratio_parts[0])
ratio_h = int(ratio_parts[1])
if orientation == "portrait":
ratio_w, ratio_h = ratio_h, ratio_w
width, height = _calculate_dimensions(total_pixels, ratio_w, ratio_h)
multiplier = int(clip_multiplier.replace('x', ''))
clip_width = width * multiplier
clip_height = height * multiplier
match model_type:
case "FLUX1.D":
scale_factor = 8
channels = 16
case "FLUX2.D":
scale_factor = 16
channels = 128
case "SDXL":
scale_factor = 8
channels = 4
latent_width = width // scale_factor
latent_height = height // scale_factor
generator = torch.Generator().manual_seed(seed)
latent = torch.randn(
batch_size,
channels,
latent_height,
latent_width,
generator=generator
)
latent_dict = {"samples": latent}
noise = RandomNoise(noise_seed)
return (latent_dict, noise, seed, noise_seed, megapixels, width, height, clip_width, clip_height,)
def vae_encode(latent, image, vae):
tile_width, tile_height, overlap = 512, 512, 64
samples = vae.encode_tiled(
image,
tile_x=tile_width,
tile_y=tile_height,
overlap=overlap
)
latent["samples"] = samples
return (latent,)
@@ -7,6 +7,12 @@ from .loader_helpers import load_unet, patch_flux_sampling, load_flux_clip, appl
class TT_FluxModelsLoaderAdvanced():
def __init__(self):
self.loaded_lora_1 = None
self.loaded_lora_2 = None
self.loaded_lora_3 = None
self.loaded_lora_4 = None
@classmethod
def INPUT_TYPES(cls):
UNET_DTYPES = ["default", "fp8_e4m3fn", "fp8_e4m3fn_fast", "fp8_e5m2"]
+12
View File
@@ -156,3 +156,15 @@ def load_clip(clip_name, type="flux2"):
clip_type = getattr(SD.CLIPType, type.upper(), SD.CLIPType.FLUX2)
return _load_patcher(clip_type, _load_data([clip_path]))
def apply_bypass_lora_for_models(loaded_lora, model, clip, lora_name, strength):
if loaded_lora == None:
lora_path = FP.get_full_path_or_raise("loras", lora_name)
lora = U.load_torch_file(lora_path, safe_load=True)
else:
lora = loaded_lora
patched_model, patched_clip = SD.load_bypass_lora_for_models(model, clip, lora, strength, strength)
return (patched_model, patched_clip, lora,)
+2 -2
View File
@@ -10,7 +10,7 @@ update_folder_names_and_paths("unet_gguf", ["diffusion_models", "unet"])
update_folder_names_and_paths("clip_gguf", ["text_encoders", "clip"])
class TT_GgufModelsLoader():
class TT_Flux2GgufModelsLoader():
@classmethod
def INPUT_TYPES(cls):
UNET_NAMES = [x for x in FP.get_filename_list("unet_gguf")]
@@ -28,7 +28,7 @@ class TT_GgufModelsLoader():
RETURN_TYPES = ("MODEL", "CLIP", "VAE",)
RETURN_NAMES = ("MODEL", "CLIP", "VAE",)
FUNCTION = "load_models"
CATEGORY = "TenserTensor/Loaders/GGUF"
CATEGORY = "TenserTensor/Loaders/FLUX2 GGUF"
VAE_DEVICE = "cpu"
VAE_DTYPE = "bfloat16"
+13 -7
View File
@@ -3,14 +3,20 @@
import folder_paths as FP
from nodes import VAELoader as VL, MAX_RESOLUTION
from .gguf_loader_helpers import update_folder_names_and_paths, load_unet, load_clip
from .loader_helpers import load_vae, patch_flux_sampling, apply_lora
from .gguf_loader_helpers import update_folder_names_and_paths, load_unet, load_clip, apply_bypass_lora_for_models
from .loader_helpers import load_vae, patch_flux_sampling
update_folder_names_and_paths("unet_gguf", ["diffusion_models", "unet"])
update_folder_names_and_paths("clip_gguf", ["text_encoders", "clip"])
class TT_GgufModelsLoaderAdvanced():
class TT_Flux2GgufModelsLoaderAdvanced():
def __init__(self):
self.loaded_lora_1 = None
self.loaded_lora_2 = None
self.loaded_lora_3 = None
self.loaded_lora_4 = None
@classmethod
def INPUT_TYPES(cls):
UNET_NAMES = [x for x in FP.get_filename_list("unet_gguf")]
@@ -24,8 +30,8 @@ class TT_GgufModelsLoaderAdvanced():
return {
"required": {
"unet_name": (UNET_NAMES,),
"dequant_dtype": (DTYPES, {"default": "default", "advanced": True},),
"patch_dtype": (DTYPES, {"default": "default", "advanced": True},),
"dequant_dtype": (DTYPES, {"default": "bfloat16", "advanced": True},),
"patch_dtype": (DTYPES, {"default": "bfloat16", "advanced": True},),
"apply_sampling": ("BOOLEAN", {"default": True, "label_on": "Enabled", "label_off": "Disabled"},),
"base_sampling_shift": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 100.0, "step": 0.01, "advanced": True},),
"max_sampling_shift": ("FLOAT", {"default": 1.15, "min": 0.0, "max": 100.0, "step": 0.01, "advanced": True},),
@@ -49,7 +55,7 @@ class TT_GgufModelsLoaderAdvanced():
RETURN_TYPES = ("MODEL", "CLIP", "VAE",)
RETURN_NAMES = ("MODEL", "CLIP", "VAE",)
FUNCTION = "load_models"
CATEGORY = "TenserTensor/Loaders/GGUF"
CATEGORY = "TenserTensor/Loaders/FLUX2 GGUF"
def load_models(self, **kwargs):
model = load_unet(
@@ -82,7 +88,7 @@ class TT_GgufModelsLoaderAdvanced():
for name, strength, attr in loras:
if name != "None" and strength != 0:
cached = getattr(self, attr)
model, clip, lora = apply_lora(cached, model, clip, name, strength)
model, clip, lora = apply_bypass_lora_for_models(cached, model, clip, name, strength)
setattr(self, attr, lora)
vae = load_vae(kwargs.get("vae_name"), kwargs.get("vae_device"), kwargs.get("vae_dtype"))
@@ -196,6 +196,7 @@ def solarize(image, thresholds=0.5, additions=None):
return kornia.enhance.solarize(timage, thresholds, additions).movedim(1, -1)
def enhance_image(
timage,
brightness_factor,
@@ -26,7 +26,6 @@ class TT_QuickImageUpscaler():
FUNCTION = "upscale_image"
CATEGORY = "TenserTensor/Postproduction"
def upscale_image(self, image, device, upscale_model_name, tile, overlap):
timage = image.clone()
+1 -1
View File
@@ -23,7 +23,7 @@ class TT_KSampler():
}
RETURN_TYPES = ("LATENT",)
RETURN_NAMES = ("LATENT",)
RETURN_NAMES = ("SAMPLES",)
FUNCTION = "execute_sampling"
CATEGORY = "TenserTensor/Sampling"
+1 -1
View File
@@ -28,7 +28,7 @@ class TT_KSamplerAdvanced():
}
RETURN_TYPES = ("LATENT",)
RETURN_NAMES = ("LATENT",)
RETURN_NAMES = ("SAMPLES",)
FUNCTION = "execute_sampling"
CATEGORY = "TenserTensor/Sampling"
+1 -1
View File
@@ -13,7 +13,7 @@ class TT_KSamplerContext():
}
RETURN_TYPES = ("TT_CONTEXT", "LATENT",)
RETURN_NAMES = ("CONTEXT", "LATENT",)
RETURN_NAMES = ("CONTEXT", "SAMPLES",)
FUNCTION = "execute_sampling"
CATEGORY = "TenserTensor/Sampling"
+25
View File
@@ -0,0 +1,25 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
from .ksampler_helpers import guided_sample_latents
class TT_KSamplerGuided:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"latent": ("LATENT",),
"guider": ("GUIDER",),
"sigmas": ("SIGMAS",),
"sampler": ("SAMPLER",),
"random_noise": ("NOISE",),
},
}
RETURN_TYPES = ("LATENT",)
RETURN_NAMES = ("SAMPLES",)
FUNCTION = "execute_sampling"
CATEGORY = "TenserTensor/Sampling"
def execute_sampling(self, latent, guider, sigmas, sampler, random_noise):
return (guided_sample_latents(latent, guider, sigmas, sampler, random_noise),)
+39 -2
View File
@@ -1,8 +1,9 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
import comfy
import latent_preview
import latent_preview as LP
import torch
from comfy import sample as S, utils as U, model_management as MM, nested_tensor as NT
def sample_latents(**kwargs):
@@ -26,7 +27,7 @@ def sample_latents(**kwargs):
else:
noise = torch.zeros(latent_img.size(), dtype=latent_img.dtype, layout=latent_img.layout, device="cpu")
callback = latent_preview.prepare_callback(model, steps)
callback = LP.prepare_callback(model, steps)
args = {
"model": model,
@@ -55,3 +56,39 @@ def sample_latents(**kwargs):
retval["samples"] = samples
return retval
def guided_sample_latents(latent, guider, sigmas, sampler, random_noise):
model_patcher = guider.model_patcher
latent_dict = latent.copy()
latent_tensor = S.fix_empty_latent_channels(model_patcher, latent_dict["samples"], latent.get("downscale_ratio_spacial", None))
latent_dict["samples"] = latent_tensor
noise_mask = latent.get("noise_mask", None)
interim_buffer = {}
callback = LP.prepare_callback(model_patcher, sigmas.shape[-1] - 1, interim_buffer)
disable_pbar = not U.PROGRESS_BAR_ENABLED
samples = guider.sample(
random_noise.generate_noise(latent),
latent_tensor,
sampler,
sigmas,
denoise_mask=noise_mask,
callback=callback,
disable_pbar=disable_pbar,
seed=random_noise.seed
).to(MM.intermediate_device())
retval = latent_dict.copy()
retval.pop("downscale_ratio_spacial", None)
retval["samples"] = samples
# if "x0" in interim_buffer:
# clean_latent = guider.model_patcher.model.process_latent_out(interim_buffer["x0"].cpu())
# if samples.is_nested:
# clean_latent = NT.NestedTensor(U.unpack_latents(clean_latent, [t.shape for t in samples.unbind()]))
# retval["samples"] = clean_latent
return retval
+1 -1
View File
@@ -28,7 +28,7 @@ class TT_KSamplerTwoStage():
}
RETURN_TYPES = ("LATENT",)
RETURN_NAMES = ("LATENT",)
RETURN_NAMES = ("SAMPLES",)
FUNCTION = "execute_sampling"
CATEGORY = "TenserTensor/Sampling"
+6 -18
View File
@@ -9,22 +9,10 @@ class TT_ClipTextEncodeFlux():
return {
"required": {
"clip": ("CLIP",),
"clip_l_positive": (
"STRING",
{"multiline": True, "placeholder": "CLIP_L Positive", "dynamicPrompts": True}
),
"t5xxl_positive": (
"STRING",
{"multiline": True, "placeholder": "T5XXL Positive", "dynamicPrompts": True}
),
"clip_l_negative": (
"STRING",
{"multiline": True, "placeholder": "CLIP_L Negative", "dynamicPrompts": True}
),
"t5xxl_negative": (
"STRING",
{"multiline": True, "placeholder": "CLIP_L Negative", "dynamicPrompts": True}
),
"clip_l_positive": ("STRING", {"multiline": True, "placeholder": "CLIP_L Positive", "dynamicPrompts": True}),
"t5xxl_positive": ("STRING", {"multiline": True, "placeholder": "T5XXL Positive", "dynamicPrompts": True}),
"clip_l_negative": ("STRING", {"multiline": True, "placeholder": "CLIP_L Negative", "dynamicPrompts": True}),
"t5xxl_negative": ("STRING", {"multiline": True, "placeholder": "CLIP_L Negative", "dynamicPrompts": True}),
"guidance": ("FLOAT", {"default": 3.5, "min": 1.0, "max": 10.0, "step": 0.1})
}
}
@@ -34,5 +22,5 @@ class TT_ClipTextEncodeFlux():
FUNCTION = "execute"
CATEGORY = "TenserTensor/Text Encoders/FLUX"
def execute(self, clip, clip_l_positive, t5xxl_positive, clip_l_negative, t5xxl_negative, guidance):
return encode_prompts_flux(clip, clip_l_positive, t5xxl_positive, clip_l_negative, t5xxl_negative, guidance)
def execute(self, **kwargs):
return encode_prompts_flux(**kwargs)
@@ -0,0 +1,25 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
from .encoder_helpers import encode_prompts_flux2
class TT_ClipTextEncodeFlux2():
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": ("MODEL",),
"clip": ("CLIP",),
"prompt": ("STRING", {"multiline": True, "placeholder": "Prompt", "dynamicPrompts": True}),
"lora_triggers": ("STRING", {"multiline": True, "placeholder": "LoRA Triggers", "dynamicPrompts": True}),
"guidance": ("FLOAT", {"default": 3.5, "min": 1.0, "max": 10.0, "step": 0.1})
}
}
RETURN_TYPES = ("GUIDER",)
RETURN_NAMES = ("GUIDER",)
FUNCTION = "execute"
CATEGORY = "TenserTensor/Text Encoders/FLUX2"
def execute(self, model, clip, prompt, lora_triggers, guidance):
return encode_prompts_flux2(model, clip, prompt, lora_triggers, guidance)
@@ -0,0 +1,41 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
from .encoder_helpers import encode_prompts_flux2
class TT_ClipTextEncodeFlux2Context():
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"context": ("TT_CONTEXT",)
}
}
RETURN_TYPES = ("TT_CONTEXT", "GUIDER",)
RETURN_NAMES = ("CONTEXT", "GUIDER",)
FUNCTION = "execute"
CATEGORY = "TenserTensor/Text Encoders/FLUX2"
def execute(self, context):
model = context.get("model", None)
clip = context.get("clip", None)
config = context.get("workflow_config", None)
if model is None:
raise ValueError("Model is required for text encoder")
if clip is None:
raise ValueError("CLIP is required for text encoder")
if config is None:
raise ValueError("Workflow Config is required for text encoder")
guider = encode_prompts_flux2(
model,
clip,
config.get("prompt", None),
config.get("lora_triggers", None),
config.get("guidance", None),
)
context["guider"] = guider
return (context, guider,)
+11 -25
View File
@@ -1,6 +1,6 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
import nodes
from nodes import MAX_RESOLUTION
from .encoder_helpers import encode_prompts_sdxl
@@ -10,28 +10,16 @@ class TT_ClipTextEncodeSdxl():
return {
"required": {
"clip": ("CLIP",),
"clip_l_positive": (
"STRING",
{"multiline": True, "placeholder": "CLIP_L Positive", "dynamicPrompts": True}
),
"clip_g_positive": (
"STRING",
{"multiline": True, "placeholder": "CLIP_G Positive", "dynamicPrompts": True}
),
"clip_l_negative": (
"STRING",
{"multiline": True, "placeholder": "CLIP_L Negative", "dynamicPrompts": True}
),
"clip_g_negative": (
"STRING",
{"multiline": True, "placeholder": "CLIP_G Negative", "dynamicPrompts": True}
),
"clip_l_positive": ("STRING", {"multiline": True, "placeholder": "CLIP_L Positive", "dynamicPrompts": True}),
"clip_g_positive": ("STRING", {"multiline": True, "placeholder": "CLIP_G Positive", "dynamicPrompts": True}),
"clip_l_negative": ("STRING", {"multiline": True, "placeholder": "CLIP_L Negative", "dynamicPrompts": True}),
"clip_g_negative": ("STRING", {"multiline": True, "placeholder": "CLIP_G Negative", "dynamicPrompts": True}),
"ascore_positive": ("FLOAT", {"default": 9.0, "min": 0.0, "max": 1000.0, "step": 0.1}),
"ascore_negative": ("FLOAT", {"default": 6.0, "min": 0.0, "max": 1000.0, "step": 0.1}),
"width": ("INT", {"default": 512, "min": 0.0, "max": nodes.MAX_RESOLUTION}),
"height": ("INT", {"default": 512, "min": 0.0, "max": nodes.MAX_RESOLUTION}),
"target_width": ("INT", {"default": 512, "min": 0.0, "max": nodes.MAX_RESOLUTION}),
"target_height": ("INT", {"default": 512, "min": 0.0, "max": nodes.MAX_RESOLUTION}),
"width": ("INT", {"default": 512, "min": 0.0, "max": MAX_RESOLUTION}),
"height": ("INT", {"default": 512, "min": 0.0, "max": MAX_RESOLUTION}),
"target_width": ("INT", {"default": 512, "min": 0.0, "max": MAX_RESOLUTION}),
"target_height": ("INT", {"default": 512, "min": 0.0, "max": MAX_RESOLUTION}),
}
}
@@ -40,7 +28,5 @@ class TT_ClipTextEncodeSdxl():
FUNCTION = "execute"
CATEGORY = "TenserTensor/Text Encoders/SDXL"
def execute(self, clip, clip_l_positive, clip_g_positive, clip_l_negative, clip_g_negative,
ascore_positive, ascore_negative, width, height, target_width, target_height):
return encode_prompts_sdxl(clip, clip_l_positive, clip_g_positive, clip_l_negative, clip_g_negative,
ascore_positive, ascore_negative, width, height, target_width, target_height)
def execute(self, **kwargs):
return encode_prompts_sdxl(**kwargs)
+38 -15
View File
@@ -1,25 +1,20 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
def encode_prompts_flux(clip, clip_l_positive, t5xxl_positive, clip_l_negative, t5xxl_negative, guidance):
positive_tokens = clip.tokenize(clip_l_positive)
positive_tokens["t5xxl"] = clip.tokenize(t5xxl_positive)["t5xxl"]
negative_tokens = clip.tokenize(clip_l_negative)
negative_tokens["t5xxl"] = clip.tokenize(t5xxl_negative)["t5xxl"]
import comfy.samplers as S
return (
clip.encode_from_tokens_scheduled(
positive_tokens,
add_dict={"guidance": guidance, }
),
clip.encode_from_tokens_scheduled(
negative_tokens,
add_dict={"guidance": guidance, }
)
)
from ...lib.common import BasicGuider
class Guider_Basic(S.CFGGuider):
def set_conds(self, positive):
self.inner_set_conds({"positive": positive})
def encode_prompts_sdxl(clip, clip_l_positive, clip_g_positive, clip_l_negative, clip_g_negative,
ascore_positive, ascore_negative, width, height, target_width, target_height):
if clip is None:
raise ValueError("ERROR: CLIP is required for text encoder")
positive_tokens = clip.tokenize(clip_g_positive)
positive_tokens["l"] = clip.tokenize(clip_l_positive)["l"]
negative_tokens = clip.tokenize(clip_g_negative)
@@ -47,3 +42,31 @@ def encode_prompts_sdxl(clip, clip_l_positive, clip_g_positive, clip_l_negative,
}
)
)
def encode_prompts_flux(clip, clip_l_positive, t5xxl_positive, clip_l_negative, t5xxl_negative, guidance):
if clip is None:
raise ValueError("ERROR: CLIP is required for text encoder")
positive_tokens = clip.tokenize(clip_l_positive)
positive_tokens["t5xxl"] = clip.tokenize(t5xxl_positive)["t5xxl"]
negative_tokens = clip.tokenize(clip_l_negative)
negative_tokens["t5xxl"] = clip.tokenize(t5xxl_negative)["t5xxl"]
return (
clip.encode_from_tokens_scheduled(positive_tokens, add_dict={"guidance": guidance, }),
clip.encode_from_tokens_scheduled(negative_tokens, add_dict={"guidance": guidance, })
)
def encode_prompts_flux2(model, clip, prompt, lora_triggers, guidance):
if clip is None:
raise ValueError("ERROR: CLIP is required for text encoder")
full_prompt = f"{lora_triggers}, {prompt}" if lora_triggers.strip() else prompt
tokens = clip.tokenize(full_prompt)
conditioning = clip.encode_from_tokens_scheduled(tokens, add_dict={"guidance": guidance, })
guider = BasicGuider(model)
guider.set_conds(conditioning)
return (guider,)
+5 -11
View File
@@ -1,4 +1,6 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
from .vae_helpers import vae_decode
class TT_VaeDecodeContext:
@classmethod
@@ -19,19 +21,11 @@ class TT_VaeDecodeContext:
latent = context["latent"]
if vae is None:
raise ValueError("VAE is required for decode")
raise ValueError("ERROR: VAE is required for decode")
if latent is None:
raise ValueError("Latent image is required for decode")
samples = latent["samples"]
if samples.is_nested:
samples = samples.unbind()[0]
images = vae.decode(samples)
if len(images.shape) == 5:
images = images.reshape(-1, images.shape[-3], images.shape[-2], images.shape[-1])
raise ValueError("ERROR: Latent image is required for decode")
images = vae_decode(latent, vae)
context["image"] = images
return (context, images,)
+3 -21
View File
@@ -1,6 +1,7 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
import torch
from .vae_helpers import vae_decode
class TT_VaeDecodeTiled:
@@ -23,23 +24,4 @@ class TT_VaeDecodeTiled:
CATEGORY = "TenserTensor/VAE"
def vae_decode(self, latent, vae, tile_width, tile_height, overlap, circular):
samples = latent["samples"]
if samples.is_nested:
samples = samples.unbind()[0]
if circular == True:
for layer in [layer for layer in vae.first_stage_model.modules() if isinstance(layer, torch.nn.Conv2d)]:
layer.padding_mode = "circular"
compression = vae.spacial_compression_decode()
images = vae.decode_tiled(
samples,
tile_x=tile_width // compression,
tile_y=tile_height // compression,
overlap=overlap // compression
)
if len(images.shape) == 5:
images = images.reshape(-1, images.shape[-3], images.shape[-2], images.shape[-1])
return (images,)
return (vae_decode(latent, vae, tile_width, tile_height, overlap, circular),)
+6 -4
View File
@@ -1,5 +1,8 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
from .vae_helpers import vae_encode
class TT_VaeEncodeContext:
@classmethod
def INPUT_TYPES(cls):
@@ -23,8 +26,7 @@ class TT_VaeEncodeContext:
if image is None:
raise ValueError("Pixel image is required for encode")
samples = vae.encode(image)
latent_dict = {"samples": samples}
context["latent"] = latent_dict
samples = vae_encode(image, vae)
context["latent"] = samples
return (context, latent_dict,)
return (context, samples,)
+4 -9
View File
@@ -1,5 +1,8 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
from .vae_helpers import vae_encode
class TT_VaeEncodeTiled:
@classmethod
def INPUT_TYPES(cls):
@@ -19,12 +22,4 @@ class TT_VaeEncodeTiled:
CATEGORY = "TenserTensor/VAE"
def vae_encode(self, image, vae, tile_width, tile_height, overlap):
samples = vae.encode_tiled(
image,
tile_x=tile_width,
tile_y=tile_height,
overlap=overlap
)
latent_dict = {"samples": samples}
return (latent_dict,)
return (vae_encode(image, vae, tile_width, tile_height, overlap),)
+36
View File
@@ -1,2 +1,38 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
import torch
def vae_encode(image, vae, tile_width=512, tile_height=512, overlap=64):
samples = vae.encode_tiled(
image,
tile_x=tile_width,
tile_y=tile_height,
overlap=overlap
)
latent_dict = {"samples": samples}
return latent_dict
def vae_decode(latent, vae, tile_width=512, tile_height=512, overlap=64, circular=False):
samples = latent["samples"]
if samples.is_nested:
samples = samples.unbind()[0]
if circular == True:
for layer in [layer for layer in vae.first_stage_model.modules() if isinstance(layer, torch.nn.Conv2d)]:
layer.padding_mode = "circular"
compression = vae.spacial_compression_decode()
images = vae.decode_tiled(
samples,
tile_x=tile_width // compression,
tile_y=tile_height // compression,
overlap=overlap // compression
)
if len(images.shape) == 5:
images = images.reshape(-1, images.shape[-3], images.shape[-2], images.shape[-1])
return images
+67
View File
@@ -0,0 +1,67 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
import comfy.samplers as S
from nodes import MAX_RESOLUTION
from .workflow_settings_helpers import get_schedule
class TT_Flux2WorkflowSettings:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"steps": ("INT", {"default": 25, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 3.0, "min": 0.0, "max": 100.0, "step": 0.1}),
"sampler_name": (S.KSampler.SAMPLERS,),
"width": ("INT", {"default": 1024, "min": 16, "max": MAX_RESOLUTION, "step": 8}),
"height": ("INT", {"default": 1024, "min": 16, "max": MAX_RESOLUTION, "step": 8}),
"guidance": ("FLOAT", {"default": 3.5, "min": 1.0, "max": 10.0, "step": 0.1})
}
}
RETURN_TYPES = (
"TT_WORKFLOW_CONFIG",
"INT",
"INT",
"FLOAT",
"SAMPLER",
S.KSampler.SAMPLERS,
"SIGMAS",
"INT",
"INT",
"FLOAT"
)
RETURN_NAMES = (
"WORKFLOW_CONFIG",
"SEED",
"STEPS",
"CFG",
"SAMPLER",
"SAMPLER_NAME",
"SCHEDULER",
"WIDTH",
"HEIGHT",
"GUIDANCE"
)
FUNCTION = "get_settings"
CATEGORY = "TenserTensor/Workflow/FLUX2"
def get_settings(self, seed, steps, cfg, sampler_name, width, height, guidance):
scheduler = get_schedule(steps, width, height)
sampler = S.sampler_object(sampler_name)
workflow_config = {
"seed": seed,
"steps": steps,
"cfg": cfg,
"sampler": sampler,
"sampler_name": sampler_name,
"scheduler": scheduler,
"width": width,
"height": height,
"guidance": guidance
}
return (workflow_config, seed, steps, cfg, sampler, sampler_name, scheduler, width, height, guidance)
@@ -0,0 +1,75 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
import comfy.samplers as S
from nodes import MAX_RESOLUTION
from .workflow_settings_helpers import get_schedule
class TT_Flux2WorkflowSettingsAdvanced:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"steps": ("INT", {"default": 25, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 3.0, "min": 0.0, "max": 100.0, "step": 0.1}),
"sampler_name": (S.KSampler.SAMPLERS,),
"width": ("INT", {"default": 1024, "min": 16, "max": MAX_RESOLUTION, "step": 8}),
"height": ("INT", {"default": 1024, "min": 16, "max": MAX_RESOLUTION, "step": 8}),
"prompt": ("STRING", {"multiline": True, "placeholder": "Prompt", "dynamicPrompts": True}),
"lora_triggers": ("STRING", {"multiline": True, "placeholder": "LoRA Triggers", "dynamicPrompts": True}),
"guidance": ("FLOAT", {"default": 3.5, "min": 1.0, "max": 10.0, "step": 0.1})
}
}
RETURN_TYPES = (
"TT_WORKFLOW_CONFIG",
"INT",
"INT",
"FLOAT",
S.KSampler.SAMPLERS,
"SAMPLER",
"SIGMAS",
"INT",
"INT",
"STRING",
"STRING",
"FLOAT"
)
RETURN_NAMES = (
"WORKFLOW_CONFIG",
"SEED",
"STEPS",
"CFG",
"SAMPLER_NAME",
"SAMPLER",
"SCHEDULER",
"WIDTH",
"HEIGHT",
"PROMPT",
"LORA_TRIGGERS",
"GUIDANCE"
)
FUNCTION = "get_settings"
CATEGORY = "TenserTensor/Workflow/FLUX2"
def get_settings(self, seed, steps, cfg, sampler_name, width, height, prompt, lora_triggers, guidance):
scheduler = get_schedule(steps, width, height)
sampler = S.sampler_object(sampler_name)
workflow_config = {
"seed": seed,
"steps": steps,
"cfg": cfg,
"sampler": sampler,
"sampler_name": sampler_name,
"scheduler": scheduler,
"width": width,
"height": height,
"prompt": prompt,
"lora_triggers": lora_triggers,
"guidance": guidance
}
return (workflow_config, seed, steps, cfg, sampler_name, sampler, scheduler, width, height, prompt, lora_triggers, guidance)
@@ -1,6 +1,6 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
import comfy.samplers
import comfy.samplers as S
class TT_FluxWorkflowSettings:
@@ -11,14 +11,30 @@ class TT_FluxWorkflowSettings:
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"steps": ("INT", {"default": 25, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 1.5, "min": 0.0, "max": 100.0, "step": 0.1}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
"sampler_name": (S.KSampler.SAMPLERS,),
"scheduler": (S.KSampler.SCHEDULERS,),
"guidance": ("FLOAT", {"default": 3.5, "min": 1.0, "max": 10.0, "step": 0.1})
}
}
RETURN_TYPES = ("TT_WORKFLOW_CONFIG", "INT", "INT", "FLOAT", "STRING", "STRING", "FLOAT")
RETURN_NAMES = ("WORKFLOW_CONFIG", "SEED", "STEPS", "CFG", "SAMPLER_NAME", "SCHEDULER", "GUIDANCE")
RETURN_TYPES = (
"TT_WORKFLOW_CONFIG",
"INT",
"INT",
"FLOAT",
S.KSampler.SAMPLERS,
S.KSampler.SCHEDULERS,
"FLOAT"
)
RETURN_NAMES = (
"WORKFLOW_CONFIG",
"SEED",
"STEPS",
"CFG",
"SAMPLER_NAME",
"SCHEDULER",
"GUIDANCE"
)
FUNCTION = "get_settings"
CATEGORY = "TenserTensor/Workflow/FLUX"
@@ -1,6 +1,6 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
import comfy.samplers
import comfy.samplers as S
class TT_FluxWorkflowSettingsAdvanced:
@@ -11,24 +11,12 @@ class TT_FluxWorkflowSettingsAdvanced:
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"steps": ("INT", {"default": 25, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 1.5, "min": 0.0, "max": 100.0, "step": 0.1}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
"clip_l_positive": (
"STRING",
{"multiline": True, "placeholder": "CLIP_L Positive", "dynamicPrompts": True}
),
"t5xxl_positive": (
"STRING",
{"multiline": True, "placeholder": "T5XXL Positive", "dynamicPrompts": True}
),
"clip_l_negative": (
"STRING",
{"multiline": True, "placeholder": "CLIP_L Negative", "dynamicPrompts": True}
),
"t5xxl_negative": (
"STRING",
{"multiline": True, "placeholder": "CLIP_L Negative", "dynamicPrompts": True}
),
"sampler_name": (S.KSampler.SAMPLERS,),
"scheduler": (S.KSampler.SCHEDULERS,),
"clip_l_positive": ("STRING", {"multiline": True, "placeholder": "CLIP_L Positive", "dynamicPrompts": True}),
"t5xxl_positive": ("STRING", {"multiline": True, "placeholder": "T5XXL Positive", "dynamicPrompts": True}),
"clip_l_negative": ("STRING", {"multiline": True, "placeholder": "CLIP_L Negative", "dynamicPrompts": True}),
"t5xxl_negative": ("STRING", {"multiline": True, "placeholder": "CLIP_L Negative", "dynamicPrompts": True}),
"guidance": ("FLOAT", {"default": 3.5, "min": 1.0, "max": 10.0, "step": 0.1})
}
}
@@ -38,8 +26,8 @@ class TT_FluxWorkflowSettingsAdvanced:
"INT",
"INT",
"FLOAT",
"STRING",
"STRING",
S.KSampler.SAMPLERS,
S.KSampler.SCHEDULERS,
"STRING",
"STRING",
"STRING",
@@ -62,8 +50,7 @@ class TT_FluxWorkflowSettingsAdvanced:
FUNCTION = "get_settings"
CATEGORY = "TenserTensor/Workflow/FLUX"
def get_settings(self, seed, steps, cfg, sampler_name, scheduler, guidance, clip_l_positive, t5xxl_positive,
clip_l_negative, t5xxl_negative):
def get_settings(self, seed, steps, cfg, sampler_name, scheduler, guidance, clip_l_positive, t5xxl_positive, clip_l_negative, t5xxl_negative):
workflow_config = {
"seed": seed,
"steps": steps,
@@ -77,5 +64,5 @@ class TT_FluxWorkflowSettingsAdvanced:
"guidance": guidance
}
return (workflow_config, seed, steps, cfg, sampler_name, scheduler, guidance,
clip_l_positive, t5xxl_positive, clip_l_negative, t5xxl_negative)
return (workflow_config, seed, steps, cfg, sampler_name, scheduler, guidance, clip_l_positive, t5xxl_positive, clip_l_negative,
t5xxl_negative)
@@ -1,6 +1,6 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
import comfy.samplers
import comfy.samplers as S
class TT_SdxlWorkflowSettings:
@@ -11,8 +11,8 @@ class TT_SdxlWorkflowSettings:
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"steps": ("INT", {"default": 30, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0, "step": 0.1}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
"sampler_name": (S.KSampler.SAMPLERS,),
"scheduler": (S.KSampler.SCHEDULERS,),
}
}
@@ -21,8 +21,8 @@ class TT_SdxlWorkflowSettings:
"INT",
"INT",
"FLOAT",
comfy.samplers.KSampler.SAMPLERS,
comfy.samplers.KSampler.SCHEDULERS,
S.KSampler.SAMPLERS,
S.KSampler.SCHEDULERS,
)
RETURN_NAMES = (
@@ -1,8 +1,8 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
import comfy.samplers
import comfy.samplers as S
import nodes
from nodes import MAX_RESOLUTION
class TT_SdxlWorkflowSettingsAdvanced:
@@ -13,30 +13,18 @@ class TT_SdxlWorkflowSettingsAdvanced:
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"steps": ("INT", {"default": 30, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0, "step": 0.1}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
"clip_l_positive": (
"STRING",
{"multiline": True, "placeholder": "CLIP_L Positive", "dynamicPrompts": True}
),
"clip_g_positive": (
"STRING",
{"multiline": True, "placeholder": "CLIP_G Positive", "dynamicPrompts": True}
),
"clip_l_negative": (
"STRING",
{"multiline": True, "placeholder": "CLIP_L Negative", "dynamicPrompts": True}
),
"clip_g_negative": (
"STRING",
{"multiline": True, "placeholder": "CLIP_G Negative", "dynamicPrompts": True}
),
"sampler_name": (S.KSampler.SAMPLERS,),
"scheduler": (S.KSampler.SCHEDULERS,),
"clip_l_positive": ("STRING", {"multiline": True, "placeholder": "CLIP_L Positive", "dynamicPrompts": True}),
"clip_g_positive": ("STRING", {"multiline": True, "placeholder": "CLIP_G Positive", "dynamicPrompts": True}),
"clip_l_negative": ("STRING", {"multiline": True, "placeholder": "CLIP_L Negative", "dynamicPrompts": True}),
"clip_g_negative": ("STRING", {"multiline": True, "placeholder": "CLIP_G Negative", "dynamicPrompts": True}),
"ascore_positive": ("FLOAT", {"default": 9.0, "min": 0.0, "max": 1000.0, "step": 0.1}),
"ascore_negative": ("FLOAT", {"default": 6.0, "min": 0.0, "max": 1000.0, "step": 0.1}),
"width": ("INT", {"default": 512, "min": 0.0, "max": nodes.MAX_RESOLUTION}),
"height": ("INT", {"default": 512, "min": 0.0, "max": nodes.MAX_RESOLUTION}),
"target_width": ("INT", {"default": 512, "min": 0.0, "max": nodes.MAX_RESOLUTION}),
"target_height": ("INT", {"default": 512, "min": 0.0, "max": nodes.MAX_RESOLUTION}),
"width": ("INT", {"default": 512, "min": 0.0, "max": MAX_RESOLUTION}),
"height": ("INT", {"default": 512, "min": 0.0, "max": MAX_RESOLUTION}),
"target_width": ("INT", {"default": 512, "min": 0.0, "max": MAX_RESOLUTION}),
"target_height": ("INT", {"default": 512, "min": 0.0, "max": MAX_RESOLUTION}),
}
}
@@ -45,8 +33,8 @@ class TT_SdxlWorkflowSettingsAdvanced:
"INT",
"INT",
"FLOAT",
"STRING",
"STRING",
S.KSampler.SAMPLERS,
S.KSampler.SCHEDULERS,
"STRING",
"STRING",
"STRING",
@@ -0,0 +1,36 @@
# (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
import math
import torch
def _compute_empirical_mu(image_seq_len, num_steps):
a1, b1 = 8.73809524e-05, 1.89833333
a2, b2 = 0.00016927, 0.45666666
if image_seq_len > 4300:
mu = a2 * image_seq_len + b2
return float(mu)
m_200 = a2 * image_seq_len + b2
m_10 = a1 * image_seq_len + b1
a = (m_200 - m_10) / 190.0
b = m_200 - 200.0 * a
mu = a * num_steps + b
return float(mu)
def _generalized_time_snr_shift(t, mu, sigma):
return math.exp(mu) / (math.exp(mu) + (1 / t - 1) ** sigma)
def get_schedule(num_steps, width, height):
image_seq_len = (width * height / (16 * 16))
mu = _compute_empirical_mu(image_seq_len, num_steps)
timesteps = torch.linspace(1, 0, num_steps + 1)
timesteps = _generalized_time_snr_shift(timesteps, mu, 1.0)
return timesteps
+2 -2
View File
@@ -1,8 +1,8 @@
# pyproject.toml
[project]
name = "ComfyUI-TenserTensor"
description = "A collection of custom nodes for ComfyUI that streamline common workflows. The main goal is to reduce visual clutter by combining frequently used node chains into single, convenient loaders."
version = "1.3.1"
description = "A collection of custom nodes for ComfyUI that streamline common workflows. Includes all-in-one model loaders (checkpoint/unet, LoRA, CLIP, VAE), GGUF quantized model support (FLUX, FLUX2, SDXL), workflow settings nodes, latent factory (aspect ratio, megapixels, FLUX/SDXL), context nodes, CLIP text encoders, KSamplers (context, advanced, two-stage - draft/refine), VAE encoders/decoders (context, tiled), and post-production tools (brightness, contrast, LUT color grading, film grain, upscaler)."
version = "1.4.7"
license = { file = "LICENSE" }
dependencies = ["gguf"]
+31
View File
@@ -0,0 +1,31 @@
## TT Clip Text Encode Flux2 *TT_ClipTextEncodeFlux2*
*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.*
Text prompt encoder for FLUX2 models with LoRA trigger word support and built-in guidance configuration.
### Inputs
| Parameter | Type | Required | Description |
|-----------------|--------|----------|--------------------------------------------------------------|
| `model` | MODEL | Yes | Diffusion model |
| `clip` | CLIP | Yes | CLIP model for text encoding |
| `prompt` | STRING | Yes | Main generation prompt (multiline, supports dynamic prompts) |
| `lora_triggers` | STRING | Yes | LoRA trigger words (multiline, supports dynamic prompts) |
| `guidance` | FLOAT | Yes | Guidance scale for generation (1.0–10.0, default: 3.5) |
### Outputs
| Parameter | Type | Description |
|-----------|--------|----------------------------------------------------|
| `GUIDER` | GUIDER | Configured guider with encoded prompt and guidance |
### Usage
Encodes text prompts specifically for FLUX2 models. Enter your main prompt in `prompt` and any LoRA-specific trigger
words separately in `lora_triggers` — the node combines them internally. Adjust `guidance` to control how strongly
the model follows the prompt (3.5 is a good starting point for FLUX2).
---
[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor)
+29
View File
@@ -0,0 +1,29 @@
## TT Clip Text Encode Flux2 (Context) *TT_ClipTextEncodeFlux2Context*
*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.*
Context-based text prompt encoder for FLUX2 models. Extracts model, CLIP, and prompt configuration directly from a TT_CONTEXT object.
### Inputs
| Parameter | Type | Required | Description |
|-----------|------------|----------|---------------------------------------------------------------------------------------|
| `context` | TT_CONTEXT | Yes | Context object containing `model`, `clip`, and `workflow_config` with prompt settings |
### Outputs
| Parameter | Type | Description |
|-----------|------------|----------------------------------------------------|
| `CONTEXT` | TT_CONTEXT | Updated context with `guider` written back into it |
| `GUIDER` | GUIDER | Configured guider with encoded prompt and guidance |
### Usage
Context-aware version of the FLUX2 text encoder. Instead of individual inputs, it reads `model`, `clip`, and
`workflow_config` (containing `prompt`, `lora_triggers`, and `guidance`) from the passed `context`. The resulting
guider is both returned as an output and stored back into the context under the `guider` key. Raises an error if
any of the required fields are missing from the context.
---
[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor)
+27
View File
@@ -0,0 +1,27 @@
## TT Context Set Guider *TT_ContextSetGuider*
*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.*
Writes a GUIDER object into the TT_CONTEXT, making it available for downstream context-aware nodes.
### Inputs
| Parameter | Type | Required | Description |
|-----------|------------|----------|--------------------------------|
| `context` | TT_CONTEXT | Yes | Context object to update |
| `guider` | GUIDER | Yes | Guider to store in the context |
### Outputs
| Parameter | Type | Description |
|-----------|------------|-----------------------------------------------|
| `CONTEXT` | TT_CONTEXT | Updated context with `guider` written into it |
### Usage
Sets the `guider` field inside a TT_CONTEXT object. Use this node when you have a guider produced outside the
context pipeline and need to inject it back in for use by subsequent context-aware nodes.
---
[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor)
+7 -7
View File
@@ -6,16 +6,16 @@ Adds or updates image in context object.
### Inputs
| Parameter | Type | Required | Description |
|-----------|------------|----------|--------------------------------|
| `context` | TT_CONTEXT | Yes | Context to update |
| `image` | IMAGE | Yes | Image to store in context |
| Parameter | Type | Required | Description |
|-----------|------------|----------|---------------------------|
| `context` | TT_CONTEXT | Yes | Context to update |
| `image` | IMAGE | Yes | Image to store in context |
### Outputs
| Parameter | Type | Description |
|-----------|------------|-------------------------|
| `CONTEXT` | TT_CONTEXT | Updated context object |
| Parameter | Type | Description |
|-----------|------------|------------------------|
| `CONTEXT` | TT_CONTEXT | Updated context object |
### Usage
@@ -1,4 +1,4 @@
## TT GGUF Models Loader *TT_GgufModelsLoader*
## TT FLUX2 GGUF Models Loader *TT_GgufModelsLoader*
*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.*
@@ -14,15 +14,16 @@ Load GGUF-quantized diffusion model (UNet), CLIP text encoder, and VAE in a sing
### Outputs
| Parameter | Type | Description |
|-----------|-------|-------------------------|
| `MODEL` | MODEL | Loaded diffusion model |
| `CLIP` | CLIP | Loaded text encoder |
| `VAE` | VAE | Loaded VAE decoder |
| Parameter | Type | Description |
|-----------|-------|------------------------|
| `MODEL` | MODEL | Loaded diffusion model |
| `CLIP` | CLIP | Loaded text encoder |
| `VAE` | VAE | Loaded VAE decoder |
### Usage
All-in-one loader for GGUF-quantized models. Place your `.gguf` diffusion model files in the `diffusion_models` or `unet` directory, CLIP files in `text_encoders` or `clip` directory. VAE can be any standard ComfyUI-compatible VAE file.
All-in-one loader for GGUF-quantized FLUX2 models. Place your `.gguf` diffusion model files in the `diffusion_models` or `unet` directory, CLIP files in
`text_encoders` or `clip` directory. VAE can be any standard ComfyUI-compatible VAE file.
---
@@ -1,4 +1,4 @@
## TT GGUF Models Loader (Advanced) *TT_GgufModelsLoaderAdvanced*
## TT FLUX2 GGUF Models Loader (Advanced) *TT_GgufModelsLoaderAdvanced*
*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.*
@@ -6,22 +6,22 @@ All-in-one loader for GGUF-quantized models with extended control over quantizat
### Inputs
| Parameter | Type | Required | Advanced | Description |
|-----------------------|---------|----------|----------|----------------------------------------------------------|
| `unet_name` | STRING | Yes | | GGUF UNet/diffusion model filename |
| Parameter | Type | Required | Advanced | Description |
|-----------------------|---------|----------|----------|------------------------------------------------------------------------|
| `unet_name` | STRING | Yes | | GGUF UNet/diffusion model filename |
| `dequant_dtype` | STRING | Yes | Yes | Data type for dequantization (default/target/float32/float16/bfloat16) |
| `patch_dtype` | STRING | Yes | Yes | Data type for weight patching |
| `apply_sampling` | BOOLEAN | Yes | | Enable/disable flux sampling shift patch |
| `base_sampling_shift` | FLOAT | Yes | Yes | Base shift value for flux sampling (default: 0.5) |
| `max_sampling_shift` | FLOAT | Yes | Yes | Maximum shift value for flux sampling (default: 1.15) |
| `sampling_width` | INT | Yes | Yes | Reference width for sampling shift calculation |
| `sampling_height` | INT | Yes | Yes | Reference height for sampling shift calculation |
| `lora_name_1..4` | STRING | Yes | Yes | LoRA filename (None to skip) |
| `strength_1..4` | FLOAT | Yes | Yes | LoRA blend strength (-10.0 to 10.0, default: 1.0) |
| `clip_name` | STRING | Yes | | GGUF CLIP text encoder filename |
| `vae_name` | STRING | Yes | | VAE model filename |
| `vae_device` | STRING | Yes | Yes | Device for VAE (default/cpu) |
| `vae_dtype` | STRING | Yes | Yes | Data type for VAE (bfloat16/float16/float32) |
| `patch_dtype` | STRING | Yes | Yes | Data type for weight patching |
| `apply_sampling` | BOOLEAN | Yes | | Enable/disable flux sampling shift patch |
| `base_sampling_shift` | FLOAT | Yes | Yes | Base shift value for flux sampling (default: 0.5) |
| `max_sampling_shift` | FLOAT | Yes | Yes | Maximum shift value for flux sampling (default: 1.15) |
| `sampling_width` | INT | Yes | Yes | Reference width for sampling shift calculation |
| `sampling_height` | INT | Yes | Yes | Reference height for sampling shift calculation |
| `lora_name_1..4` | STRING | Yes | Yes | LoRA filename (None to skip) |
| `strength_1..4` | FLOAT | Yes | Yes | LoRA blend strength (-10.0 to 10.0, default: 1.0) |
| `clip_name` | STRING | Yes | | GGUF CLIP text encoder filename |
| `vae_name` | STRING | Yes | | VAE model filename |
| `vae_device` | STRING | Yes | Yes | Device for VAE (default/cpu) |
| `vae_dtype` | STRING | Yes | Yes | Data type for VAE (bfloat16/float16/float32) |
### Outputs
@@ -33,7 +33,9 @@ All-in-one loader for GGUF-quantized models with extended control over quantizat
### Usage
Extended version of GGUF Models Loader. Supports up to 4 LoRAs loaded simultaneously. Enable `apply_sampling` to apply flux sampling shift patch — useful for improving quality at non-standard resolutions. Advanced parameters are hidden by default and can be revealed via the node's show/hide controls.
Extended version of FLUX2 GGUF Models Loader. Supports up to 4 LoRAs loaded simultaneously. Enable `apply_sampling` to apply flux sampling shift patch —
useful for improving quality at non-standard resolutions. Advanced parameters are hidden by default and can be revealed via the node's show/hide
controls.
---
+44
View File
@@ -0,0 +1,44 @@
## TT Flux2 Workflow Settings *TT_Flux2WorkflowSettings*
*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.*
Central workflow configuration node for FLUX2 pipelines. Consolidates all sampling parameters into a single
TT_WORKFLOW_CONFIG object while also exposing each value as an individual output.
### Inputs
| Parameter | Type | Required | Description |
|----------------|--------|----------|---------------------------------------------------------------------------|
| `seed` | INT | Yes | Generation seed (0 – 18446744073709551615, default: 0) |
| `steps` | INT | Yes | Number of sampling steps (1–10000, default: 25) |
| `cfg` | FLOAT | Yes | CFG scale (0.0–100.0, default: 1.5) |
| `sampler_name` | STRING | Yes | Sampler algorithm (KSampler sampler list) |
| `width` | INT | Yes | Output image width in pixels (16–MAX_RESOLUTION, step: 8, default: 1024) |
| `height` | INT | Yes | Output image height in pixels (16–MAX_RESOLUTION, step: 8, default: 1024) |
| `guidance` | FLOAT | Yes | Guidance scale for text encoder (1.0–10.0, default: 3.5) |
### Outputs
| Parameter | Type | Description |
|-------------------|--------------------|------------------------------------------------------|
| `WORKFLOW_CONFIG` | TT_WORKFLOW_CONFIG | Full configuration object with all settings bundled |
| `SEED` | INT | Generation seed |
| `STEPS` | INT | Number of sampling steps |
| `CFG` | FLOAT | CFG scale |
| `SAMPLER` | SAMPLER | Instantiated sampler object |
| `SAMPLER_NAME` | STRING | Name of the selected sampler |
| `SCHEDULER` | SIGMAS | Computed sigma schedule based on seed and resolution |
| `WIDTH` | INT | Output image width |
| `HEIGHT` | INT | Output image height |
| `GUIDANCE` | FLOAT | Guidance scale |
### Usage
Use this node as the single source of truth for FLUX2 workflow parameters. The `WORKFLOW_CONFIG` output is
designed for context-aware nodes that consume a TT_WORKFLOW_CONFIG directly. Individual outputs are available
for connecting to standard ComfyUI nodes. The sigma schedule (`SCHEDULER`) is computed automatically from
the seed and resolution via `get_schedule`.
---
[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor)
@@ -0,0 +1,47 @@
## TT Flux2 Workflow Settings (Advanced) *TT_Flux2WorkflowSettingsAdvanced*
*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.*
Extended workflow configuration node for FLUX2 pipelines. Includes all parameters from the base version plus
built-in prompt and LoRA trigger fields, bundling everything into a single TT_WORKFLOW_CONFIG object.
### Inputs
| Parameter | Type | Required | Description |
|-----------------|--------|----------|---------------------------------------------------------------------------|
| `seed` | INT | Yes | Generation seed (0 – 18446744073709551615, default: 0) |
| `steps` | INT | Yes | Number of sampling steps (1–10000, default: 25) |
| `cfg` | FLOAT | Yes | CFG scale (0.0–100.0, default: 1.5) |
| `sampler_name` | STRING | Yes | Sampler algorithm (KSampler sampler list) |
| `width` | INT | Yes | Output image width in pixels (16–MAX_RESOLUTION, step: 8, default: 1024) |
| `height` | INT | Yes | Output image height in pixels (16–MAX_RESOLUTION, step: 8, default: 1024) |
| `prompt` | STRING | Yes | Main generation prompt (multiline, supports dynamic prompts) |
| `lora_triggers` | STRING | Yes | LoRA trigger words (multiline, supports dynamic prompts) |
| `guidance` | FLOAT | Yes | Guidance scale for text encoder (1.0–10.0, default: 3.5) |
### Outputs
| Parameter | Type | Description |
|-------------------|--------------------|------------------------------------------------------|
| `WORKFLOW_CONFIG` | TT_WORKFLOW_CONFIG | Full configuration object with all settings bundled |
| `SEED` | INT | Generation seed |
| `STEPS` | INT | Number of sampling steps |
| `CFG` | FLOAT | CFG scale |
| `SAMPLER` | SAMPLER | Instantiated sampler object |
| `SAMPLER_NAME` | STRING | Name of the selected sampler |
| `SCHEDULER` | SIGMAS | Computed sigma schedule based on seed and resolution |
| `WIDTH` | INT | Output image width |
| `HEIGHT` | INT | Output image height |
| `PROMPT` | STRING | Main generation prompt |
| `GUIDANCE` | FLOAT | Guidance scale |
### Usage
Advanced version of TT Flux2 Workflow Settings with `prompt` and `lora_triggers` fields included directly in the
node. Both are stored in the `WORKFLOW_CONFIG` output alongside all other sampling parameters, making this node
sufficient as the sole configuration source for full context-driven FLUX2 pipelines. Individual outputs remain
available for connecting to standard ComfyUI nodes.
---
[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor)
+32
View File
@@ -0,0 +1,32 @@
## TT Guider Image Reference *TT_GuiderImageReference*
*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.*
Encode a reference image into latent space and inject it into a Guider's positive conditioning for reference-based generation.
### Inputs
| Parameter | Type | Required | Description |
|------------------|--------|----------|---------------------------------------------------------------------------|
| `vae` | VAE | Yes | VAE model used to encode the reference image |
| `guider` | GUIDER | Yes | Guider to inject reference latent into |
| `megapixels` | COMBO | Yes | Target resolution for the reference image before encoding |
| `upscale_method` | STRING | Yes | Resampling method for resizing (default: bicubic) - advanced |
| `dimension_step` | INT | Yes | Snaps image dimensions to multiples of this value (default: 1) - advanced |
| `image` | STRING | Yes | Reference image from input directory |
### Outputs
| Parameter | Type | Description |
|-----------|--------|---------------------------------------|
| `VAE` | VAE | Passthrough VAE for chaining |
| `GUIDER` | GUIDER | Guider with reference latent injected |
### Usage
Loads a reference image, resizes it to the target megapixel resolution, encodes it with VAE, and injects the resulting latent into the Guider's
positive conditioning as `reference_latents`. Raises an error if the Guider has no positive conditioning.
---
[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor)
+35
View File
@@ -0,0 +1,35 @@
## TT Image Preview / Upscale / Save *TT_ImagePreviewUpscaleSave*
*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.*
Combined preview, upscale, and save node with flexible output options.
### Inputs
| Parameter | Type | Required | Description |
|----------------------|---------|----------|-------------------------------------------------------------------------------------------------------------------------------------------|
| `image` | IMAGE | Yes | Input image tensor to preview, upscale, or save |
| `save_image` | BOOLEAN | Yes | Enable/disable saving to disk (default: Enabled) |
| `filename_prefix` | STRING | Yes | Prefix for output filename (default: "tenser-tensor") |
| `filename_format` | STRING | Yes | Naming pattern: "name-###" (prefix-00001), "date-name-###" (2026-02-16-prefix-00001), "name-datetime" (prefix-20260216-143045) - advanced |
| `subfolder` | STRING | Yes | Optional subfolder within output directory (empty = root output folder) - advanced |
| `image_format` | STRING | Yes | Output format: PNG, JPEG, or WEBP (default: PNG) - advanced |
| `image_quality` | INT | Yes | Quality for JPEG and WEBP formats (0-100, default: 100). Ignored for PNG - advanced |
| `compression_level` | INT | Yes | Compression level for PNG format (0-9, default: 9). Ignored for JPEG and WEBP - advanced |
| `upscale_image` | BOOLEAN | Yes | Enable/disable upscaling (default: Enabled) |
| `upscaler_device` | STRING | Yes | Device for upscaling: "default" or "cpu" - advanced |
| `upscale_model_name` | STRING | Yes | Upscale model selected from available upscale_models - advanced |
| `tile` | INT | Yes | Tile size for tiled upscaling (256-4096, step 64, default: 512) - advanced |
| `overlap` | INT | Yes | Overlap between tiles in pixels (0-256, step 8, default: 64) - advanced |
### Outputs
This is an output node with no return values. Images are displayed in the UI and optionally upscaled and saved to disk.
### Usage
All-in-one node that can preview, upscale, and save images in a single step. Upscaling is applied before saving — the upscaled result is both displayed in the UI and written to disk. When `upscale_image` is disabled, the original image is used. When `save_image` is disabled, the image is only previewed in the UI without writing to disk.
---
[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor)
+31
View File
@@ -0,0 +1,31 @@
## TT KSampler (Guided) *TT_KSamplerGuided*
*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.*
Guided latent sampler that takes a pre-configured guider, sigmas, sampler, and noise to produce denoised latent samples.
### Inputs
| Parameter | Type | Required | Description |
|----------------|---------|----------|-----------------------------------------|
| `latent` | LATENT | Yes | Input latent to sample from |
| `guider` | GUIDER | Yes | Configured guider with encoded prompt |
| `sigmas` | SIGMAS | Yes | Sigma schedule for the sampling process |
| `sampler` | SAMPLER | Yes | Instantiated sampler object |
| `random_noise` | NOISE | Yes | Noise source for the diffusion process |
### Outputs
| Parameter | Type | Description |
|-----------|--------|-------------------------|
| `SAMPLES` | LATENT | Denoised latent samples |
### Usage
Runs guided diffusion sampling on the input latent. Connect a guider from a text encoder node, sigmas from a
scheduler, and a sampler from a workflow settings node. `random_noise` controls stochasticity — use a seeded
noise node for reproducible results.
---
[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor)
+30 -24
View File
@@ -1,37 +1,43 @@
# TT Latent Factory *TT_LatentFactory*
## TT Latent Factory *TT_LatentFactory*
*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.*
Generate empty latent images with precise dimension control based on aspect ratio and megapixel count.
Generates empty latent images with precise dimension control based on aspect ratio and megapixel count.
Automatically calculates correct dimensions for FLUX and SDXL models, plus separate CLIP conditioning sizes.
## Parameters
### Inputs
| Parameter | Type | Description |
|-------------------|----------|---------------------------------------------------|
| `aspect_ratio` | DROPDOWN | Image proportions (1:1, 4:3, 3:2, 16:9, 21:9) |
| `seed` | INT | Seed for reproducible results (0 - 2^64-1) |
| `megapixels` | DROPDOWN | Total resolution (0.5 MP, 1 MP, 2 MP, 4 MP, 8 MP) |
| `orientation` | DROPDOWN | landscape or portrait |
| `model_type` | DROPDOWN | FLUX (16 channels) or SDXL (4 channels) |
| `clip_multiplier` | DROPDOWN | CLIP target size multiplier (1x, 2x, 4x) |
| `batch_size` | INT | Number of latents to generate (default: 1) |
| Parameter | Type | Required | Description |
|-------------------|--------|----------|---------------------------------------------------------------|
| `seed` | INT | Yes | Seed for reproducible results (0 – 18446744073709551615) |
| `noise_seed` | INT | Yes | Separate seed for noise generation (0 – 18446744073709551615) |
| `aspect_ratio` | STRING | Yes | Image proportions (1:1, 4:3, 3:2, 16:9, 21:9) |
| `megapixels` | COMBO | Yes | Total resolution (0.25 MP, 0.5 MP, 1 MP, 2 MP, 4 MP, 8 MP) |
| `orientation` | STRING | Yes | Image orientation (landscape / portrait) |
| `model_type` | STRING | Yes | Target model architecture (FLUX1.D, FLUX2.D, SDXL) |
| `batch_size` | INT | Yes | Number of latents to generate (1–64, default: 1) - advanced |
| `clip_multiplier` | STRING | Yes | CLIP target size multiplier (1x, 2x, 4x) - advanced |
## Outputs
### Outputs
| Output | Type | Description |
|-----------------|--------|-----------------------------------------------------|
| `LATENT` | LATENT | Empty latent image tensor with random noise |
| `SEED` | INT | Seed value |
| `WIDTH` | INT | Calculated image width (rounded to multiple of 64) |
| `HEIGHT` | INT | Calculated image height (rounded to multiple of 64) |
| `TARGET_WIDTH` | INT | CLIP conditioning target width |
| `TARGET_HEIGHT` | INT | CLIP conditioning target height |
| Parameter | Type | Description |
|-----------------|--------|------------------------------------------|
| `LATENT` | LATENT | Empty latent tensor ready for sampling |
| `RANDOM_NOISE` | NOISE | Noise object seeded with `noise_seed` |
| `SEED` | INT | Generation seed |
| `NOISE_SEED` | INT | Noise seed |
| `MEGAPIXELS` | COMBO | Selected megapixel value for chaining |
| `WIDTH` | INT | Calculated image width (multiple of 64) |
| `HEIGHT` | INT | Calculated image height (multiple of 64) |
| `TARGET_WIDTH` | INT | CLIP conditioning target width |
| `TARGET_HEIGHT` | INT | CLIP conditioning target height |
## Example
### Usage
Select 16:9, 2 MP, landscape, FLUX, 2x → generates 1920×1088 latent with 3840×2176 CLIP dimensions.
Select aspect ratio, resolution, and orientation to automatically compute pixel dimensions suited for the chosen
model. FLUX1.D uses 16-channel latents, FLUX2.D uses 128-channel, SDXL uses 4-channel. `clip_multiplier` scales the TARGET
dimensions relative to the image dimensions — useful for multi-resolution CLIP conditioning. Connect `RANDOM_NOISE`
directly to a sampler node for reproducible noise tied to `noise_seed`.
---
+1 -1
View File
@@ -41,7 +41,7 @@ Extended post-production node with image enhancement, LUT color grading, film gr
| Parameter | Type | Description |
|-----------|-------|--------------------------------------|
| `IMAGE` | IMAGE | Processed image with applied effects |
| `IMAGE` | IMAGE | Processed image with applied effects |
### Usage