Download studio/rigging/segment.py from AlterProgramming/venture-studio: direct link, hf CLI and curl.
- Browser
- Download file 2.22 kB
-
https://huggingface.co/spaces/AlterProgramming/venture-studio/resolve/main/studio/rigging/segment.py
- Command line
-
hf download hf://spaces/AlterProgramming/venture-studio/studio/rigging/segment.py
-
curl -L -o segment.py https://huggingface.co/spaces/AlterProgramming/venture-studio/resolve/main/studio/rigging/segment.py
2.22 kB
| """studio.rigging.segment — flood-fill-from-corners cartoon segmentation. | |
| Real alpha channel short-circuits the heuristic: `alpha > 127` is the mask. | |
| Otherwise: connected components of the "background-similar" mask, take the | |
| union of components that touch any of the 4 image corners, invert. | |
| Deterministic, numpy + scipy.ndimage.label, no model imports. | |
| """ | |
| from __future__ import annotations | |
| import numpy as np | |
| from scipy.ndimage import label | |
| DEFAULT_TOLERANCE: int = 15 | |
| def flood_fill_segment( | |
| image: np.ndarray, | |
| *, | |
| tolerance: int = DEFAULT_TOLERANCE, | |
| alpha_threshold: int = 127, | |
| ) -> np.ndarray: | |
| """Return a boolean (H, W) foreground mask. | |
| image: (H, W) uint8 grayscale, (H, W, 3) RGB uint8, or (H, W, 4) RGBA uint8. | |
| tolerance: per-channel Chebyshev tolerance for "background-similar" pixels. | |
| alpha_threshold: cutoff for the RGBA fast path (only used if the alpha | |
| channel actually varies across the image). | |
| """ | |
| if image.ndim == 3 and image.shape[-1] == 4: | |
| alpha = image[..., 3] | |
| if alpha.min() < 255 and alpha.max() > 0 and alpha.max() != alpha.min(): | |
| return alpha > alpha_threshold | |
| image = image[..., :3] | |
| if image.ndim == 2: | |
| rgb = np.stack([image, image, image], axis=-1) | |
| elif image.ndim == 3 and image.shape[-1] == 3: | |
| rgb = image | |
| else: | |
| raise ValueError(f"unsupported image shape {image.shape}") | |
| H, W, _ = rgb.shape | |
| rgb_i16 = rgb.astype(np.int16) | |
| corners = np.stack( | |
| [rgb_i16[0, 0], rgb_i16[0, -1], rgb_i16[-1, 0], rgb_i16[-1, -1]], | |
| axis=0, | |
| ) | |
| bg_color = corners.mean(axis=0) | |
| diff = np.abs(rgb_i16 - bg_color).max(axis=-1) | |
| similar = diff <= tolerance | |
| labels, _ = label(similar.astype(np.uint8), structure=np.ones((3, 3), dtype=np.int8)) | |
| bg_label_ids = set() | |
| for y, x in ((0, 0), (0, W - 1), (H - 1, 0), (H - 1, W - 1)): | |
| L = int(labels[y, x]) | |
| if L > 0: | |
| bg_label_ids.add(L) | |
| if not bg_label_ids: | |
| return np.ones((H, W), dtype=bool) | |
| bg_mask = np.isin(labels, np.asarray(sorted(bg_label_ids), dtype=labels.dtype)) | |
| return ~bg_mask | |
| __all__ = ["flood_fill_segment", "DEFAULT_TOLERANCE"] | |