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:
@@ -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
|
||||
@@ -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
@@ -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}")
|
||||
|
||||
@@ -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
@@ -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)",
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,)
|
||||
@@ -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,)
|
||||
@@ -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)
|
||||
|
||||
@@ -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}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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}}
|
||||
@@ -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)
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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,)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ class TT_KSampler():
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LATENT",)
|
||||
RETURN_NAMES = ("LATENT",)
|
||||
RETURN_NAMES = ("SAMPLES",)
|
||||
FUNCTION = "execute_sampling"
|
||||
CATEGORY = "TenserTensor/Sampling"
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class TT_KSamplerAdvanced():
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LATENT",)
|
||||
RETURN_NAMES = ("LATENT",)
|
||||
RETURN_NAMES = ("SAMPLES",)
|
||||
FUNCTION = "execute_sampling"
|
||||
CATEGORY = "TenserTensor/Sampling"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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),)
|
||||
@@ -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
|
||||
|
||||
@@ -28,7 +28,7 @@ class TT_KSamplerTwoStage():
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LATENT",)
|
||||
RETURN_NAMES = ("LATENT",)
|
||||
RETURN_NAMES = ("SAMPLES",)
|
||||
FUNCTION = "execute_sampling"
|
||||
CATEGORY = "TenserTensor/Sampling"
|
||||
|
||||
|
||||
@@ -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,)
|
||||
@@ -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)
|
||||
|
||||
@@ -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,)
|
||||
|
||||
@@ -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,)
|
||||
|
||||
@@ -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),)
|
||||
|
||||
@@ -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,)
|
||||
|
||||
@@ -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),)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
+12
-25
@@ -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 = (
|
||||
+14
-26
@@ -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
@@ -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"]
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
+19
-17
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user