{"record":{"id":"3f17c286e209716f","repo":"Comfy-Org/ComfyUI","slug":"triposplatpreprocessimage-mask-is-empty-no-foreg","errorCode":null,"errorMessage":"TripoSplatPreprocessImage: mask is empty (no foreground pixels).","messagePattern":"TripoSplatPreprocessImage: mask is empty \\(no foreground pixels\\)\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"comfy_extras/nodes_triposplat.py","lineNumber":44,"sourceCode":"    # Match original preprocessing:\n    # resize min side to `size` -> erode alpha -> alpha bbox -> 1.2x square crop -> resize -> composite on black.\n    rgb = image[..., :3].clamp(0, 1).movedim(-1, 0)        # (3, H, W)\n    alpha = mask.clamp(0, 1)[None]                         # (1, H, W)\n    rgba = torch.cat([rgb, alpha], 0)[None]                # (1, 4, H, W)\n\n    h, w = rgba.shape[-2:]\n    s = size / min(w, h)\n    rgba = comfy.utils.common_upscale(rgba, max(1, round(w * s)), max(1, round(h * s)), \"lanczos\", \"disabled\").clamp(0, 1)\n\n    a = rgba[:, 3:4]\n    if erode_radius > 0:\n        # min filter over a (2r+1) window == morphological erosion of the alpha matte.\n        a = -F.max_pool2d(-a, 2 * erode_radius + 1, stride=1, padding=erode_radius)\n        rgba = torch.cat([rgba[:, :3], a], 1)\n\n    ys, xs = torch.nonzero(a[0, 0] > 0, as_tuple=True)\n    if xs.numel() == 0:\n        raise ValueError(\"TripoSplatPreprocessImage: mask is empty (no foreground pixels).\")\n    x0, x1 = int(xs.min()), int(xs.max())\n    y0, y1 = int(ys.min()), int(ys.max())\n    cx, cy = (x0 + x1) / 2, (y0 + y1) / 2\n    half = max(x1 - x0, y1 - y0) / 2 * 1.2\n    left, upper, right, lower = int(cx - half), int(cy - half), int(cx + half), int(cy + half)\n\n    H, W = rgba.shape[-2:]\n    crop = rgba.new_zeros((1, 4, lower - upper, right - left))  # out-of-bounds stays 0, matching PIL.crop\n    sx0, sy0, sx1, sy1 = max(left, 0), max(upper, 0), min(right, W), min(lower, H)\n    if sx1 > sx0 and sy1 > sy0:\n        crop[:, :, sy0 - upper:sy1 - upper, sx0 - left:sx1 - left] = rgba[:, :, sy0:sy1, sx0:sx1]\n\n    crop = comfy.utils.common_upscale(crop, size, size, \"lanczos\", \"disabled\").clamp(0, 1)\n    out = (crop[:, :3] * crop[:, 3:4])[0].movedim(0, -1)   # composite over black == rgb * alpha\n    return out.unsqueeze(0)  # (1, 1024, 1024, 3)\n\n\nclass TripoSplatPreprocessImage(IO.ComfyNode):","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/Comfy-Org/ComfyUI/blob/1c6d8d45b3693bfbb32385b410d813a7fd6be216/comfy_extras/nodes_triposplat.py#L26-L62","documentation":"TripoSplatPreprocessImage crops an RGBA image around its alpha foreground. After optional erosion (a min-filter over the alpha channel) it finds nonzero alpha pixels; if none remain, the mask is empty and there is nothing to crop, so it raises. The erosion step can shrink a thin or weak alpha matte to nothing.","triggerScenarios":"Input RGBA image whose alpha channel is all zeros, or alpha that becomes all zeros after erode_radius erosion — e.g. a faint silhouette with alpha values of 1-2 out of 255, or a fine hair/thin-object matte eroded by a radius larger than the feature width.","commonSituations":"Background-removed images saved without alpha (fully transparent); PNG with black alpha channel; overly large erode_radius for delicate mattes; images where segmentation produced an empty mask.","solutions":["Verify the input image actually has foreground alpha > 0 (inspect the alpha channel statistics)","Reduce erode_radius so erosion does not consume the whole matte","Re-export the image with a correct alpha channel from the background-removal tool"],"exampleFix":"# before: erosion wipes out a thin matte\npre = TripoSplatPreprocessImage.execute(rgba, size=512, erode_radius=15)\n\n# after: smaller erosion radius\npre = TripoSplatPreprocessImage.execute(rgba, size=512, erode_radius=2)","handlingStrategy":"validation","validationCode":"alpha = rgba[:, 3:4]\n# simulate the erosion the node applies\neroded = -torch.nn.functional.max_pool2d(-alpha, 2 * erode_radius + 1, 1, erode_radius)\nif (eroded[0, 0] > 0).sum() == 0:\n    erode_radius = 0  # or reject the image","typeGuard":"def has_foreground(rgba: torch.Tensor) -> bool:\n    return bool((rgba[:, 3] > 0).any())","tryCatchPattern":null,"preventionTips":["Check alpha statistics (min/max/nonzero count) before preprocessing","Keep erode_radius small relative to the thinnest foreground feature"],"tags":["triposplat","image-preprocessing","alpha-mask","erosion"],"backgroundTag":null,"analyzedSha":"1c6d8d45b3693bfbb32385b410d813a7fd6be216","analyzedAt":"2026-08-14T19:37:18.893Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}