Files

2242 lines
104 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import json
import base64
from io import BytesIO
from pathlib import Path
import sys
import types
import numpy as np
from PIL import Image, ImageDraw
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT))
def install_comfy_stubs():
for name in ["cv2", "node_helpers", "latent_preview"]:
sys.modules[name] = types.ModuleType(name)
folder_paths = types.ModuleType("folder_paths")
folder_paths.get_input_directory = lambda: "."
folder_paths.get_output_directory = lambda: "."
folder_paths.filter_files_content_types = lambda files, _types: files
folder_paths.get_annotated_filepath = lambda image: image
folder_paths.exists_annotated_filepath = lambda image: True
folder_paths.get_save_image_path = lambda filename_prefix, output_dir, image_width=0, image_height=0: (
".",
filename_prefix,
1,
"",
filename_prefix,
)
sys.modules["folder_paths"] = folder_paths
torch = types.ModuleType("torch")
class Tensor:
pass
class FakeTensor:
def __init__(self, array):
self.array = array
self.shape = getattr(array, "shape", ())
def unsqueeze(self, dim):
if dim == 0:
return FakeTensor(self.array[None, ...])
return self
def __getitem__(self, item):
return FakeTensor(self.array[item])
def cpu(self):
return self
def numpy(self):
return self.array
torch.Tensor = Tensor
torch.from_numpy = lambda array: FakeTensor(array)
torch.cat = lambda tensors, dim=0: tensors
torch.unsqueeze = lambda tensor, dim: tensor
sys.modules["torch"] = torch
comfy = types.ModuleType("comfy")
comfy.__path__ = []
sys.modules["comfy"] = comfy
for name in ["model_management", "sd", "samplers", "sample", "utils"]:
module = types.ModuleType(f"comfy.{name}")
sys.modules[f"comfy.{name}"] = module
setattr(comfy, name, module)
cli_args = types.ModuleType("comfy.cli_args")
cli_args.args = types.SimpleNamespace(disable_metadata=False)
sys.modules["comfy.cli_args"] = cli_args
def assert_equal(actual, expected, message):
if actual != expected:
raise AssertionError(f"{message}: expected {expected}, got {actual}")
def assert_image_close(actual, expected, message, tolerance=1e-6):
actual_array = getattr(actual, "array", actual)
expected_array = getattr(expected, "array", expected)
if not np.allclose(actual_array, expected_array, atol=tolerance, rtol=0.0):
max_delta = float(np.max(np.abs(actual_array - expected_array)))
raise AssertionError(f"{message}: max delta {max_delta} > {tolerance}")
def call_assemble_like_comfy(node, widget_values, **linked_inputs):
kwargs = {}
required = node.INPUT_TYPES()["required"]
for index, (name, spec) in enumerate(required.items()):
if index < len(widget_values):
kwargs[name] = widget_values[index]
continue
if isinstance(spec, tuple) and len(spec) > 1 and isinstance(spec[1], dict) and "default" in spec[1]:
kwargs[name] = spec[1]["default"]
kwargs.update(linked_inputs)
return node.assemble_tiles(**kwargs)
install_comfy_stubs()
import TTP_toolsets as ttp # noqa: E402
assert_equal(
"TTP_Smart_Tile_Layout_Experimental" in ttp.NODE_CLASS_MAPPINGS,
False,
"old Smart Tile layout step node should not be registered",
)
assert_equal(
"TTP_Smart_Tile_Crop_Experimental" in ttp.NODE_CLASS_MAPPINGS,
False,
"old Smart Tile crop step node should not be registered",
)
assert_equal(
"TTP_Smart_Tile_Visual_Crop_Experimental" in ttp.NODE_CLASS_MAPPINGS,
False,
"old Smart Tile param crop step node should not be registered",
)
assert_equal(
"(Experimental)" in ttp.NODE_DISPLAY_NAME_MAPPINGS["TTP_Smart_Tile_Interactive_Crop_Experimental"],
False,
"interactive crop display name should not include Experimental",
)
assert_equal(
ttp.NODE_DISPLAY_NAME_MAPPINGS["TTP_Smart_Tile_Loop_Source_Experimental"],
"TTP Smart Tile Loop Source",
"loop workflow display names should be stable and non-experimental",
)
assert_equal(
ttp.TTP_Smart_Tile_Interactive_Crop_Experimental.CATEGORY,
"TTP/Smart Tile",
"Smart Tile nodes should use the non-experimental menu category",
)
interactive_inputs = ttp.TTP_Smart_Tile_Interactive_Crop_Experimental.INPUT_TYPES()
required_inputs = interactive_inputs["required"]
optional_inputs = interactive_inputs["optional"]
hidden_inputs = interactive_inputs["hidden"]
assert_equal(
ttp.TTP_Smart_Tile_Interactive_Crop_Experimental.RETURN_TYPES,
("IMAGE", "IMAGE", "TTP_SMART_TILE_SET", "TTP_SMART_TILE_META", "LIST", "IMAGE", "STRING"),
"interactive crop should expose a tile_set output before metadata",
)
assert_equal(
required_inputs["auto_detect_mode"][0],
["none", "sam3.1", "qwenvl3"],
"interactive crop should expose the requested auto detect modes",
)
assert_equal("auto_detect_request" in required_inputs, True, "interactive crop should expose an inference request trigger")
assert_equal("auto_prompt" in required_inputs, True, "interactive crop should expose a visual prompt")
assert_equal("allow_object_overlap" in required_inputs, True, "interactive crop should expose object overlap control")
assert_equal("auto_object_padding" in required_inputs, True, "interactive crop should expose object padding control")
assert_equal("auto_mask_expand" in required_inputs, True, "interactive crop should expose auto mask expansion control")
assert_equal("auto_max_tiles" in required_inputs, True, "interactive crop should expose auto max tiles control")
assert_equal("auto_paint_mask" in required_inputs, True, "interactive crop should expose hidden painted mask input")
assert_equal("vision_model" in optional_inputs, True, "interactive crop should expose a vision model input")
assert_equal("vision_conditioning" in optional_inputs, True, "interactive crop should expose official SAM3 conditioning input")
assert_equal("clip" in optional_inputs, True, "interactive crop should optionally encode SAM3 prompts with CLIP")
assert_equal("qwen_vl_model" in optional_inputs, True, "interactive crop should accept local QwenVL model for bbox auto tile")
assert_equal(hidden_inputs["unique_id"], "UNIQUE_ID", "interactive crop should receive its ComfyUI node id")
assemble_inputs = ttp.TTP_Smart_Tile_Assemble_Experimental.INPUT_TYPES()
expected_assemble_required_order = [
"blend_multiplier",
"output_scale",
"use_priority",
"tile_alignment",
"edge_crop_px",
"color_correction",
"color_strength",
"mask_blend_mode",
"pixel_alignment",
"pixel_alignment_radius",
"pixel_alignment_device",
"large_tile_policy",
"large_tile_area_threshold",
"min_tile_scale_ratio",
"context_tile_weight",
"assemble_device",
"assemble_mode",
"base_canvas_mode",
"weight_preview_mode",
"small_tile_on_top",
"auto_composite_policy",
"replace_tile_shape",
]
assert_equal(
list(assemble_inputs["required"].keys()),
expected_assemble_required_order,
"assemble required widgets should keep the same order ComfyUI stores in workflow widget values",
)
assert_equal("sampled_tiles" in assemble_inputs["optional"], True, "assemble should keep batch tiles as an optional compatibility input")
assert_equal("tile_meta" in assemble_inputs["optional"], True, "assemble should keep batch metadata as an optional compatibility input")
assert_equal("tile_set" in assemble_inputs["optional"], True, "assemble should accept true variable-size tile sets")
assert_equal("local_mean_std" in assemble_inputs["required"]["color_correction"][0], True, "assemble should expose local mean/std color correction")
assert_equal("tile_alignment" in assemble_inputs["required"], True, "assemble should expose tile alignment")
assert_equal("edge_crop_px" in assemble_inputs["required"], True, "assemble should expose edge crop control")
assert_equal("mask_blend_mode" in assemble_inputs["required"], True, "assemble should expose object mask blending")
assert_equal("pixel_alignment" in assemble_inputs["required"], True, "assemble should expose pixel alignment")
assert_equal("pixel_alignment_radius" in assemble_inputs["required"], True, "assemble should expose pixel alignment radius")
assert_equal(assemble_inputs["required"]["pixel_alignment_device"][0], ["auto", "cpu", "gpu"], "assemble should expose CPU/GPU pixel alignment selection")
assert_equal(assemble_inputs["required"]["assemble_device"][0], ["auto", "cpu", "gpu"], "assemble should expose CPU/GPU paste selection")
assert_equal(assemble_inputs["required"]["assemble_mode"][0], ["final_only", "always"], "assemble should expose final-only mode first")
assert_equal(assemble_inputs["required"]["assemble_mode"][1]["default"], "final_only", "assemble should default to final-only loop compositing")
assert_equal(assemble_inputs["required"]["base_canvas_mode"][0], ["auto", "black", "base_image", "source_image"], "assemble should expose base canvas source selection")
assert_equal(assemble_inputs["required"]["weight_preview_mode"][0], ["raw_weight", "coverage"], "assemble should expose raw weight and coverage preview modes")
assert_equal(assemble_inputs["required"]["weight_preview_mode"][1]["default"], "raw_weight", "assemble should keep raw overlap weight preview as the default")
assert_equal("small_tile_on_top" in assemble_inputs["required"], True, "assemble should expose small tile top stacking")
assert_equal(assemble_inputs["required"]["auto_composite_policy"][0], ["safe_auto", "strict_layer", "soft_detail", "replace_object", "replace_tile"], "assemble should expose automatic compositing policy")
assert_equal(assemble_inputs["required"]["auto_composite_policy"][1]["default"], "safe_auto", "assemble should default to safe auto compositing")
assert_equal(assemble_inputs["required"]["replace_tile_shape"][0], ["mask_first", "tile_box_first"], "assemble should expose replace tile shape priority")
assert_equal(assemble_inputs["required"]["replace_tile_shape"][1]["default"], "mask_first", "assemble should default replace tile to mask priority")
assert_equal(assemble_inputs["optional"]["done"][0], "BOOLEAN", "assemble should accept loop done gate")
preview_inputs = ttp.TTP_Smart_Tile_Set_Preview_Experimental.INPUT_TYPES()
assert_equal(preview_inputs["required"]["tile_set"][0], "TTP_SMART_TILE_SET", "tile set preview should accept Smart Tile Set")
assert_equal(
ttp.NODE_CLASS_MAPPINGS["TTP_Smart_Tile_Set_Preview_Experimental"],
ttp.TTP_Smart_Tile_Set_Preview_Experimental,
"tile set preview should be registered",
)
prompt_builder_inputs = ttp.TTP_Smart_Tile_QwenVL_Prompt_Set_Builder_Experimental.INPUT_TYPES()
assert_equal("tile_set" in prompt_builder_inputs["required"], False, "prompt builder should not put forceInput tile_set before visible widgets")
assert_equal(prompt_builder_inputs["optional"]["tile_set"][1].get("forceInput"), True, "prompt builder tile_set should be an optional forceInput connection")
assert_equal(prompt_builder_inputs["required"]["reference_image_mode"][0], ["none", "first_message", "every_tile", "contact_sheet"], "prompt builder should support reference image and contact sheet strategies")
assert_equal("mode" in prompt_builder_inputs["required"], False, "prompt builder should auto-select template or local QwenVL mode from the model input")
assert_equal("prompt_preset" in prompt_builder_inputs["required"], True, "prompt builder should expose preset prompt selection")
assert_equal("qwen_max_side" in prompt_builder_inputs["required"], True, "prompt builder should expose Qwen image resize control")
assert_equal("use_tile_cache" in prompt_builder_inputs["required"], True, "prompt builder should expose tile-level cache control")
assert_equal("endpoint_url" in prompt_builder_inputs["required"], False, "prompt builder should not expose API endpoint fields")
assert_equal("model_name" in prompt_builder_inputs["required"], False, "prompt builder should use the connected local QwenVL model instead of a model-name widget")
prompt_required_order = list(prompt_builder_inputs["required"].keys())
assert_equal(
prompt_required_order.index("prompt_preset") > prompt_required_order.index("temperature"),
True,
"new prompt builder widgets should be appended after legacy prompt fields to avoid saved-widget value shifts",
)
assert_equal(
prompt_required_order.index("global_negative") > prompt_required_order.index("use_tile_cache"),
True,
"global negative should stay at the end to avoid shifting core prompt widgets",
)
assert_equal(prompt_builder_inputs["required"]["system_prompt"][1]["dynamicPrompts"], False, "system prompt should not use ComfyUI dynamic prompt parsing")
assert_equal(prompt_builder_inputs["required"]["tile_instruction"][1]["dynamicPrompts"], False, "tile instruction should not use ComfyUI dynamic prompt parsing")
assert_equal("seed" in prompt_builder_inputs["required"], False, "prompt builder should avoid ComfyUI's special seed widget name")
assert_equal("seed" in prompt_builder_inputs["optional"], False, "prompt builder should not expose the legacy seed widget name")
assert_equal(prompt_builder_inputs["required"]["qwen_seed"][0], "INT", "prompt builder should expose qwen_seed for local QwenVL sampling")
assert_equal(prompt_builder_inputs["optional"]["qwen_vl_model"][0], "TTP_QWENVL3_MODEL", "prompt builder should accept local QwenVL loader output")
qwen_loader_inputs = ttp.TTP_QwenVL3_Local_Loader_Experimental.INPUT_TYPES()
assert_equal("model_file" in qwen_loader_inputs["required"], True, "QwenVL local loader should expose a safetensors file picker")
assert_equal(qwen_loader_inputs["required"]["model_family"][0], ["auto", "qwen_vl"], "QwenVL local loader should focus on visual tagging model family")
assert_equal(
ttp.NODE_CLASS_MAPPINGS["TTP_QwenVL3_Local_Loader_Experimental"],
ttp.TTP_QwenVL3_Local_Loader_Experimental,
"QwenVL local loader should be registered",
)
assert_equal(
ttp.NODE_CLASS_MAPPINGS["TTP_Smart_Tile_QwenVL_Prompt_Set_Builder_Experimental"],
ttp.TTP_Smart_Tile_QwenVL_Prompt_Set_Builder_Experimental,
"prompt builder should be registered",
)
prompt_override_inputs = ttp.TTP_Smart_Tile_Prompt_Override_Experimental.INPUT_TYPES()
assert_equal(prompt_override_inputs["required"]["tile_set"][0], "TTP_SMART_TILE_SET", "prompt override should accept Smart Tile Set")
assert_equal(prompt_override_inputs["required"]["tile_set"][1].get("forceInput"), True, "prompt override tile_set should be a forceInput connection")
assert_equal("regex" in prompt_override_inputs["required"]["selector_type"][0], True, "prompt override should support regex selectors")
assert_equal(prompt_override_inputs["required"]["unmatched_mode"][0], ["keep", "drop_unmatched"], "prompt override should allow dropping unmatched tiles")
assert_equal(prompt_override_inputs["required"]["prompt_text"][1]["dynamicPrompts"], False, "prompt override text should not use dynamic prompt parsing")
assert_equal(
ttp.NODE_CLASS_MAPPINGS["TTP_Smart_Tile_Prompt_Override_Experimental"],
ttp.TTP_Smart_Tile_Prompt_Override_Experimental,
"prompt override should be registered",
)
override_tile_set = {
"version": 1,
"type": "ttp_smart_tile_set",
"original_size": [100, 100],
"tile_meta": {
"version": 3,
"type": "ttp_smart_tile",
"storage": "tile_set",
"original_size": [100, 100],
"tiles": [
{
"id": 0,
"name": "tile_1",
"label": "face",
"source": "paint_mask",
"core_box": [0, 0, 50, 50],
"sample_box": [0, 0, 50, 50],
"paste_box": [0, 0, 50, 50],
"prompt": "old face prompt",
"negative": "old face negative",
},
{
"id": 1,
"name": "tile_2",
"label": "background",
"source": "manual",
"core_box": [50, 0, 50, 50],
"sample_box": [50, 0, 50, 50],
"paste_box": [50, 0, 50, 50],
"prompt": "old background prompt",
"negative": "old background negative",
},
],
},
"tile_images": ["face_image", "background_image"],
"positions": [(0, 0, 50, 50), (50, 0, 100, 50)],
}
override_node = ttp.TTP_Smart_Tile_Prompt_Override_Experimental()
overridden_set, override_json, override_summary = override_node.override_prompts(
override_tile_set,
selector_type="label",
selector="face",
unmatched_mode="keep",
prompt_mode="replace",
prompt_text="edit only the face",
negative_mode="append",
negative_text="avoid changing identity",
)
assert_equal(overridden_set["tile_meta"]["tiles"][0]["prompt"], "edit only the face", "prompt override should replace matched tile prompt")
assert_equal(overridden_set["tile_meta"]["tiles"][0]["negative"], "old face negative\navoid changing identity", "prompt override should append matched tile negative")
assert_equal(overridden_set["tile_meta"]["tiles"][1]["prompt"], "old background prompt", "prompt override should keep unmatched tile prompt")
assert_equal(json.loads(override_json)["matched"], 1, "prompt override JSON should report matched tile count")
assert_equal("matched=1" in override_summary, True, "prompt override summary should report matched count")
dropped_set, _dropped_json, _dropped_summary = override_node.override_prompts(
override_tile_set,
selector_type="label",
selector="face",
unmatched_mode="drop_unmatched",
prompt_mode="replace",
prompt_text="edit only the face",
)
assert_equal(len(dropped_set["tile_meta"]["tiles"]), 1, "prompt override should drop unmatched tile metadata")
assert_equal(len(dropped_set["tile_images"]), 1, "prompt override should drop unmatched tile images")
assert_equal(dropped_set["tile_meta"]["tiles"][0]["id"], 0, "prompt override should reindex kept tile ids")
composite_override_inputs = ttp.TTP_Smart_Tile_Composite_Override_Experimental.INPUT_TYPES()
assert_equal(composite_override_inputs["required"]["tile_set"][0], "TTP_SMART_TILE_SET", "composite override should accept Smart Tile Set")
assert_equal(composite_override_inputs["required"]["tile_set"][1].get("forceInput"), True, "composite override tile_set should be a forceInput connection")
assert_equal("replace_tile" in composite_override_inputs["required"]["composite_mode"][0], True, "composite override should expose replace_tile mode")
assert_equal(composite_override_inputs["required"]["replace_tile_shape"][0], ["keep", "mask_first", "tile_box_first"], "composite override should expose replace tile shape priority")
assert_equal(composite_override_inputs["required"]["selector_type"][0], prompt_override_inputs["required"]["selector_type"][0], "composite override selectors should match prompt override selectors")
assert_equal(
ttp.NODE_CLASS_MAPPINGS["TTP_Smart_Tile_Composite_Override_Experimental"],
ttp.TTP_Smart_Tile_Composite_Override_Experimental,
"composite override should be registered",
)
composite_node = ttp.TTP_Smart_Tile_Composite_Override_Experimental()
composited_set, composite_json, composite_summary = composite_node.override_composite(
override_tile_set,
selector_type="label",
selector="face",
composite_mode="replace_tile",
replace_tile_shape="tile_box_first",
layer_mode="max",
layer=12,
priority_mode="max",
priority=220,
occlusion_priority_mode="max",
occlusion_priority=60000,
blend_mode="set",
blend=24,
)
composited_tile = composited_set["tile_meta"]["tiles"][0]
assert_equal(composited_tile["composite_mode"], "replace_tile", "composite override should set replace_tile mode")
assert_equal(composited_tile["recommended_composite_mode"], "replace_tile", "composite override should update recommended composite mode")
assert_equal(composited_tile["replace_tile_shape"], "tile_box_first", "composite override should set replace tile shape priority")
assert_equal(composited_tile["layer"], 12, "composite override should raise the selected tile layer")
assert_equal(composited_tile["priority"], 220, "composite override should raise the selected tile priority")
assert_equal(composited_tile["occlusion_priority"], 60000, "composite override should raise the selected tile occlusion priority")
assert_equal(composited_tile["blend"], 24, "composite override should set selected tile blend")
assert_equal(json.loads(composite_json)["matched"], 1, "composite override JSON should report matched tile count")
assert_equal("matched=1" in composite_summary, True, "composite override summary should report matched count")
stack_order_inputs = ttp.TTP_Smart_Tile_Stack_Order_Experimental.INPUT_TYPES()
assert_equal(stack_order_inputs["required"]["tile_set"][0], "TTP_SMART_TILE_SET", "stack order should accept Smart Tile Set")
assert_equal(stack_order_inputs["required"]["tile_set"][1].get("forceInput"), True, "stack order tile_set should be a forceInput connection")
assert_equal(stack_order_inputs["required"]["top_order"][0], ["first_on_top", "last_on_top"], "stack order should expose top order direction")
assert_equal(stack_order_inputs["required"]["composite_mode"][1]["default"], "replace_tile", "stack order should default to replace-tile compositing")
assert_equal(
ttp.NODE_CLASS_MAPPINGS["TTP_Smart_Tile_Stack_Order_Experimental"],
ttp.TTP_Smart_Tile_Stack_Order_Experimental,
"stack order should be registered",
)
stack_order_tile_set = {
"version": 1,
"type": "ttp_smart_tile_set",
"original_size": [100, 100],
"tile_meta": {
"version": 3,
"type": "ttp_smart_tile",
"storage": "tile_set",
"original_size": [100, 100],
"tiles": [
{"id": 0, "name": "tile_1", "core_box": [0, 0, 50, 50], "sample_box": [0, 0, 50, 50], "layer": 0, "occlusion_priority": 0},
{"id": 1, "name": "tile_2", "core_box": [0, 0, 50, 50], "sample_box": [0, 0, 50, 50], "layer": 0, "occlusion_priority": 0},
{"id": 2, "name": "tile_3", "core_box": [0, 0, 50, 50], "sample_box": [0, 0, 50, 50], "layer": 0, "occlusion_priority": 0},
{"id": 3, "name": "tile_4", "core_box": [0, 0, 50, 50], "sample_box": [0, 0, 50, 50], "layer": 1, "occlusion_priority": 80000},
],
},
"tile_images": ["tile_1", "tile_2", "tile_3", "tile_4"],
}
stack_node = ttp.TTP_Smart_Tile_Stack_Order_Experimental()
stacked_set, stack_json, stack_summary = stack_node.apply_stack_order(
stack_order_tile_set,
tile_order="T1,T3,T2",
)
stacked_tiles = stacked_set["tile_meta"]["tiles"]
assert_equal(stacked_tiles[0]["occlusion_priority"] > stacked_tiles[2]["occlusion_priority"], True, "stack order should make the first listed tile topmost by default")
assert_equal(stacked_tiles[2]["occlusion_priority"] > stacked_tiles[1]["occlusion_priority"], True, "stack order should preserve listed ordering between selected tiles")
assert_equal(stacked_tiles[1]["occlusion_priority"] > stacked_tiles[3]["occlusion_priority"], True, "stack order should place listed tiles above unlisted tiles")
assert_equal(stacked_tiles[0]["composite_mode"], "replace_tile", "stack order should default selected tiles to replace_tile mode")
assert_equal("composite_mode" in stacked_tiles[3], False, "stack order should not change unlisted tile composite mode")
assert_equal(json.loads(stack_json)["tiles"][1]["selector"], "T3", "stack order JSON should preserve selector order")
assert_equal("T1" in stack_summary and "T3" in stack_summary and "T2" in stack_summary, True, "stack order summary should include selected tiles")
loop_source_inputs = ttp.TTP_Smart_Tile_Loop_Source_Experimental.INPUT_TYPES()
assert_equal(loop_source_inputs["required"]["tile_set"][0], "TTP_SMART_TILE_SET", "loop source should accept Smart Tile Set")
assert_equal(loop_source_inputs["optional"]["clip"][0], "CLIP", "loop source should optionally encode tile prompts with CLIP")
assert_equal(loop_source_inputs["hidden"]["unique_id"], "UNIQUE_ID", "loop source should know its node id for auto queue events")
assert_equal("prompt" in ttp.TTP_Smart_Tile_Loop_Source_Experimental.RETURN_NAMES, True, "loop source should output tile prompts")
assert_equal("negative" in ttp.TTP_Smart_Tile_Loop_Source_Experimental.RETURN_NAMES, True, "loop source should output tile negatives")
assert_equal("positive_conditioning" in ttp.TTP_Smart_Tile_Loop_Source_Experimental.RETURN_NAMES, True, "loop source should output encoded positive conditioning")
assert_equal("negative_conditioning" in ttp.TTP_Smart_Tile_Loop_Source_Experimental.RETURN_NAMES, True, "loop source should output encoded negative conditioning")
loop_collect_inputs = ttp.TTP_Smart_Tile_Loop_Collect_Experimental.INPUT_TYPES()
assert_equal(loop_collect_inputs["required"]["tile_task"][0], "TTP_SMART_TILE_TASK", "loop collect should accept tile tasks")
assert_equal(
ttp.NODE_CLASS_MAPPINGS["TTP_Smart_Tile_Loop_Source_Experimental"],
ttp.TTP_Smart_Tile_Loop_Source_Experimental,
"loop source should be registered",
)
assert_equal(
ttp.NODE_CLASS_MAPPINGS["TTP_Smart_Tile_Loop_Collect_Experimental"],
ttp.TTP_Smart_Tile_Loop_Collect_Experimental,
"loop collect should be registered",
)
upscale_inputs = ttp.TTP_Smart_Tile_Image_Upscale_Prep_Experimental.INPUT_TYPES()
assert_equal(upscale_inputs["required"]["image"][0], "IMAGE", "upscale prep should accept one tile image")
assert_equal("max_megapixels" in upscale_inputs["required"], True, "upscale prep should expose a megapixel cap")
assert_equal("use_upscale_model" in upscale_inputs["required"], True, "upscale prep should allow disabling a connected upscale model")
assert_equal(upscale_inputs["optional"]["upscale_model"][0], "UPSCALE_MODEL", "upscale prep should accept ComfyUI upscale models")
assert_equal(
ttp.NODE_CLASS_MAPPINGS["TTP_Smart_Tile_Image_Upscale_Prep_Experimental"],
ttp.TTP_Smart_Tile_Image_Upscale_Prep_Experimental,
"upscale prep should be registered",
)
output_size_inputs = ttp.TTP_Smart_Tile_Output_Size_Estimate_Experimental.INPUT_TYPES()
assert_equal(output_size_inputs["required"]["tile_set"][0], "TTP_SMART_TILE_SET", "output size estimate should accept tile sets")
assert_equal(output_size_inputs["required"]["scale_strategy"][0], ["median", "mean", "min", "max", "focus_weighted"], "output size estimate should expose scale strategies")
assert_equal(output_size_inputs["optional"]["done"][0], "BOOLEAN", "output size estimate should accept a loop done gate")
semantic_rank_inputs = ttp.TTP_Smart_Tile_Semantic_Rank_Experimental.INPUT_TYPES()
assert_equal(semantic_rank_inputs["required"]["tile_set"][0], "TTP_SMART_TILE_SET", "semantic rank should accept Smart Tile Set")
assert_equal(semantic_rank_inputs["required"]["rank_policy"][0], ["portrait", "balanced", "product", "text"], "semantic rank should expose rank policies")
assert_equal("apply_composite_rank" in semantic_rank_inputs["required"], True, "semantic rank should optionally write composite rank metadata")
assert_equal(
ttp.NODE_CLASS_MAPPINGS["TTP_Smart_Tile_Semantic_Rank_Experimental"],
ttp.TTP_Smart_Tile_Semantic_Rank_Experimental,
"semantic rank should be registered",
)
assert_equal(
ttp.NODE_CLASS_MAPPINGS["TTP_Smart_Tile_Output_Size_Estimate_Experimental"],
ttp.TTP_Smart_Tile_Output_Size_Estimate_Experimental,
"output size estimate should be registered",
)
save_final_inputs = ttp.TTP_Smart_Tile_Save_Final_Image_Experimental.INPUT_TYPES()
assert_equal(save_final_inputs["required"]["images"][0], "IMAGE", "save final image should accept images")
assert_equal("done" in save_final_inputs["required"], True, "save final image should expose a done gate")
assert_equal(save_final_inputs["hidden"]["extra_pnginfo"], "EXTRA_PNGINFO", "save final image should embed workflow metadata")
assert_equal(
ttp.NODE_CLASS_MAPPINGS["TTP_Smart_Tile_Save_Final_Image_Experimental"],
ttp.TTP_Smart_Tile_Save_Final_Image_Experimental,
"save final image should be registered",
)
# This mirrors JSON.stringify output from the browser: exact 0/1 edges are
# serialized as integers, while inner grid lines stay fractional.
browser_grid_layout = {
"tiles": [
{
"name": f"tile_{row}_{col}",
"x0": 0 if col == 0 else col / 3,
"y0": 0 if row == 0 else row / 3,
"x1": 1 if col == 2 else (col + 1) / 3,
"y1": 1 if row == 2 else (row + 1) / 3,
}
for row in range(3)
for col in range(3)
]
}
layout_json = json.dumps(browser_grid_layout, separators=(",", ":"))
normalized_layout = ttp._ttp_interactive_layout_with_defaults(layout_json, 64, 32, False)
tiles_meta = ttp._ttp_parse_smart_tile_layout(normalized_layout, 900, 600)
expected_core_boxes = [
[0, 0, 300, 200],
[300, 0, 300, 200],
[600, 0, 300, 200],
[0, 200, 300, 200],
[300, 200, 300, 200],
[600, 200, 300, 200],
[0, 400, 300, 200],
[300, 400, 300, 200],
[600, 400, 300, 200],
]
assert_equal([tile["core_box"] for tile in tiles_meta], expected_core_boxes, "3x3 core boxes")
expected_sample_boxes = [
[0, 0, 364, 264],
[236, 0, 428, 264],
[536, 0, 364, 264],
[0, 136, 364, 328],
[236, 136, 428, 328],
[536, 136, 364, 328],
[0, 336, 364, 264],
[236, 336, 428, 264],
[536, 336, 364, 264],
]
assert_equal([tile["sample_box"] for tile in tiles_meta], expected_sample_boxes, "3x3 overlap sample boxes")
_tiles, crop_meta, _positions, _preview = ttp._ttp_crop_smart_tiles_from_meta(
Image.new("RGB", (900, 600), "white"),
[dict(tile) for tile in tiles_meta],
8,
)
expected_batched_sample_boxes = [
[0, 0, 432, 328],
[236, 0, 432, 328],
[468, 0, 432, 328],
[0, 136, 432, 328],
[236, 136, 432, 328],
[468, 136, 432, 328],
[0, 272, 432, 328],
[236, 272, 432, 328],
[468, 272, 432, 328],
]
assert_equal(
[tile["sample_box"] for tile in crop_meta["tiles"]],
expected_batched_sample_boxes,
"3x3 batch crops should expand inward with real image pixels instead of transport padding",
)
assert_equal(
crop_meta["tiles"][0]["tile_canvas_box"],
[0, 0, 432, 328],
"top-left crop should fill the batch canvas with real pixels",
)
assert_equal(
crop_meta["tiles"][0]["tile_canvas_size"],
[432, 328],
"top-left batch canvas should match largest crop",
)
assert_equal(
crop_meta["tiles"][8]["tile_canvas_box"],
[0, 0, 432, 328],
"bottom-right crop should fill the batch canvas with real pixels",
)
assert_equal(
crop_meta["tiles"][0]["overlap_edges_px_source"],
{"left": 0, "right": 132, "top": 0, "bottom": 128},
"top-left batch crop should grow only inward",
)
assert_equal(
crop_meta["tiles"][8]["overlap_edges_px_source"],
{"left": 132, "right": 0, "top": 128, "bottom": 0},
"bottom-right batch crop should grow only inward",
)
tile_set = ttp._ttp_crop_smart_tile_set_from_meta(
Image.new("RGB", (900, 600), "white"),
[dict(tile) for tile in tiles_meta],
8,
)
expected_tile_set_sample_boxes = [
[0, 0, 368, 264],
[236, 0, 432, 264],
[532, 0, 368, 264],
[0, 136, 368, 328],
[236, 136, 432, 328],
[532, 136, 368, 328],
[0, 336, 368, 264],
[236, 336, 432, 264],
[532, 336, 368, 264],
]
assert_equal(tile_set["type"], "ttp_smart_tile_set", "tile_set should use the custom Smart Tile Set type")
assert_equal(
[tile["sample_box"] for tile in tile_set["tile_meta"]["tiles"]],
expected_tile_set_sample_boxes,
"tile_set crops should keep per-tile sample boxes after round_to alignment",
)
assert_equal(
[tile["tile_canvas_size"] for tile in tile_set["tile_meta"]["tiles"]],
[[box[2], box[3]] for box in expected_tile_set_sample_boxes],
"tile_set canvas sizes should stay per tile instead of batch-sized",
)
assert_equal(
[list(image.shape[1:3]) for image in tile_set["tile_images"]],
[[box[3], box[2]] for box in expected_tile_set_sample_boxes],
"tile_set image tensors should preserve different tile dimensions",
)
preview_node = ttp.TTP_Smart_Tile_Set_Preview_Experimental()
contact_sheet, sheet_info = preview_node.preview_tile_set(
tile_set,
mode="contact_sheet",
thumbnail_size=128,
columns=3,
)
assert_equal(contact_sheet.shape[0], 1, "tile set contact sheet should output a regular IMAGE tensor")
assert_equal("368x264" in sheet_info and "432x328" in sheet_info, True, "tile set preview info should report real tile sizes")
selected_tile, selected_info = preview_node.preview_tile_set(
tile_set,
mode="selected_tile",
selected_index=4,
)
assert_equal(list(selected_tile.shape[1:3]), [328, 432], "selected tile preview should preserve the selected tile size")
assert_equal("4:" in selected_info, True, "selected tile preview should include tile index info")
grid_assemble_node = ttp.TTP_Smart_Tile_Assemble_Experimental()
_grid_output_raw, grid_raw_weight_preview = grid_assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
base_canvas_mode="black",
mask_blend_mode="mask_feather",
tile_set=tile_set,
)
assert_equal(float(grid_raw_weight_preview.array.max()), 1.0, "raw 3x3 weight preview should normalize the highest overlap to white")
assert_equal(float(grid_raw_weight_preview.array.min()) < 1.0, True, "raw 3x3 weight preview should show lower-weight single tile areas darker than overlap zones")
_grid_output, grid_coverage_preview = grid_assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
base_canvas_mode="black",
weight_preview_mode="coverage",
mask_blend_mode="mask_feather",
tile_set=tile_set,
)
assert_equal(float(grid_coverage_preview.array.min()), 1.0, "black canvas 3x3 coverage preview should be uniformly covered")
assert_equal(float(grid_coverage_preview.array.max()), 1.0, "black canvas 3x3 coverage preview should not show raw overlap weight grid lines")
comfy_assemble_widget_values = [
1.0,
0.0,
True,
"resize",
0,
"off",
0.35,
"mask_feather",
"off",
0,
"cpu",
"use_if_higher_resolution",
0.55,
0.95,
0.25,
"cpu",
"final_only",
"black",
"raw_weight",
False,
"safe_auto",
]
direct_comfy_output, direct_comfy_weights = grid_assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
tile_alignment="resize",
edge_crop_px=0,
color_correction="off",
color_strength=0.35,
mask_blend_mode="mask_feather",
pixel_alignment="off",
pixel_alignment_radius=0,
pixel_alignment_device="cpu",
large_tile_policy="use_if_higher_resolution",
large_tile_area_threshold=0.55,
min_tile_scale_ratio=0.95,
context_tile_weight=0.25,
assemble_device="cpu",
assemble_mode="final_only",
base_canvas_mode="black",
weight_preview_mode="raw_weight",
small_tile_on_top=False,
auto_composite_policy="safe_auto",
tile_set=tile_set,
done=True,
)
replayed_comfy_output, replayed_comfy_weights = call_assemble_like_comfy(
grid_assemble_node,
comfy_assemble_widget_values,
output_scale=1.0,
tile_set=tile_set,
done=True,
)
assert_image_close(replayed_comfy_output, direct_comfy_output, "ComfyUI widget-order assemble replay should match direct assemble output")
assert_image_close(replayed_comfy_weights, direct_comfy_weights, "ComfyUI widget-order assemble replay should match direct assemble weight preview")
ranked_grid_meta = dict(tile_set["tile_meta"])
ranked_grid_tiles = []
for tile_index, tile in enumerate(tile_set["tile_meta"]["tiles"]):
ranked_tile = dict(tile)
ranked_tile["layer"] = tile_index
ranked_tile["occlusion_priority"] = tile_index
ranked_grid_tiles.append(ranked_tile)
ranked_grid_meta["tiles"] = ranked_grid_tiles
ranked_grid_tile_set = dict(tile_set)
ranked_grid_tile_set["tile_meta"] = ranked_grid_meta
_ranked_grid_output, ranked_grid_weight_preview = grid_assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
base_canvas_mode="black",
mask_blend_mode="mask_feather",
auto_composite_policy="safe_auto",
tile_set=ranked_grid_tile_set,
)
ranked_width, ranked_height = ranked_grid_meta["original_size"]
internal_x = sorted({tile["core_box"][0] for tile in ranked_grid_tiles if tile["core_box"][0] > 0})
internal_y = sorted({tile["core_box"][1] for tile in ranked_grid_tiles if tile["core_box"][1] > 0})
column_centers = sorted({min(ranked_width - 1, tile["core_box"][0] + tile["core_box"][2] // 2) for tile in ranked_grid_tiles[:3]})
row_centers = sorted({min(ranked_height - 1, ranked_grid_tiles[row * 3]["core_box"][1] + ranked_grid_tiles[row * 3]["core_box"][3] // 2) for row in range(3)})
ranked_seam_values = []
for seam_x in internal_x:
for center_y in row_centers:
ranked_seam_values.append(float(ranked_grid_weight_preview.array[0, center_y, seam_x, 0]))
for seam_y in internal_y:
for center_x in column_centers:
ranked_seam_values.append(float(ranked_grid_weight_preview.array[0, seam_y, center_x, 0]))
assert_equal(min(ranked_seam_values) > 0.45, True, "safe auto ranked 3x3 grid seams should keep both tile contributions instead of showing dark split lines")
prompt_builder = ttp.TTP_Smart_Tile_QwenVL_Prompt_Set_Builder_Experimental()
prompt_tile_set, prompt_set_json, prompt_summary = prompt_builder.build_prompt_set(
tile_set,
mode="template",
reference_image_mode="every_tile",
global_prompt="masterpiece",
global_negative="blurry",
)
prompt_data = json.loads(prompt_set_json)
assert_equal(prompt_data["type"], "ttp_smart_tile_prompt_set", "prompt builder should output prompt set JSON")
assert_equal(len(prompt_data["tiles"]), len(tile_set["tile_images"]), "prompt builder should create one prompt per tile")
assert_equal("masterpiece" in prompt_tile_set["tile_meta"]["tiles"][0]["prompt"], True, "prompt builder should merge global prompt")
assert_equal(prompt_tile_set["tile_meta"]["tiles"][0]["negative"], "blurry", "prompt builder should write tile negative prompts")
assert_equal("0:" in prompt_summary, True, "prompt builder should summarize prompts")
shifted_tile_set, shifted_prompt_json, _shifted_summary = prompt_builder.build_prompt_set(
tile_set,
mode="template",
reference_image_mode="every_tile",
system_prompt="tile_json_strict",
tile_instruction="custom system",
global_prompt="custom tile instruction",
global_negative="masterpiece",
prompt_merge_mode="blurry",
output_language="global_plus_caption",
max_new_tokens="chinese",
temperature=768,
prompt_preset=0.2,
)
shifted_prompt_data = json.loads(shifted_prompt_json)
assert_equal(shifted_prompt_data["prompt_preset"], "tile_json_strict", "prompt builder should recover preset from shifted widget values")
assert_equal(shifted_prompt_data["tiles"][0]["negative"], "blurry", "prompt builder should recover global negative from shifted widget values")
assert_equal("masterpiece" in shifted_tile_set["tile_meta"]["tiles"][0]["prompt"], True, "prompt builder should recover global prompt from shifted widget values")
class FakeQwenClip:
last_seed = None
last_text = ""
last_kwargs = {}
def tokenize(self, text, **kwargs):
self.last_text = text
self.last_kwargs = kwargs
return {"text": text, **kwargs}
def generate(self, tokens, **kwargs):
self.last_seed = kwargs.get("seed")
return ["fake_ids"]
def decode(self, output_ids, skip_special_tokens=True):
tile_index = "unknown"
marker = "Tile index:"
if marker in self.last_text:
tile_index = self.last_text.split(marker, 1)[1].split(".", 1)[0].strip()
return '{"label":"qwen tile %s","caption":"Qwen saw tile %s.","prompt":"qwen detailed tile %s prompt","negative":"qwen negative %s"}' % (tile_index, tile_index, tile_index, tile_index)
fake_qwen_clip = FakeQwenClip()
qwen_tile_set, qwen_prompt_json, qwen_summary = prompt_builder.build_prompt_set(
tile_set,
mode="template",
reference_image_mode="every_tile",
global_prompt="masterpiece",
global_negative="blurry",
qwen_seed=1234,
qwen_vl_model={
"type": "ttp_qwenvl3_model",
"model_file": "fake_qwen_vl.safetensors",
"clip": fake_qwen_clip,
},
)
qwen_prompt_data = json.loads(qwen_prompt_json)
assert_equal(qwen_prompt_data["mode"], "qwen_vl_local", "connected local QwenVL model should override template fallback")
assert_equal(qwen_prompt_data["model_file"], "fake_qwen_vl.safetensors", "QwenVL prompt set JSON should record the connected local model file")
assert_equal(qwen_prompt_data["qwen_seed"], 1234, "QwenVL prompt set JSON should record the local sampling seed")
assert_equal(qwen_prompt_data["prompt_preset"], "tile_img2img_prompt", "QwenVL prompt set JSON should record the prompt preset")
assert_equal("mode=qwen_vl_local" in qwen_summary, True, "QwenVL summary should report the effective mode")
assert_equal(qwen_tile_set["tile_meta"]["tiles"][0]["prompt"], "qwen detailed tile 0 prompt", "QwenVL prompt should come from model output")
assert_equal(qwen_tile_set["tile_meta"]["tiles"][1]["prompt"], "qwen detailed tile 1 prompt", "QwenVL should write a distinct prompt per tile")
assert_equal(qwen_tile_set["tile_meta"]["tiles"][0]["qwen_cache"], "miss", "first QwenVL prompt build should miss tile cache")
assert_equal(fake_qwen_clip.last_seed, 1234, "local QwenVL generate should receive an integer seed")
assert_equal("llama_template" in fake_qwen_clip.last_kwargs, False, "local QwenVL should use ComfyUI native Qwen image chat template")
assert_equal("images" in fake_qwen_clip.last_kwargs, True, "local QwenVL should pass images through the native tokenizer path")
cached_qwen_tile_set, _cached_qwen_prompt_json, _cached_qwen_summary = prompt_builder.build_prompt_set(
tile_set,
mode="qwen_vl_local",
reference_image_mode="every_tile",
prompt_preset="tile_img2img_prompt",
global_prompt="masterpiece",
global_negative="blurry",
qwen_seed=1234,
qwen_vl_model={
"type": "ttp_qwenvl3_model",
"model_file": "fake_qwen_vl.safetensors",
"clip": fake_qwen_clip,
},
)
assert_equal(cached_qwen_tile_set["tile_meta"]["tiles"][0]["qwen_cache"], "hit", "repeated QwenVL prompt build should hit tile cache")
legacy_seed_clip = FakeQwenClip()
_legacy_seed_tile_set, legacy_seed_prompt_json, _legacy_seed_summary = prompt_builder.build_prompt_set(
tile_set,
mode="qwen_vl_local",
seed=5678,
qwen_vl_model={
"type": "ttp_qwenvl3_model",
"model_file": "fake_legacy_seed_qwen_vl.safetensors",
"clip": legacy_seed_clip,
},
)
legacy_seed_prompt_data = json.loads(legacy_seed_prompt_json)
assert_equal(legacy_seed_prompt_data["qwen_seed"], 5678, "legacy seed kwargs should map to qwen_seed")
assert_equal(legacy_seed_clip.last_seed, 5678, "legacy seed kwargs should still reach local QwenVL generate")
small_qwen_clip = FakeQwenClip()
small_qwen_tile_set, _small_qwen_prompt_json, _small_qwen_summary = prompt_builder.build_prompt_set(
tile_set,
mode="qwen_vl_local",
reference_image_mode="contact_sheet",
prompt_preset="tile_json_strict",
qwen_max_side=96,
qwen_max_pixels=0,
use_tile_cache=False,
qwen_vl_model={
"type": "ttp_qwenvl3_model",
"model_file": "fake_small_qwen_vl.safetensors",
"clip": small_qwen_clip,
},
)
small_size = small_qwen_tile_set["tile_meta"]["tiles"][0]["qwen_input_size"]
assert_equal(max(small_size) <= 96, True, "QwenVL prompt builder should resize tile inputs for inference")
miswired_qwen_clip = FakeQwenClip()
miswired_tile_set, miswired_prompt_json, _miswired_summary = prompt_builder.build_prompt_set(
tile_set,
mode="template",
reference_image= {
"type": "ttp_qwenvl3_model",
"model_file": "fake_miswired_qwen_vl.safetensors",
"clip": miswired_qwen_clip,
},
)
miswired_prompt_data = json.loads(miswired_prompt_json)
assert_equal(miswired_prompt_data["mode"], "qwen_vl_local", "miswired QwenVL model in reference_image should be recovered")
assert_equal(miswired_tile_set["tile_meta"]["tiles"][0]["prompt"], "qwen detailed tile 0 prompt", "recovered QwenVL model should still generate prompts")
class FakeQwenRetryClip(FakeQwenClip):
attempts = 0
def decode(self, output_ids, skip_special_tokens=True):
self.attempts += 1
if "Return compact JSON only" in self.last_text:
return '{"label":"retry tile","caption":"Recovered concise tile facts.","prompt":"recovered concise tile prompt","negative":"retry negative"}'
return "This response keeps describing the tile without JSON until it reaches the token limit."
retry_qwen_clip = FakeQwenRetryClip()
retry_tile_set, _retry_prompt_json, _retry_summary = prompt_builder.build_prompt_set(
tile_set,
mode="qwen_vl_local",
qwen_vl_model={
"type": "ttp_qwenvl3_model",
"model_file": "fake_retry_qwen_vl.safetensors",
"clip": retry_qwen_clip,
},
)
assert_equal(retry_tile_set["tile_meta"]["tiles"][0]["prompt"], "recovered concise tile prompt", "QwenVL prompt builder should retry with compact JSON-only prompt")
assert_equal("qwen_raw_retry" in retry_tile_set["tile_meta"]["tiles"][0], True, "QwenVL retry raw output should be stored for debugging")
class FakeQwenEchoClip(FakeQwenClip):
def decode(self, output_ids, skip_special_tokens=True):
return "Refine this tile with attention to subject identity, clothing, pose, material, lighting, camera perspective, and local details. Preserve all visible visual facts without adding or altering non-visible elements."
try:
prompt_builder.build_prompt_set(
tile_set,
mode="qwen_vl_local",
qwen_vl_model={
"type": "ttp_qwenvl3_model",
"model_file": "fake_echo_qwen_vl.safetensors",
"clip": FakeQwenEchoClip(),
},
)
raise AssertionError("QwenVL instruction echo should not be accepted as a tile prompt")
except RuntimeError as exc:
assert_equal("did not return JSON fields" in str(exc), True, "QwenVL instruction echo should fail with a clear JSON error")
partial_qwen = '{ "label": "object_5", "caption": "close-up portrait with warm light", "prompt": "close-up portrait with warm'
partial_record = ttp._ttp_parse_qwen_tile_record(partial_qwen, 5)
assert_equal(partial_record["label"], "object_5", "partial Qwen JSON should preserve label")
assert_equal(partial_record["caption"], "close-up portrait with warm light", "partial Qwen JSON should preserve completed caption")
assert_equal(partial_record["prompt"], "close-up portrait with warm", "partial Qwen JSON should salvage truncated prompt text")
messy_qwen = 'assistant:\n<think>hidden reasoning</think>```json\n{"label":"face","caption":"sharp eyes","prompt":"detailed face","negative":""}\n```'
messy_record = ttp._ttp_parse_qwen_tile_record(messy_qwen, 0)
assert_equal(messy_record["label"], "face", "Qwen cleaner should remove assistant prefixes, markdown fences, and thinking blocks")
bbox_items = ttp._ttp_qwen_bbox_items('```json\n[{"label":"face","bbox":[300,200,500,450],"score":0.9}]\n```', 1000, 800)
assert_equal(len(bbox_items), 1, "Qwen bbox parser should read JSON list outputs")
assert_equal(bbox_items[0]["label"], "face", "Qwen bbox parser should preserve labels")
assert_equal(round(bbox_items[0]["x"]), 300, "Qwen bbox parser should convert normalized x coordinates")
single_bbox_item = ttp._ttp_qwen_bbox_items('{"label":"face","bbox_2d":[300,200,500,450],"score":0.9}', 1000, 800)
assert_equal(len(single_bbox_item), 1, "Qwen bbox parser should accept a single JSON object output")
wrapped_bbox_items = ttp._ttp_qwen_bbox_items('{"objects":[{"label":"hand","box_2d":[100,120,260,360],"score":0.8}]}', 1000, 800)
assert_equal(wrapped_bbox_items[0]["label"], "hand", "Qwen bbox parser should accept objects-wrapped outputs")
dict_bbox_items = ttp._ttp_qwen_bbox_items('{"label":"text","rect":{"x":100,"y":120,"width":200,"height":80},"score":0.7}', 1000, 800)
assert_equal(round(dict_bbox_items[0]["width"]), 200, "Qwen bbox parser should accept x/y/width/height rectangle outputs")
xywh_bbox_items = ttp._ttp_qwen_bbox_items('[{"label":"detail","bbox":[700,100,200,300],"bbox_format":"xywh"}]', 1000, 800)
assert_equal(round(xywh_bbox_items[0]["x"] + xywh_bbox_items[0]["width"]), 900, "Qwen bbox parser should accept xywh list outputs")
large_qwen_items, large_qwen_expanded = ttp._ttp_expand_single_large_qwen_bbox(
[{"x": 0, "y": 0, "width": 1000, "height": 800, "label": "full image", "score": 1.0}],
1000,
800,
8,
)
assert_equal(large_qwen_expanded, True, "Qwen auto tile should split a single full-frame bbox into useful regions")
assert_equal(len(large_qwen_items), 4, "Qwen large bbox fallback should create a 2x2 tile set")
loop_source = ttp.TTP_Smart_Tile_Loop_Source_Experimental()
loop_collect = ttp.TTP_Smart_Tile_Loop_Collect_Experimental()
class FakeClip:
def tokenize(self, text):
return {"text": text}
def encode_from_tokens_scheduled(self, tokens):
return [["conditioning", tokens["text"]]]
sam3_conditioning = ttp._ttp_encode_sam3_prompt_conditioning(FakeClip(), "person, face, hands", max_detections=2)
sam3_meta = sam3_conditioning[0][1]
assert_equal(len(sam3_meta["sam3_multi_cond"]), 3, "SAM3 internal prompt encoder should split comma-separated prompts")
assert_equal(sam3_meta["sam3_multi_cond"][0]["max_detections"], 2, "SAM3 internal prompt encoder should preserve max detections")
loop_image, loop_task, loop_index, loop_count, loop_done, loop_status, loop_prompt, loop_negative, loop_caption, loop_label, loop_prompt_tag, loop_positive, loop_negative_cond = loop_source.loop_source(
prompt_tile_set,
session_id="smoke_loop",
restart_request=1,
loop_request=1,
clip=FakeClip(),
unique_id="42",
)
assert_equal(loop_index, 0, "loop source should start at tile 0")
assert_equal(loop_count, len(tile_set["tile_images"]), "loop source should report tile count")
assert_equal(loop_done, False, "loop source should not be done at start")
assert_equal(loop_task["source_node_id"], "42", "loop task should carry source node id")
assert_equal(list(loop_image.shape[1:3]), [264, 368], "loop source should output a single real-size tile image")
assert_equal("masterpiece" in loop_prompt, True, "loop source should output the current tile prompt")
assert_equal(loop_negative, "blurry", "loop source should output the current tile negative prompt")
assert_equal(loop_positive, [["conditioning", loop_prompt]], "loop source should encode current tile positive prompt")
assert_equal(loop_negative_cond, [["conditioning", loop_negative]], "loop source should encode current tile negative prompt")
assert_equal(bool(loop_caption), True, "loop source should output the current tile caption")
processed_tile_set, done_after_first, next_index, collect_status = loop_collect.loop_collect(loop_task, loop_image)
assert_equal(done_after_first, False, "loop collect should continue after first tile")
assert_equal(next_index, 1, "loop collect should advance to tile 1")
assert_equal(processed_tile_set["type"], "ttp_smart_tile_set", "loop collect should output a processed tile set")
next_image, next_task, next_loop_index, _next_count, _next_done, _next_status, _next_prompt, _next_negative, _next_caption, _next_label, _next_prompt_tag, _next_positive, _next_negative_cond = loop_source.loop_source(
prompt_tile_set,
session_id="smoke_loop",
restart_request=1,
loop_request=2,
unique_id="42",
)
assert_equal(next_loop_index, 1, "loop source should emit the next tile after collect")
assert_equal(list(next_image.shape[1:3]), [264, 432], "loop source next tile should keep its own size")
assert_equal(_next_prompt != loop_prompt, True, "loop source should emit a distinct prompt for the next tile when prompts differ")
session = ttp._TTP_SMART_TILE_LOOP_SESSIONS["smoke_loop"]
session["index"] = len(tile_set["tile_images"]) - 1
last_image, last_task, _last_index, _last_count, _last_done, _last_status, _last_prompt, _last_negative, _last_caption, _last_label, _last_prompt_tag, _last_positive, _last_negative_cond = loop_source.loop_source(
prompt_tile_set,
session_id="smoke_loop",
restart_request=1,
loop_request=3,
unique_id="42",
)
_final_tile_set, final_done, final_next, final_status = loop_collect.loop_collect(last_task, last_image)
assert_equal(final_done, True, "loop collect should mark done after the last tile")
assert_equal("done" in final_status, True, "loop collect should report done status")
qwen_loop_image, qwen_loop_task, qwen_loop_index, _qwen_count, _qwen_done, _qwen_status, qwen_loop_prompt, _qwen_negative, _qwen_caption, _qwen_label, _qwen_prompt_tag, _qwen_positive, _qwen_negative_cond = loop_source.loop_source(
qwen_tile_set,
session_id="smoke_qwen_loop",
restart_request=1,
loop_request=1,
unique_id="43",
)
assert_equal(qwen_loop_prompt, "qwen detailed tile 0 prompt", "Qwen prompt tile 0 should reach loop source")
_qwen_processed, _qwen_done_first, _qwen_next_index, _qwen_collect_status = loop_collect.loop_collect(qwen_loop_task, qwen_loop_image)
_qwen_next_image, _qwen_next_task, qwen_next_index, _qwen_next_count, _qwen_next_done, _qwen_next_status, qwen_next_prompt, _qwen_next_negative, _qwen_next_caption, _qwen_next_label, _qwen_next_prompt_tag, _qwen_next_positive, _qwen_next_negative_cond = loop_source.loop_source(
qwen_tile_set,
session_id="smoke_qwen_loop",
restart_request=1,
loop_request=2,
unique_id="43",
)
assert_equal(qwen_next_index, 1, "Qwen loop should advance to tile 1")
assert_equal(qwen_next_prompt, "qwen detailed tile 1 prompt", "Qwen prompt tile 1 should reach loop source")
upscale_node = ttp.TTP_Smart_Tile_Image_Upscale_Prep_Experimental()
upscaled_tile, upscale_info = upscale_node.upscale_tile(loop_image, scale=1.5, round_to=16)
assert_equal(list(upscaled_tile.shape[1:3]), [400, 560], "upscale prep should scale and round tile dimensions")
assert_equal("368x264 -> 560x400" in upscale_info, True, "upscale prep should report size changes")
capped_tile, capped_info = upscale_node.upscale_tile(
ttp.pil2tensor(Image.new("RGB", (100, 100), "white")),
scale=4.0,
round_to=16,
max_megapixels=0.04,
use_upscale_model=False,
)
assert_equal(list(capped_tile.shape[1:3]), [192, 192], "upscale prep should round down under the megapixel cap")
assert_equal(capped_tile.shape[1] * capped_tile.shape[2] <= 40000, True, "upscale prep should stay under max megapixels after rounding")
assert_equal("max_megapixels=0.04 capped" in capped_info, True, "upscale prep should report megapixel capping")
semantic_node = ttp.TTP_Smart_Tile_Semantic_Rank_Experimental()
semantic_input_tile_set = {
"type": "ttp_smart_tile_set",
"original_size": [100, 100],
"tile_meta": {
"type": "ttp_smart_tile",
"original_size": [100, 100],
"tiles": [
{"name": "bg", "label": "background", "sample_box": [0, 0, 100, 100]},
{"name": "face", "label": "face", "caption": "portrait face", "sample_box": [20, 20, 50, 50]},
{"name": "eyes", "label": "eyes", "caption": "sharp eyes and eyelashes", "sample_box": [35, 35, 20, 12]},
],
},
"tile_images": [
ttp.pil2tensor(Image.new("RGB", (100, 100), "white")),
ttp.pil2tensor(Image.new("RGB", (50, 50), "white")),
ttp.pil2tensor(Image.new("RGB", (80, 48), "white")),
],
}
ranked_tile_set, semantic_report = semantic_node.rank_tiles(semantic_input_tile_set)
ranked_tiles = ranked_tile_set["tile_meta"]["tiles"]
assert_equal(ranked_tiles[0]["semantic_category"], "background", "semantic rank should detect background tiles")
assert_equal(ranked_tiles[1]["semantic_category"], "face", "semantic rank should detect face tiles")
assert_equal(ranked_tiles[2]["semantic_category"], "eyes", "semantic rank should detect eye detail tiles")
assert_equal(ranked_tiles[2]["semantic_score"] > ranked_tiles[1]["semantic_score"] > ranked_tiles[0]["semantic_score"], True, "semantic rank should score detail tiles above face and background")
assert_equal(ranked_tiles[2]["recommended_composite_mode"], "soft_overlay", "semantic rank should recommend soft overlay for eye detail tiles")
assert_equal(ranked_tiles[2]["occlusion_priority"] > ranked_tiles[1]["occlusion_priority"] > ranked_tiles[0]["occlusion_priority"], True, "semantic rank should write composite priority metadata")
assert_equal("category=eyes" in semantic_report, True, "semantic rank report should include ranked categories")
size_node = ttp.TTP_Smart_Tile_Output_Size_Estimate_Experimental()
mixed_scale_meta = {
"type": "ttp_smart_tile",
"original_size": [100, 50],
"tiles": [
{
"name": "left",
"sample_box": [0, 0, 50, 50],
"tile_canvas_size": [50, 50],
"tile_canvas_box": [0, 0, 50, 50],
},
{
"name": "right",
"sample_box": [50, 0, 50, 50],
"tile_canvas_size": [50, 50],
"tile_canvas_box": [0, 0, 50, 50],
},
],
}
mixed_scale_tile_set = {
"type": "ttp_smart_tile_set",
"tile_meta": mixed_scale_meta,
"tile_images": [
ttp.pil2tensor(Image.new("RGB", (200, 200), "white")),
ttp.pil2tensor(Image.new("RGB", (100, 100), "white")),
],
}
deferred_scale, deferred_w, deferred_h, deferred_x, deferred_y, deferred_info = size_node.estimate_output_size(mixed_scale_tile_set, done=False)
assert_equal([deferred_scale, deferred_w, deferred_h, deferred_x, deferred_y], [1.0, 100, 50, 1.0, 1.0], "output size estimate should return safe pending dimensions before loop completion")
assert_equal("deferred" in deferred_info, True, "output size estimate should report deferred status")
size_scale, size_w, size_h, size_scale_x, size_scale_y, size_info = size_node.estimate_output_size(mixed_scale_tile_set)
assert_equal(round(size_scale, 4), 3.0, "output size estimate should match assemble median scale inference")
assert_equal([size_w, size_h], [300, 150], "output size estimate should report final resolution")
assert_equal(round(size_scale_x, 4), 3.0, "output size estimate should report median x scale")
assert_equal(round(size_scale_y, 4), 3.0, "output size estimate should report median y scale")
assert_equal("warning=mixed_tile_scales" in size_info, True, "output size estimate should warn about mixed tile scales")
min_scale, min_w, min_h, _min_x, _min_y, _min_info = size_node.estimate_output_size(mixed_scale_tile_set, scale_strategy="min")
assert_equal(round(min_scale, 4), 2.0, "output size estimate should support min strategy")
assert_equal([min_w, min_h], [200, 100], "output size estimate min strategy should resize final resolution")
focus_scale_meta = {
"type": "ttp_smart_tile",
"original_size": [100, 50],
"tiles": [
{
"label": "background",
"sample_box": [0, 0, 100, 50],
"tile_canvas_size": [100, 50],
"tile_canvas_box": [0, 0, 100, 50],
},
{
"label": "eyes",
"sample_box": [25, 10, 10, 10],
"tile_canvas_size": [10, 10],
"tile_canvas_box": [0, 0, 10, 10],
},
],
}
focus_scale_tile_set = {
"type": "ttp_smart_tile_set",
"tile_meta": focus_scale_meta,
"tile_images": [
ttp.pil2tensor(Image.new("RGB", (100, 50), "white")),
ttp.pil2tensor(Image.new("RGB", (40, 40), "white")),
],
}
focus_scale, focus_w, focus_h, focus_x, focus_y, focus_info = size_node.estimate_output_size(focus_scale_tile_set, scale_strategy="focus_weighted")
assert_equal(round(focus_scale, 4), 4.0, "focus-weighted output size should prefer high-detail tile scale")
assert_equal([focus_w, focus_h], [400, 200], "focus-weighted output size should report focus-detail resolution")
assert_equal(round(focus_x, 4), 4.0, "focus-weighted x scale should prefer high-detail tile scale")
assert_equal(round(focus_y, 4), 4.0, "focus-weighted y scale should prefer high-detail tile scale")
assert_equal("weight=" in focus_info, True, "focus-weighted info should report semantic weights")
done_scale, done_w, done_h, _done_x, _done_y, _done_info = size_node.estimate_output_size(mixed_scale_tile_set, done=True)
assert_equal(round(done_scale, 4), 3.0, "output size estimate should compute when done is true")
assert_equal([done_w, done_h], [300, 150], "output size estimate done gate should preserve final resolution")
aligned = ttp._ttp_align_pil_to_aspect(Image.new("RGB", (561, 401), "white"), 560, 400, "center_crop")
assert_equal(aligned.size, (560, 400), "assemble alignment should crop/resize drifted sampler output")
resized = ttp._ttp_align_pil_to_aspect(Image.new("RGB", (500, 500), "white"), 560, 400, "resize")
assert_equal(resized.size, (560, 400), "assemble resize alignment should force expected size")
save_final_node = ttp.TTP_Smart_Tile_Save_Final_Image_Experimental()
skip_result = save_final_node.save_final_image(upscaled_tile, done=False)
assert_equal(skip_result, {"ui": {"images": []}}, "save final image should skip intermediate loop frames")
assert_equal(
tiles_meta[0]["overlap_edges_px_source"],
{"left": 0, "right": 64, "top": 0, "bottom": 64},
"top-left grid overlap edges",
)
assert_equal(
tiles_meta[4]["overlap_edges_px_source"],
{"left": 64, "right": 64, "top": 64, "bottom": 64},
"center grid overlap edges",
)
assert_equal(
tiles_meta[8]["overlap_edges_px_source"],
{"left": 64, "right": 0, "top": 64, "bottom": 0},
"bottom-right grid overlap edges",
)
gappy_layout = json.dumps({
"tiles": [
{"name": "left", "x0": 0, "y0": 0, "x1": 0.25, "y1": 1},
{"name": "right", "x0": 0.5, "y0": 0, "x1": 1, "y1": 1},
]
}, separators=(",", ":"))
gappy_normalized = ttp._ttp_interactive_layout_with_defaults(gappy_layout, 64, 32, False)
gappy_meta = ttp._ttp_parse_smart_tile_layout(gappy_normalized, 800, 600)
assert_equal(
[tile["sample_box"] for tile in gappy_meta],
[[0, 0, 200, 600], [400, 0, 400, 600]],
"non-adjacent manual tiles must not overlap across a gap",
)
assert_equal(
[tile["overlap_edges_px_source"] for tile in gappy_meta],
[
{"left": 0, "right": 0, "top": 0, "bottom": 0},
{"left": 0, "right": 0, "top": 0, "bottom": 0},
],
"non-adjacent manual overlap edges",
)
touching_layout = json.dumps({
"tiles": [
{"name": "left", "x0": 0, "y0": 0, "x1": 0.5, "y1": 1},
{"name": "right", "x0": 0.5, "y0": 0, "x1": 1, "y1": 1},
]
}, separators=(",", ":"))
touching_normalized = ttp._ttp_interactive_layout_with_defaults(touching_layout, 64, 32, False)
touching_meta = ttp._ttp_parse_smart_tile_layout(touching_normalized, 800, 600)
assert_equal(
[tile["sample_box"] for tile in touching_meta],
[[0, 0, 464, 600], [336, 0, 464, 600]],
"adjacent manual tiles should overlap only on the shared edge",
)
assert_equal(
[tile["overlap_edges_px_source"] for tile in touching_meta],
[
{"left": 0, "right": 64, "top": 0, "bottom": 0},
{"left": 64, "right": 0, "top": 0, "bottom": 0},
],
"adjacent manual overlap edges",
)
right_bottom_mask = ttp._ttp_create_sample_blend_mask(
432,
328,
{"left": 0, "right": 132, "top": 0, "bottom": 128},
32,
)
assert_equal(float(right_bottom_mask[100, 0, 0]), 1.0, "non-overlap left edge should stay fully weighted")
assert_equal(float(right_bottom_mask[0, 100, 0]), 1.0, "non-overlap top edge should stay fully weighted")
assert_equal(float(right_bottom_mask[100, 300, 0]), 1.0, "right overlap should start at full weight")
assert_equal(float(right_bottom_mask[100, 431, 0]), 0.0, "right overlap outer edge should fade out")
assert_equal(float(right_bottom_mask[200, 100, 0]), 1.0, "bottom overlap should start at full weight")
assert_equal(float(right_bottom_mask[327, 100, 0]), 0.0, "bottom overlap outer edge should fade out")
left_top_mask = ttp._ttp_create_sample_blend_mask(
432,
328,
{"left": 64, "right": 0, "top": 64, "bottom": 0},
32,
)
assert_equal(float(left_top_mask[100, 0, 0]), 0.0, "left overlap outer edge should fade in")
assert_equal(float(left_top_mask[100, 63, 0]), 1.0, "left overlap should reach full weight at the core")
assert_equal(float(left_top_mask[0, 100, 0]), 0.0, "top overlap outer edge should fade in")
assert_equal(float(left_top_mask[63, 100, 0]), 1.0, "top overlap should reach full weight at the core")
assert_equal(float(left_top_mask[100, 431, 0]), 1.0, "non-overlap right edge should stay fully weighted")
assert_equal(float(left_top_mask[327, 100, 0]), 1.0, "non-overlap bottom edge should stay fully weighted")
auto_layout = ttp._ttp_boxes_to_auto_layout(
[{"x": 250, "y": 120, "width": 180, "height": 260, "label": "person", "score": 0.91}],
900,
600,
default_pad=64,
default_blend=32,
object_padding=40,
max_tiles=4,
include_background=True,
allow_object_overlap=True,
)
auto_meta = ttp._ttp_parse_smart_tile_layout(auto_layout, 900, 600)
assert_equal(len(auto_meta), 2, "auto layout should include background and one object tile")
object_tile = auto_meta[1]
ox, oy, ow, oh = object_tile["core_box"]
assert_equal(ox <= 250 and oy <= 120 and ox + ow >= 430 and oy + oh >= 380, True, "auto object tile must contain the source bbox")
assert_equal(object_tile["source"], "auto", "auto layout should preserve source metadata")
assert_equal(object_tile["label"], "person", "auto layout should preserve labels")
assert_equal(object_tile["layer"], 2, "auto object tile should be above background")
assert_equal(object_tile["occlusion_priority"] > auto_meta[0]["occlusion_priority"], True, "object tile should outrank background")
detail_layout = ttp._ttp_boxes_to_auto_layout(
[
{"x": 220, "y": 80, "width": 300, "height": 440, "label": "person", "score": 0.95},
{"x": 315, "y": 120, "width": 92, "height": 86, "label": "face", "score": 0.88},
],
900,
600,
default_pad=64,
default_blend=32,
object_padding=24,
max_tiles=4,
include_background=True,
allow_object_overlap=True,
)
detail_meta = ttp._ttp_parse_smart_tile_layout(detail_layout, 900, 600)
person_tile = next(tile for tile in detail_meta if tile.get("label") == "person")
face_tile = next(tile for tile in detail_meta if tile.get("label") == "face")
assert_equal(face_tile["layer"] > person_tile["layer"], True, "face/detail auto tile should sit above generic object tile")
assert_equal(face_tile["occlusion_priority"] > person_tile["occlusion_priority"], True, "face/detail auto tile should outrank generic object tile")
mask_image = Image.new("L", (900, 600), 0)
for x in range(260, 420):
for y in range(140, 360):
mask_image.putpixel((x, y), 255)
masked_auto_layout = ttp._ttp_boxes_to_auto_layout(
[{"x": 250, "y": 120, "width": 180, "height": 260, "label": "person", "score": 0.91}],
900,
600,
default_pad=64,
default_blend=32,
object_padding=40,
max_tiles=4,
include_background=False,
allow_object_overlap=True,
masks=[mask_image],
)
masked_auto_meta = ttp._ttp_parse_smart_tile_layout(masked_auto_layout, 900, 600)
assert_equal("object_mask" in masked_auto_meta[0], True, "auto layout should preserve SAM object masks")
mask_array = ttp._ttp_tile_object_mask_array(masked_auto_meta[0], masked_auto_meta[0]["sample_box"][2], masked_auto_meta[0]["sample_box"][3], "mask_feather", 32)
assert_equal(mask_array.shape[2], 1, "decoded object mask should be a single-channel weight map")
assert_equal(float(mask_array.max()) > 0.5, True, "decoded object mask should keep foreground weight")
small_expand_mask = Image.new("L", (32, 32), 0)
for x in range(10, 14):
for y in range(10, 14):
small_expand_mask.putpixel((x, y), 255)
small_mask_layout = ttp._ttp_boxes_to_auto_layout(
[{"x": 10, "y": 10, "width": 4, "height": 4, "label": "eye", "score": 0.9}],
32,
32,
object_padding=4,
mask_expand=0,
max_tiles=1,
include_background=False,
masks=[small_expand_mask],
)
expanded_mask_layout = ttp._ttp_boxes_to_auto_layout(
[{"x": 10, "y": 10, "width": 4, "height": 4, "label": "eye", "score": 0.9}],
32,
32,
object_padding=4,
mask_expand=2,
max_tiles=1,
include_background=False,
masks=[small_expand_mask],
)
small_mask_tile = ttp._ttp_parse_smart_tile_layout(small_mask_layout, 32, 32)[0]
expanded_mask_tile = ttp._ttp_parse_smart_tile_layout(expanded_mask_layout, 32, 32)[0]
small_mask_pixels = np.count_nonzero(np.array(ttp._ttp_decode_object_mask_data(small_mask_tile["object_mask"])) > 0)
expanded_mask_pixels = np.count_nonzero(np.array(ttp._ttp_decode_object_mask_data(expanded_mask_tile["object_mask"])) > 0)
assert_equal(expanded_mask_pixels > small_mask_pixels, True, "auto mask expand should dilate object mask foreground pixels")
assert_equal(expanded_mask_tile.get("object_mask_expand"), 2.0, "auto mask expand should be preserved in tile metadata")
large_body_mask = Image.new("L", (900, 600), 255)
large_body_layout = ttp._ttp_boxes_to_auto_layout(
[{"x": 0, "y": 0, "width": 900, "height": 600, "label": "person body", "score": 0.93}],
900,
600,
default_pad=64,
default_blend=32,
object_padding=0,
max_tiles=4,
include_background=False,
allow_object_overlap=True,
masks=[large_body_mask],
)
large_body_meta = ttp._ttp_parse_smart_tile_layout(large_body_layout, 900, 600)
large_body_tile = large_body_meta[0]
assert_equal("large context" in large_body_tile["label"], True, "huge non-detail auto body tiles should be marked as large context")
assert_equal(large_body_tile["layer"], 0, "huge non-detail auto body tiles should stay on the lowest layer")
assert_equal(large_body_tile["occlusion_priority"], 0, "huge non-detail auto body tiles should not outrank face/detail masks")
assert_equal(float(large_body_tile["priority"]) <= 10.0, True, "huge non-detail auto body tiles should keep low composite priority")
assert_equal(float(large_body_tile["importance"]) <= 0.35, True, "huge non-detail auto body tiles should remain a low-weight context layer")
assert_equal("object_mask" in large_body_tile, True, "huge body context tiles should still preserve their body mask")
assert_equal(large_body_tile["recommended_composite_mode"], "context", "huge body context tiles should advertise context compositing")
semantic_mask_layout = json.dumps({
"tiles": [{
"name": "person_grid_1",
"x0": 0.2,
"y0": 0.2,
"x1": 0.5,
"y1": 0.6,
"label": "person",
"semantic_category": "subject",
"recommended_scale_weight": 1.75,
"recommended_composite_mode": "replace",
"recommended_occlusion_priority": 6200,
"object_mask": masked_auto_meta[0]["object_mask"],
"object_mask_source": masked_auto_meta[0]["object_mask"],
}],
}, separators=(",", ":"))
semantic_mask_meta = ttp._ttp_parse_smart_tile_layout(semantic_mask_layout, 900, 600)
assert_equal(semantic_mask_meta[0]["semantic_category"], "subject", "interactive parser should preserve inherited semantic category")
assert_equal(round(semantic_mask_meta[0]["recommended_scale_weight"], 2), 1.75, "interactive parser should preserve inherited scale weight")
assert_equal(semantic_mask_meta[0]["recommended_composite_mode"], "replace", "interactive parser should preserve inherited composite mode")
assert_equal("object_mask" in semantic_mask_meta[0], True, "interactive parser should preserve inherited cropped object mask")
assert_equal("object_mask_source" in semantic_mask_meta[0], True, "interactive parser should preserve parent object mask source for refresh")
paint_mask = Image.new("L", (64, 48), 0)
for x in range(8, 22):
for y in range(6, 20):
paint_mask.putpixel((x, y), 255)
for x in range(42, 56):
for y in range(28, 42):
paint_mask.putpixel((x, y), 255)
paint_buffer = BytesIO()
paint_mask.save(paint_buffer, format="PNG")
paint_payload = json.dumps({
"format": "png_base64",
"width": 64,
"height": 48,
"data": base64.b64encode(paint_buffer.getvalue()).decode("ascii"),
})
decoded_paint = ttp._ttp_decode_interactive_paint_mask(paint_payload, 64, 48)
paint_items, paint_masks = ttp._ttp_paint_mask_to_items(decoded_paint, 64, 48)
assert_equal(len(paint_items), 2, "paint mask should become one bbox per painted island")
paint_layout, paint_message = ttp._ttp_run_paint_mask_auto_layout(
Image.new("RGB", (64, 48), "black"),
paint_payload,
default_pad=8,
default_blend=4,
object_padding=2,
mask_expand=2,
max_tiles=8,
allow_object_overlap=True,
)
paint_meta = ttp._ttp_parse_smart_tile_layout(paint_layout, 64, 48)
assert_equal(len(paint_meta), 2, "paint-only auto tile should create tiles from painted regions")
assert_equal(all(tile.get("object_mask") for tile in paint_meta), True, "paint-created tiles should carry object masks")
assert_equal("paint mask" in paint_message.lower(), True, "paint-only auto tile should report paint mask inference")
half_mask = Image.new("L", (8, 8), 128)
half_mask_data = ttp._ttp_encode_object_mask_data([half_mask], [0], [0, 0, 8, 8])
assemble_node = ttp.TTP_Smart_Tile_Assemble_Experimental()
stack_assemble_tile_set = {
"type": "ttp_smart_tile_set",
"original_size": [8, 8],
"tile_meta": {
"type": "ttp_smart_tile",
"original_size": [8, 8],
"tiles": [
{
"name": f"stack_tile_{index + 1}",
"core_box": [0, 0, 8, 8],
"sample_box": [0, 0, 8, 8],
"tile_canvas_size": [8, 8],
"tile_canvas_box": [0, 0, 8, 8],
"overlap_edges_px_source": {"left": 0, "right": 0, "top": 0, "bottom": 0},
"blend": 0,
"importance": 1.0,
"priority": 0.0,
"layer": 1 if index == 3 else 0,
"occlusion_priority": 80000 if index == 3 else 0,
}
for index in range(4)
],
},
"tile_images": [
ttp.pil2tensor(Image.new("RGB", (8, 8), (220, 20, 20)))[0],
ttp.pil2tensor(Image.new("RGB", (8, 8), (20, 220, 20)))[0],
ttp.pil2tensor(Image.new("RGB", (8, 8), (20, 20, 220)))[0],
ttp.pil2tensor(Image.new("RGB", (8, 8), (220, 220, 20)))[0],
],
}
stack_assemble_set, _stack_assemble_json, _stack_assemble_summary = stack_node.apply_stack_order(
stack_assemble_tile_set,
tile_order="T1,T3,T2",
)
stack_assembled, _stack_assembled_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
base_canvas_mode="black",
auto_composite_policy="safe_auto",
tile_set=stack_assemble_set,
)
stack_center = stack_assembled.array[0, 4, 4]
assert_equal(float(stack_center[0]) > float(stack_center[2]), True, "stack order assemble should put the first listed tile on top by default")
assert_equal(float(stack_center[0]) > float(stack_center[1]), True, "stack order assemble should keep unlisted and later listed tiles behind the first listed tile")
soft_tile_meta = {
"type": "ttp_smart_tile",
"original_size": [8, 8],
"tiles": [{
"name": "soft_object",
"core_box": [0, 0, 8, 8],
"sample_box": [0, 0, 8, 8],
"tile_canvas_size": [8, 8],
"tile_canvas_box": [0, 0, 8, 8],
"overlap_edges_px_source": {"left": 0, "right": 0, "top": 0, "bottom": 0},
"blend": 0,
"importance": 1.0,
"priority": 0.0,
"layer": 2,
"occlusion_priority": 10,
"object_mask": half_mask_data,
}],
}
soft_tile_set = {
"type": "ttp_smart_tile_set",
"tile_meta": soft_tile_meta,
"tile_images": [ttp.pil2tensor(Image.new("RGB", (8, 8), "white"))[0]],
}
soft_output, _soft_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
mask_blend_mode="mask_only",
tile_set=soft_tile_set,
base_image=ttp.pil2tensor(Image.new("RGB", (8, 8), "black")),
)
soft_value = float(soft_output.array[0, 4, 4, 0])
assert_equal(0.45 < soft_value < 0.55, True, "occlusion mask feather should alpha blend with lower pixels instead of hard replacing them")
blue_tile_set = {
"type": "ttp_smart_tile_set",
"tile_meta": soft_tile_meta,
"tile_images": [ttp.pil2tensor(Image.new("RGB", (8, 8), (32, 64, 192)))[0]],
}
red_reference = ttp.pil2tensor(Image.new("RGB", (8, 8), (192, 64, 32)))
color_matched, _color_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
color_correction="local_mean_std",
color_strength=1.0,
mask_blend_mode="mask_only",
tile_set=blue_tile_set,
base_image=red_reference,
color_reference_image=red_reference,
)
matched_pixel = color_matched.array[0, 4, 4]
assert_equal(float(matched_pixel[0]) > float(matched_pixel[2]), True, "local mean/std color correction should match tile color toward the local reference")
black_canvas, _black_canvas_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
mask_blend_mode="mask_only",
base_canvas_mode="black",
tile_set=blue_tile_set,
base_image=red_reference,
)
black_canvas_pixel = black_canvas.array[0, 4, 4]
assert_equal(float(black_canvas_pixel[2]) > float(black_canvas_pixel[0]), True, "black canvas mode should not mix the connected base image into semi-transparent tiles")
black_canvas_color_matched, _black_canvas_color_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
color_correction="local_mean_std",
color_strength=1.0,
mask_blend_mode="mask_only",
base_canvas_mode="black",
tile_set=blue_tile_set,
base_image=red_reference,
)
black_canvas_color_pixel = black_canvas_color_matched.array[0, 4, 4]
assert_equal(float(black_canvas_color_pixel[0]) > float(black_canvas_color_pixel[2]), True, "black canvas mode should still allow base_image color reference")
stack_meta = {
"type": "ttp_smart_tile",
"original_size": [8, 8],
"tiles": [
{
"name": "large_body",
"label": "person body",
"core_box": [0, 0, 8, 8],
"sample_box": [0, 0, 8, 8],
"tile_canvas_size": [8, 8],
"tile_canvas_box": [0, 0, 8, 8],
"overlap_edges_px_source": {"left": 0, "right": 0, "top": 0, "bottom": 0},
"blend": 0,
"importance": 1.0,
"priority": 100.0,
"layer": 2,
"occlusion_priority": 1000,
},
{
"name": "small_face",
"label": "local detail",
"core_box": [2, 2, 4, 4],
"sample_box": [2, 2, 4, 4],
"tile_canvas_size": [4, 4],
"tile_canvas_box": [0, 0, 4, 4],
"overlap_edges_px_source": {"left": 0, "right": 0, "top": 0, "bottom": 0},
"blend": 0,
"importance": 1.0,
"priority": 0.0,
"layer": 0,
"occlusion_priority": 0,
},
],
}
stack_tile_set = {
"type": "ttp_smart_tile_set",
"tile_meta": stack_meta,
"tile_images": [
ttp.pil2tensor(Image.new("RGB", (8, 8), (220, 20, 20)))[0],
ttp.pil2tensor(Image.new("RGB", (4, 4), (20, 20, 220)))[0],
],
}
large_above, _large_above_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
base_canvas_mode="black",
auto_composite_policy="strict_layer",
tile_set=stack_tile_set,
)
large_above_pixel = large_above.array[0, 4, 4]
assert_equal(float(large_above_pixel[0]) > float(large_above_pixel[2]), True, "explicit large tile priority should cover a small tile when small_tile_on_top is disabled")
small_above, _small_above_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
base_canvas_mode="black",
small_tile_on_top=True,
auto_composite_policy="strict_layer",
tile_set=stack_tile_set,
)
small_above_pixel = small_above.array[0, 4, 4]
assert_equal(float(small_above_pixel[2]) > float(small_above_pixel[0]), True, "small_tile_on_top should stack smaller tiles above larger context tiles")
legacy_bad_bool, _legacy_bad_bool_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
base_canvas_mode="black",
small_tile_on_top="safe_auto",
auto_composite_policy="strict_layer",
tile_set=stack_tile_set,
)
legacy_bad_bool_pixel = legacy_bad_bool.array[0, 4, 4]
assert_equal(float(legacy_bad_bool_pixel[0]) > float(legacy_bad_bool_pixel[2]), True, "legacy string values should not be treated as small_tile_on_top=true")
fingerprint_mask_a = Image.new("L", (8, 8), 0)
ImageDraw.Draw(fingerprint_mask_a).rectangle((0, 0, 3, 7), fill=255)
fingerprint_mask_b = Image.new("L", (8, 8), 0)
ImageDraw.Draw(fingerprint_mask_b).rectangle((4, 0, 7, 7), fill=255)
fingerprint_source_a = ttp._ttp_encode_object_mask_data([fingerprint_mask_a], [0], [0, 0, 8, 8])
fingerprint_source_b = ttp._ttp_encode_object_mask_data([fingerprint_mask_b], [0], [0, 0, 8, 8])
fingerprint_tile = {
"name": "context_grid_1",
"label": "large context",
"core_box": [0, 0, 8, 8],
"sample_box": [0, 0, 8, 8],
"object_mask": fingerprint_source_a,
"object_mask_source": fingerprint_source_a,
"layer": 0,
"occlusion_priority": 0,
}
fingerprint_tile_set_a = {
"type": "ttp_smart_tile_set",
"original_size": [8, 8],
"tile_meta": {"type": "ttp_smart_tile", "original_size": [8, 8], "tiles": [fingerprint_tile]},
"tile_images": [None],
}
fingerprint_tile_b = dict(fingerprint_tile)
fingerprint_tile_b["object_mask_source"] = fingerprint_source_b
fingerprint_tile_set_b = {
"type": "ttp_smart_tile_set",
"original_size": [8, 8],
"tile_meta": {"type": "ttp_smart_tile", "original_size": [8, 8], "tiles": [fingerprint_tile_b]},
"tile_images": [None],
}
assert_equal(
ttp._ttp_tile_set_fingerprint(fingerprint_tile_set_a) != ttp._ttp_tile_set_fingerprint(fingerprint_tile_set_b),
True,
"loop fingerprint should change when object_mask_source changes",
)
feather_stack_meta = {
"type": "ttp_smart_tile",
"original_size": [16, 16],
"tiles": [
{
"name": "background",
"label": "background",
"core_box": [0, 0, 16, 16],
"sample_box": [0, 0, 16, 16],
"tile_canvas_size": [16, 16],
"tile_canvas_box": [0, 0, 16, 16],
"overlap_edges_px_source": {"left": 0, "right": 0, "top": 0, "bottom": 0},
"blend": 0,
"importance": 1.0,
"priority": 0.0,
"layer": 0,
"occlusion_priority": 0,
},
{
"name": "face_detail",
"label": "face detail",
"core_box": [4, 4, 8, 8],
"sample_box": [4, 4, 8, 8],
"tile_canvas_size": [8, 8],
"tile_canvas_box": [0, 0, 8, 8],
"overlap_edges_px_source": {"left": 0, "right": 0, "top": 0, "bottom": 0},
"blend": 4,
"importance": 1.0,
"priority": 0.0,
"layer": 2,
"occlusion_priority": 1000,
},
],
}
feather_tile_set = {
"type": "ttp_smart_tile_set",
"tile_meta": feather_stack_meta,
"tile_images": [
ttp.pil2tensor(Image.new("RGB", (16, 16), (220, 20, 20)))[0],
ttp.pil2tensor(Image.new("RGB", (8, 8), (20, 20, 220)))[0],
],
}
feathered_stack, _feathered_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
base_canvas_mode="black",
mask_blend_mode="mask_feather",
tile_set=feather_tile_set,
)
feather_edge_pixel = feathered_stack.array[0, 4, 8]
feather_center_pixel = feathered_stack.array[0, 8, 8]
assert_equal(float(feather_edge_pixel[0]) > float(feather_edge_pixel[2]), True, "focus tile fallback feather should keep lower pixels visible at the rectangle edge")
assert_equal(float(feather_center_pixel[2]) > float(feather_center_pixel[0]), True, "focus tile fallback feather should keep the focus tile strong in the center")
eye_mask_full = Image.new("L", (16, 16), 0)
eye_mask_tile = Image.new("L", (8, 8), 128)
ImageDraw.Draw(eye_mask_tile).rectangle((2, 2, 5, 5), fill=255)
eye_mask_full.paste(eye_mask_tile, (4, 4))
eye_mask_data = ttp._ttp_encode_object_mask_data([eye_mask_full], [0], [4, 4, 12, 12])
eye_stack_meta = {
"type": "ttp_smart_tile",
"original_size": [16, 16],
"tiles": [
{
"name": "face_base",
"label": "face",
"core_box": [0, 0, 16, 16],
"sample_box": [0, 0, 16, 16],
"tile_canvas_size": [16, 16],
"tile_canvas_box": [0, 0, 16, 16],
"overlap_edges_px_source": {"left": 0, "right": 0, "top": 0, "bottom": 0},
"blend": 0,
"importance": 1.0,
"priority": 0.0,
"layer": 1,
"occlusion_priority": 100,
},
{
"name": "eyes_detail",
"label": "eyes glasses",
"core_box": [4, 4, 8, 8],
"sample_box": [4, 4, 8, 8],
"tile_canvas_size": [8, 8],
"tile_canvas_box": [0, 0, 8, 8],
"overlap_edges_px_source": {"left": 0, "right": 0, "top": 0, "bottom": 0},
"blend": 0,
"importance": 1.0,
"priority": 0.0,
"layer": 4,
"occlusion_priority": 2000,
"object_mask": eye_mask_data,
},
],
}
eye_tile_set = {
"type": "ttp_smart_tile_set",
"tile_meta": eye_stack_meta,
"tile_images": [
ttp.pil2tensor(Image.new("RGB", (16, 16), (220, 20, 20)))[0],
ttp.pil2tensor(Image.new("RGB", (8, 8), (20, 20, 220)))[0],
],
}
strict_eye, _strict_eye_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
base_canvas_mode="black",
mask_blend_mode="mask_feather",
auto_composite_policy="strict_layer",
tile_set=eye_tile_set,
)
safe_eye, _safe_eye_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
base_canvas_mode="black",
mask_blend_mode="mask_feather",
auto_composite_policy="safe_auto",
tile_set=eye_tile_set,
)
strict_eye_edge = strict_eye.array[0, 4, 8]
safe_eye_edge = safe_eye.array[0, 4, 8]
safe_eye_center = safe_eye.array[0, 8, 8]
assert_equal(float(safe_eye_edge[0]) > float(strict_eye_edge[0]), True, "safe auto detail overlay should preserve more face color at eye mask edges")
assert_equal(float(safe_eye_center[2]) > float(safe_eye_center[0]), True, "safe auto detail overlay should keep eye details stronger at the mask center")
replace_mask_full = Image.new("L", (16, 16), 0)
ImageDraw.Draw(replace_mask_full).rectangle((7, 7, 8, 8), fill=255)
replace_mask_data = ttp._ttp_encode_object_mask_data([replace_mask_full], [0], [4, 4, 12, 12])
replace_mask_tile_set = {
"type": "ttp_smart_tile_set",
"original_size": [16, 16],
"tile_meta": {
"type": "ttp_smart_tile",
"original_size": [16, 16],
"tiles": [
{
"name": "masked_edit",
"label": "paint mask edit",
"core_box": [4, 4, 8, 8],
"sample_box": [4, 4, 8, 8],
"tile_canvas_size": [8, 8],
"tile_canvas_box": [0, 0, 8, 8],
"overlap_edges_px_source": {"left": 0, "right": 0, "top": 0, "bottom": 0},
"blend": 0,
"importance": 1.0,
"priority": 0.0,
"layer": 0,
"occlusion_priority": 0,
"object_mask": replace_mask_data,
"composite_mode": "replace_tile",
},
],
},
"tile_images": [
ttp.pil2tensor(Image.new("RGB", (8, 8), (20, 20, 220)))[0],
],
}
replace_source = ttp.pil2tensor(Image.new("RGB", (16, 16), (220, 20, 20)))
replace_masked, _replace_masked_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
base_canvas_mode="source_image",
mask_blend_mode="off",
auto_composite_policy="replace_tile",
source_image=replace_source,
tile_set=replace_mask_tile_set,
)
replace_mask_corner = replace_masked.array[0, 4, 4]
replace_mask_center = replace_masked.array[0, 8, 8]
assert_equal(float(replace_mask_corner[0]) > float(replace_mask_corner[2]), True, "replace_tile must preserve source pixels outside an object mask instead of pasting a rectangle")
assert_equal(float(replace_mask_center[2]) > float(replace_mask_center[0]), True, "replace_tile must hard replace pixels inside an object mask")
replace_boxed, _replace_boxed_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
base_canvas_mode="source_image",
mask_blend_mode="off",
auto_composite_policy="replace_tile",
replace_tile_shape="tile_box_first",
source_image=replace_source,
tile_set=replace_mask_tile_set,
)
replace_box_corner = replace_boxed.array[0, 4, 4]
replace_box_center = replace_boxed.array[0, 8, 8]
assert_equal(float(replace_box_corner[2]) > float(replace_box_corner[0]), True, "replace_tile tile_box_first should paste the full tile rectangle even when an object mask exists")
assert_equal(float(replace_box_center[2]) > float(replace_box_center[0]), True, "replace_tile tile_box_first should still replace the object center")
replace_soft_mask_full = Image.new("L", (16, 16), 0)
ImageDraw.Draw(replace_soft_mask_full).rectangle((4, 4, 11, 11), fill=128)
replace_soft_mask_data = ttp._ttp_encode_object_mask_data([replace_soft_mask_full], [0], [4, 4, 12, 12])
replace_soft_mask_tile_set = {
"type": "ttp_smart_tile_set",
"original_size": [16, 16],
"tile_meta": {
"type": "ttp_smart_tile",
"original_size": [16, 16],
"tiles": [
{
"name": "soft_masked_edit",
"label": "paint mask edit",
"core_box": [4, 4, 8, 8],
"sample_box": [4, 4, 8, 8],
"tile_canvas_size": [8, 8],
"tile_canvas_box": [0, 0, 8, 8],
"overlap_edges_px_source": {"left": 0, "right": 0, "top": 0, "bottom": 0},
"blend": 0,
"importance": 1.0,
"priority": 0.0,
"layer": 0,
"occlusion_priority": 0,
"object_mask": replace_soft_mask_data,
"composite_mode": "replace_tile",
},
],
},
"tile_images": [
ttp.pil2tensor(Image.new("RGB", (8, 8), (20, 20, 220)))[0],
],
}
replace_soft_masked, _replace_soft_masked_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
base_canvas_mode="source_image",
mask_blend_mode="off",
auto_composite_policy="replace_tile",
source_image=replace_source,
tile_set=replace_soft_mask_tile_set,
)
replace_soft_center = replace_soft_masked.array[0, 8, 8]
assert_equal(float(replace_soft_center[2]) > 0.8, True, "replace_tile mask_first should harden soft mask interiors instead of making the whole edit translucent")
assert_equal(float(replace_soft_center[0]) < 0.2, True, "replace_tile mask_first should remove source pixels from soft mask interiors")
replace_own_mask_full = Image.new("L", (16, 16), 0)
ImageDraw.Draw(replace_own_mask_full).rectangle((7, 7, 8, 8), fill=255)
replace_source_mask_full = Image.new("L", (16, 16), 0)
ImageDraw.Draw(replace_source_mask_full).rectangle((4, 4, 11, 11), fill=255)
replace_own_mask_data = ttp._ttp_encode_object_mask_data([replace_own_mask_full], [0], [4, 4, 12, 12])
replace_source_mask_data = ttp._ttp_encode_object_mask_data([replace_source_mask_full], [0], [4, 4, 12, 12])
replace_own_mask_tile_set = {
"type": "ttp_smart_tile_set",
"original_size": [16, 16],
"tile_meta": {
"type": "ttp_smart_tile",
"original_size": [16, 16],
"tiles": [
{
"name": "own_mask_edit",
"label": "paint mask edit",
"core_box": [4, 4, 8, 8],
"sample_box": [4, 4, 8, 8],
"tile_canvas_size": [8, 8],
"tile_canvas_box": [0, 0, 8, 8],
"overlap_edges_px_source": {"left": 0, "right": 0, "top": 0, "bottom": 0},
"blend": 0,
"importance": 1.0,
"priority": 0.0,
"layer": 0,
"occlusion_priority": 0,
"object_mask": replace_own_mask_data,
"object_mask_source": replace_source_mask_data,
"composite_mode": "replace_tile",
},
],
},
"tile_images": [
ttp.pil2tensor(Image.new("RGB", (8, 8), (20, 20, 220)))[0],
],
}
replace_own_masked, _replace_own_masked_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
base_canvas_mode="source_image",
mask_blend_mode="off",
auto_composite_policy="replace_tile",
source_image=replace_source,
tile_set=replace_own_mask_tile_set,
)
replace_source_only_pixel = replace_own_masked.array[0, 4, 4]
replace_own_center_pixel = replace_own_masked.array[0, 8, 8]
assert_equal(float(replace_source_only_pixel[0]) > float(replace_source_only_pixel[2]), True, "replace_tile mask_first should prefer the tile's own cropped object mask over a broader source mask")
assert_equal(float(replace_own_center_pixel[2]) > float(replace_own_center_pixel[0]), True, "replace_tile mask_first should still replace pixels inside the tile's own mask")
subdivided_mask_full = Image.new("L", (64, 64), 0)
ImageDraw.Draw(subdivided_mask_full).rectangle((24, 24, 39, 39), fill=255)
subdivided_mask_data = ttp._ttp_encode_object_mask_data([subdivided_mask_full], [0], [24, 24, 40, 40])
subdivided_detail_tile = {
"name": "object_5_second_grid_child",
"label": "object_5",
"core_box": [24, 24, 16, 16],
"sample_box": [8, 8, 48, 48],
"tile_canvas_size": [48, 48],
"tile_canvas_box": [0, 0, 48, 48],
"overlap_edges_px_source": {"left": 16, "right": 16, "top": 16, "bottom": 16},
"blend": 0,
"importance": 1.0,
"priority": 0.0,
"layer": 4,
"occlusion_priority": 2000,
"object_mask": subdivided_mask_data,
"object_mask_source": subdivided_mask_data,
}
subdivided_meta = {
"type": "ttp_smart_tile",
"original_size": [64, 64],
"tiles": [
{
"name": "face_base",
"label": "face",
"core_box": [0, 0, 64, 64],
"sample_box": [0, 0, 64, 64],
"tile_canvas_size": [64, 64],
"tile_canvas_box": [0, 0, 64, 64],
"overlap_edges_px_source": {"left": 0, "right": 0, "top": 0, "bottom": 0},
"blend": 0,
"importance": 1.0,
"priority": 0.0,
"layer": 1,
"occlusion_priority": 100,
},
subdivided_detail_tile,
],
}
subdivided_tile_set = {
"type": "ttp_smart_tile_set",
"tile_meta": subdivided_meta,
"tile_images": [
ttp.pil2tensor(Image.new("RGB", (64, 64), (220, 20, 20)))[0],
ttp.pil2tensor(Image.new("RGB", (48, 48), (20, 20, 220)))[0],
],
}
assert_equal(ttp._ttp_tile_area_ratio(subdivided_detail_tile, 64 * 64) < 0.07, True, "subdivided detail ranking should use core area instead of padded sample area")
subdivided_mask_array = ttp._ttp_tile_object_mask_array(subdivided_detail_tile, 48, 48, "mask_feather", 0)
assert_equal(0.0 < float(subdivided_mask_array[16, 15, 0]) < 1.0, True, "mask_feather should soften subdivided object masks even when blend is zero")
strict_subdivided, _strict_subdivided_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
base_canvas_mode="black",
mask_blend_mode="mask_feather",
auto_composite_policy="strict_layer",
tile_set=subdivided_tile_set,
)
safe_subdivided, _safe_subdivided_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
base_canvas_mode="black",
mask_blend_mode="mask_feather",
auto_composite_policy="safe_auto",
tile_set=subdivided_tile_set,
)
strict_subdivided_edge = strict_subdivided.array[0, 32, 24]
safe_subdivided_edge = safe_subdivided.array[0, 32, 24]
safe_subdivided_center = safe_subdivided.array[0, 32, 32]
assert_equal(float(safe_subdivided_edge[0]) > float(strict_subdivided_edge[0]), True, "safe auto should not let a repeatedly subdivided masked tile cut a hard hole in the lower face tile")
assert_equal(float(safe_subdivided_center[2]) > float(safe_subdivided_center[0]), True, "safe auto should still keep the subdivided detail strong at the mask center")
split_body_mask = Image.new("L", (64, 32), 255)
split_body_source_mask = ttp._ttp_encode_object_mask_data([split_body_mask], [0], [0, 0, 64, 32])
split_body_left_mask = ttp._ttp_encode_object_mask_data([split_body_mask], [0], [0, 0, 32, 32])
split_body_right_mask = ttp._ttp_encode_object_mask_data([split_body_mask], [0], [32, 0, 64, 32])
split_body_left_tile = {
"name": "body_context_grid_1",
"label": "person body large context",
"source": "auto_context",
"core_box": [0, 0, 32, 32],
"sample_box": [0, 0, 40, 32],
"tile_canvas_size": [40, 32],
"tile_canvas_box": [0, 0, 40, 32],
"overlap_edges_px_source": {"left": 0, "right": 8, "top": 0, "bottom": 0},
"blend": 8,
"importance": 0.35,
"priority": 10.0,
"layer": 0,
"occlusion_priority": 0,
"recommended_composite_mode": "context",
"object_mask": split_body_left_mask,
"object_mask_source": split_body_source_mask,
}
split_body_right_tile = {
"name": "body_context_grid_2",
"label": "person body large context",
"source": "auto_context",
"core_box": [32, 0, 32, 32],
"sample_box": [24, 0, 40, 32],
"tile_canvas_size": [40, 32],
"tile_canvas_box": [0, 0, 40, 32],
"overlap_edges_px_source": {"left": 8, "right": 0, "top": 0, "bottom": 0},
"blend": 8,
"importance": 0.35,
"priority": 10.0,
"layer": 0,
"occlusion_priority": 0,
"recommended_composite_mode": "context",
"object_mask": split_body_right_mask,
"object_mask_source": split_body_source_mask,
}
split_source_mask_array = ttp._ttp_tile_object_mask_array(split_body_left_tile, 40, 32, "mask_feather", 8)
assert_equal(float(split_source_mask_array[16, 36, 0]) > 0.5, True, "subdivided context masks should use source masks so overlap regions stay available")
split_body_tile_set = {
"type": "ttp_smart_tile_set",
"tile_meta": {
"type": "ttp_smart_tile",
"original_size": [64, 32],
"tiles": [split_body_left_tile, split_body_right_tile],
},
"tile_images": [
ttp.pil2tensor(Image.new("RGB", (40, 32), (90, 90, 90)))[0],
ttp.pil2tensor(Image.new("RGB", (40, 32), (90, 90, 90)))[0],
],
}
_split_body_output, split_body_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
base_canvas_mode="black",
mask_blend_mode="mask_feather",
auto_composite_policy="safe_auto",
tile_set=split_body_tile_set,
)
split_body_single_weight = float(split_body_weights.array[0, 16, 16, 0])
split_body_seam_weight = float(split_body_weights.array[0, 16, 32, 0])
assert_equal(split_body_seam_weight > split_body_single_weight + 0.2, True, "subdivided context mask seams should show real overlap blending")
fractional_scale_tile_set = {
"type": "ttp_smart_tile_set",
"tile_meta": {
"type": "ttp_smart_tile",
"original_size": [3, 1],
"tiles": [
{
"name": f"column_{index}",
"label": "grid",
"core_box": [index, 0, 1, 1],
"sample_box": [index, 0, 1, 1],
"tile_canvas_size": [1, 1],
"tile_canvas_box": [0, 0, 1, 1],
"overlap_edges_px_source": {"left": 0, "right": 0, "top": 0, "bottom": 0},
"blend": 0,
"importance": 1.0,
"priority": 0.0,
}
for index in range(3)
],
},
"tile_images": [
ttp.pil2tensor(Image.new("RGB", (1, 1), (index * 80, index * 80, index * 80)))[0]
for index in range(3)
],
}
_fractional_output, fractional_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.4,
use_priority=False,
base_canvas_mode="black",
weight_preview_mode="coverage",
tile_set=fractional_scale_tile_set,
)
assert_equal(list(fractional_weights.shape), [1, 1, 4, 3], "fractional output scale should round the full canvas size")
assert_equal(float(fractional_weights.array.min()), 1.0, "fractional output scale should not leave rounded tile boundary gaps")
fractional_edges = ttp._ttp_scaled_overlap_edges([1, 0, 3, 1], {"left": 1, "right": 0, "top": 0, "bottom": 0}, 1.4, 5, 1)
assert_equal(fractional_edges["left"], 2, "fractional overlap edges should use source endpoints instead of independent length rounding")
deferred_output, deferred_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
assemble_mode="final_only",
done=False,
tile_set=blue_tile_set,
base_image=red_reference,
)
deferred_pixel = deferred_output.array[0, 4, 4]
assert_equal(float(deferred_pixel[0]) > float(deferred_pixel[2]), True, "final-only assemble should return the base preview before loop completion")
assert_equal(float(deferred_weights.array.max()), 0.0, "final-only assemble should skip weight drawing before loop completion")
forced_final_output, forced_final_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=1.0,
use_priority=True,
assemble_mode="always",
pixel_alignment="edge_match",
done=False,
tile_set=blue_tile_set,
base_image=red_reference,
)
forced_pixel = forced_final_output.array[0, 4, 4]
assert_equal(float(forced_pixel[0]) > float(forced_pixel[2]), True, "pixel alignment should force final-only preview during unfinished loops")
assert_equal(float(forced_final_weights.array.max()), 0.0, "pixel alignment forced final-only mode should skip intermediate weights")
base_scale_meta = {
"type": "ttp_smart_tile",
"original_size": [8, 8],
"tiles": [{
"name": "large_context",
"label": "large context tile",
"core_box": [0, 0, 8, 8],
"sample_box": [0, 0, 8, 8],
"tile_canvas_size": [8, 8],
"tile_canvas_box": [0, 0, 8, 8],
"overlap_edges_px_source": {"left": 0, "right": 0, "top": 0, "bottom": 0},
"blend": 0,
"importance": 1.0,
"priority": 100.0,
"layer": 2,
"occlusion_priority": 100,
}],
}
base_canvas = ttp.pil2tensor(Image.new("RGB", (16, 16), (200, 20, 20)))
low_res_context = {
"type": "ttp_smart_tile_set",
"tile_meta": base_scale_meta,
"tile_images": [ttp.pil2tensor(Image.new("RGB", (8, 8), (20, 20, 220)))[0]],
}
kept_base, _kept_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=0.0,
use_priority=True,
tile_set=low_res_context,
base_image=base_canvas,
large_tile_policy="use_if_higher_resolution",
context_tile_weight=0.0,
)
assert_equal(list(kept_base.shape), [1, 16, 16, 3], "base_image should set the inferred assemble output size")
kept_pixel = kept_base.array[0, 8, 8]
assert_equal(float(kept_pixel[0]) > 0.75 and float(kept_pixel[2]) < 0.1, True, "low-resolution large tiles should not replace a higher-resolution base canvas")
high_res_context = {
"type": "ttp_smart_tile_set",
"tile_meta": base_scale_meta,
"tile_images": [ttp.pil2tensor(Image.new("RGB", (16, 16), (20, 20, 220)))[0]],
}
used_high_res, _used_weights = assemble_node.assemble_tiles(
blend_multiplier=1.0,
output_scale=0.0,
use_priority=True,
tile_set=high_res_context,
base_image=base_canvas,
large_tile_policy="use_if_higher_resolution",
context_tile_weight=0.0,
)
used_pixel = used_high_res.array[0, 8, 8]
assert_equal(float(used_pixel[2]) > 0.75 and float(used_pixel[0]) < 0.1, True, "higher-resolution large tiles should be allowed to replace the base canvas")
reference = np.zeros((16, 16, 3), dtype=np.float32)
for y in range(16):
for x in range(16):
reference[y, x, :] = [(x + y) / 30.0, x / 15.0, y / 15.0]
shifted_region = reference[5:13, 6:14].copy()
alignment_mask = np.ones((8, 8, 1), dtype=np.float32)
alignment_weights = np.ones((16, 16, 1), dtype=np.float32)
dx, dy = ttp._ttp_find_pixel_alignment_offset(
shifted_region,
reference,
alignment_weights,
4,
4,
alignment_mask,
radius=4,
mode="mask_edge_match",
)
assert_equal((dx, dy), (2, 1), "pixel alignment should find the best local offset from surrounding pixels")
auto_dx, auto_dy, auto_info = ttp._ttp_find_pixel_alignment_offset_auto(
shifted_region,
reference,
alignment_weights,
4,
4,
alignment_mask,
radius=4,
mode="mask_edge_match",
device_mode="cpu",
)
assert_equal((auto_dx, auto_dy), (2, 1), "pixel alignment auto wrapper should preserve the CPU result when forced to CPU")
assert_equal(auto_info["device"], "cpu", "pixel alignment info should report forced CPU execution")
fallback_layout = ttp._ttp_boxes_to_auto_layout([], 900, 600, max_tiles=4, include_background=False)
fallback_meta = ttp._ttp_parse_smart_tile_layout(fallback_layout, 900, 600)
assert_equal(len(fallback_meta), 4, "auto fallback should produce a 2x2 layout")
print("smoke_smart_tile_grid_crop: ok")