| import copy |
|
|
| import comfy |
| import torch |
| from comfy_extras.nodes_custom_sampler import SamplerCustomAdvanced |
| from comfy_extras.nodes_lt import LTXVAddGuide, LTXVCropGuides |
|
|
| from .latents import LTXVAddLatentGuide, LTXVSelectLatents |
| from .nodes_registry import comfy_node |
|
|
|
|
| @comfy_node( |
| name="LTXVTiledSampler", |
| ) |
| class LTXVTiledSampler: |
|
|
| @classmethod |
| def INPUT_TYPES(s): |
| return { |
| "required": { |
| "model": ("MODEL",), |
| "vae": ("VAE",), |
| "noise": ("NOISE",), |
| "sampler": ("SAMPLER",), |
| "sigmas": ("SIGMAS",), |
| "guider": ("GUIDER",), |
| "latents": ("LATENT",), |
| "horizontal_tiles": ("INT", {"default": 1, "min": 1, "max": 6}), |
| "vertical_tiles": ("INT", {"default": 1, "min": 1, "max": 6}), |
| "overlap": ("INT", {"default": 1, "min": 1, "max": 8}), |
| "latents_cond_strength": ( |
| "FLOAT", |
| {"default": 0.15, "min": 0.0, "max": 1.0, "step": 0.01}, |
| ), |
| "boost_latent_similarity": ( |
| "BOOLEAN", |
| {"default": False}, |
| ), |
| "crop": (["center", "disabled"], {"default": "disabled"}), |
| }, |
| "optional": { |
| "optional_cond_images": ("IMAGE",), |
| "optional_cond_indices": ("STRING", {"default": "0"}), |
| "images_cond_strengths": ("STRING", {"default": "0.9"}), |
| }, |
| } |
|
|
| RETURN_TYPES = ( |
| "LATENT", |
| "LATENT", |
| ) |
| RETURN_NAMES = ( |
| "output", |
| "denoised_output", |
| ) |
|
|
| FUNCTION = "sample" |
|
|
| CATEGORY = "sampling" |
|
|
| def sample( |
| self, |
| model, |
| vae, |
| noise, |
| sampler, |
| sigmas, |
| guider, |
| latents, |
| horizontal_tiles, |
| vertical_tiles, |
| overlap, |
| latents_cond_strength, |
| boost_latent_similarity, |
| crop="disabled", |
| optional_cond_images=None, |
| optional_cond_indices="0", |
| images_cond_strengths="0.9", |
| ): |
|
|
| |
| samples = latents["samples"] |
|
|
| batch, channels, frames, height, width = samples.shape |
| time_scale_factor, width_scale_factor, height_scale_factor = ( |
| vae.downscale_index_formula |
| ) |
| |
| if optional_cond_images is not None: |
| img_height = height * height_scale_factor |
| img_width = width * width_scale_factor |
| cond_images = comfy.utils.common_upscale( |
| optional_cond_images.movedim(-1, 1), |
| img_width, |
| img_height, |
| "bicubic", |
| crop=crop, |
| ).movedim(1, -1) |
| img_batch, img_height, img_width, img_channels = cond_images.shape |
| else: |
| cond_images = None |
|
|
| if optional_cond_indices is not None and optional_cond_images is not None: |
| optional_cond_indices = optional_cond_indices.split(",") |
| optional_cond_indices = [int(i) for i in optional_cond_indices] |
| assert len(optional_cond_indices) == len( |
| optional_cond_images |
| ), "Number of optional cond images must match number of optional cond indices" |
|
|
| images_cond_strengths = [float(i) for i in images_cond_strengths.split(",")] |
| if optional_cond_images is not None and len(images_cond_strengths) < len( |
| optional_cond_images |
| ): |
| |
| images_cond_strengths = images_cond_strengths + [ |
| images_cond_strengths[-1] |
| ] * (len(optional_cond_images) - len(images_cond_strengths)) |
|
|
| |
| base_tile_height = (height + (vertical_tiles - 1) * overlap) // vertical_tiles |
| base_tile_width = (width + (horizontal_tiles - 1) * overlap) // horizontal_tiles |
|
|
| |
| output = torch.zeros_like(samples) |
| denoised_output = torch.zeros_like(samples) |
| weights = torch.zeros_like(samples) |
|
|
| |
| try: |
| positive, negative = guider.raw_conds |
| except AttributeError: |
| raise ValueError( |
| "Guider does not have raw conds, cannot use it as a guider. " |
| "Please use STGGuiderAdvanced." |
| ) |
|
|
| |
| for v in range(vertical_tiles): |
| for h in range(horizontal_tiles): |
| |
| h_start = h * (base_tile_width - overlap) |
| v_start = v * (base_tile_height - overlap) |
|
|
| |
| h_end = ( |
| min(h_start + base_tile_width, width) |
| if h < horizontal_tiles - 1 |
| else width |
| ) |
| v_end = ( |
| min(v_start + base_tile_height, height) |
| if v < vertical_tiles - 1 |
| else height |
| ) |
|
|
| |
| tile_height = v_end - v_start |
| tile_width = h_end - h_start |
|
|
| print(f"Processing tile at row {v}, col {h}:") |
| print(f" Position: ({v_start}:{v_end}, {h_start}:{h_end})") |
| print(f" Size: {tile_height}x{tile_width}") |
|
|
| |
| tile = samples[:, :, :, v_start:v_end, h_start:h_end] |
|
|
| |
| tile_latents = {"samples": tile} |
| unconditioned_tile_latents = tile_latents.copy() |
|
|
| |
| if cond_images is not None: |
| |
| img_h_start = v_start * height_scale_factor |
| img_h_end = v_end * height_scale_factor |
| img_w_start = h_start * width_scale_factor |
| img_w_end = h_end * width_scale_factor |
|
|
| |
| tile_positive = positive.copy() |
| tile_negative = negative.copy() |
|
|
| for i_cond_image, ( |
| cond_image, |
| cond_image_idx, |
| cond_image_strength, |
| ) in enumerate( |
| zip(cond_images, optional_cond_indices, images_cond_strengths) |
| ): |
| |
| img_tile = cond_image[ |
| img_h_start:img_h_end, img_w_start:img_w_end, : |
| ].unsqueeze(0) |
|
|
| print( |
| f"Applying image conditioning on cond image {i_cond_image} for tile at row {v}, col {h} with strength {cond_image_strength} at position {cond_image_idx}:" |
| ) |
| print( |
| f" Image tile position: ({img_h_start}:{img_h_end}, {img_w_start}:{img_w_end})" |
| ) |
| print(f" Image tile size: {img_tile.shape}") |
|
|
| |
| ( |
| tile_positive, |
| tile_negative, |
| tile_latents, |
| ) = LTXVAddGuide().execute( |
| positive=tile_positive, |
| negative=tile_negative, |
| vae=vae, |
| latent=tile_latents, |
| image=img_tile, |
| frame_idx=cond_image_idx, |
| strength=cond_image_strength, |
| ) |
| if boost_latent_similarity: |
| middle_latent_idx = (frames - 1) // 2 |
| middle_index_latent = LTXVSelectLatents().select_latents( |
| samples=unconditioned_tile_latents, |
| start_index=middle_latent_idx, |
| end_index=middle_latent_idx, |
| )[0] |
| last_index_latent = LTXVSelectLatents().select_latents( |
| samples=unconditioned_tile_latents, |
| start_index=-1, |
| end_index=-1, |
| )[0] |
| print( |
| f"using LTXVAddLatentGuide on tiled latent with latent index {middle_latent_idx} and strength {latents_cond_strength}" |
| ) |
| ( |
| tile_positive, |
| tile_negative, |
| tile_latents, |
| ) = LTXVAddLatentGuide().generate( |
| vae=vae, |
| positive=tile_positive, |
| negative=tile_negative, |
| latent=tile_latents, |
| guiding_latent=middle_index_latent, |
| latent_idx=middle_latent_idx, |
| strength=latents_cond_strength, |
| ) |
| print( |
| f"using LTXVAddLatentGuide on tiled latent with latent index {frames-1} and strength {latents_cond_strength}" |
| ) |
| ( |
| tile_positive, |
| tile_negative, |
| tile_latents, |
| ) = LTXVAddLatentGuide().generate( |
| vae=vae, |
| positive=tile_positive, |
| negative=tile_negative, |
| latent=tile_latents, |
| guiding_latent=last_index_latent, |
| latent_idx=frames - 1, |
| strength=latents_cond_strength, |
| ) |
|
|
| guider = copy.copy(guider) |
| guider.set_conds(tile_positive, tile_negative) |
|
|
| |
| denoised_tile = SamplerCustomAdvanced().sample( |
| noise=noise, |
| guider=guider, |
| sampler=sampler, |
| sigmas=sigmas, |
| latent_image=tile_latents, |
| )[0] |
|
|
| |
| if cond_images is not None: |
| ( |
| tile_positive, |
| tile_negative, |
| denoised_tile, |
| ) = LTXVCropGuides().execute( |
| positive=tile_positive, |
| negative=tile_negative, |
| latent=denoised_tile, |
| ) |
|
|
| |
| tile_weights = torch.ones_like(tile) |
|
|
| |
| if h > 0: |
| h_blend = torch.linspace(0, 1, overlap, device=tile.device) |
| tile_weights[:, :, :, :, :overlap] *= h_blend.view(1, 1, 1, 1, -1) |
| if h < horizontal_tiles - 1: |
| h_blend = torch.linspace(1, 0, overlap, device=tile.device) |
| tile_weights[:, :, :, :, -overlap:] *= h_blend.view(1, 1, 1, 1, -1) |
|
|
| |
| if v > 0: |
| v_blend = torch.linspace(0, 1, overlap, device=tile.device) |
| tile_weights[:, :, :, :overlap, :] *= v_blend.view(1, 1, 1, -1, 1) |
| if v < vertical_tiles - 1: |
| v_blend = torch.linspace(1, 0, overlap, device=tile.device) |
| tile_weights[:, :, :, -overlap:, :] *= v_blend.view(1, 1, 1, -1, 1) |
|
|
| |
| output[:, :, :, v_start:v_end, h_start:h_end] += ( |
| denoised_tile["samples"] * tile_weights |
| ) |
| denoised_output[:, :, :, v_start:v_end, h_start:h_end] += ( |
| denoised_tile["samples"] * tile_weights |
| ) |
|
|
| |
| weights[:, :, :, v_start:v_end, h_start:h_end] += tile_weights |
|
|
| |
| output = output / (weights + 1e-8) |
| denoised_output = denoised_output / (weights + 1e-8) |
|
|
| return {"samples": output}, {"samples": denoised_output} |
|
|