import json import base64 from io import BytesIO from pathlib import Path import sys import types import numpy as np from PIL import Image 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}") 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_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() 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("small_tile_on_top" in assemble_inputs["required"], True, "assemble should expose small tile top stacking") 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", ) 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", ) 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") 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:\nhidden reasoning```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") 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") 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") 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, 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() 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, 6, 6], "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", 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, 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") 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") 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")