AlterProgramming's picture
add txt2img tab + infer_txt2img endpoint (SD 1.5)
7faaef2 verified
Raw
History Blame Contribute Delete
2.67 kB
"""studio.rigging.skin — inverse-distance-falloff mesh skinning weights.
For each foreground pixel and each bone, weight ∝ 1 / distance_to_bone^p,
normalised across bones to sum to 1 per pixel. Background pixels get zero
weight on every bone.
This is the "fast / good-enough" baseline picked in PHASE_3_RESEARCH.md.
Heat-diffusion skinning (better crease quality) is the upgrade path for
Session 4 if creases hurt visual quality.
"""
from __future__ import annotations
import numpy as np
from pixel_cursor.rigging import BONES, NUM_JOINTS, Skeleton
DEFAULT_FALLOFF: float = 4.0
DEFAULT_EPSILON: float = 0.5
def _point_segment_distance(
points: np.ndarray, a: np.ndarray, b: np.ndarray
) -> np.ndarray:
"""Distance from each point to the closed segment AB.
points: shape (..., 2) in (y, x)
a, b: shape (2,) endpoints
Returns shape (...)
"""
ab = b - a
ap = points - a
ab_len_sq = float((ab * ab).sum())
if ab_len_sq < 1e-6:
return np.linalg.norm(ap, axis=-1)
t = (ap * ab).sum(axis=-1) / ab_len_sq
t = np.clip(t, 0.0, 1.0)
closest = a + t[..., None] * ab
return np.linalg.norm(points - closest, axis=-1)
def compute_skin_weights(
skeleton: Skeleton,
mask: np.ndarray,
*,
falloff: float = DEFAULT_FALLOFF,
epsilon: float = DEFAULT_EPSILON,
) -> np.ndarray:
"""Return (H, W, N_BONES) inverse-distance-falloff skinning weights.
Properties:
- Each foreground pixel's weights sum to 1.0 (partition of unity).
- Each background pixel's weights are exactly 0 across all bones.
- Deterministic: same skeleton + mask → same weights, bitwise.
"""
if mask.ndim != 2:
raise ValueError(f"mask must be 2D, got {mask.shape}")
if skeleton.image_shape != mask.shape:
raise ValueError(
f"skeleton.image_shape={skeleton.image_shape} != mask.shape={mask.shape}"
)
H, W = mask.shape
ys, xs = np.indices((H, W))
coords = np.stack([ys, xs], axis=-1).astype(np.float32)
n_bones = len(BONES)
dists = np.empty((H, W, n_bones), dtype=np.float32)
rest = skeleton.positions
for b, (parent_idx, child_idx) in enumerate(BONES):
dists[..., b] = _point_segment_distance(
coords, rest[parent_idx], rest[child_idx]
)
inv = 1.0 / np.maximum(dists, epsilon) ** falloff
total = inv.sum(axis=-1, keepdims=True)
weights = inv / np.maximum(total, 1e-12)
mask_bool = mask.astype(bool)[..., None]
weights = np.where(mask_bool, weights, 0.0).astype(np.float32)
return weights
__all__ = ["compute_skin_weights", "DEFAULT_FALLOFF", "DEFAULT_EPSILON"]