Some cleanup

This commit is contained in:
kijai 2026-06-01 00:44:15 +03:00
parent 84ae698281
commit ebd9c6e620
2 changed files with 54 additions and 97 deletions

View File

@ -144,8 +144,7 @@ class SAM3DBody_Predict(io.ComfyNode):
"bboxes", optional=True, force_input=True, "bboxes", optional=True, force_input=True,
tooltip=( tooltip=(
"Per-frame person boxes (e.g. RT-DETR Detect with class_name='person'). " "Per-frame person boxes (e.g. RT-DETR Detect with class_name='person'). "
"Used when no SAM3 track is wired — gives the top-down model a tight, " "Use for better detection as alternative to SAM3 tracks."
"person-centered crop. Multi-person supported (one box = one person)."
), ),
), ),
io.Boolean.Input( io.Boolean.Input(
@ -154,7 +153,7 @@ class SAM3DBody_Predict(io.ComfyNode):
io.Float.Input( io.Float.Input(
"fov_degrees", "fov_degrees",
default=0.0, min=0.0, max=170.0, step=0.5, default=0.0, min=0.0, max=170.0, step=0.5,
tooltip=( tooltip=( #TODO: get FoV from moge another way?
"Vertical FOV in degrees. Affects predicted depth (cam_t.z) and " "Vertical FOV in degrees. Affects predicted depth (cam_t.z) and "
"absolute scale. 0 = use moge_geometry or fall back to ~53° (16:9). " "absolute scale. 0 = use moge_geometry or fall back to ~53° (16:9). "
"Any non-zero value overrides moge_geometry." "Any non-zero value overrides moge_geometry."
@ -164,7 +163,7 @@ class SAM3DBody_Predict(io.ComfyNode):
"moge_geometry", "moge_geometry",
optional=True, optional=True,
tooltip=( tooltip=(
"MoGe geometry (from MoGeInference), used to calculate camera field of view." "MoGe geometry, used to calculate camera field of view."
"For batches choose the most representative frame, or leave unset" "For batches choose the most representative frame, or leave unset"
), ),
), ),
@ -345,10 +344,9 @@ class SAM3DBody_FaceExpression(io.ComfyNode):
crop_factor = 1.2 crop_factor = 1.2
# Auto-pick full-frame vs per-person crops. BlazeFace full-range needs # Auto-pick full-frame vs per-person crops. BlazeFace full-range needs
# ≥32px face in its 192px input; below that we escalate to per-person # ≥32px face in its 192px input; below that we escalate to per-person crops
# crops. Face height ≈ 20% of body-bbox height (rough but stable).
H_img0, W_img0 = img_np.shape[1], img_np.shape[2] H_img0, W_img0 = img_np.shape[1], img_np.shape[2]
min_bbox_px = 32.0 * max(H_img0, W_img0) / (192.0 * 0.20) min_bbox_px = 32.0 * max(H_img0, W_img0) / (192.0 * 0.20) # Face height ≈ 20% of body-bbox height.
use_per_person_crops = any( use_per_person_crops = any(
(p["bbox"][3] - p["bbox"][1]) < min_bbox_px (p["bbox"][3] - p["bbox"][1]) < min_bbox_px
for persons in new_frames for p in persons for persons in new_frames for p in persons
@ -400,10 +398,9 @@ class SAM3DBody_FaceExpression(io.ComfyNode):
pbar.update(1) pbar.update(1)
# Baseline subtraction. MP has subject-specific rest bias (e.g. # Baseline subtraction. MP has subject-specific rest bias, without subtraction, strength
# naturally-raised brow at 0.15); without subtraction, strength # multipliers bake that into every frame.
# multipliers bake that into every frame. Per-clip needs ~30 frames # Per-clip needs ~30 frames or it would zero out the expression.
# or it would zero out the expression.
BASELINE_MIN_FRAMES = 30 BASELINE_MIN_FRAMES = 30
if n_total_frames_with_persons >= BASELINE_MIN_FRAMES: if n_total_frames_with_persons >= BASELINE_MIN_FRAMES:
for pid in range(max_persons): for pid in range(max_persons):
@ -417,7 +414,7 @@ class SAM3DBody_FaceExpression(io.ComfyNode):
f"got {n_total_frames_with_persons}. Skipping subtraction." f"got {n_total_frames_with_persons}. Skipping subtraction."
) )
# Smooth raw signal AFTER baseline subtraction but BEFORE gap fill # Smooth raw signal AFTER baseline subtraction but BEFORE gap fill
# MP's per-frame noise gets averaged out at the source. # MP's per-frame noise gets averaged out at the source.
bs_win = int(blendshape_smooth_window) bs_win = int(blendshape_smooth_window)
if bs_win > 1: if bs_win > 1:
@ -484,9 +481,8 @@ class SAM3DBody_Smooth(io.ComfyNode):
options=["gaussian", "savgol"], options=["gaussian", "savgol"],
default="gaussian", advanced=True, default="gaussian", advanced=True,
tooltip=( tooltip=(
"'gaussian': symmetric weighted average — phase-preserving " "'gaussian': symmetric weighted average, best general-purpose smoother. "
"(no time-shift), best general-purpose smoother. " "'savgol': sliding polynomial fit, preserves sharp peaks."
"'savgol': sliding polynomial fit — preserves sharp peaks "
), ),
), ),
io.Int.Input( io.Int.Input(
@ -498,10 +494,9 @@ class SAM3DBody_Smooth(io.ComfyNode):
"rotation_threshold_deg", "rotation_threshold_deg",
default=15.0, min=0.0, max=45.0, step=1.0, advanced=True, default=15.0, min=0.0, max=45.0, step=1.0, advanced=True,
tooltip=( tooltip=(
"Geometry smoothing drops to RAW above this root-rotation " "Disables smoothing for this root-rotation rate (deg/frame) to preserve fast spins. "
"rate (deg/frame) to preserve fast spins. 15° suits most " "15° suits most content, low values trigger on ordinary jitter and "
"content; low values trigger on ordinary jitter and " "silently sabotage smoothing. 0 = disable."
"silently sabotage smoothing. 0 = disable backoff."
), ),
), ),
], ],
@ -908,12 +903,10 @@ class SAM3DBody_Render(io.ComfyNode):
], ],
tooltip=( tooltip=(
"'mesh' = 3D MHR mesh rasterized through the camera. " "'mesh' = 3D MHR mesh rasterized through the camera. "
"'silhouette' = binary mask of the mesh (white-on-black, " "'silhouette' = binary mask of the mesh. "
"background ignored). 'openpose_2d' = flat 2D skeleton " "'openpose_2d' = flat 2D skeleton "
"from pred_keypoints_2d (DWPose look, ControlNet-ready). " "'openpose_3d' = openpose skeleton as flat-shaded 3D model "
"'openpose_3d' = same skeleton as flat-shaded 3D capsules " "'scail' = SCAIL 3D capsules "
"(camera-aware, proper depth). 'scail' = SCAIL 3D capsules "
"via torch SDF ray-march (proper occlusion / depth)."
), ),
), ),
], ],

View File

@ -425,6 +425,21 @@ def rainbow_tilt_inputs():
] ]
def camera_translation_input():
"""Shared camera_translation combo (BuildPoseGLB + SavePoseBVH)."""
return IO.Combo.Input(
"camera_translation",
options=["off", "centered", "absolute"],
default="off",
tooltip=(
"Bake pred_cam_t into the root's translation "
"'off' = bind position "
"'centered' = delta from frame 0 "
"'absolute' = raw (Z is camera depth — usually meters away)."
),
)
class BuildPoseGLB(IO.ComfyNode): class BuildPoseGLB(IO.ComfyNode):
"""Convert pose_data to an in-memory animated GLB""" """Convert pose_data to an in-memory animated GLB"""
@ -438,24 +453,13 @@ class BuildPoseGLB(IO.ComfyNode):
inputs=[ inputs=[
IO.MultiType.Input( IO.MultiType.Input(
"pose_data", types=[MHRPoseData, KimodoPoseData], "pose_data", types=[MHRPoseData, KimodoPoseData],
tooltip=( tooltip=("MHR pose data from SAM3DBody_Predict, Kimodo. "),
"MHR pose data from SAM3DBody_Predict, or external-rig "
"pose data from Kimodo (`_skeleton_override`-augmented)."
),
), ),
SAM3DBodyModel.Input("sam3d_body_model", optional=True), SAM3DBodyModel.Input("sam3d_body_model", optional=True),
IO.DynamicCombo.Input( IO.DynamicCombo.Input(
"mesh_style", "mesh_style",
options=[ options=[
IO.DynamicCombo.Option("body_mesh", [ IO.DynamicCombo.Option("body_mesh", [
IO.Int.Input(
"bone_smooth_window",
default=0, min=0, max=51, step=2,
tooltip=(
"Gaussian window on per-bone rotation keyframes. 0 = off. "
"7-15 helps cartwheels/spins where upstream Smooth misses spikes."
),
),
IO.DynamicCombo.Input( IO.DynamicCombo.Input(
"bone_vis", "bone_vis",
options=[ options=[
@ -485,11 +489,7 @@ class BuildPoseGLB(IO.ComfyNode):
), ),
]), ]),
], ],
tooltip=( tooltip=("Bone vis shape, rigidly skinned to each joint. "),
"Bone vis shape, rigidly skinned to each joint. "
"'octahedrons' = Blender-style directional bones (joint → "
"primary child); 'sticks' = thin lines."
),
), ),
IO.DynamicCombo.Input( IO.DynamicCombo.Input(
"shader", "shader",
@ -500,7 +500,7 @@ class BuildPoseGLB(IO.ComfyNode):
IO.Float.Input( IO.Float.Input(
"person_palette_falloff", "person_palette_falloff",
default=0.6, min=0.1, max=1.0, step=0.05, default=0.6, min=0.1, max=1.0, step=0.05,
tooltip="Per-person desaturation: track k gets (1 - falloff^k) pastel mix.", tooltip="Per-person desaturation: each track gets (1 - falloff^k) pastel mix.",
), ),
]), ]),
IO.DynamicCombo.Option("rainbow_face_normal", [ IO.DynamicCombo.Option("rainbow_face_normal", [
@ -508,7 +508,7 @@ class BuildPoseGLB(IO.ComfyNode):
IO.Float.Input( IO.Float.Input(
"person_palette_falloff", "person_palette_falloff",
default=0.6, min=0.1, max=1.0, step=0.05, default=0.6, min=0.1, max=1.0, step=0.05,
tooltip="Per-person desaturation: track k gets (1 - falloff^k) pastel mix.", tooltip="Per-person desaturation: each track gets (1 - falloff^k) pastel mix.",
), ),
]), ]),
IO.DynamicCombo.Option("rainbow_face_semantic", [ IO.DynamicCombo.Option("rainbow_face_semantic", [
@ -516,7 +516,7 @@ class BuildPoseGLB(IO.ComfyNode):
IO.Float.Input( IO.Float.Input(
"person_palette_falloff", "person_palette_falloff",
default=0.6, min=0.1, max=1.0, step=0.05, default=0.6, min=0.1, max=1.0, step=0.05,
tooltip="Per-person desaturation: track k gets (1 - falloff^k) pastel mix.", tooltip="Per-person desaturation: each track gets (1 - falloff^k) pastel mix.",
), ),
]), ]),
], ],
@ -527,14 +527,6 @@ class BuildPoseGLB(IO.ComfyNode):
), ),
]), ]),
IO.DynamicCombo.Option("bones_only", [ IO.DynamicCombo.Option("bones_only", [
IO.Int.Input(
"bone_smooth_window",
default=0, min=0, max=51, step=2,
tooltip=(
"Gaussian window on per-bone rotation keyframes. 0 = off. "
"7-15 helps cartwheels/spins where upstream Smooth misses spikes."
),
),
IO.DynamicCombo.Input( IO.DynamicCombo.Input(
"bone_vis", "bone_vis",
options=[ options=[
@ -571,14 +563,6 @@ class BuildPoseGLB(IO.ComfyNode):
), ),
]), ]),
IO.DynamicCombo.Option("openpose", [ IO.DynamicCombo.Option("openpose", [
IO.Int.Input(
"bone_smooth_window",
default=0, min=0, max=51, step=2,
tooltip=(
"Gaussian window on keypoint tracks. 0 = off. "
"7-15 calms jitter where upstream Smooth misses spikes."
),
),
IO.Float.Input( IO.Float.Input(
"marker_radius_m", default=0.010, min=0.005, max=0.1, step=0.001, advanced=True, "marker_radius_m", default=0.010, min=0.005, max=0.1, step=0.001, advanced=True,
tooltip="Sphere radius in m.", tooltip="Sphere radius in m.",
@ -618,14 +602,6 @@ class BuildPoseGLB(IO.ComfyNode):
), ),
]), ]),
IO.DynamicCombo.Option("scail", [ IO.DynamicCombo.Option("scail", [
IO.Int.Input(
"bone_smooth_window",
default=0, min=0, max=51, step=2,
tooltip=(
"Gaussian window on keypoint tracks. 0 = off. "
"7-15 calms jitter where upstream Smooth misses spikes."
),
),
IO.Float.Input( IO.Float.Input(
"stick_radius_m", default=0.022, min=0.002, max=0.1, step=0.001, advanced=True, "stick_radius_m", default=0.022, min=0.002, max=0.1, step=0.001, advanced=True,
tooltip=( tooltip=(
@ -667,28 +643,25 @@ class BuildPoseGLB(IO.ComfyNode):
]), ]),
], ],
tooltip=( tooltip=(
"'body_mesh' = real Armature (127 bones, skinning, TRS " "'body_mesh' = real Armature (127 bones, skinning, TRS keyframes, 72 face morphs; needs model). "
"keyframes, 72 face morphs; needs model). "
"'bones_only' = bone-shape primitives at each joint (preview armature). " "'bones_only' = bone-shape primitives at each joint (preview armature). "
"'openpose' = OpenPose-18 3D skeleton from keypoints " "'openpose' = OpenPose-18 3D skeleton from keypoints "
"(no model needed). 'scail' = SCAIL 3D capsule rig (open " "'scail' = SCAIL 3D capsule rig (open cylinders capped flush by joint spheres)."
"cylinders capped flush by joint spheres)." ),
),
IO.Int.Input(
"bone_smooth_window",
default=0, min=0, max=51, step=2,
tooltip=(
"Gaussian smoothing window on per-bone rotation keyframes / keypoint "
"tracks. 0 = off. 7-15 calms spins/jitter where upstream Smooth misses spikes."
), ),
), ),
IO.Float.Input( IO.Float.Input(
"fps", default=24.0, min=1.0, max=240.0, step=1.0, "fps", default=24.0, min=1.0, max=240.0, step=1.0,
tooltip="Animation frame rate.", tooltip="Animation frame rate.",
), ),
IO.Combo.Input( camera_translation_input(),
"camera_translation",
options=["off", "centered", "absolute"],
default="off",
tooltip=(
"Bake pred_cam_t into per-track root translation. "
"'off' = origin; 'centered' = delta from frame 0; "
"'absolute' = raw (Z is camera depth — usually meters away)."
),
),
IO.Int.Input( IO.Int.Input(
"track_index", default=-1, min=-1, max=15, "track_index", default=-1, min=-1, max=15,
tooltip="-1 = all tracks; ≥0 = single track.", tooltip="-1 = all tracks; ≥0 = single track.",
@ -698,7 +671,7 @@ class BuildPoseGLB(IO.ComfyNode):
) )
@classmethod @classmethod
def execute(cls, pose_data, mesh_style, sam3d_body_model=None, fps=24.0, camera_translation="off", track_index=-1) -> IO.NodeOutput: def execute(cls, pose_data, mesh_style, sam3d_body_model=None, bone_smooth_window=0, fps=24.0, camera_translation="off", track_index=-1) -> IO.NodeOutput:
mesh_style = mesh_style or {"mesh_style": "body_mesh"} mesh_style = mesh_style or {"mesh_style": "body_mesh"}
mode_key = mesh_style["mesh_style"] mode_key = mesh_style["mesh_style"]
# `shader` is nested in body_mesh; absent for bones_only. # `shader` is nested in body_mesh; absent for bones_only.
@ -730,7 +703,7 @@ class BuildPoseGLB(IO.ComfyNode):
bone_vis_color = str(bone_vis_dict.get("bone_vis_color", "white")) bone_vis_color = str(bone_vis_dict.get("bone_vis_color", "white"))
glb_bytes = build_glb_skeletal( glb_bytes = build_glb_skeletal(
pose_data, sam3d_body_model, pose_data, sam3d_body_model,
bone_smooth_window=int(mesh_style.get("bone_smooth_window", 0)), bone_smooth_window=int(bone_smooth_window),
bone_vis=bone_vis, bone_vis=bone_vis,
bone_vis_radius_m=bone_vis_radius_m, bone_vis_radius_m=bone_vis_radius_m,
bone_vis_color=bone_vis_color, bone_vis_color=bone_vis_color,
@ -754,7 +727,7 @@ class BuildPoseGLB(IO.ComfyNode):
face_marker_radius_m=float(mesh_style.get("face_marker_radius_m", 0.0)), face_marker_radius_m=float(mesh_style.get("face_marker_radius_m", 0.0)),
palette="openpose", palette="openpose",
shape="ellipsoid", shape="ellipsoid",
bone_smooth_window=int(mesh_style.get("bone_smooth_window", 0)), bone_smooth_window=int(bone_smooth_window),
) )
elif mode_key == "scail": elif mode_key == "scail":
# SCAIL rig: open cylinders capped flush by joint spheres (sphere # SCAIL rig: open cylinders capped flush by joint spheres (sphere
@ -781,7 +754,7 @@ class BuildPoseGLB(IO.ComfyNode):
# inside of the open cylinders shades sensibly at grazing angles. # inside of the open cylinders shades sensibly at grazing angles.
material_roughness=float(mesh_style.get("material_roughness", 0.3)), material_roughness=float(mesh_style.get("material_roughness", 0.3)),
material_double_sided=True, material_double_sided=True,
bone_smooth_window=int(mesh_style.get("bone_smooth_window", 0)), bone_smooth_window=int(bone_smooth_window),
) )
else: else:
raise ValueError(f"BuildPoseGLB: unknown mesh_style {mode_key!r}") raise ValueError(f"BuildPoseGLB: unknown mesh_style {mode_key!r}")
@ -813,16 +786,7 @@ class SavePoseBVH(IO.ComfyNode):
"fps", default=24.0, min=1.0, max=240.0, step=1.0, "fps", default=24.0, min=1.0, max=240.0, step=1.0,
tooltip="Animation frame rate (BVH `Frame Time`).", tooltip="Animation frame rate (BVH `Frame Time`).",
), ),
IO.Combo.Input( camera_translation_input(),
"camera_translation",
options=["off", "centered", "absolute"],
default="off",
tooltip=(
"Bake pred_cam_t into the root's position channels. "
"'off' = bind position; 'centered' = delta from frame 0; "
"'absolute' = raw (Z is camera depth — usually meters away)."
),
),
IO.Combo.Input( IO.Combo.Input(
"units", "units",
options=["cm", "m"], options=["cm", "m"],