153 lines
6.0 KiB
Python
153 lines
6.0 KiB
Python
"""
|
|
Box Reinsert Node - Nodo per rimettere l'immagine generata nel punto originale
|
|
"""
|
|
|
|
import json
|
|
import torch
|
|
import numpy as np
|
|
from PIL import Image
|
|
|
|
|
|
class BoxReinsertNode:
|
|
"""
|
|
Nodo che rimette l'immagine generata nel punto originale.
|
|
|
|
Workflow:
|
|
1. BoxSelector → estrae una regione (metadata con x1, x2, y1, y2)
|
|
2. BoxCrop → ritaglia la regione
|
|
3. BoxResize → ridimensiona per generazione (metadata con scale info)
|
|
4. [Generazione AI] → produce immagine generata
|
|
5. BoxReinsert → annulla il resize e rimette nel punto originale
|
|
|
|
Input:
|
|
- original_image: Immagine originale intera
|
|
- generated_image: Immagine generata (ridimensionata)
|
|
- box_metadata: Metadata dal BoxSelector (coordinate selezione)
|
|
- resize_metadata: Metadata dal BoxResize (scale info)
|
|
|
|
Output:
|
|
- image: Immagine originale con generated_image rimessa nel posto corretto
|
|
"""
|
|
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"original_image": ("IMAGE",),
|
|
"generated_image": ("IMAGE",),
|
|
"box_metadata": ("STRING",),
|
|
"resize_metadata": ("STRING",),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
RETURN_NAMES = ("image",)
|
|
FUNCTION = "reinsert_image"
|
|
CATEGORY = "image/box"
|
|
|
|
def reinsert_image(self, original_image, generated_image, box_metadata, resize_metadata):
|
|
"""
|
|
Rimette l'immagine generata nel punto originale.
|
|
|
|
Se resize_metadata è vuoto, bypassa il resize e rimette direttamente l'immagine generata.
|
|
|
|
Args:
|
|
original_image: Immagine originale (B, H, W, C)
|
|
generated_image: Immagine generata/elaborata (B, H, W, C)
|
|
box_metadata: JSON metadata dal BoxSelector con x1, x2, y1, y2
|
|
resize_metadata: JSON metadata dal BoxResize con scale_x, scale_y (opzionale)
|
|
|
|
Returns:
|
|
(final_image,): Immagine con generated_image rimessa nel punto corretto
|
|
"""
|
|
|
|
try:
|
|
box_meta = json.loads(box_metadata)
|
|
except json.JSONDecodeError:
|
|
print("[BoxReinsertNode] Invalid box_metadata JSON")
|
|
return (original_image,)
|
|
|
|
# Prova a leggere resize_metadata
|
|
resize_meta = {}
|
|
use_resize = False
|
|
if resize_metadata and resize_metadata.strip() and resize_metadata != "{}":
|
|
try:
|
|
resize_meta = json.loads(resize_metadata)
|
|
use_resize = True
|
|
except json.JSONDecodeError:
|
|
print("[BoxReinsertNode] Invalid resize_metadata JSON, bypassing resize")
|
|
use_resize = False
|
|
|
|
# Estrai coordinate della selezione originale
|
|
x1 = box_meta.get("x1", 0)
|
|
x2 = box_meta.get("x2", 0)
|
|
y1 = box_meta.get("y1", 0)
|
|
y2 = box_meta.get("y2", 0)
|
|
|
|
# Applica fattore di scala se la preview era stata scalata
|
|
display_scale_factor = box_meta.get("displayScaleFactor", 1.0)
|
|
if display_scale_factor and display_scale_factor != 1.0:
|
|
# Se le coordinate sono state prese da una preview scalata,
|
|
# dividi per il fattore di scala per ottenere le coordinate originali
|
|
x1 = x1 / display_scale_factor
|
|
x2 = x2 / display_scale_factor
|
|
y1 = y1 / display_scale_factor
|
|
y2 = y2 / display_scale_factor
|
|
print(f"[BoxReinsertNode] Scale factor detected: {display_scale_factor}x. Adjusted coordinates.")
|
|
|
|
# Normalizza coordinate (assicura che x1 < x2, y1 < y2)
|
|
box_x_start = int(round(min(x1, x2)))
|
|
box_x_end = int(round(max(x1, x2)))
|
|
box_y_start = int(round(min(y1, y2)))
|
|
box_y_end = int(round(max(y1, y2)))
|
|
|
|
crop_width = box_x_end - box_x_start
|
|
crop_height = box_y_end - box_y_start
|
|
|
|
# Converti immagini a PIL con casting esplicito
|
|
# original_image es (B, H, W, C)
|
|
original_np = (original_image[0].detach().cpu().numpy() * 255).round().astype(np.uint8)
|
|
original_pil = Image.fromarray(original_np)
|
|
|
|
generated_np = (generated_image[0].detach().cpu().numpy() * 255).round().astype(np.uint8)
|
|
generated_pil = Image.fromarray(generated_np)
|
|
|
|
print(f"[BoxReinsertNode] Generated image size: {generated_pil.size}")
|
|
print(f"[BoxReinsertNode] Target crop size: {crop_width}x{crop_height}")
|
|
print(f"[BoxReinsertNode] Use resize: {use_resize}")
|
|
|
|
# Step 1: Ridimensiona
|
|
if use_resize:
|
|
generated_resized = generated_pil.resize((crop_width, crop_height), Image.Resampling.LANCZOS)
|
|
else:
|
|
generated_resized = generated_pil
|
|
|
|
# Step 2: Rimetti nel punto originale
|
|
final_image = original_pil.copy()
|
|
final_image.paste(generated_resized, (box_x_start, box_y_start))
|
|
|
|
# Converti back a tensor con casting sicuro per NumPy 2.0
|
|
final_np_array = np.array(final_image, dtype=np.float32) / 255.0
|
|
final_tensor = torch.from_numpy(final_np_array).unsqueeze(0)
|
|
|
|
# Assicurati che il tensor abbia le giuste dimensioni
|
|
channels = original_image.shape[3]
|
|
if final_tensor.shape[-1] != channels:
|
|
if channels == 4 and final_tensor.shape[-1] == 3:
|
|
alpha = torch.ones((final_tensor.shape[0], final_tensor.shape[1], final_tensor.shape[2], 1))
|
|
final_tensor = torch.cat([final_tensor, alpha], dim=-1)
|
|
elif channels == 3 and final_tensor.shape[-1] == 4:
|
|
final_tensor = final_tensor[:, :, :, :3]
|
|
|
|
print(f"[BoxReinsertNode] Reinserted generated image at position ({box_x_start}, {box_y_start})")
|
|
print(f"[BoxReinsertNode] Final image size: {final_tensor.shape[2]}x{final_tensor.shape[1]}")
|
|
|
|
return (final_tensor,)
|
|
|
|
|
|
NODE_CLASS_MAPPINGS = {"BoxReinsert": BoxReinsertNode}
|
|
NODE_DISPLAY_NAME_MAPPINGS = {"BoxReinsert": "🎨 BoxReinsert"}
|