workflow updates
This commit is contained in:
@@ -0,0 +1 @@
|
||||
{"id":"d97cddfb-e226-4b06-a66a-f57669cdb19c","revision":0,"last_node_id":4,"last_link_id":2,"nodes":[{"id":3,"type":"MaskPreview","pos":[1014.7427734375,130],"size":[140,246],"flags":{},"order":3,"mode":0,"inputs":[{"localized_name":"mask","name":"mask","type":"MASK","link":2}],"outputs":[],"properties":{"cnr_id":"comfy-core","ver":"0.15.1","Node name for S&R":"MaskPreview","ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{},"version":"7.5.1"}},"widgets_values":[]},{"id":2,"type":"LoadImage","pos":[100,130],"size":[282.798828125,314],"flags":{},"order":0,"mode":0,"inputs":[{"localized_name":"image","name":"image","type":"COMBO","widget":{"name":"image"},"link":null},{"localized_name":"choose file to upload","name":"upload","type":"IMAGEUPLOAD","widget":{"name":"upload"},"link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":[1]},{"localized_name":"MASK","name":"MASK","type":"MASK","links":null}],"properties":{"cnr_id":"comfy-core","ver":"0.15.1","Node name for S&R":"LoadImage","ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{},"version":"7.5.1"}},"widgets_values":["example.png","image"]},{"id":1,"type":"ForbiddenVisionFaceFixerMaskOnly","pos":[570,130],"size":[344.7427734375,106],"flags":{},"order":2,"mode":0,"inputs":[{"localized_name":"image","name":"image","type":"IMAGE","link":1},{"localized_name":"face_selection","name":"face_selection","type":"INT","widget":{"name":"face_selection"},"link":null},{"localized_name":"enable_segmentation","name":"enable_segmentation","type":"BOOLEAN","widget":{"name":"enable_segmentation"},"link":null},{"localized_name":"detection_confidence","name":"detection_confidence","type":"FLOAT","widget":{"name":"detection_confidence"},"link":null}],"outputs":[{"localized_name":"face_masks","name":"face_masks","type":"MASK","links":[2]}],"properties":{"cnr_id":"forbidden-vision","ver":"096f144c59815518bd12e2c8c7c3763a7e1d8e36","Node name for S&R":"ForbiddenVisionFaceFixerMaskOnly","ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{},"version":"7.5.1"}},"widgets_values":[0,true,0.8]},{"id":4,"type":"MarkdownNote","pos":[110,-120],"size":[460,200],"flags":{},"order":1,"mode":0,"inputs":[],"outputs":[],"title":"Welcome to Forbidden Vision","properties":{"ue_properties":{"widget_ue_connectable":{},"version":"7.5.1","input_ue_unconnectable":{}}},"widgets_values":["# Mask only\n\n**Nice and easy.**\n\n**Face Selection** — 0 captures all detected faces. 1 and up selects a face by highest detection confidence level\n\nLowering detection confidence makes the detection model return more \"unsure\" detections.\n\nYou can also disable segmentation and just use the detection box as the mask\n\nThe output is the face mask within the input image resolution.\n\n"],"color":"#223","bgcolor":"#335","shape":4}],"links":[[1,2,0,1,0,"IMAGE"],[2,1,0,3,0,"MASK"]],"groups":[],"config":{},"extra":{"workflowRendererVersion":"LG","ue_links":[],"ds":{"scale":1.1814332247557002,"offset":[239.3724787347927,262.3333333333333]},"links_added_by_ue":[]},"version":0.4}
|
||||
File diff suppressed because one or more lines are too long
+40
-1
@@ -12,6 +12,7 @@ from .face_processor_integrated import ForbiddenVisionFaceProcessorIntegrated
|
||||
from .utils import check_for_interruption, ensure_model_directories, get_ordered_upscaler_model_list, clean_model_name
|
||||
|
||||
|
||||
|
||||
class ForbiddenVisionFaceEditPrep:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -29,6 +30,12 @@ class ForbiddenVisionFaceEditPrep:
|
||||
"processing_resolution": ("INT", {"default": 1024, "min": 512, "max": 2048, "step": 64}),
|
||||
"crop_padding": ("FLOAT", {"default": 1.6, "min": 1.0, "max": 3.0, "step": 0.1}),
|
||||
"mask_expansion": ("INT", {"default": 2, "min": 0, "max": 100, "step": 1}),
|
||||
# ── NEW ──────────────────────────────────────────────────────────
|
||||
"sampling_mask_blur_size": ("INT", {"default": 21, "min": 0, "max": 101, "step": 2,
|
||||
"tooltip": "Blur kernel size for the output mask. 0 or 1 = no blur."}),
|
||||
"sampling_mask_blur_strength": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 6.0, "step": 0.1,
|
||||
"tooltip": "Controls blur sigma relative to kernel size."}),
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
"enable_pre_upscale": ("BOOLEAN", {"default": True}),
|
||||
"upscaler_model": (upscaler_models, {"default": default_upscaler}),
|
||||
"isolate_face": ("BOOLEAN", {"default": False}),
|
||||
@@ -50,6 +57,34 @@ class ForbiddenVisionFaceEditPrep:
|
||||
self.upscaler_model = None
|
||||
self.upscaler_model_name = None
|
||||
|
||||
# ── NEW HELPER ────────────────────────────────────────────────────────────
|
||||
def _blur_mask(self, mask_tensor, blur_size, blur_strength):
|
||||
"""
|
||||
Apply Gaussian blur to a (B, H, W) mask tensor.
|
||||
Returns the mask unchanged when blur_size <= 1.
|
||||
Mirrors the logic in process_inpaint_mask() from the main node.
|
||||
"""
|
||||
if blur_size <= 1:
|
||||
return mask_tensor
|
||||
|
||||
# kernel size must be odd
|
||||
if blur_size % 2 == 0:
|
||||
blur_size += 1
|
||||
|
||||
device = mask_tensor.device
|
||||
mask_4d = mask_tensor.unsqueeze(1) # (B, 1, H, W)
|
||||
|
||||
base_sigma = (blur_size - 1) / 8.0
|
||||
strength_t = torch.tensor(blur_strength - 1.0, device=device, dtype=torch.float32)
|
||||
multiplier = 1.0 + torch.tanh(strength_t) * 2.0
|
||||
actual_sigma = base_sigma * multiplier.item()
|
||||
|
||||
blurred = kornia.filters.gaussian_blur2d(
|
||||
mask_4d, (blur_size, blur_size), (actual_sigma, actual_sigma)
|
||||
)
|
||||
return blurred.squeeze(1) # back to (B, H, W)
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def load_upscaler_model(self, model_name):
|
||||
clean_model_name_val = clean_model_name(model_name)
|
||||
|
||||
@@ -193,6 +228,7 @@ class ForbiddenVisionFaceEditPrep:
|
||||
|
||||
def prepare_face(self, image, face_selection, enable_segmentation, detection_confidence,
|
||||
processing_resolution, crop_padding, mask_expansion,
|
||||
sampling_mask_blur_size, sampling_mask_blur_strength, # ← NEW
|
||||
enable_pre_upscale, upscaler_model, isolate_face, mask=None):
|
||||
try:
|
||||
check_for_interruption()
|
||||
@@ -259,6 +295,10 @@ class ForbiddenVisionFaceEditPrep:
|
||||
cropped_face = cropped_face.to(device)
|
||||
sampler_mask = sampler_mask.to(device)
|
||||
|
||||
# ── NEW: apply mask blur if requested ────────────────────────────
|
||||
sampler_mask = self._blur_mask(sampler_mask, sampling_mask_blur_size, sampling_mask_blur_strength)
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
|
||||
if isolate_face:
|
||||
mask_bchw = sampler_mask.unsqueeze(1)
|
||||
kh = max(3, (processing_resolution // 32) | 1)
|
||||
@@ -309,7 +349,6 @@ class ForbiddenVisionFaceEditPrep:
|
||||
return (empty_face, empty_mask, empty_info)
|
||||
|
||||
|
||||
|
||||
class ForbiddenVisionFaceEditMerge:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
|
||||
Reference in New Issue
Block a user