From d3f242cda78626f810dcff054e626aea932ab6b8 Mon Sep 17 00:00:00 2001 From: ProGamerGov Date: Thu, 28 Aug 2025 19:54:20 -0600 Subject: [PATCH] Add pixel radius option to pole seam mask node --- example_workflows/masked_seam_removal.json | 1158 ++++++++++++++++++++ nodes.py | 41 +- 2 files changed, 1191 insertions(+), 8 deletions(-) create mode 100644 example_workflows/masked_seam_removal.json diff --git a/example_workflows/masked_seam_removal.json b/example_workflows/masked_seam_removal.json new file mode 100644 index 0000000..0974fa0 --- /dev/null +++ b/example_workflows/masked_seam_removal.json @@ -0,0 +1,1158 @@ +{ + "id": "304b49a0-9813-423d-9b0f-e11825dc71b4", + "revision": 0, + "last_node_id": 83, + "last_link_id": 175, + "nodes": [ + { + "id": 53, + "type": "Roll Image Axes", + "pos": [ + 7915.486328125, + -160.32659912109375 + ], + "size": [ + 278.6666564941406, + 106 + ], + "flags": {}, + "order": 19, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 161 + } + ], + "outputs": [ + { + "name": "Rolled Image", + "type": "IMAGE", + "links": [ + 162 + ] + } + ], + "properties": { + "aux_id": "ProGamerGov/ComfyUI_pytorch360convert", + "ver": "1.0.2", + "Node name for S&R": "Roll Image Axes", + "cnr_id": "comfyui-pytorch360convert" + }, + "widgets_values": [ + 0, + 0, + true + ] + }, + { + "id": 76, + "type": "VAEDecode", + "pos": [ + 7740.466796875, + -159.83090209960938 + ], + "size": [ + 140, + 46 + ], + "flags": {}, + "order": 18, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 160 + }, + { + "name": "vae", + "type": "VAE", + "link": 164 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 161 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.33", + "Node name for S&R": "VAEDecode" + }, + "widgets_values": [] + }, + { + "id": 57, + "type": "SaveImage", + "pos": [ + 8235.3115234375, + -160.64053344726562 + ], + "size": [ + 270, + 270 + ], + "flags": {}, + "order": 20, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 162 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.33" + }, + "widgets_values": [ + "ComfyUI" + ] + }, + { + "id": 77, + "type": "SetLatentNoiseMask", + "pos": [ + 7172.34228515625, + -101.7217788696289 + ], + "size": [ + 181.31817626953125, + 46 + ], + "flags": {}, + "order": 16, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 157 + }, + { + "name": "mask", + "type": "MASK", + "link": 163 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 158 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.33", + "Node name for S&R": "SetLatentNoiseMask" + }, + "widgets_values": [] + }, + { + "id": 78, + "type": "VAEDecode", + "pos": [ + 6437.6005859375, + -122.40325164794922 + ], + "size": [ + 140, + 46 + ], + "flags": { + "collapsed": false + }, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 167 + }, + { + "name": "vae", + "type": "VAE", + "link": 169 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 165, + 166 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.33", + "Node name for S&R": "VAEDecode" + }, + "widgets_values": [] + }, + { + "id": 74, + "type": "CLIPTextEncode", + "pos": [ + 5538.2265625, + 74.62285614013672 + ], + "size": [ + 422.84503173828125, + 164.31304931640625 + ], + "flags": { + "collapsed": false + }, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 153 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "slot_index": 0, + "links": [ + 139, + 155 + ] + } + ], + "title": "CLIP Text Encode (Negative Prompt)", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.33", + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "blurry, low quality, render, painting, drawing" + ], + "color": "#322", + "bgcolor": "#533" + }, + { + "id": 73, + "type": "CLIPTextEncode", + "pos": [ + 5533.26953125, + -144.67410278320312 + ], + "size": [ + 422.84503173828125, + 164.31304931640625 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 152 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "slot_index": 0, + "links": [ + 140, + 154 + ] + } + ], + "title": "CLIP Text Encode (Positive Prompt)", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.33", + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "equirectangular 360 view, wide open grassy alpine field scattered with wildflowers, distant rocky mountains under a beautiful cloudy sunset sky" + ], + "color": "#232", + "bgcolor": "#353" + }, + { + "id": 75, + "type": "VAEEncode", + "pos": [ + 6985.05029296875, + -102.14224243164062 + ], + "size": [ + 140, + 46 + ], + "flags": {}, + "order": 13, + "mode": 0, + "inputs": [ + { + "name": "pixels", + "type": "IMAGE", + "link": 156 + }, + { + "name": "vae", + "type": "VAE", + "link": 168 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 157 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.33", + "Node name for S&R": "VAEEncode" + }, + "widgets_values": [] + }, + { + "id": 43, + "type": "PrimitiveInt", + "pos": [ + 5159.72216796875, + 416.3063049316406 + ], + "size": [ + 270, + 82 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [ + 131, + 132 + ] + } + ], + "title": "Steps", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.33", + "Node name for S&R": "PrimitiveInt" + }, + "widgets_values": [ + 20, + "fixed" + ] + }, + { + "id": 70, + "type": "EmptySD3LatentImage", + "pos": [ + 5106.650390625, + 136.48558044433594 + ], + "size": [ + 315, + 106 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "slot_index": 0, + "links": [ + 143 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.33", + "Node name for S&R": "EmptySD3LatentImage" + }, + "widgets_values": [ + 2048, + 1024, + 1 + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 80, + "type": "Note", + "pos": [ + 7922.35107421875, + -12.84326171875 + ], + "size": [ + 268.5602722167969, + 104.66071319580078 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [], + "outputs": [], + "properties": {}, + "widgets_values": [ + "Optionally move the seam back to where it was before inpainting." + ], + "color": "#432", + "bgcolor": "#653" + }, + { + "id": 71, + "type": "CheckpointLoaderSimple", + "pos": [ + 5114.5576171875, + -13.159783363342285 + ], + "size": [ + 315, + 98 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "slot_index": 0, + "links": [ + 150, + 151 + ] + }, + { + "name": "CLIP", + "type": "CLIP", + "slot_index": 1, + "links": [ + 152, + 153 + ] + }, + { + "name": "VAE", + "type": "VAE", + "slot_index": 2, + "links": [ + 164, + 168, + 169 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.33", + "Node name for S&R": "CheckpointLoaderSimple" + }, + "widgets_values": [ + "sdxl_360_diffusion.safetensors" + ] + }, + { + "id": 36, + "type": "Create Seam Mask", + "pos": [ + 6656.7880859375, + 14.484803199768066 + ], + "size": [ + 270, + 130 + ], + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 166 + } + ], + "outputs": [ + { + "name": "Seam Mask", + "type": "MASK", + "links": [ + 163, + 170 + ] + } + ], + "properties": { + "aux_id": "ProGamerGov/ComfyUI_pytorch360convert", + "ver": "1.0.2", + "Node name for S&R": "Create Seam Mask", + "cnr_id": "comfyui-pytorch360convert" + }, + "widgets_values": [ + 0, + 96, + 0, + false + ] + }, + { + "id": 35, + "type": "Roll Image Axes", + "pos": [ + 6659.3291015625, + -283.3916320800781 + ], + "size": [ + 265.3009948730469, + 106 + ], + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 165 + } + ], + "outputs": [ + { + "name": "Rolled Image", + "type": "IMAGE", + "links": [ + 156, + 171 + ] + } + ], + "properties": { + "aux_id": "ProGamerGov/ComfyUI_pytorch360convert", + "ver": "1.0.2", + "Node name for S&R": "Roll Image Axes", + "cnr_id": "comfyui-pytorch360convert" + }, + "widgets_values": [ + 0, + 0, + true + ] + }, + { + "id": 83, + "type": "PrimitiveFloat", + "pos": [ + 5154.7119140625, + 542.1605224609375 + ], + "size": [ + 270, + 58 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "FLOAT", + "type": "FLOAT", + "links": [ + 172, + 173 + ] + } + ], + "title": "CFG", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.33", + "Node name for S&R": "PrimitiveFloat" + }, + "widgets_values": [ + 6 + ] + }, + { + "id": 39, + "type": "KSampler", + "pos": [ + 7379.5185546875, + -160.2920684814453 + ], + "size": [ + 315, + 474 + ], + "flags": {}, + "order": 17, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 151 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 154 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 155 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 158 + }, + { + "name": "seed", + "type": "INT", + "widget": { + "name": "seed" + }, + "link": 175 + }, + { + "name": "steps", + "type": "INT", + "widget": { + "name": "steps" + }, + "link": 132 + }, + { + "name": "cfg", + "type": "FLOAT", + "widget": { + "name": "cfg" + }, + "link": 173 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "slot_index": 0, + "links": [ + 160 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.50", + "Node name for S&R": "KSampler" + }, + "widgets_values": [ + 0, + "randomize", + 20, + 6, + "dpmpp_2m_sde", + "exponential", + 0.5000000000000001 + ] + }, + { + "id": 7, + "type": "KSampler", + "pos": [ + 6084.37451171875, + -122.31721496582031 + ], + "size": [ + 300, + 262 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 150 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 140 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 139 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 143 + }, + { + "name": "seed", + "type": "INT", + "widget": { + "name": "seed" + }, + "link": 174 + }, + { + "name": "steps", + "type": "INT", + "widget": { + "name": "steps" + }, + "link": 131 + }, + { + "name": "cfg", + "type": "FLOAT", + "widget": { + "name": "cfg" + }, + "link": 172 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "slot_index": 0, + "links": [ + 167 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.33", + "Node name for S&R": "KSampler" + }, + "widgets_values": [ + 0, + "randomize", + 20, + 6, + "dpmpp_2m_sde", + "exponential", + 1 + ], + "color": "#2a363b", + "bgcolor": "#3f5159" + }, + { + "id": 44, + "type": "PrimitiveInt", + "pos": [ + 5159.3037109375, + 289.54718017578125 + ], + "size": [ + 270, + 82 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [ + 174, + 175 + ] + } + ], + "title": "Seed", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.33", + "Node name for S&R": "PrimitiveInt" + }, + "widgets_values": [ + 0, + "randomize" + ] + }, + { + "id": 82, + "type": "PreviewImage", + "pos": [ + 7024.06591796875, + 64.98095703125 + ], + "size": [ + 140, + 246 + ], + "flags": {}, + "order": 14, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 171 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.33", + "Node name for S&R": "PreviewImage" + }, + "widgets_values": [] + }, + { + "id": 81, + "type": "MaskPreview", + "pos": [ + 7182.974609375, + 64.69951629638672 + ], + "size": [ + 140, + 246 + ], + "flags": {}, + "order": 15, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 170 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.33", + "Node name for S&R": "MaskPreview" + }, + "widgets_values": [] + }, + { + "id": 79, + "type": "Note", + "pos": [ + 6660.12255859375, + -141.8073272705078 + ], + "size": [ + 271.34478759765625, + 102.99000549316406 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [], + "outputs": [], + "properties": {}, + "widgets_values": [ + "Move the seam to the center of the image, and generate a mask for it. The best mask size to use can vary based on the concepts you using in your prompts." + ], + "color": "#432", + "bgcolor": "#653" + } + ], + "links": [ + [ + 131, + 43, + 0, + 7, + 5, + "INT" + ], + [ + 132, + 43, + 0, + 39, + 5, + "INT" + ], + [ + 139, + 74, + 0, + 7, + 2, + "CONDITIONING" + ], + [ + 140, + 73, + 0, + 7, + 1, + "CONDITIONING" + ], + [ + 143, + 70, + 0, + 7, + 3, + "LATENT" + ], + [ + 150, + 71, + 0, + 7, + 0, + "MODEL" + ], + [ + 151, + 71, + 0, + 39, + 0, + "MODEL" + ], + [ + 152, + 71, + 1, + 73, + 0, + "CLIP" + ], + [ + 153, + 71, + 1, + 74, + 0, + "CLIP" + ], + [ + 154, + 73, + 0, + 39, + 1, + "CONDITIONING" + ], + [ + 155, + 74, + 0, + 39, + 2, + "CONDITIONING" + ], + [ + 156, + 35, + 0, + 75, + 0, + "IMAGE" + ], + [ + 157, + 75, + 0, + 77, + 0, + "LATENT" + ], + [ + 158, + 77, + 0, + 39, + 3, + "LATENT" + ], + [ + 160, + 39, + 0, + 76, + 0, + "LATENT" + ], + [ + 161, + 76, + 0, + 53, + 0, + "IMAGE" + ], + [ + 162, + 53, + 0, + 57, + 0, + "IMAGE" + ], + [ + 163, + 36, + 0, + 77, + 1, + "MASK" + ], + [ + 164, + 71, + 2, + 76, + 1, + "VAE" + ], + [ + 165, + 78, + 0, + 35, + 0, + "IMAGE" + ], + [ + 166, + 78, + 0, + 36, + 0, + "IMAGE" + ], + [ + 167, + 7, + 0, + 78, + 0, + "LATENT" + ], + [ + 168, + 71, + 2, + 75, + 1, + "VAE" + ], + [ + 169, + 71, + 2, + 78, + 1, + "VAE" + ], + [ + 170, + 36, + 0, + 81, + 0, + "MASK" + ], + [ + 171, + 35, + 0, + 82, + 0, + "IMAGE" + ], + [ + 172, + 83, + 0, + 7, + 6, + "FLOAT" + ], + [ + 173, + 83, + 0, + 39, + 6, + "FLOAT" + ], + [ + 174, + 44, + 0, + 7, + 4, + "INT" + ], + [ + 175, + 44, + 0, + 39, + 4, + "INT" + ] + ], + "groups": [], + "config": {}, + "extra": { + "frontendVersion": "1.25.11", + "ds": { + "scale": 1.8337361792737823, + "offset": [ + -6187.338417120422, + 261.99400329602946 + ] + } + }, + "version": 0.4 +} \ No newline at end of file diff --git a/nodes.py b/nodes.py index 552d2f3..d502f7d 100644 --- a/nodes.py +++ b/nodes.py @@ -1092,18 +1092,23 @@ class E2Face: def _create_centered_circle_mask( - x: torch.Tensor, circle_radius: float, feather: int = 0 + x: torch.Tensor, + circle_radius: float, + feather: int = 0, + pixel_radius: int = 0, ) -> torch.Tensor: """ Create a centered circle mask with optional feathering. Args: x (torch.Tensor): Reference tensor with shape (B, C, H, W). - The mask will copy device, dtype, and batch size from x. + The mask will copy device, dtype, and batch size from x. circle_radius (float): Fraction (0.0–1.0) of max possible radius (min(H,W)/2). - 1.0 = circle touches edges. - feather (int): Feather width in pixels, extending outward from circle_radius. - 0 = hard edge, >0 = smooth falloff. + 1.0 = circle touches edges. + feather (int, optional): Feather width in pixels, extending outward from + circle_radius. 0 = hard edge, >0 = smooth falloff. Default: 0 + pixel_radius (int, optional): Explicit radius in pixels. If >0, overrides + circle_radius. Default: 0 Returns: torch.Tensor: Mask tensor of shape (1, C, H, W), values in [0.0, 1.0]. @@ -1113,7 +1118,10 @@ def _create_centered_circle_mask( # Circle radius in pixels max_radius = size / 2.0 - inner_radius = circle_radius * max_radius + if pixel_radius > 0: + inner_radius = float(pixel_radius) + else: + inner_radius = circle_radius * max_radius outer_radius = inner_radius + feather # Coordinate grid @@ -1156,6 +1164,14 @@ class CreatePoleMask: "required": { "image": ("IMAGE", {"default": None}), "circle_radius": ("FLOAT", {"default": 0.10}), + "pixel_radius": ( + "INT", + { + "default": 0, + "tooltip": "Size of the radius in pixels." + + " If > 0, circle_radius is ignored in favour of pixel_radius.", + }, + ), "feather": ("INT", {"default": 0}), "mode": (["face", "equirectangular"], {"default": "face"}), }, @@ -1172,6 +1188,7 @@ class CreatePoleMask: self, image: torch.Tensor, circle_radius: float = 0.10, + pixel_radius: int = 0, feather: int = 0, mode: str = "face", ) -> Tuple[torch.Tensor]: @@ -1179,7 +1196,12 @@ class CreatePoleMask: if mode == "face": image = image.permute(0, 3, 1, 2) # BHWC -> BCHW - output_mask = _create_centered_circle_mask(image, circle_radius, feather) + output_mask = _create_centered_circle_mask( + image, + circle_radius=circle_radius, + pixel_radius=pixel_radius, + feather=feather, + ) output_mask = output_mask else: output_mask = [] @@ -1194,7 +1216,10 @@ class CreatePoleMask: face_tensor = cubemap[face] # [H_face, W_face, C] face_tensor = face_tensor.unsqueeze(0).permute(0, 3, 1, 2) face_tensor = _create_centered_circle_mask( - face_tensor, circle_radius, feather + face_tensor, + circle_radius=circle_radius, + pixel_radius=pixel_radius, + feather=feather, ) cubemap[face] = face_tensor.permute(0, 2, 3, 1).squeeze(0)