|
import os
|
|
import sys
|
|
|
|
import impact.impact_server
|
|
from nodes import MAX_RESOLUTION
|
|
|
|
from impact.utils import *
|
|
from . import core
|
|
from .core import SEG
|
|
import impact.utils as utils
|
|
from . import defs
|
|
from . import segs_upscaler
|
|
from comfy.cli_args import args
|
|
import math
|
|
|
|
|
|
class SEGSDetailer:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"image": ("IMAGE", ),
|
|
"segs": ("SEGS", ),
|
|
"guide_size": ("FLOAT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}),
|
|
"guide_size_for": ("BOOLEAN", {"default": True, "label_on": "bbox", "label_off": "crop_region"}),
|
|
"max_size": ("FLOAT", {"default": 768, "min": 64, "max": MAX_RESOLUTION, "step": 8}),
|
|
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
|
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
|
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
|
|
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
|
|
"scheduler": (core.SCHEDULERS,),
|
|
"denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}),
|
|
"noise_mask": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
|
|
"force_inpaint": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
|
|
"basic_pipe": ("BASIC_PIPE",),
|
|
"refiner_ratio": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0}),
|
|
"batch_size": ("INT", {"default": 1, "min": 1, "max": 100}),
|
|
|
|
"cycle": ("INT", {"default": 1, "min": 1, "max": 10, "step": 1}),
|
|
},
|
|
"optional": {
|
|
"refiner_basic_pipe_opt": ("BASIC_PIPE",),
|
|
"inpaint_model": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
|
"noise_mask_feather": ("INT", {"default": 20, "min": 0, "max": 100, "step": 1}),
|
|
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("SEGS", "IMAGE")
|
|
RETURN_NAMES = ("segs", "cnet_images")
|
|
OUTPUT_IS_LIST = (False, True)
|
|
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Detailer"
|
|
|
|
@staticmethod
|
|
def do_detail(image, segs, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler,
|
|
denoise, noise_mask, force_inpaint, basic_pipe, refiner_ratio=None, batch_size=1, cycle=1,
|
|
refiner_basic_pipe_opt=None, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None):
|
|
|
|
model, clip, vae, positive, negative = basic_pipe
|
|
if refiner_basic_pipe_opt is None:
|
|
refiner_model, refiner_clip, refiner_positive, refiner_negative = None, None, None, None
|
|
else:
|
|
refiner_model, refiner_clip, _, refiner_positive, refiner_negative = refiner_basic_pipe_opt
|
|
|
|
segs = core.segs_scale_match(segs, image.shape)
|
|
|
|
new_segs = []
|
|
cnet_pil_list = []
|
|
|
|
for i in range(batch_size):
|
|
seed += 1
|
|
for seg in segs[1]:
|
|
cropped_image = seg.cropped_image if seg.cropped_image is not None \
|
|
else crop_ndarray4(image.numpy(), seg.crop_region)
|
|
cropped_image = to_tensor(cropped_image)
|
|
|
|
is_mask_all_zeros = (seg.cropped_mask == 0).all().item()
|
|
if is_mask_all_zeros:
|
|
print(f"Detailer: segment skip [empty mask]")
|
|
new_segs.append(seg)
|
|
continue
|
|
|
|
if noise_mask:
|
|
cropped_mask = seg.cropped_mask
|
|
else:
|
|
cropped_mask = None
|
|
|
|
cropped_positive = [
|
|
[condition, {
|
|
k: core.crop_condition_mask(v, image, seg.crop_region) if k == "mask" else v
|
|
for k, v in details.items()
|
|
}]
|
|
for condition, details in positive
|
|
]
|
|
|
|
cropped_negative = [
|
|
[condition, {
|
|
k: core.crop_condition_mask(v, image, seg.crop_region) if k == "mask" else v
|
|
for k, v in details.items()
|
|
}]
|
|
for condition, details in negative
|
|
]
|
|
|
|
enhanced_image, cnet_pils = core.enhance_detail(cropped_image, model, clip, vae, guide_size, guide_size_for, max_size,
|
|
seg.bbox, seed, steps, cfg, sampler_name, scheduler,
|
|
cropped_positive, cropped_negative, denoise, cropped_mask, force_inpaint,
|
|
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
|
|
refiner_clip=refiner_clip, refiner_positive=refiner_positive, refiner_negative=refiner_negative,
|
|
control_net_wrapper=seg.control_net_wrapper, cycle=cycle,
|
|
inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func=scheduler_func_opt)
|
|
|
|
if cnet_pils is not None:
|
|
cnet_pil_list.extend(cnet_pils)
|
|
|
|
if enhanced_image is None:
|
|
new_cropped_image = cropped_image
|
|
else:
|
|
new_cropped_image = enhanced_image
|
|
|
|
new_seg = SEG(to_numpy(new_cropped_image), seg.cropped_mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, None)
|
|
new_segs.append(new_seg)
|
|
|
|
return (segs[0], new_segs), cnet_pil_list
|
|
|
|
def doit(self, image, segs, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler,
|
|
denoise, noise_mask, force_inpaint, basic_pipe, refiner_ratio=None, batch_size=1, cycle=1,
|
|
refiner_basic_pipe_opt=None, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None):
|
|
|
|
if len(image) > 1:
|
|
raise Exception('[Impact Pack] ERROR: SEGSDetailer does not allow image batches.\nPlease refer to https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/batching-detailer.md for more information.')
|
|
|
|
segs, cnet_pil_list = SEGSDetailer.do_detail(image, segs, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name,
|
|
scheduler, denoise, noise_mask, force_inpaint, basic_pipe, refiner_ratio, batch_size, cycle=cycle,
|
|
refiner_basic_pipe_opt=refiner_basic_pipe_opt,
|
|
inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func_opt=scheduler_func_opt)
|
|
|
|
|
|
if len(cnet_pil_list) == 0:
|
|
cnet_pil_list = [empty_pil_tensor()]
|
|
|
|
return segs, cnet_pil_list
|
|
|
|
|
|
class SEGSPaste:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"image": ("IMAGE", ),
|
|
"segs": ("SEGS", ),
|
|
"feather": ("INT", {"default": 5, "min": 0, "max": 100, "step": 1}),
|
|
"alpha": ("INT", {"default": 255, "min": 0, "max": 255, "step": 1}),
|
|
},
|
|
"optional": {"ref_image_opt": ("IMAGE", ), }
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE", )
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Detailer"
|
|
|
|
@staticmethod
|
|
def doit(image, segs, feather, alpha=255, ref_image_opt=None):
|
|
|
|
segs = core.segs_scale_match(segs, image.shape)
|
|
|
|
result = None
|
|
for i, single_image in enumerate(image):
|
|
image_i = single_image.unsqueeze(0).clone()
|
|
|
|
for seg in segs[1]:
|
|
ref_image = None
|
|
if ref_image_opt is None and seg.cropped_image is not None:
|
|
cropped_image = seg.cropped_image
|
|
if isinstance(cropped_image, np.ndarray):
|
|
cropped_image = torch.from_numpy(cropped_image)
|
|
ref_image = cropped_image[i].unsqueeze(0)
|
|
elif ref_image_opt is not None:
|
|
ref_tensor = ref_image_opt[i].unsqueeze(0)
|
|
ref_image = crop_image(ref_tensor, seg.crop_region)
|
|
if ref_image is not None:
|
|
if seg.cropped_mask.ndim == 3 and len(seg.cropped_mask) == len(image):
|
|
mask = seg.cropped_mask[i]
|
|
elif seg.cropped_mask.ndim == 3 and len(seg.cropped_mask) > 1:
|
|
print(f"[Impact Pack] WARN: SEGSPaste - The number of the mask batch({len(seg.cropped_mask)}) and the image batch({len(image)}) are different. Combine the mask frames and apply.")
|
|
combined_mask = (seg.cropped_mask[0] * 255).to(torch.uint8)
|
|
|
|
for frame_mask in seg.cropped_mask[1:]:
|
|
combined_mask |= (frame_mask * 255).to(torch.uint8)
|
|
|
|
combined_mask = (combined_mask/255.0).to(torch.float32)
|
|
mask = utils.to_binary_mask(combined_mask, 0.1)
|
|
else:
|
|
mask = seg.cropped_mask
|
|
|
|
mask = tensor_gaussian_blur_mask(mask, feather) * (alpha/255)
|
|
x, y, *_ = seg.crop_region
|
|
|
|
|
|
mask = mask.to(image_i.device)
|
|
ref_image = ref_image.to(image_i.device)
|
|
|
|
tensor_paste(image_i, ref_image, (x, y), mask)
|
|
|
|
if result is None:
|
|
result = image_i
|
|
else:
|
|
result = torch.concat((result, image_i), dim=0)
|
|
|
|
if not args.highvram and not args.gpu_only:
|
|
result = result.cpu()
|
|
|
|
return (result, )
|
|
|
|
|
|
class SEGSPreviewCNet:
|
|
def __init__(self):
|
|
self.output_dir = folder_paths.get_temp_directory()
|
|
self.type = "temp"
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {"segs": ("SEGS", ),}, }
|
|
|
|
RETURN_TYPES = ("IMAGE", )
|
|
OUTPUT_IS_LIST = (True, )
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
OUTPUT_NODE = True
|
|
|
|
def doit(self, segs):
|
|
full_output_folder, filename, counter, subfolder, filename_prefix = \
|
|
folder_paths.get_save_image_path("impact_seg_preview", self.output_dir, segs[0][1], segs[0][0])
|
|
|
|
results = list()
|
|
result_image_list = []
|
|
|
|
for seg in segs[1]:
|
|
file = f"{filename}_{counter:05}_.webp"
|
|
|
|
if seg.control_net_wrapper is not None and seg.control_net_wrapper.control_image is not None:
|
|
cnet_image = seg.control_net_wrapper.control_image
|
|
result_image_list.append(cnet_image)
|
|
else:
|
|
cnet_image = empty_pil_tensor(64, 64)
|
|
|
|
cnet_pil = utils.tensor2pil(cnet_image)
|
|
cnet_pil.save(os.path.join(full_output_folder, file))
|
|
|
|
results.append({
|
|
"filename": file,
|
|
"subfolder": subfolder,
|
|
"type": self.type
|
|
})
|
|
|
|
counter += 1
|
|
|
|
return {"ui": {"images": results}, "result": (result_image_list,)}
|
|
|
|
|
|
class SEGSPreview:
|
|
def __init__(self):
|
|
self.output_dir = folder_paths.get_temp_directory()
|
|
self.type = "temp"
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"segs": ("SEGS", ),
|
|
"alpha_mode": ("BOOLEAN", {"default": True, "label_on": "enable", "label_off": "disable"}),
|
|
"min_alpha": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0, "step": 0.01}),
|
|
},
|
|
"optional": {
|
|
"fallback_image_opt": ("IMAGE", ),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE", )
|
|
OUTPUT_IS_LIST = (True, )
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
OUTPUT_NODE = True
|
|
|
|
def doit(self, segs, alpha_mode=True, min_alpha=0.0, fallback_image_opt=None):
|
|
full_output_folder, filename, counter, subfolder, filename_prefix = \
|
|
folder_paths.get_save_image_path("impact_seg_preview", self.output_dir, segs[0][1], segs[0][0])
|
|
|
|
results = list()
|
|
result_image_list = []
|
|
|
|
if fallback_image_opt is not None:
|
|
segs = core.segs_scale_match(segs, fallback_image_opt.shape)
|
|
|
|
if min_alpha != 0:
|
|
min_alpha = int(255 * min_alpha)
|
|
|
|
if len(segs[1]) > 0:
|
|
if segs[1][0].cropped_image is not None:
|
|
batch_count = len(segs[1][0].cropped_image)
|
|
elif fallback_image_opt is not None:
|
|
batch_count = len(fallback_image_opt)
|
|
else:
|
|
return {"ui": {"images": results}}
|
|
|
|
for seg in segs[1]:
|
|
result_image_batch = None
|
|
cached_mask = None
|
|
|
|
def get_combined_mask():
|
|
nonlocal cached_mask
|
|
|
|
if cached_mask is not None:
|
|
return cached_mask
|
|
else:
|
|
if isinstance(seg.cropped_mask, np.ndarray):
|
|
masks = torch.tensor(seg.cropped_mask)
|
|
else:
|
|
masks = seg.cropped_mask
|
|
|
|
cached_mask = (masks[0] * 255).to(torch.uint8)
|
|
for x in masks[1:]:
|
|
cached_mask |= (x * 255).to(torch.uint8)
|
|
cached_mask = (cached_mask/255.0).to(torch.float32)
|
|
cached_mask = utils.to_binary_mask(cached_mask, 0.1)
|
|
cached_mask = cached_mask.numpy()
|
|
|
|
return cached_mask
|
|
|
|
def stack_image(image, mask=None):
|
|
nonlocal result_image_batch
|
|
|
|
if isinstance(image, np.ndarray):
|
|
image = torch.from_numpy(image)
|
|
|
|
if mask is not None:
|
|
image *= torch.tensor(mask)[None, ..., None]
|
|
|
|
if result_image_batch is None:
|
|
result_image_batch = image
|
|
else:
|
|
result_image_batch = torch.concat((result_image_batch, image), dim=0)
|
|
|
|
for i in range(batch_count):
|
|
cropped_image = None
|
|
|
|
if seg.cropped_image is not None:
|
|
cropped_image = seg.cropped_image[i, None]
|
|
elif fallback_image_opt is not None:
|
|
|
|
ref_image = fallback_image_opt[i].unsqueeze(0)
|
|
cropped_image = crop_image(ref_image, seg.crop_region)
|
|
|
|
if cropped_image is not None:
|
|
if isinstance(cropped_image, np.ndarray):
|
|
cropped_image = torch.from_numpy(cropped_image)
|
|
|
|
cropped_image = cropped_image.clone()
|
|
cropped_pil = to_pil(cropped_image)
|
|
|
|
if alpha_mode:
|
|
if isinstance(seg.cropped_mask, np.ndarray):
|
|
cropped_mask = seg.cropped_mask
|
|
else:
|
|
if seg.cropped_image is not None and len(seg.cropped_image) != len(seg.cropped_mask):
|
|
cropped_mask = get_combined_mask()
|
|
else:
|
|
cropped_mask = seg.cropped_mask[i].numpy()
|
|
|
|
mask_array = (cropped_mask * 255).astype(np.uint8)
|
|
|
|
if min_alpha != 0:
|
|
mask_array[mask_array < min_alpha] = min_alpha
|
|
|
|
mask_pil = Image.fromarray(mask_array, mode='L').resize(cropped_pil.size)
|
|
cropped_pil.putalpha(mask_pil)
|
|
stack_image(cropped_image, cropped_mask)
|
|
else:
|
|
stack_image(cropped_image)
|
|
|
|
file = f"{filename}_{counter:05}_.webp"
|
|
cropped_pil.save(os.path.join(full_output_folder, file))
|
|
results.append({
|
|
"filename": file,
|
|
"subfolder": subfolder,
|
|
"type": self.type
|
|
})
|
|
|
|
counter += 1
|
|
|
|
if result_image_batch is not None:
|
|
result_image_list.append(result_image_batch)
|
|
|
|
return {"ui": {"images": results}, "result": (result_image_list,) }
|
|
|
|
|
|
class SEGSLabelFilter:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"segs": ("SEGS", ),
|
|
"preset": (['all'] + defs.detection_labels, ),
|
|
"labels": ("STRING", {"multiline": True, "placeholder": "List the types of segments to be allowed, separated by commas"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("SEGS", "SEGS",)
|
|
RETURN_NAMES = ("filtered_SEGS", "remained_SEGS",)
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
@staticmethod
|
|
def filter(segs, labels):
|
|
labels = set([label.strip() for label in labels])
|
|
|
|
if 'all' in labels:
|
|
return (segs, (segs[0], []), )
|
|
else:
|
|
res_segs = []
|
|
remained_segs = []
|
|
|
|
for x in segs[1]:
|
|
if x.label in labels:
|
|
res_segs.append(x)
|
|
elif 'eyes' in labels and x.label in ['left_eye', 'right_eye']:
|
|
res_segs.append(x)
|
|
elif 'eyebrows' in labels and x.label in ['left_eyebrow', 'right_eyebrow']:
|
|
res_segs.append(x)
|
|
elif 'pupils' in labels and x.label in ['left_pupil', 'right_pupil']:
|
|
res_segs.append(x)
|
|
else:
|
|
remained_segs.append(x)
|
|
|
|
return ((segs[0], res_segs), (segs[0], remained_segs), )
|
|
|
|
def doit(self, segs, preset, labels):
|
|
labels = labels.split(',')
|
|
return SEGSLabelFilter.filter(segs, labels)
|
|
|
|
|
|
class SEGSLabelAssign:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"segs": ("SEGS", ),
|
|
"labels": ("STRING", {"multiline": True, "placeholder": "List the label to be assigned in order of segs, separated by commas"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("SEGS",)
|
|
RETURN_NAMES = ("SEGS",)
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
@staticmethod
|
|
def assign(segs, labels):
|
|
labels = [label.strip() for label in labels]
|
|
|
|
if len(labels) != len(segs[1]):
|
|
print(f'Warning (SEGSLabelAssign): length of labels ({len(labels)}) != length of segs ({len(segs[1])})')
|
|
|
|
labeled_segs = []
|
|
|
|
idx = 0
|
|
for x in segs[1]:
|
|
if len(labels) > idx:
|
|
x = x._replace(label=labels[idx])
|
|
labeled_segs.append(x)
|
|
idx += 1
|
|
|
|
return ((segs[0], labeled_segs), )
|
|
|
|
def doit(self, segs, labels):
|
|
labels = labels.split(',')
|
|
return SEGSLabelAssign.assign(segs, labels)
|
|
|
|
|
|
class SEGSOrderedFilter:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"segs": ("SEGS", ),
|
|
"target": (["area(=w*h)", "width", "height", "x1", "y1", "x2", "y2", "confidence"],),
|
|
"order": ("BOOLEAN", {"default": True, "label_on": "descending", "label_off": "ascending"}),
|
|
"take_start": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
|
|
"take_count": ("INT", {"default": 1, "min": 0, "max": sys.maxsize, "step": 1}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("SEGS", "SEGS",)
|
|
RETURN_NAMES = ("filtered_SEGS", "remained_SEGS",)
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
def doit(self, segs, target, order, take_start, take_count):
|
|
segs_with_order = []
|
|
|
|
for seg in segs[1]:
|
|
x1 = seg.crop_region[0]
|
|
y1 = seg.crop_region[1]
|
|
x2 = seg.crop_region[2]
|
|
y2 = seg.crop_region[3]
|
|
|
|
if target == "area(=w*h)":
|
|
value = (y2 - y1) * (x2 - x1)
|
|
elif target == "width":
|
|
value = x2 - x1
|
|
elif target == "height":
|
|
value = y2 - y1
|
|
elif target == "x1":
|
|
value = x1
|
|
elif target == "x2":
|
|
value = x2
|
|
elif target == "y1":
|
|
value = y1
|
|
elif target == "y2":
|
|
value = y2
|
|
elif target == "confidence":
|
|
value = seg.confidence
|
|
else:
|
|
raise Exception(f"[Impact Pack] SEGSOrderedFilter - Unexpected target '{target}'")
|
|
|
|
segs_with_order.append((value, seg))
|
|
|
|
if order:
|
|
sorted_list = sorted(segs_with_order, key=lambda x: x[0], reverse=True)
|
|
else:
|
|
sorted_list = sorted(segs_with_order, key=lambda x: x[0], reverse=False)
|
|
|
|
result_list = []
|
|
remained_list = []
|
|
|
|
for i, item in enumerate(sorted_list):
|
|
if take_start <= i < take_start + take_count:
|
|
result_list.append(item[1])
|
|
else:
|
|
remained_list.append(item[1])
|
|
|
|
return (segs[0], result_list), (segs[0], remained_list),
|
|
|
|
|
|
class SEGSRangeFilter:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"segs": ("SEGS", ),
|
|
"target": (["area(=w*h)", "width", "height", "x1", "y1", "x2", "y2", "length_percent", "confidence(0-100)"],),
|
|
"mode": ("BOOLEAN", {"default": True, "label_on": "inside", "label_off": "outside"}),
|
|
"min_value": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
|
|
"max_value": ("INT", {"default": 67108864, "min": 0, "max": sys.maxsize, "step": 1}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("SEGS", "SEGS",)
|
|
RETURN_NAMES = ("filtered_SEGS", "remained_SEGS",)
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
def doit(self, segs, target, mode, min_value, max_value):
|
|
new_segs = []
|
|
remained_segs = []
|
|
|
|
for seg in segs[1]:
|
|
x1 = seg.crop_region[0]
|
|
y1 = seg.crop_region[1]
|
|
x2 = seg.crop_region[2]
|
|
y2 = seg.crop_region[3]
|
|
|
|
if target == "area(=w*h)":
|
|
value = (y2 - y1) * (x2 - x1)
|
|
elif target == "length_percent":
|
|
h = y2 - y1
|
|
w = x2 - x1
|
|
value = max(h/w, w/h)*100
|
|
print(f"value={value}")
|
|
elif target == "width":
|
|
value = x2 - x1
|
|
elif target == "height":
|
|
value = y2 - y1
|
|
elif target == "x1":
|
|
value = x1
|
|
elif target == "x2":
|
|
value = x2
|
|
elif target == "y1":
|
|
value = y1
|
|
elif target == "y2":
|
|
value = y2
|
|
elif target == "confidence(0-100)":
|
|
value = seg.confidence*100
|
|
else:
|
|
raise Exception(f"[Impact Pack] SEGSRangeFilter - Unexpected target '{target}'")
|
|
|
|
if mode and min_value <= value <= max_value:
|
|
print(f"[in] value={value} / {mode}, {min_value}, {max_value}")
|
|
new_segs.append(seg)
|
|
elif not mode and (value < min_value or value > max_value):
|
|
print(f"[out] value={value} / {mode}, {min_value}, {max_value}")
|
|
new_segs.append(seg)
|
|
else:
|
|
remained_segs.append(seg)
|
|
print(f"[filter] value={value} / {mode}, {min_value}, {max_value}")
|
|
|
|
return (segs[0], new_segs), (segs[0], remained_segs),
|
|
|
|
|
|
class SEGSToImageList:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"segs": ("SEGS", ),
|
|
},
|
|
"optional": {
|
|
"fallback_image_opt": ("IMAGE", ),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
OUTPUT_IS_LIST = (True,)
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
def doit(self, segs, fallback_image_opt=None):
|
|
results = list()
|
|
|
|
if fallback_image_opt is not None:
|
|
segs = core.segs_scale_match(segs, fallback_image_opt.shape)
|
|
|
|
for seg in segs[1]:
|
|
if seg.cropped_image is not None:
|
|
cropped_image = to_tensor(seg.cropped_image)
|
|
elif fallback_image_opt is not None:
|
|
|
|
cropped_image = to_tensor(crop_image(fallback_image_opt, seg.crop_region))
|
|
else:
|
|
cropped_image = empty_pil_tensor()
|
|
|
|
results.append(cropped_image)
|
|
|
|
if len(results) == 0:
|
|
results.append(empty_pil_tensor())
|
|
|
|
return (results,)
|
|
|
|
|
|
class SEGSToMaskList:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"segs": ("SEGS", ),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("MASK",)
|
|
OUTPUT_IS_LIST = (True,)
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
def doit(self, segs):
|
|
masks = core.segs_to_masklist(segs)
|
|
if len(masks) == 0:
|
|
empty_mask = torch.zeros(segs[0], dtype=torch.float32, device="cpu")
|
|
masks = [empty_mask]
|
|
masks = [utils.make_3d_mask(mask) for mask in masks]
|
|
return (masks,)
|
|
|
|
|
|
class SEGSToMaskBatch:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"segs": ("SEGS", ),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("MASK",)
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
def doit(self, segs):
|
|
masks = core.segs_to_masklist(segs)
|
|
masks = [utils.make_3d_mask(mask) for mask in masks]
|
|
mask_batch = torch.concat(masks)
|
|
return (mask_batch,)
|
|
|
|
|
|
class SEGSConcat:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"segs1": ("SEGS", ),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("SEGS",)
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
def doit(self, **kwargs):
|
|
dim = None
|
|
res = None
|
|
|
|
for k, v in list(kwargs.items()):
|
|
if v[0] == (0, 0) or len(v[1]) == 0:
|
|
continue
|
|
|
|
if dim is None:
|
|
dim = v[0]
|
|
res = v[1]
|
|
else:
|
|
if v[0] == dim:
|
|
res = res + v[1]
|
|
else:
|
|
print(f"ERROR: source shape of 'segs1'{dim} and '{k}'{v[0]} are different. '{k}' will be ignored")
|
|
|
|
if dim is None:
|
|
empty_segs = ((0, 0), [])
|
|
return (empty_segs, )
|
|
else:
|
|
return ((dim, res), )
|
|
|
|
|
|
class Count_Elts_in_SEGS:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"segs": ("SEGS", ),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("INT",)
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
def doit(self, segs):
|
|
return (len(segs[1]), )
|
|
|
|
|
|
class DecomposeSEGS:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"segs": ("SEGS", ),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("SEGS_HEADER", "SEG_ELT",)
|
|
OUTPUT_IS_LIST = (False, True, )
|
|
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
def doit(self, segs):
|
|
return segs
|
|
|
|
|
|
class AssembleSEGS:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"seg_header": ("SEGS_HEADER", ),
|
|
"seg_elt": ("SEG_ELT", ),
|
|
},
|
|
}
|
|
|
|
INPUT_IS_LIST = True
|
|
|
|
RETURN_TYPES = ("SEGS", )
|
|
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
def doit(self, seg_header, seg_elt):
|
|
return ((seg_header[0], seg_elt), )
|
|
|
|
|
|
class From_SEG_ELT:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"seg_elt": ("SEG_ELT", ),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("SEG_ELT", "IMAGE", "MASK", "SEG_ELT_crop_region", "SEG_ELT_bbox", "SEG_ELT_control_net_wrapper", "FLOAT", "STRING")
|
|
RETURN_NAMES = ("seg_elt", "cropped_image", "cropped_mask", "crop_region", "bbox", "control_net_wrapper", "confidence", "label")
|
|
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
def doit(self, seg_elt):
|
|
cropped_image = to_tensor(seg_elt.cropped_image) if seg_elt.cropped_image is not None else None
|
|
return (seg_elt, cropped_image, to_tensor(seg_elt.cropped_mask), seg_elt.crop_region, seg_elt.bbox, seg_elt.control_net_wrapper, seg_elt.confidence, seg_elt.label,)
|
|
|
|
|
|
class From_SEG_ELT_bbox:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"bbox": ("SEG_ELT_bbox", ),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("INT", "INT", "INT", "INT")
|
|
RETURN_NAMES = ("left", "top", "right", "bottom")
|
|
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
def doit(self, bbox):
|
|
return bbox
|
|
|
|
|
|
class From_SEG_ELT_crop_region:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"crop_region": ("SEG_ELT_crop_region", ),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("INT", "INT", "INT", "INT")
|
|
RETURN_NAMES = ("left", "top", "right", "bottom")
|
|
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
def doit(self, crop_region):
|
|
return crop_region
|
|
|
|
|
|
class Edit_SEG_ELT:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"seg_elt": ("SEG_ELT", ),
|
|
},
|
|
"optional": {
|
|
"cropped_image_opt": ("IMAGE", ),
|
|
"cropped_mask_opt": ("MASK", ),
|
|
"crop_region_opt": ("SEG_ELT_crop_region", ),
|
|
"bbox_opt": ("SEG_ELT_bbox", ),
|
|
"control_net_wrapper_opt": ("SEG_ELT_control_net_wrapper", ),
|
|
"confidence_opt": ("FLOAT", {"min": 0, "max": 1.0, "step": 0.1, "forceInput": True}),
|
|
"label_opt": ("STRING", {"multiline": False, "forceInput": True}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("SEG_ELT", )
|
|
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
def doit(self, seg_elt, cropped_image_opt=None, cropped_mask_opt=None, confidence_opt=None, crop_region_opt=None,
|
|
bbox_opt=None, label_opt=None, control_net_wrapper_opt=None):
|
|
|
|
cropped_image = seg_elt.cropped_image if cropped_image_opt is None else cropped_image_opt
|
|
cropped_mask = seg_elt.cropped_mask if cropped_mask_opt is None else cropped_mask_opt
|
|
confidence = seg_elt.confidence if confidence_opt is None else confidence_opt
|
|
crop_region = seg_elt.crop_region if crop_region_opt is None else crop_region_opt
|
|
bbox = seg_elt.bbox if bbox_opt is None else bbox_opt
|
|
label = seg_elt.label if label_opt is None else label_opt
|
|
control_net_wrapper = seg_elt.control_net_wrapper if control_net_wrapper_opt is None else control_net_wrapper_opt
|
|
|
|
cropped_image = cropped_image.numpy() if cropped_image is not None else None
|
|
|
|
if isinstance(cropped_mask, torch.Tensor):
|
|
if len(cropped_mask.shape) == 3:
|
|
cropped_mask = cropped_mask.squeeze(0)
|
|
|
|
cropped_mask = cropped_mask.numpy()
|
|
|
|
seg = SEG(cropped_image, cropped_mask, confidence, crop_region, bbox, label, control_net_wrapper)
|
|
|
|
return (seg,)
|
|
|
|
|
|
class DilateMask:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"mask": ("MASK", ),
|
|
"dilation": ("INT", {"default": 10, "min": -512, "max": 512, "step": 1}),
|
|
}}
|
|
|
|
RETURN_TYPES = ("MASK", )
|
|
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
def doit(self, mask, dilation):
|
|
mask = core.dilate_mask(mask.numpy(), dilation)
|
|
mask = torch.from_numpy(mask)
|
|
mask = utils.make_3d_mask(mask)
|
|
return (mask, )
|
|
|
|
|
|
class GaussianBlurMask:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"mask": ("MASK", ),
|
|
"kernel_size": ("INT", {"default": 10, "min": 0, "max": 100, "step": 1}),
|
|
"sigma": ("FLOAT", {"default": 10.0, "min": 0.1, "max": 100.0, "step": 0.1}),
|
|
}}
|
|
|
|
RETURN_TYPES = ("MASK", )
|
|
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
def doit(self, mask, kernel_size, sigma):
|
|
|
|
mask = make_3d_mask(mask)
|
|
mask = torch.unsqueeze(mask, dim=-1)
|
|
mask = utils.tensor_gaussian_blur_mask(mask, kernel_size, sigma)
|
|
mask = torch.squeeze(mask, dim=-1)
|
|
return (mask, )
|
|
|
|
|
|
class DilateMaskInSEGS:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"segs": ("SEGS", ),
|
|
"dilation": ("INT", {"default": 10, "min": -512, "max": 512, "step": 1}),
|
|
}}
|
|
|
|
RETURN_TYPES = ("SEGS", )
|
|
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
def doit(self, segs, dilation):
|
|
new_segs = []
|
|
for seg in segs[1]:
|
|
mask = core.dilate_mask(seg.cropped_mask, dilation)
|
|
seg = SEG(seg.cropped_image, mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, seg.control_net_wrapper)
|
|
new_segs.append(seg)
|
|
|
|
return ((segs[0], new_segs), )
|
|
|
|
|
|
class GaussianBlurMaskInSEGS:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"segs": ("SEGS", ),
|
|
"kernel_size": ("INT", {"default": 10, "min": 0, "max": 100, "step": 1}),
|
|
"sigma": ("FLOAT", {"default": 10.0, "min": 0.1, "max": 100.0, "step": 0.1}),
|
|
}}
|
|
|
|
RETURN_TYPES = ("SEGS", )
|
|
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
def doit(self, segs, kernel_size, sigma):
|
|
new_segs = []
|
|
for seg in segs[1]:
|
|
mask = utils.tensor_gaussian_blur_mask(seg.cropped_mask, kernel_size, sigma)
|
|
mask = torch.squeeze(mask, dim=-1).squeeze(0).numpy()
|
|
seg = SEG(seg.cropped_image, mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, seg.control_net_wrapper)
|
|
new_segs.append(seg)
|
|
|
|
return ((segs[0], new_segs), )
|
|
|
|
|
|
class Dilate_SEG_ELT:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"seg_elt": ("SEG_ELT", ),
|
|
"dilation": ("INT", {"default": 10, "min": -512, "max": 512, "step": 1}),
|
|
}}
|
|
|
|
RETURN_TYPES = ("SEG_ELT", )
|
|
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
def doit(self, seg, dilation):
|
|
mask = core.dilate_mask(seg.cropped_mask, dilation)
|
|
seg = SEG(seg.cropped_image, mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, seg.control_net_wrapper)
|
|
return (seg,)
|
|
|
|
|
|
class SEG_ELT_BBOX_ScaleBy:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"seg": ("SEG_ELT", ),
|
|
"scale_by": ("FLOAT", {"default": 1.0, "min": 0.01, "max": 8.0, "step": 0.01}), }
|
|
}
|
|
|
|
RETURN_TYPES = ("SEG_ELT", )
|
|
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
@staticmethod
|
|
def fill_zero_outside_bbox(mask, crop_region, bbox):
|
|
cx1, cy1, _, _ = crop_region
|
|
x1, y1, x2, y2 = bbox
|
|
x1, y1, x2, y2 = x1-cx1, y1-cy1, x2-cx1, y2-cy1
|
|
h, w = mask.shape
|
|
|
|
x1 = min(w-1, max(0, x1))
|
|
x2 = min(w-1, max(0, x2))
|
|
y1 = min(h-1, max(0, y1))
|
|
y2 = min(h-1, max(0, y2))
|
|
|
|
mask_cropped = mask.copy()
|
|
mask_cropped[:, :x1] = 0
|
|
mask_cropped[:, x2:] = 0
|
|
mask_cropped[:y1, :] = 0
|
|
mask_cropped[y2:, :] = 0
|
|
return mask_cropped
|
|
|
|
def doit(self, seg, scale_by):
|
|
x1, y1, x2, y2 = seg.bbox
|
|
w = x2-x1
|
|
h = y2-y1
|
|
|
|
dw = int((w * scale_by - w)/2)
|
|
dh = int((h * scale_by - h)/2)
|
|
|
|
bbox = (x1-dw, y1-dh, x2+dw, y2+dh)
|
|
|
|
cropped_mask = SEG_ELT_BBOX_ScaleBy.fill_zero_outside_bbox(seg.cropped_mask, seg.crop_region, bbox)
|
|
seg = SEG(seg.cropped_image, cropped_mask, seg.confidence, seg.crop_region, bbox, seg.label, seg.control_net_wrapper)
|
|
return (seg,)
|
|
|
|
|
|
class EmptySEGS:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {}, }
|
|
|
|
RETURN_TYPES = ("SEGS",)
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
def doit(self):
|
|
shape = 0, 0
|
|
return ((shape, []),)
|
|
|
|
|
|
class SegsToCombinedMask:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {"segs": ("SEGS",), }}
|
|
|
|
RETURN_TYPES = ("MASK",)
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Operation"
|
|
|
|
def doit(self, segs):
|
|
mask = core.segs_to_combined_mask(segs)
|
|
mask = utils.make_3d_mask(mask)
|
|
return (mask,)
|
|
|
|
|
|
class MediaPipeFaceMeshToSEGS:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
bool_true_widget = ("BOOLEAN", {"default": True, "label_on": "Enabled", "label_off": "Disabled"})
|
|
bool_false_widget = ("BOOLEAN", {"default": False, "label_on": "Enabled", "label_off": "Disabled"})
|
|
return {"required": {
|
|
"image": ("IMAGE",),
|
|
"crop_factor": ("FLOAT", {"default": 3.0, "min": 1.0, "max": 100, "step": 0.1}),
|
|
"bbox_fill": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
|
"crop_min_size": ("INT", {"min": 10, "max": MAX_RESOLUTION, "step": 1, "default": 50}),
|
|
"drop_size": ("INT", {"min": 1, "max": MAX_RESOLUTION, "step": 1, "default": 1}),
|
|
"dilation": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1}),
|
|
"face": bool_true_widget,
|
|
"mouth": bool_false_widget,
|
|
"left_eyebrow": bool_false_widget,
|
|
"left_eye": bool_false_widget,
|
|
"left_pupil": bool_false_widget,
|
|
"right_eyebrow": bool_false_widget,
|
|
"right_eye": bool_false_widget,
|
|
"right_pupil": bool_false_widget,
|
|
},
|
|
|
|
}
|
|
|
|
RETURN_TYPES = ("SEGS",)
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Operation"
|
|
|
|
def doit(self, image, crop_factor, bbox_fill, crop_min_size, drop_size, dilation, face, mouth, left_eyebrow, left_eye, left_pupil, right_eyebrow, right_eye, right_pupil):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
result = core.mediapipe_facemesh_to_segs(image, crop_factor, bbox_fill, crop_min_size, drop_size, dilation, face, mouth, left_eyebrow, left_eye, left_pupil, right_eyebrow, right_eye, right_pupil)
|
|
return (result, )
|
|
|
|
|
|
class MaskToSEGS:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"mask": ("MASK",),
|
|
"combined": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
|
|
"crop_factor": ("FLOAT", {"default": 3.0, "min": 1.0, "max": 100, "step": 0.1}),
|
|
"bbox_fill": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
|
"drop_size": ("INT", {"min": 1, "max": MAX_RESOLUTION, "step": 1, "default": 10}),
|
|
"contour_fill": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("SEGS",)
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Operation"
|
|
|
|
@staticmethod
|
|
def doit(mask, combined, crop_factor, bbox_fill, drop_size, contour_fill=False):
|
|
mask = make_2d_mask(mask)
|
|
result = core.mask_to_segs(mask, combined, crop_factor, bbox_fill, drop_size, is_contour=contour_fill)
|
|
|
|
return (result, )
|
|
|
|
|
|
class MaskToSEGS_for_AnimateDiff:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"mask": ("MASK",),
|
|
"combined": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
|
|
"crop_factor": ("FLOAT", {"default": 3.0, "min": 1.0, "max": 100, "step": 0.1}),
|
|
"bbox_fill": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
|
"drop_size": ("INT", {"min": 1, "max": MAX_RESOLUTION, "step": 1, "default": 10}),
|
|
"contour_fill": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("SEGS",)
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Operation"
|
|
|
|
@staticmethod
|
|
def doit(mask, combined, crop_factor, bbox_fill, drop_size, contour_fill=False):
|
|
if (len(mask.shape) == 4 and mask.shape[1] > 1) or (len(mask.shape) == 3 and mask.shape[0] > 1):
|
|
mask = make_3d_mask(mask)
|
|
if contour_fill:
|
|
print(f"[Impact Pack] MaskToSEGS_for_AnimateDiff: 'contour_fill' is ignored because batch mask 'contour_fill' is not supported.")
|
|
result = core.batch_mask_to_segs(mask, combined, crop_factor, bbox_fill, drop_size)
|
|
return (result, )
|
|
|
|
mask = make_2d_mask(mask)
|
|
segs = core.mask_to_segs(mask, combined, crop_factor, bbox_fill, drop_size, is_contour=contour_fill)
|
|
all_masks = SEGSToMaskList().doit(segs)[0]
|
|
|
|
result_mask = (all_masks[0] * 255).to(torch.uint8)
|
|
for mask in all_masks[1:]:
|
|
result_mask |= (mask * 255).to(torch.uint8)
|
|
|
|
result_mask = (result_mask/255.0).to(torch.float32)
|
|
result_mask = utils.to_binary_mask(result_mask, 0.1)[0]
|
|
|
|
return MaskToSEGS.doit(result_mask, False, crop_factor, False, drop_size, contour_fill)
|
|
|
|
|
|
class IPAdapterApplySEGS:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"segs": ("SEGS",),
|
|
"ipadapter_pipe": ("IPADAPTER_PIPE",),
|
|
"weight": ("FLOAT", {"default": 0.7, "min": -1, "max": 3, "step": 0.05}),
|
|
"noise": ("FLOAT", {"default": 0.4, "min": 0.0, "max": 1.0, "step": 0.01}),
|
|
"weight_type": (["original", "linear", "channel penalty"], {"default": 'channel penalty'}),
|
|
"start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
|
"end_at": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.001}),
|
|
"unfold_batch": ("BOOLEAN", {"default": False}),
|
|
"faceid_v2": ("BOOLEAN", {"default": False}),
|
|
"weight_v2": ("FLOAT", {"default": 1.0, "min": -1, "max": 3, "step": 0.05}),
|
|
"context_crop_factor": ("FLOAT", {"default": 1.2, "min": 1.0, "max": 100, "step": 0.1}),
|
|
"reference_image": ("IMAGE",),
|
|
},
|
|
"optional": {
|
|
"combine_embeds": (["concat", "add", "subtract", "average", "norm average"],),
|
|
"neg_image": ("IMAGE",),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("SEGS",)
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
@staticmethod
|
|
def doit(segs, ipadapter_pipe, weight, noise, weight_type, start_at, end_at, unfold_batch, faceid_v2, weight_v2, context_crop_factor, reference_image, combine_embeds="concat", neg_image=None):
|
|
|
|
if len(ipadapter_pipe) == 4:
|
|
print(f"[Impact Pack] IPAdapterApplySEGS: Installed Inspire Pack is outdated.")
|
|
raise Exception("Inspire Pack is outdated.")
|
|
|
|
new_segs = []
|
|
|
|
h, w = segs[0]
|
|
|
|
if reference_image.shape[2] != w or reference_image.shape[1] != h:
|
|
reference_image = tensor_resize(reference_image, w, h)
|
|
|
|
for seg in segs[1]:
|
|
|
|
context_crop_region = make_crop_region(w, h, seg.crop_region, context_crop_factor)
|
|
cropped_image = crop_image(reference_image, context_crop_region)
|
|
|
|
control_net_wrapper = core.IPAdapterWrapper(ipadapter_pipe, weight, noise, weight_type, start_at, end_at, unfold_batch, weight_v2, cropped_image, neg_image=neg_image, prev_control_net=seg.control_net_wrapper, combine_embeds=combine_embeds)
|
|
new_seg = SEG(seg.cropped_image, seg.cropped_mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, control_net_wrapper)
|
|
new_segs.append(new_seg)
|
|
|
|
return ((segs[0], new_segs), )
|
|
|
|
|
|
class ControlNetApplySEGS:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"segs": ("SEGS",),
|
|
"control_net": ("CONTROL_NET",),
|
|
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
|
},
|
|
"optional": {
|
|
"segs_preprocessor": ("SEGS_PREPROCESSOR",),
|
|
"control_image": ("IMAGE",)
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("SEGS",)
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
@staticmethod
|
|
def doit(segs, control_net, strength, segs_preprocessor=None, control_image=None):
|
|
new_segs = []
|
|
|
|
for seg in segs[1]:
|
|
control_net_wrapper = core.ControlNetWrapper(control_net, strength, segs_preprocessor, seg.control_net_wrapper,
|
|
original_size=segs[0], crop_region=seg.crop_region, control_image=control_image)
|
|
new_seg = SEG(seg.cropped_image, seg.cropped_mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, control_net_wrapper)
|
|
new_segs.append(new_seg)
|
|
|
|
return ((segs[0], new_segs), )
|
|
|
|
|
|
class ControlNetApplyAdvancedSEGS:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"segs": ("SEGS",),
|
|
"control_net": ("CONTROL_NET",),
|
|
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
|
"start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
|
"end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001})
|
|
},
|
|
"optional": {
|
|
"segs_preprocessor": ("SEGS_PREPROCESSOR",),
|
|
"control_image": ("IMAGE",)
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("SEGS",)
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
@staticmethod
|
|
def doit(segs, control_net, strength, start_percent, end_percent, segs_preprocessor=None, control_image=None):
|
|
new_segs = []
|
|
|
|
for seg in segs[1]:
|
|
control_net_wrapper = core.ControlNetAdvancedWrapper(control_net, strength, start_percent, end_percent, segs_preprocessor,
|
|
seg.control_net_wrapper, original_size=segs[0], crop_region=seg.crop_region,
|
|
control_image=control_image)
|
|
new_seg = SEG(seg.cropped_image, seg.cropped_mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, control_net_wrapper)
|
|
new_segs.append(new_seg)
|
|
|
|
return ((segs[0], new_segs), )
|
|
|
|
|
|
class ControlNetClearSEGS:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {"segs": ("SEGS",), }, }
|
|
|
|
RETURN_TYPES = ("SEGS",)
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
@staticmethod
|
|
def doit(segs):
|
|
new_segs = []
|
|
|
|
for seg in segs[1]:
|
|
new_seg = SEG(seg.cropped_image, seg.cropped_mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, None)
|
|
new_segs.append(new_seg)
|
|
|
|
return ((segs[0], new_segs), )
|
|
|
|
|
|
class SEGSSwitch:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"select": ("INT", {"default": 1, "min": 1, "max": 99999, "step": 1}),
|
|
"segs1": ("SEGS",),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("SEGS", )
|
|
|
|
OUTPUT_NODE = True
|
|
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
def doit(self, *args, **kwargs):
|
|
input_name = f"segs{int(kwargs['select'])}"
|
|
|
|
if input_name in kwargs:
|
|
return (kwargs[input_name],)
|
|
else:
|
|
print(f"SEGSSwitch: invalid select index ('segs1' is selected)")
|
|
return (kwargs['segs1'],)
|
|
|
|
|
|
class SEGSPicker:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"picks": ("STRING", {"multiline": True, "dynamicPrompts": False, "pysssss.autocomplete": False}),
|
|
"segs": ("SEGS",),
|
|
},
|
|
"optional": {
|
|
"fallback_image_opt": ("IMAGE", ),
|
|
},
|
|
"hidden": {"unique_id": "UNIQUE_ID"},
|
|
}
|
|
|
|
RETURN_TYPES = ("SEGS", )
|
|
|
|
OUTPUT_NODE = True
|
|
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
@staticmethod
|
|
def doit(picks, segs, fallback_image_opt=None, unique_id=None):
|
|
if fallback_image_opt is not None:
|
|
segs = core.segs_scale_match(segs, fallback_image_opt.shape)
|
|
|
|
|
|
cands = []
|
|
for seg in segs[1]:
|
|
if seg.cropped_image is not None:
|
|
cropped_image = seg.cropped_image
|
|
elif fallback_image_opt is not None:
|
|
|
|
cropped_image = crop_image(fallback_image_opt, seg.crop_region)
|
|
else:
|
|
cropped_image = empty_pil_tensor()
|
|
|
|
mask_array = seg.cropped_mask.copy()
|
|
mask_array[mask_array < 0.3] = 0.3
|
|
mask_array = mask_array[None, ..., None]
|
|
cropped_image = cropped_image * mask_array
|
|
|
|
cands.append(cropped_image)
|
|
|
|
impact.impact_server.segs_picker_map[unique_id] = cands
|
|
|
|
|
|
pick_ids = set()
|
|
|
|
for pick in picks.split(","):
|
|
try:
|
|
pick_ids.add(int(pick)-1)
|
|
except Exception:
|
|
pass
|
|
|
|
new_segs = []
|
|
for i in pick_ids:
|
|
if 0 <= i < len(segs[1]):
|
|
new_segs.append(segs[1][i])
|
|
|
|
return ((segs[0], new_segs),)
|
|
|
|
|
|
class DefaultImageForSEGS:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"segs": ("SEGS", ),
|
|
"image": ("IMAGE", ),
|
|
"override": ("BOOLEAN", {"default": True}),
|
|
}}
|
|
|
|
RETURN_TYPES = ("SEGS", )
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
@staticmethod
|
|
def doit(segs, image, override):
|
|
results = []
|
|
|
|
segs = core.segs_scale_match(segs, image.shape)
|
|
|
|
if len(segs[1]) > 0:
|
|
if segs[1][0].cropped_image is not None:
|
|
batch_count = len(segs[1][0].cropped_image)
|
|
else:
|
|
batch_count = len(image)
|
|
|
|
for seg in segs[1]:
|
|
if seg.cropped_image is not None and not override:
|
|
cropped_image = seg.cropped_image
|
|
else:
|
|
cropped_image = None
|
|
for i in range(0, batch_count):
|
|
|
|
ref_image = image[i].unsqueeze(0)
|
|
cropped_image2 = crop_image(ref_image, seg.crop_region)
|
|
|
|
if cropped_image is None:
|
|
cropped_image = cropped_image2
|
|
else:
|
|
cropped_image = torch.cat((cropped_image, cropped_image2), dim=0)
|
|
|
|
new_seg = SEG(cropped_image, seg.cropped_mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, seg.control_net_wrapper)
|
|
results.append(new_seg)
|
|
|
|
return ((segs[0], results), )
|
|
else:
|
|
return (segs, )
|
|
|
|
|
|
class RemoveImageFromSEGS:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {"segs": ("SEGS", ), }}
|
|
|
|
RETURN_TYPES = ("SEGS", )
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Util"
|
|
|
|
@staticmethod
|
|
def doit(segs):
|
|
results = []
|
|
|
|
if len(segs[1]) > 0:
|
|
for seg in segs[1]:
|
|
new_seg = SEG(None, seg.cropped_mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, seg.control_net_wrapper)
|
|
results.append(new_seg)
|
|
|
|
return ((segs[0], results), )
|
|
else:
|
|
return (segs, )
|
|
|
|
|
|
class MakeTileSEGS:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"images": ("IMAGE", ),
|
|
"bbox_size": ("INT", {"default": 512, "min": 64, "max": 4096, "step": 8}),
|
|
"crop_factor": ("FLOAT", {"default": 3.0, "min": 1.0, "max": 10, "step": 0.01}),
|
|
"min_overlap": ("INT", {"default": 5, "min": 0, "max": 512, "step": 1}),
|
|
"filter_segs_dilation": ("INT", {"default": 20, "min": -255, "max": 255, "step": 1}),
|
|
"mask_irregularity": ("FLOAT", {"default": 0, "min": 0, "max": 1.0, "step": 0.01}),
|
|
"irregular_mask_mode": (["Reuse fast", "Reuse quality", "All random fast", "All random quality"],)
|
|
},
|
|
"optional": {
|
|
"filter_in_segs_opt": ("SEGS", ),
|
|
"filter_out_segs_opt": ("SEGS", ),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("SEGS",)
|
|
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/__for_testing"
|
|
|
|
@staticmethod
|
|
def doit(images, bbox_size, crop_factor, min_overlap, filter_segs_dilation, mask_irregularity=0, irregular_mask_mode="Reuse fast", filter_in_segs_opt=None, filter_out_segs_opt=None):
|
|
if bbox_size <= 2*min_overlap:
|
|
new_min_overlap = bbox_size / 2
|
|
print(f"[MakeTileSEGS] min_overlap should be greater than bbox_size. (value changed: {min_overlap} => {new_min_overlap})")
|
|
min_overlap = new_min_overlap
|
|
|
|
_, ih, iw, _ = images.size()
|
|
|
|
mask_cache = None
|
|
mask_quality = 512
|
|
if mask_irregularity > 0:
|
|
if irregular_mask_mode == "Reuse fast":
|
|
mask_quality = 128
|
|
mask_cache = np.zeros((128, 128)).astype(np.float32)
|
|
core.random_mask(mask_cache, (0, 0, 128, 128), factor=mask_irregularity, size=mask_quality)
|
|
elif irregular_mask_mode == "Reuse quality":
|
|
mask_quality = 512
|
|
mask_cache = np.zeros((512, 512)).astype(np.float32)
|
|
core.random_mask(mask_cache, (0, 0, 512, 512), factor=mask_irregularity, size=mask_quality)
|
|
elif irregular_mask_mode == "All random fast":
|
|
mask_quality = 512
|
|
|
|
|
|
if mask_irregularity > 0:
|
|
compensate = max(6, int(mask_quality * mask_irregularity / 4))
|
|
min_overlap += compensate
|
|
bbox_size += compensate*2
|
|
|
|
|
|
if filter_out_segs_opt is not None:
|
|
exclusion_mask = core.segs_to_combined_mask(filter_out_segs_opt)
|
|
exclusion_mask = utils.make_3d_mask(exclusion_mask)
|
|
exclusion_mask = utils.resize_mask(exclusion_mask, (ih, iw))
|
|
exclusion_mask = dilate_mask(exclusion_mask.cpu().numpy(), filter_segs_dilation)
|
|
else:
|
|
exclusion_mask = None
|
|
|
|
if filter_in_segs_opt is not None:
|
|
and_mask = core.segs_to_combined_mask(filter_in_segs_opt)
|
|
and_mask = utils.make_3d_mask(and_mask)
|
|
and_mask = utils.resize_mask(and_mask, (ih, iw))
|
|
and_mask = dilate_mask(and_mask.cpu().numpy(), filter_segs_dilation)
|
|
|
|
a, b = core.mask_to_segs(and_mask, True, 1.0, False, 0)
|
|
if len(b) == 0:
|
|
return ((a, b),)
|
|
|
|
start_x, start_y, c, d = b[0].crop_region
|
|
w = c - start_x
|
|
h = d - start_y
|
|
else:
|
|
start_x = 0
|
|
start_y = 0
|
|
h, w = ih, iw
|
|
and_mask = None
|
|
|
|
|
|
if bbox_size > h or bbox_size > w:
|
|
new_bbox_size = min(bbox_size, min(w, h))
|
|
print(f"[MaskTileSEGS] bbox_size is greater than resolution (value changed: {bbox_size} => {new_bbox_size}")
|
|
bbox_size = new_bbox_size
|
|
|
|
n_horizontal = math.ceil(w / (bbox_size - min_overlap))
|
|
n_vertical = math.ceil(h / (bbox_size - min_overlap))
|
|
|
|
w_overlap_sum = (bbox_size * n_horizontal) - w
|
|
if w_overlap_sum < 0:
|
|
n_horizontal += 1
|
|
w_overlap_sum = (bbox_size * n_horizontal) - w
|
|
|
|
w_overlap_size = 0 if n_horizontal == 1 else int(w_overlap_sum/(n_horizontal-1))
|
|
|
|
h_overlap_sum = (bbox_size * n_vertical) - h
|
|
if h_overlap_sum < 0:
|
|
n_vertical += 1
|
|
h_overlap_sum = (bbox_size * n_vertical) - h
|
|
|
|
h_overlap_size = 0 if n_vertical == 1 else int(h_overlap_sum/(n_vertical-1))
|
|
|
|
new_segs = []
|
|
|
|
if w_overlap_size == bbox_size:
|
|
n_horizontal = 1
|
|
|
|
if h_overlap_size == bbox_size:
|
|
n_vertical = 1
|
|
|
|
y = start_y
|
|
for j in range(0, n_vertical):
|
|
x = start_x
|
|
for i in range(0, n_horizontal):
|
|
x1 = x
|
|
y1 = y
|
|
|
|
if x+bbox_size < iw-1:
|
|
x2 = x+bbox_size
|
|
else:
|
|
x2 = iw
|
|
x1 = iw-bbox_size
|
|
|
|
if y+bbox_size < ih-1:
|
|
y2 = y+bbox_size
|
|
else:
|
|
y2 = ih
|
|
y1 = ih-bbox_size
|
|
|
|
bbox = x1, y1, x2, y2
|
|
crop_region = make_crop_region(iw, ih, bbox, crop_factor)
|
|
cx1, cy1, cx2, cy2 = crop_region
|
|
|
|
mask = np.zeros((cy2 - cy1, cx2 - cx1)).astype(np.float32)
|
|
|
|
rel_left = x1 - cx1
|
|
rel_top = y1 - cy1
|
|
rel_right = x2 - cx1
|
|
rel_bot = y2 - cy1
|
|
|
|
if mask_irregularity > 0:
|
|
if mask_cache is not None:
|
|
core.adaptive_mask_paste(mask, mask_cache, (rel_left, rel_top, rel_right, rel_bot))
|
|
else:
|
|
core.random_mask(mask, (rel_left, rel_top, rel_right, rel_bot), factor=mask_irregularity, size=mask_quality)
|
|
|
|
|
|
if rel_left == 0:
|
|
pad = int((x2 - x1) / 8)
|
|
mask[rel_top:rel_bot, :pad] = 1.0
|
|
|
|
if rel_top == 0:
|
|
pad = int((y2 - y1) / 8)
|
|
mask[:pad, rel_left:rel_right] = 1.0
|
|
|
|
if rel_right == mask.shape[1]:
|
|
pad = int((x2 - x1) / 8)
|
|
mask[rel_top:rel_bot, -pad:] = 1.0
|
|
|
|
if rel_bot == mask.shape[0]:
|
|
pad = int((y2 - y1) / 8)
|
|
mask[-pad:, rel_left:rel_right] = 1.0
|
|
else:
|
|
mask[rel_top:rel_bot, rel_left:rel_right] = 1.0
|
|
|
|
mask = torch.tensor(mask)
|
|
|
|
if exclusion_mask is not None:
|
|
exclusion_mask_cropped = exclusion_mask[cy1:cy2, cx1:cx2]
|
|
mask[exclusion_mask_cropped != 0] = 0.0
|
|
|
|
if and_mask is not None:
|
|
and_mask_cropped = and_mask[cy1:cy2, cx1:cx2]
|
|
mask[and_mask_cropped == 0] = 0.0
|
|
|
|
is_mask_zero = torch.all(mask == 0.0).item()
|
|
|
|
if not is_mask_zero:
|
|
item = SEG(None, mask.numpy(), 1.0, crop_region, bbox, "", None)
|
|
new_segs.append(item)
|
|
|
|
x += bbox_size - w_overlap_size
|
|
y += bbox_size - h_overlap_size
|
|
|
|
res = (ih, iw), new_segs
|
|
return (res,)
|
|
|
|
|
|
class SEGSUpscaler:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
resampling_methods = ["lanczos", "nearest", "bilinear", "bicubic"]
|
|
|
|
return {"required": {
|
|
"image": ("IMAGE",),
|
|
"segs": ("SEGS",),
|
|
"model": ("MODEL",),
|
|
"clip": ("CLIP",),
|
|
"vae": ("VAE",),
|
|
"rescale_factor": ("FLOAT", {"default": 2, "min": 0.01, "max": 100.0, "step": 0.01}),
|
|
"resampling_method": (resampling_methods,),
|
|
"supersample": (["true", "false"],),
|
|
"rounding_modulus": ("INT", {"default": 8, "min": 8, "max": 1024, "step": 8}),
|
|
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
|
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
|
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
|
|
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
|
|
"scheduler": (core.SCHEDULERS,),
|
|
"positive": ("CONDITIONING",),
|
|
"negative": ("CONDITIONING",),
|
|
"denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}),
|
|
"feather": ("INT", {"default": 5, "min": 0, "max": 100, "step": 1}),
|
|
"inpaint_model": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
|
"noise_mask_feather": ("INT", {"default": 20, "min": 0, "max": 100, "step": 1}),
|
|
},
|
|
"optional": {
|
|
"upscale_model_opt": ("UPSCALE_MODEL",),
|
|
"upscaler_hook_opt": ("UPSCALER_HOOK",),
|
|
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Upscale"
|
|
|
|
@staticmethod
|
|
def doit(image, segs, model, clip, vae, rescale_factor, resampling_method, supersample, rounding_modulus,
|
|
seed, steps, cfg, sampler_name, scheduler, positive, negative, denoise, feather, inpaint_model, noise_mask_feather,
|
|
upscale_model_opt=None, upscaler_hook_opt=None, scheduler_func_opt=None):
|
|
|
|
new_image = segs_upscaler.upscaler(image, upscale_model_opt, rescale_factor, resampling_method, supersample, rounding_modulus)
|
|
|
|
segs = core.segs_scale_match(segs, new_image.shape)
|
|
|
|
ordered_segs = segs[1]
|
|
|
|
for i, seg in enumerate(ordered_segs):
|
|
cropped_image = crop_ndarray4(new_image.numpy(), seg.crop_region)
|
|
cropped_image = to_tensor(cropped_image)
|
|
mask = to_tensor(seg.cropped_mask)
|
|
mask = tensor_gaussian_blur_mask(mask, feather)
|
|
|
|
is_mask_all_zeros = (seg.cropped_mask == 0).all().item()
|
|
if is_mask_all_zeros:
|
|
print(f"SEGSUpscaler: segment skip [empty mask]")
|
|
continue
|
|
|
|
cropped_mask = seg.cropped_mask
|
|
|
|
seg_seed = seed + i
|
|
|
|
enhanced_image = segs_upscaler.img2img_segs(cropped_image, model, clip, vae, seg_seed, steps, cfg, sampler_name, scheduler,
|
|
positive, negative, denoise,
|
|
noise_mask=cropped_mask, control_net_wrapper=seg.control_net_wrapper,
|
|
inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func_opt=scheduler_func_opt)
|
|
if not (enhanced_image is None):
|
|
new_image = new_image.cpu()
|
|
enhanced_image = enhanced_image.cpu()
|
|
left = seg.crop_region[0]
|
|
top = seg.crop_region[1]
|
|
tensor_paste(new_image, enhanced_image, (left, top), mask)
|
|
|
|
if upscaler_hook_opt is not None:
|
|
new_image = upscaler_hook_opt.post_paste(new_image)
|
|
|
|
enhanced_img = tensor_convert_rgb(new_image)
|
|
|
|
return (enhanced_img,)
|
|
|
|
|
|
class SEGSUpscalerPipe:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
resampling_methods = ["lanczos", "nearest", "bilinear", "bicubic"]
|
|
|
|
return {"required": {
|
|
"image": ("IMAGE",),
|
|
"segs": ("SEGS",),
|
|
"basic_pipe": ("BASIC_PIPE",),
|
|
"rescale_factor": ("FLOAT", {"default": 2, "min": 0.01, "max": 100.0, "step": 0.01}),
|
|
"resampling_method": (resampling_methods,),
|
|
"supersample": (["true", "false"],),
|
|
"rounding_modulus": ("INT", {"default": 8, "min": 8, "max": 1024, "step": 8}),
|
|
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
|
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
|
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
|
|
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
|
|
"scheduler": (core.SCHEDULERS,),
|
|
"denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}),
|
|
"feather": ("INT", {"default": 5, "min": 0, "max": 100, "step": 1}),
|
|
"inpaint_model": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
|
"noise_mask_feather": ("INT", {"default": 20, "min": 0, "max": 100, "step": 1}),
|
|
},
|
|
"optional": {
|
|
"upscale_model_opt": ("UPSCALE_MODEL",),
|
|
"upscaler_hook_opt": ("UPSCALER_HOOK",),
|
|
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "doit"
|
|
|
|
CATEGORY = "ImpactPack/Upscale"
|
|
|
|
@staticmethod
|
|
def doit(image, segs, basic_pipe, rescale_factor, resampling_method, supersample, rounding_modulus,
|
|
seed, steps, cfg, sampler_name, scheduler, denoise, feather, inpaint_model, noise_mask_feather,
|
|
upscale_model_opt=None, upscaler_hook_opt=None, scheduler_func_opt=None):
|
|
|
|
model, clip, vae, positive, negative = basic_pipe
|
|
|
|
return SEGSUpscaler.doit(image, segs, model, clip, vae, rescale_factor, resampling_method, supersample, rounding_modulus,
|
|
seed, steps, cfg, sampler_name, scheduler, positive, negative, denoise, feather, inpaint_model, noise_mask_feather,
|
|
upscale_model_opt=upscale_model_opt, upscaler_hook_opt=upscaler_hook_opt, scheduler_func_opt=scheduler_func_opt)
|
|
|