Kimodo HOI Pipeline Spec
Superseded / legacy
本页描述的 specialized runner 已迁入 tools/kimodo/legacy/,大量中间文件也不再是 current pipeline contract。当前运行方式以 Project Pipeline 为准;本页只用于历史追溯。
Historical status: this was the working contract on 2026-07-01 and is now superseded.
This file was the canonical description of the specialized Kimodo HOI pipeline for pick/carry/place tasks. It is frozen as decision history; do not update its stage order, artifact contracts, or default quality rules.
Scope
The specialized HOI entrypoint is:
It is used for task plans whose main interaction is pick -> carry -> place. The broader batch dispatcher may call it from:
The pipeline is adapter-only: it should not modify Kimodo, HOI-FHLI, or their submodule source trees. It builds scene/task constraints, runs Kimodo body generation, attaches the task object, bakes/refines hands, audits quality, and renders the final full-scene motion.
Runtime Contract
Use one Python for the main Kimodo/ProtoMotions pipeline and a separate Python for official HOI-FHLI grasp candidate stages when needed:
# Main pipeline in this workspace.
env_protomotions_isaaclab/bin/python
# Official HOI-FHLI grasp probe/apply route.
/home/user/anaconda3/envs/isaac-sim/bin/python
--official-grasp-python can override the official-grasp interpreter. If it is
not passed, the pipeline uses /home/user/anaconda3/envs/isaac-sim/bin/python
when that executable exists, otherwise the current Python.
Stage Order
The expected mainline stage order follows main() in
run_kimodo_hoi_pipeline.py:
- Resolve task geometry.
- Read
task_plan.jsonandlayout_json. - Resolve pick/carry/place step IDs.
-
Resolve manipulated object, support object, scene origin, object mesh, object extents, initial object center, target object center, pick root, and carry endpoint in Kimodo coordinates.
-
Build Kimodo inputs.
- Write
meta.json,info.json, and baselineconstraints.json. -
Snapshot baseline constraints as
constraints_base.json. -
Materialize expected contact labels.
-
Materialize task-level expected labels into
contact.npyandcontact_labels.json. -
Add body constraints.
- Optional carry pose keyframes for two-hand carry.
- Single-hand carry defaults to Root2D/text only. Do not insert fullbody carry pose keyframes by default because they constrain legs and can turn walking into repeated pose interpolation.
- Place final reach keyframe. Single-hand place defaults to the final fullbody keyframe only; approach keyframes are an explicit ablation because they can perturb gait and release timing.
- Pick reach keyframe.
-
Single-hand tasks use active-wrist anchors; two-hand tasks use wrist midpoint behavior unless overridden.
-
Optional automatic per-object official grasp.
- Enabled by
--auto-official-pick-grasp. - Build a one-frame source motion for the current task object.
- Run
solve_grasp_keyframe.pyas a bootstrap candidate generator. - By default, single-hand candidates continue through:
probe_official_hoifhli_single_frame_grasp.pyapply_official_hoifhli_grasp_to_smplx_keyframe.py
--disable-auto-official-grasp-probe-applykeeps only the bootstrap candidate result.- Score candidates by hand/object distance, contact ratio, energy, and penetration.
- Select the best passing keyframe.
- Feed the selected keyframe into the official pick-grasp replacement stage.
-
Switch object binding to
active_hand_localunless--keep-auto-official-grasp-binding-modeis set. -
Optional official grasp replacement.
- Enabled by
--official-pick-grasp-keyframe-npz. - Also runs after automatic grasp selection, because the automatic stage writes the selected keyframe into this same argument.
- Replaces the generated pick keyframe before Kimodo generation.
- Single-hand official pick replacement defaults to
--official-pick-grasp-constraint-mode active_hand, so the initial grasp frame isleft-hand/right-handrather thanfullbody. This preserves the active grasp target without locking the inactive arm from the pickup reference pose. - Extracts
pose_handas a hand-bake target when--hand-modeis stillpriorand no explicit hand pose or hand schedule was provided. - With
--object-binding-mode active_hand_local, reuses the same keyframe as the object-in-hand binding anchor. --official-pick-grasp-hold-offset-frames 8,16,24,34,45repeats the official grasp keyframe shortly after attach by default, preventing the hand/object pair from sinking into the pickup support while preserving the generated grasp relation.-
Hold frames use
--official-pick-grasp-hold-constraint-mode active_handby default for single-hand tasks. This emitsleft-hand/right-handend-effector constraints instead of fullbody constraints, so the pickup torso bend is not locked into the post-grasp recovery. -
Optional place grasp keyframe optimization.
- Enabled by
--enable-place-grasp-keyframe-optimize. - Requires an official pick grasp keyframe from
--official-pick-grasp-keyframe-npzor--auto-official-pick-grasp. - Runs
solve_grasp_keyframe.pyonbound_place_pose.npz, reusing the official HOI-FHLI object mesh and active-hand contact setup, but with the final layout/support object rotation used by the object binding stage. - Replaces only the final place constraint with
place_hoifhli_grasp_keyframe. - Single-hand place grasp replacement defaults to
--place-grasp-constraint-mode active_hand, so final placement constrains the active hand while leaving the inactive arm under the generated motion. - Writes
place_grasp_hand_pose_schedule.json, which keeps the pick grasp through carry and fades to the optimized place grasp near release. - By default, aligns the object release end to the optimized place frame so the object is already at the final position and orientation when the place grasp keyframe is enforced.
- The optimized place keyframe is forwarded to object attachment as the
active_hand_localrelease binding anchor. The release window therefore follows the active hand while transitioning from pickup hand-local object transform to placement hand-local object transform, instead of making the object rotate to the support target by itself in world space. -
Disables wrist-anchor correction for this schedule because the schedule entries are pure hand-pose targets, not returned-wrist anchor targets.
-
Generate Kimodo base motion.
- Run
tools.kimodo.generate_with_first_heading. - Output
motion.npz. -
Run root despike unless disabled, preserving
motion_raw_kimodo.npz. -
Attach the dynamic object.
- Output
motion_augmented.npz. - Default anti-floating behavior keeps the object static before validated pickup contact.
anchor_pathbinds the object to a hand-center path.active_hand_localcomputes an object-in-active-hand transform and expands it over attach/carry/release. This is the preferred route when an official grasp keyframe is available.-
active_hand_localmust also enforce the pickup support plane from attach through the end of the pick segment, so a hand closing downward cannot press the object into the table or floor. -
Hand stage.
- Supported
--hand-mode:prior,npz,schedule,optimize. - Optional wrist-anchor correction applies scheduled HOI-FHLI hand anchors to the Kimodo arms.
-
Bake SMPL-X vertices and hands into
motion_augmented_hands.npz. -
Optional local grasp refinement.
- Output
motion_hoi_refined.npzwhen enabled. - Official pick grasp routes preserve the generated hand/object relation by
default when
--preserve-official-grasp-relationis active, so this stage is skipped andmotion_augmented_hands.npzremains the final motion. - Bounded post-attach object-local correction is enabled by default and can
be disabled with
--disable-local-grasp-object-correction. - Must not move the object before pickup to pass contact metrics.
- Its pickup surface lock is off for frozen official grasp runs because clamping only the object breaks the hand/object local transform.
-
The selected final motion is recorded as
artifacts.final_motion. -
Materialize measured contact labels.
- Run after the final motion exists.
- Output
measured_contact.npyandmeasured_contact_labels.json.
-
Run metrics and strict pose audit.
- Object-motion metrics:
motion_object_metrics.json. - Hand-object metrics:
hand_object_contact_metrics.json. - Strict hand/object pose audit:
strict_hand_object_audit.json.
- Object-motion metrics:
-
Render and aggregate case quality.
- Render the full scene with opaque scene objects by default.
- Output the configured video and final-frame image.
- Run
audit_kimodo_hoi_case_quality.pyafter render, using the final motion, metrics, strict audit, render path, and final frame.
--stop-after boundaries are:
build after build_inputs + expected_contact_labels
constraints after carry/place/pick constraints and any official grasp replacement
generate after Kimodo generation + root_despike
object after dynamic object attachment
hands after hand stage, local grasp refine, and measured contact labels
metrics after metrics + strict hand/object audit
render after render + case_quality_audit
Key Artifacts
Each run should write pipeline_manifest.json incrementally. The final motion
path used by metrics and render must be recorded in the manifest under
artifacts.final_motion.
Important artifacts:
pipeline_manifest.json
meta.json
info.json
constraints_base.json
constraints.json
contact.npy
contact_labels.json
pick_wrist_target_metadata.json
bound_carry_pose_*.npz
bound_carry_pose_metadata_*.json
bound_place_metadata.json
bound_place_pose.npz
bound_pick_pose.npz
bound_pick_metadata.json
auto_official_pick_grasp_source_motion.npz
auto_official_pick_grasp_source_motion.json
auto_official_pick_grasp/<candidate>/bootstrap/grasp_keyframe_before.npz
auto_official_pick_grasp/<candidate>/bootstrap/grasp_keyframe.npz
auto_official_pick_grasp/<candidate>/official/official_grasp_probe.json
auto_official_pick_grasp/<candidate>/apply/final_official_grasp_smplx_keyframe.npz
auto_official_pick_grasp_selection.json
official_pick_grasp_hand_pose_target.npz
official_pick_grasp_replacement_metadata.json
place_hoifhli_grasp/grasp_keyframe.npz
place_hoifhli_grasp/grasp_keyframe.json
place_hoifhli_grasp_hand_pose_target.npz
place_grasp_hand_pose_schedule.json
place_grasp_keyframe_replacement_metadata.json
motion_raw_kimodo.npz
motion.npz
object_motion_metadata.json
motion_augmented.npz
wrist_anchor_correction_metadata.json
motion_augmented_wrist_corrected.npz
hand_bake_metadata.json
motion_augmented_hands.npz
local_grasp_refine_metadata.json
motion_hoi_refined.npz
measured_contact.npy
measured_contact_labels.json
motion_object_metrics.json
hand_object_contact_metrics.json
strict_hand_object_audit.json
motion_scene_dynamic_object_hands.mp4
motion_scene_dynamic_object_hands_final_frame.png
case_quality_audit.json
Not every artifact appears in every run. For example,
auto_official_pick_grasp_selection.json only appears when
--auto-official-pick-grasp is enabled. place_hoifhli_grasp/*,
place_hoifhli_grasp_hand_pose_target.npz, and
place_grasp_hand_pose_schedule.json only appear when
--enable-place-grasp-keyframe-optimize is enabled. motion_hoi_refined.npz
only appears when local grasp refinement is enabled.
motion_augmented_wrist_corrected.npz only appears when a hand pose schedule
exists and wrist-anchor correction is enabled. Render names can be changed with
--render-output-name and --final-frame-name.
Grasp Quality Contract
Carry is object-agnostic, but good grasp quality is object-specific. Do not reuse a cup grasp anchor for a different object category unless the run is a diagnostic ablation.
Preferred route for realistic grasp:
This creates per-object grasp candidates and selects a keyframe for the current object. The selected keyframe is then used for both:
- pick fullbody constraint replacement;
active_hand_localobject binding.
For single-hand pick/carry/place tasks where final placement quality matters, also enable:
This uses the final bound place pose as the set position and the final
layout/support rotation as the set orientation, then runs the same single-frame
HOI-FHLI grasp optimizer against that pose and object mesh. The pickup grasp
remains the carry hand target, and the hand fades toward the optimized place
grasp during the release window. The same optimized place keyframe becomes the
release binding anchor for active_hand_local, so object rotation is explained
by the hand-local grasp transition instead of an independent world-space
slerp.
After hand baking and optional local grasp refinement, the final output motion
frame at the optimized place frame is overwritten with the exact HOI-FHLI place
keyframe. This is the stage that makes the final upright object pose and the
human/hand pose an exact keyframe, instead of only using that keyframe as a
soft constraint. --disable-place-grasp-align-object-release keeps the old
configured release end for ablations; --disable-place-grasp-release-binding-anchor
keeps the old world-space release interpolation; --disable-place-grasp-apply-final-keyframe
keeps the generated/refined final frame instead of forcing the exact HOI-FHLI
place keyframe. Production runs should leave all three defaults enabled.
Default candidate settings:
seeds: 17,41,73
bootstrap batch size: 16
bootstrap iterations: 120
official probe batch size: 32
official probe iterations: 600
surface samples: 1200
Default quality gates:
If no candidate passes, the default policy is fail. Use
--auto-official-grasp-no-valid-policy use_best only for smoke tests or visual
diagnostics.
Hand Routing Contract
Hand policy must be consistent across all stages.
Resolution order:
- Explicit CLI
--object-hand-policy left/right/both. - Task-plan hand metadata on carry, pick, then place steps.
- Legacy default
both.
When --object-hand-policy left/right is passed, the pipeline writes an
effective task plan in the run directory:
All downstream stages must use the effective task plan rather than the original source task plan:
- Kimodo input builder;
- expected contact labels;
- object attachment;
- hand baking/refinement;
- measured contact labels;
- metrics and strict audit.
This avoids the failure mode where a right-hand run still materializes
left_hand_object contact labels from a left-hand source task.
Keyframe Policy
The pipeline has separate keyframe responsibilities:
- Pick: one active-wrist/fullbody reach keyframe, optionally replaced by an official HOI-FHLI grasp keyframe.
- Carry: Root2D path and text should own walking. Fullbody carry pose keyframes are allowed for two-hand or diagnostic ablations, but are off by default for single-hand carry.
- Place: the default single-hand route uses only the final place keyframe. Active-hand approach keyframes remain available as explicit ablations, but they are not the default because they can perturb the walking segment and introduce place-stage snaps.
- Place grasp: when an official pick grasp exists, use
--enable-place-grasp-keyframe-optimizeto optimize the final place hand pose against the same object mesh and final support/layout object rotation. This changes the release keyframe, hand-pose schedule, and active-hand-local release anchor without adding dense walking constraints.
Do not use a static fullbody carry pose as a walking keyframe. Reusing the same reference frame at multiple carry times copies its leg configuration and can produce foot shuffling, toe-walking, or a bent-leg/straight-leg gait.
Do not place dense approach keyframes directly on top of Root2D samples, carry/place boundaries, or object-release windows. Single-hand approach constraints must be active-hand only unless the run is an explicit fullbody ablation, and they should begin after the place segment has enough room to blend from the carry posture. If an approach-keyframe run shows a large object or wrist step near place, revert to the final-only template before tuning hand pose or release windows.
Do not make --place-use-official-grasp-relation the default. That switch
derives the place wrist target directly from the official pick object-in-wrist
transform and is useful as an ablation, but on low tabletop targets it can
place the desired wrist below the floor-locked fullbody reference and fail the
place reference quality gate. The preferred production route is:
final place fullbody keyframe
-> solve_grasp_keyframe.py at bound_place_pose.npz with final object rotation
-> replace final place constraint with active-hand place_hoifhli_grasp_keyframe
-> align object release end to that optimized place keyframe
-> use the optimized place keyframe as the active-hand-local release anchor
-> fade hand pose from pick grasp to place grasp near release
This keeps walking and turning controlled by Root2D/text while letting HOI-FHLI own the grasp at both pickup and final placement.
Turn And Gait Contract
For pick/carry/place tasks, the default turn is a stable in-place heading
schedule. It does not consume the carry path. Use nonzero
--turn-walk-distance only for explicit ablations, because the walk-while-turn
route can destabilize the lower-body gait.
Default behavior:
--turn-s 1.9
--turn-rate-deg-per-sec 54
--turn-min-s 0
--turn-min-heading-deg 20
--turn-walk-distance 0
--turn-walk-max-path-fraction 0
--carry-s 9.0
--carry-speed-mps 0.60
--root2d-stride 5
--single-hand-place-template final_keyframe
--place-approach-fractions 0.48,0.62
--object-release-start-fraction 0.65
--object-release-end-fraction 0.95
--place-grasp-align-object-release
--place-grasp-release-binding-anchor
--hand-release-start-fraction 0.65
--hand-release-end-fraction 0.95
--auto-carry-duration is available for ablations or unusually short paths, but
do not enable it by default for object-carry clips unless the target speed and
max duration have been checked against foot sliding. On this cup task, shortening
the carry segment from 9.0 s to about 5.6 s doubled the root speed and caused
visible shuffling.
The builder records the split as:
turn_timing
turn_walk
kimodo_effective_turn_path_xz
kimodo_effective_carry_path_xz
turn_constraint.root_mode
The evaluator records body-relative foot/knee order metrics by segment. The turn segment must pass:
turn_foot_body_lateral_order_margin_m_p05 >= -0.03
turn_knee_body_lateral_order_margin_m_p05 >= -0.02
Negative foot order margins during a turn usually indicate crossed feet or leg-leg penetration, even when the older foot-separation metric still passes.
Single-Hand Carry Placement Contract
For single-hand carry, distinguish two different coordinate contracts:
- Grasp-local transform: the object-to-hand transform from the official grasp keyframe. This keeps the cup in the fingers.
- Carry placement: the global object location relative to the walking body. This keeps the carried object natural and away from the face/body.
Carry placement is measured in the root/body frame:
forward axis: current body heading
lateral axis: positive body-left
height: object_center_y - root_y
active lateral: left uses +body-left, right uses -body-left
Default single-hand carry bounds:
object forward from root median: 0.10 m to 0.55 m
object active-side lateral median: -0.03 m to 0.35 m
object height-from-root p95: < 0.38 m
Interpretation:
- Cups/bottles should be carried in front of the torso, slightly on the active hand side or near centerline.
- They should not cross substantially to the opposite side during carry.
- They should not rise toward the face unless the task explicitly says drink, inspect, or show the object.
- Place approach can lift the object only near the target support and should use the support height plus a small clearance, not a mouth-level carry trajectory.
evaluate_kimodo_task_object_motion.py records these fields in
motion_object_metrics.json and adds quality gates for single-hand carry.
Anti-Floating Contract
The object must not move toward the hand before pickup contact is validated, and it must not be pressed below its initial support during pickup.
Required behavior:
- pre-attach object trajectory is static at the layout pose;
- no legacy pre-pickup blend unless
--allow-pre-object-attach-blendis passed; - default
--no-valid-object-attach-policy failrefuses fake carry data when the hand never reaches the object; active_hand_localwith an official binding anchor may bypass the old wrist-center readiness gate, but only after verifying post-binding anti-floating metrics;pick_object_bottom_delta_from_initial_m_minmust stay above-0.02 m.
Relevant metadata fields in object_motion_metadata.json:
resolved_binding_start_frame
pre_attach_static
pre_attach_max_delta_m
pre_attach_vertical_lift_max_m
attach_snap_distance_m
anti_floating_gate_pass
post_binding_anti_floating_gate_pass
binding.mode
binding.local_object_translation_norm_m
binding.pickup_surface_lock
Relevant metadata fields in local_grasp_refine_metadata.json:
Frozen official grasp runs should record the local_grasp_refine stage as
disabled with preserve_official_grasp_relation=true.
Recommended Commands
Full automatic per-object grasp route:
cd /mnt/data/PhysHSI
PYTHONPATH=. env_protomotions_isaaclab/bin/python \
tools/kimodo/run_kimodo_hoi_pipeline.py \
--out-dir output/auto_experiments/<run_name> \
--task-plan <task_plan.json> \
--layout-json <layout.json> \
--auto-official-pick-grasp \
--enable-place-grasp-keyframe-optimize \
--scene-object-source all \
--scene-alpha 1.0 \
--camera-view human-front \
--camera-distance 1.3 \
--camera-elevation 8 \
--fov 78
Fast constraints-only smoke for wiring checks:
cd /mnt/data/PhysHSI
PYTHONPATH=. env_protomotions_isaaclab/bin/python \
tools/kimodo/run_kimodo_hoi_pipeline.py \
--out-dir output/auto_experiments/<smoke_run_name> \
--task-plan <task_plan.json> \
--layout-json <layout.json> \
--auto-official-pick-grasp \
--auto-official-grasp-seeds 17 \
--auto-official-grasp-bootstrap-batch-size 2 \
--auto-official-grasp-bootstrap-iterations 1 \
--auto-official-grasp-batch-size 2 \
--auto-official-grasp-iterations 1 \
--auto-official-grasp-object-surface-samples 64 \
--auto-official-grasp-no-valid-policy use_best \
--stop-after constraints \
--skip-render
Manual official grasp route when a vetted keyframe already exists:
cd /mnt/data/PhysHSI
PYTHONPATH=. env_protomotions_isaaclab/bin/python \
tools/kimodo/run_kimodo_hoi_pipeline.py \
--out-dir output/auto_experiments/<run_name> \
--task-plan <task_plan.json> \
--layout-json <layout.json> \
--official-pick-grasp-keyframe-npz <final_official_grasp_smplx_keyframe.npz> \
--object-binding-mode active_hand_local
Maintenance Rules
Update this file when any of the following changes:
- stage order;
- default artifact names;
- default grasp route or quality gates;
- object attachment or anti-floating policy;
- hand mode semantics;
- metric/audit/render final-motion path;
- required runtime environments.
Avoid documenting one-off experiment paths as default behavior. Put those in change logs or case reports, and only promote stable behavior here after the main pipeline uses it.