cozyclay 2.1.1 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +88 -0
- package/README.md +12 -30
- package/bin/agent/agent-routes.mjs +43 -116
- package/bin/agent/agent-runner.mjs +14 -15
- package/bin/agent/cliproxy-provider.mjs +135 -0
- package/bin/agent/credential-store.mjs +30 -7
- package/bin/agent/pi-tools.mjs +4 -1
- package/bin/agent/provider-keys.mjs +32 -0
- package/bin/agent/provider-login.mjs +110 -0
- package/bin/agent/provider-routes.mjs +99 -0
- package/bin/agent/providers.mjs +19 -97
- package/bin/agent/script-tool.mjs +17 -4
- package/bin/agent/session-store.mjs +2 -2
- package/bin/agent/studio-context-budget.mjs +93 -0
- package/bin/agent/studio-motion-reference.mjs +23 -0
- package/bin/agent/studio-prompt.mjs +1 -1
- package/bin/agent/studio-rebase-text.mjs +32 -0
- package/bin/agent/studio-rebase.mjs +26 -39
- package/bin/agent/studio-tools.mjs +9 -28
- package/bin/agent/tool-arguments.mjs +49 -0
- package/bin/agent/turn-frames.mjs +66 -0
- package/bin/codex-auth.mjs +9 -2
- package/bin/cozyclay.mjs +10 -116
- package/bin/live/cli.mjs +3 -3
- package/bin/mcp-runtime.mjs +2 -12
- package/bin/package-signature.mjs +0 -4
- package/dist/app/index.html +7 -6
- package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
- package/dist/assets/analytics-BWCqdgWA.js +1 -0
- package/dist/assets/app-DfTCxaKk.js +5087 -0
- package/dist/assets/app-f-fkV5uP.css +1 -0
- package/dist/assets/compile-worker-BxKXh07s.js +5 -0
- package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
- package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-DNO8kv_N.js} +1 -1
- package/dist/assets/{landing-ChUvB_zL.js → landing-yfZaJkWc.js} +1 -1
- package/dist/assets/scene-asset-sync-U3kS2rvK.js +20 -0
- package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
- package/dist/assets/workflow-D78eiKTa.js +179 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/index.html +3 -3
- package/dist/sw.js +3 -6
- package/dist/workflow/index.html +5 -4
- package/mcp/LIVE-PROTOCOL.md +7 -47
- package/mcp/README.md +16 -26
- package/mcp/live-hub.mjs +6 -10
- package/mcp/package.json +1 -3
- package/mcp/tool-handlers.mjs +4 -168
- package/package.json +3 -22
- package/src/App.jsx +151 -2625
- package/src/agent-progress.js +80 -0
- package/src/agent-turn-mark.js +30 -0
- package/src/analytics.js +1 -19
- package/src/app-context.js +12 -2
- package/src/app-stage.jsx +2 -34
- package/src/asset-pane.jsx +6 -5
- package/src/camera-block.js +21 -0
- package/src/camera-direction.js +120 -0
- package/src/character-kind.js +3 -9
- package/src/command-bus.js +10 -8
- package/src/commands/cast.js +5 -34
- package/src/commands/elements/character.js +1 -1
- package/src/commands/elements/motion.js +1 -6
- package/src/commands/elements/object.js +6 -2
- package/src/commands/elements/shot.js +4 -1
- package/src/commands/elements.js +38 -14
- package/src/commands/index.js +3 -1
- package/src/commands/kits.js +37 -0
- package/src/commands/motion-timing.js +57 -0
- package/src/commands/motion.js +9 -152
- package/src/commands/motion3-compile.js +65 -0
- package/src/commands/motion3.js +214 -0
- package/src/commands/objects.js +6 -4
- package/src/commands/shot.js +55 -13
- package/src/commands/stage.js +14 -1
- package/src/commands/view.js +4 -1
- package/src/document-mirrors.js +42 -0
- package/src/document-store.js +4 -0
- package/src/domains/cast.js +19 -194
- package/src/domains/motion/motion-range-pin-rules.js +1 -1
- package/src/domains/motion/take-source.js +53 -0
- package/src/domains/motion.js +60 -1838
- package/src/domains/scenes.js +12 -55
- package/src/domains/shots.js +2 -2
- package/src/domains/stage.js +4 -2
- package/src/draft-ghost.js +47 -0
- package/src/draft-ghost.jsx +129 -0
- package/src/dualview.jsx +4 -3
- package/src/effects/EffectsLayer.jsx +112 -0
- package/src/effects/effect-meshes.js +232 -0
- package/src/effects/effect-record.js +103 -0
- package/src/effects/emitter.js +59 -0
- package/src/effects/simulate.js +261 -0
- package/src/effects/state.js +59 -0
- package/src/execution-telemetry.js +2 -3
- package/src/first-success-guide.jsx +5 -5
- package/src/ghost-set.js +257 -0
- package/src/held-object-edit.jsx +113 -0
- package/src/held-object-pose.js +175 -0
- package/src/hierarchy-model.js +1 -1
- package/src/hierarchy-panel.jsx +1 -1
- package/src/imported-mesh.jsx +103 -0
- package/src/kits/held.js +84 -0
- package/src/kits/index.js +70 -0
- package/src/kits/smoking.js +57 -0
- package/src/light-blockers.js +93 -0
- package/src/motion/beat-board-model.js +84 -0
- package/src/motion/beat-board.css +124 -0
- package/src/motion/beat-board.jsx +60 -0
- package/src/motion/center-of-mass.js +66 -0
- package/src/motion/fix-collisions.js +850 -0
- package/src/motion/physics-review.js +118 -0
- package/src/{ardy → motion}/physics-support.js +1 -76
- package/src/motion/rig-snapshot.js +22 -0
- package/src/motion/take-segments.jsx +140 -0
- package/src/{ardy → motion}/timeline-coordinates.js +0 -21
- package/src/{ardy → motion}/timeline.css +1 -2
- package/src/{ardy → motion}/timeline.jsx +42 -413
- package/src/motion/waypoints.js +76 -0
- package/src/motion3/beats.js +83 -0
- package/src/motion3/body.js +158 -0
- package/src/motion3/bones.js +47 -0
- package/src/motion3/carried.js +47 -0
- package/src/motion3/compile-job.js +79 -0
- package/src/motion3/compile-worker.js +10 -0
- package/src/motion3/compiler/anchors.js +113 -0
- package/src/motion3/compiler/arm-orient.js +128 -0
- package/src/motion3/compiler/channels.js +125 -0
- package/src/motion3/compiler/compile.js +205 -0
- package/src/motion3/compiler/gait-rhythm.js +97 -0
- package/src/motion3/compiler/grip.js +171 -0
- package/src/motion3/compiler/head-steady.js +48 -0
- package/src/motion3/compiler/hold.js +134 -0
- package/src/motion3/compiler/idle.js +113 -0
- package/src/motion3/compiler/limbs.js +197 -0
- package/src/motion3/compiler/math.js +72 -0
- package/src/motion3/compiler/pose.js +177 -0
- package/src/motion3/compiler/quat.js +49 -0
- package/src/motion3/compiler/skeleton.js +74 -0
- package/src/motion3/compiler/validate-common.js +32 -0
- package/src/motion3/compiler/validate-goals.js +118 -0
- package/src/motion3/compiler/validate-objects.js +144 -0
- package/src/motion3/compiler/validate-parts.js +118 -0
- package/src/motion3/compiler/validate.js +122 -0
- package/src/motion3/compiler/vocab.js +74 -0
- package/src/motion3/compiler/walk-pelvis.js +275 -0
- package/src/motion3/compiler/walk.js +207 -0
- package/src/motion3/compiler/wrist-clamp.js +32 -0
- package/src/motion3/delta.js +47 -0
- package/src/motion3/describe.js +177 -0
- package/src/motion3/docs.js +27 -0
- package/src/motion3/edit-carry.js +21 -0
- package/src/motion3/fix/ease.js +16 -0
- package/src/motion3/fix/ground.js +37 -0
- package/src/motion3/fix/index.js +52 -0
- package/src/motion3/fix/push.js +71 -0
- package/src/motion3/fix/reach.js +46 -0
- package/src/motion3/fix/retime.js +79 -0
- package/src/motion3/fix/self.js +211 -0
- package/src/motion3/fix/slide.js +70 -0
- package/src/motion3/fix/turn.js +51 -0
- package/src/motion3/follow.js +39 -0
- package/src/motion3/free-motion.js +204 -0
- package/src/motion3/grip-choice.js +130 -0
- package/src/motion3/hold-plan.js +157 -0
- package/src/motion3/json-patch.js +77 -0
- package/src/motion3/key-aim.js +105 -0
- package/src/motion3/key-edit.js +82 -0
- package/src/motion3/lenient.js +127 -0
- package/src/motion3/object-keys.js +236 -0
- package/src/motion3/object-pose.js +182 -0
- package/src/motion3/object-tracks.js +12 -0
- package/src/motion3/people.js +82 -0
- package/src/motion3/pipeline.js +102 -0
- package/src/motion3/playback.js +51 -0
- package/src/motion3/probes.js +91 -0
- package/src/motion3/qa/body-checks.js +34 -0
- package/src/motion3/qa/check-names.js +14 -0
- package/src/motion3/qa/checks.js +125 -0
- package/src/motion3/qa/contact-checks.js +51 -0
- package/src/motion3/qa/explain.js +80 -0
- package/src/motion3/qa/fmt.js +5 -0
- package/src/motion3/qa/foot-checks.js +72 -0
- package/src/motion3/qa/geometry.js +77 -0
- package/src/motion3/qa/goals.js +191 -0
- package/src/motion3/qa/ground.js +60 -0
- package/src/motion3/qa/hints-contact.js +195 -0
- package/src/motion3/qa/hints-goal.js +160 -0
- package/src/motion3/qa/hints-knee.js +24 -0
- package/src/motion3/qa/hints-motion.js +209 -0
- package/src/motion3/qa/hints-object.js +91 -0
- package/src/motion3/qa/index.js +44 -0
- package/src/motion3/qa/key-checks.js +82 -0
- package/src/motion3/qa/near.js +96 -0
- package/src/motion3/qa/object-checks.js +135 -0
- package/src/motion3/qa/patch.js +161 -0
- package/src/motion3/qa/people-checks.js +37 -0
- package/src/motion3/qa/severity.js +38 -0
- package/src/motion3/qa/spec-checks.js +65 -0
- package/src/motion3/qa/style.js +228 -0
- package/src/motion3/qa/wrist-checks.js +69 -0
- package/src/motion3/qa/wrist-obs.js +60 -0
- package/src/motion3/refs.js +129 -0
- package/src/motion3/repair-tune.js +121 -0
- package/src/motion3/repair.js +83 -0
- package/src/motion3/report.js +67 -0
- package/src/motion3/rigs/index.js +14 -0
- package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
- package/src/motion3/rigs/x-bot-tpose.json +1 -0
- package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
- package/src/motion3/rigs/y-bot-tpose.json +1 -0
- package/src/motion3/scene.js +60 -0
- package/src/motion3/schema/brief.js +89 -0
- package/src/motion3/schema/fields.js +44 -0
- package/src/motion3/schema/spec-examples.js +46 -0
- package/src/motion3/schema/topics.js +113 -0
- package/src/motion3/schema/types.js +149 -0
- package/src/motion3/search.js +117 -0
- package/src/motion3/second-hand.js +112 -0
- package/src/motion3/settle.js +136 -0
- package/src/motion3/skills/basic.js +83 -0
- package/src/motion3/skills/book.js +76 -0
- package/src/motion3/skills/car.js +145 -0
- package/src/motion3/skills/flip.js +200 -0
- package/src/motion3/skills/frames-auto.js +29 -0
- package/src/motion3/skills/impact.js +109 -0
- package/src/motion3/skills/index.js +85 -0
- package/src/motion3/skills/info.js +80 -0
- package/src/motion3/skills/smoke-kit.js +109 -0
- package/src/motion3/skills/smoke.js +160 -0
- package/src/motion3/spec-store.js +59 -0
- package/src/motion3/take-edit.js +155 -0
- package/src/motion3/take.js +63 -0
- package/src/motion3/timeline.js +127 -0
- package/src/motion3/views.js +121 -0
- package/src/motion3/walk-plan.js +119 -0
- package/src/object-catalog.jsx +7 -1
- package/src/object-colors.js +98 -0
- package/src/object-gizmo-parts.js +115 -0
- package/src/object-gizmo.jsx +37 -122
- package/src/object-glow.js +139 -0
- package/src/object-hidden.js +33 -0
- package/src/object-look.js +196 -0
- package/src/object-size.js +10 -0
- package/src/object-travel.js +71 -6
- package/src/panels/CameraPanel.jsx +7 -7
- package/src/panels/HdrEnvironmentControls.jsx +59 -0
- package/src/panels/LightPanel.jsx +25 -4
- package/src/panels/ObjectLookPanel.jsx +88 -0
- package/src/panels/PosePanel.jsx +2 -17
- package/src/panels/RigControlPanel.jsx +5 -53
- package/src/panels/SubjectBox.jsx +4 -1
- package/src/panels/details.css +14 -0
- package/src/posestudio.jsx +4 -27
- package/src/project-browser.jsx +3 -4
- package/src/project-resources.js +18 -31
- package/src/project.js +13 -104
- package/src/props.jsx +25 -280
- package/src/qa-label.js +25 -0
- package/src/rail-dot-drag.js +23 -0
- package/src/render-passes.js +10 -0
- package/src/resource-status.jsx +1 -0
- package/src/result-modal.jsx +4 -19
- package/src/room.jsx +20 -12
- package/src/scene-assets.js +8 -6
- package/src/scene-light-place.js +72 -0
- package/src/scene-lights.jsx +148 -0
- package/src/scene-objects.js +31 -139
- package/src/scenes.js +15 -105
- package/src/semantic-edit.js +2 -5
- package/src/set-pieces.jsx +183 -0
- package/src/settings-menu.jsx +3 -11
- package/src/shell/BottomDock.jsx +9 -51
- package/src/shell/DetailsSlot.jsx +16 -200
- package/src/shell/GhostsMenuGroup.jsx +74 -0
- package/src/shell/LibrarySlot.jsx +1 -3
- package/src/shell/MenuBar.jsx +3 -3
- package/src/shell/PreferencesDialog.jsx +15 -264
- package/src/shell/PreferencesSlot.jsx +2 -8
- package/src/shell/TopBar.jsx +5 -84
- package/src/shell/ViewportToolbar.jsx +10 -70
- package/src/shell/agent-glass.css +19 -6
- package/src/shell/details-selection.js +42 -0
- package/src/shell/ghosts-menu.css +22 -0
- package/src/shell/glass-regions.css +29 -3
- package/src/shell/preferences.css +6 -1
- package/src/shot-authoring.js +4 -7
- package/src/soft-shadows.js +37 -0
- package/src/stage-attach-frames.js +12 -12
- package/src/stage-canvas-rigs.jsx +3 -3
- package/src/stage-capture.jsx +42 -8
- package/src/stage-characters.jsx +22 -216
- package/src/stage-editor-gizmos.jsx +6 -6
- package/src/stage-environment.js +93 -0
- package/src/stage-hdr-builtins.js +107 -0
- package/src/stage-hdr-image.js +116 -0
- package/src/stage-hdr-library.js +70 -0
- package/src/stage-hdr-map.js +94 -0
- package/src/stage-hdr.js +130 -0
- package/src/stage-lighting.js +149 -0
- package/src/stage-occlusion.js +210 -0
- package/src/stage-sky.jsx +159 -0
- package/src/stage-startup-defaults.js +5 -9
- package/src/studio-actions.js +16 -14
- package/src/studio-agent-commands.js +19 -16
- package/src/studio-agent-motion.js +9 -9
- package/src/studio-agent-protocol.js +47 -77
- package/src/studio-app-binding.js +27 -47
- package/src/studio-asset-list.js +17 -0
- package/src/studio-contact-sheet.js +37 -5
- package/src/studio-elements.js +27 -7
- package/src/studio-entity-bounds.js +18 -0
- package/src/studio-error-codes.js +15 -0
- package/src/studio-export-framing.js +152 -0
- package/src/studio-lighting-check.js +103 -0
- package/src/studio-motion-visual.js +150 -0
- package/src/studio-schema-errors.js +80 -0
- package/src/studio-support-gap.js +74 -0
- package/src/styles.css +1 -19
- package/src/timeline-defaults.js +5 -0
- package/src/timeline-extent.js +8 -20
- package/src/tutorial-walk.js +17 -0
- package/src/ui-scrub.js +1 -1
- package/src/use-shot-camera-tracks.js +12 -0
- package/src/view-ghosts.js +97 -0
- package/src/view-ghosts.jsx +49 -0
- package/src/workflow/AgentPanel.jsx +47 -166
- package/src/workflow/AgentProgressCard.jsx +109 -0
- package/src/workflow/AgentReplyText.jsx +37 -0
- package/src/workflow/AgentTurnSteps.jsx +84 -0
- package/src/workflow/LoginPanel.jsx +140 -0
- package/src/workflow/WorkflowBuilder.jsx +7 -23
- package/src/workflow/agent-client.js +54 -51
- package/src/workflow/agent-panel.css +71 -1
- package/src/workflow/agent-progress-card.css +167 -0
- package/src/workflow/agent-progress-view.js +47 -0
- package/src/workflow/agent-turn-steps.css +113 -0
- package/src/workflow/agent-turn.js +191 -0
- package/src/workflow/login-client.js +34 -0
- package/src/workflow/motion-input.js +18 -31
- package/src/workflow/node-schema.js +1 -1
- package/src/workflow/reply-markdown.js +69 -0
- package/src/workflow/scene-asset-sync.js +8 -8
- package/tools/agent-session-stats.mjs +61 -0
- package/tools/dev-full.mjs +5 -56
- package/tools/eslint-baseline.json +12 -13
- package/tools/knip-baseline.json +0 -179
- package/tools/loc-baseline.json +31 -81
- package/tools/motion3/extract-body.mjs +103 -0
- package/tools/motion3/extract-rig.mjs +85 -0
- package/tools/process-supervisor.mjs +0 -88
- package/tools/run-tests.mjs +9 -125
- package/bin/agent/motion-runtime.mjs +0 -76
- package/dist/ardy/cskel27-rest.json +0 -557
- package/dist/assets/analytics-C9pMpW7s.js +0 -1
- package/dist/assets/app-B_2XB9rw.css +0 -1
- package/dist/assets/app-DAuvKPP8.js +0 -4920
- package/dist/assets/config-Bx7pYsD9.js +0 -1
- package/dist/assets/config-DeoFTYzb.css +0 -1
- package/dist/assets/demo-CvKuBgy5.js +0 -1
- package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
- package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
- package/dist/assets/ticket-BPk2Ni95.js +0 -1
- package/dist/assets/workflow-jJRBR94Z.js +0 -179
- package/dist/demo/walk-then-stop.npz +0 -0
- package/mcp/ardy-prompts.mjs +0 -218
- package/src/ardy/auto-fix-panel.css +0 -108
- package/src/ardy/auto-physics.js +0 -1194
- package/src/ardy/client.js +0 -153
- package/src/ardy/convert.js +0 -313
- package/src/ardy/cskel27-neutral.js +0 -39
- package/src/ardy/cskel27.js +0 -77
- package/src/ardy/export.js +0 -165
- package/src/ardy/fix-collisions.js +0 -2257
- package/src/ardy/foot-lock.js +0 -230
- package/src/ardy/motion-calibration.js +0 -118
- package/src/ardy/motion-edit.js +0 -213
- package/src/ardy/motion-url.js +0 -55
- package/src/ardy/mp4-duration.js +0 -102
- package/src/ardy/npz.js +0 -595
- package/src/ardy/physics-panel.css +0 -21
- package/src/ardy/physics-panel.jsx +0 -89
- package/src/ardy/physics-refine.js +0 -111
- package/src/ardy/physics-review.js +0 -567
- package/src/ardy/physics-temporal.js +0 -54
- package/src/ardy/platform-fit-panel.css +0 -64
- package/src/ardy/platform-fit-panel.jsx +0 -41
- package/src/ardy/platform-fit.js +0 -326
- package/src/ardy/playback.js +0 -711
- package/src/ardy/pose-pin.js +0 -97
- package/src/ardy/prompt-clips.js +0 -30
- package/src/ardy/retime.js +0 -211
- package/src/ardy/root-drop.js +0 -369
- package/src/ardy/timeline-resize.js +0 -13
- package/src/ardy/to-cskel27.js +0 -225
- package/src/ardy/trim.js +0 -33
- package/src/ardy/waypoints.js +0 -369
- package/src/ardy-bridge-contract.js +0 -281
- package/src/domains/motion/motion-video-capture.js +0 -664
- package/src/i2v-motion-client.js +0 -77
- package/src/i2v-motion-studio.jsx +0 -180
- package/src/line-edit.js +0 -1348
- package/src/motion/generation.js +0 -105
- package/src/motion-readiness-ui.jsx +0 -67
- package/src/motion-readiness.js +0 -40
- package/src/motion-resources.js +0 -177
- package/src/motion-store.js +0 -65
- package/src/motion-trail.js +0 -601
- package/src/multimodel-ingest.js +0 -394
- package/src/panels/PromptBlocksPanel.jsx +0 -444
- package/src/panels/VideoCapturePanel.jsx +0 -181
- package/src/pose-extract/browser.js +0 -42
- package/src/pose-extract/detector.js +0 -100
- package/src/pose-extract/fit.js +0 -227
- package/src/pose-extract/image-frame.js +0 -51
- package/src/pose-extract/index.js +0 -58
- package/src/pose-extract/keyframes.js +0 -74
- package/src/pose-extract/landmarks.js +0 -162
- package/src/pose-extract/mirror.js +0 -153
- package/src/pose-extract/take.js +0 -131
- package/src/pose-extract/video-frames.js +0 -91
- package/src/take-recipe.js +0 -165
- package/src/trail-key-conflicts.js +0 -39
- package/src/trail-pick.js +0 -59
- package/src/use-i2v-capture-modal.js +0 -15
- package/tools/ardy/BRIDGE.md +0 -37
- package/tools/ardy/GVHMR-NPZ.md +0 -68
- package/tools/ardy/README.md +0 -124
- package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
- package/tools/ardy/artifacts.mjs +0 -34
- package/tools/ardy/bridge.mjs +0 -1732
- package/tools/ardy/bvh-cskel27.mjs +0 -1265
- package/tools/ardy/cclay_constrained_generate.py +0 -561
- package/tools/ardy/cclay_gvhmr_worker.py +0 -264
- package/tools/ardy/cclay_motion_edit.py +0 -510
- package/tools/ardy/cclay_sequence_generate.py +0 -705
- package/tools/ardy/cclay_worker.py +0 -304
- package/tools/ardy/dump-npz.py +0 -205
- package/tools/ardy/extract-rest.mjs +0 -299
- package/tools/ardy/extract.mjs +0 -507
- package/tools/ardy/footage.mjs +0 -467
- package/tools/ardy/gvhmr-floor.mjs +0 -102
- package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
- package/tools/ardy/gvhmr_fastpath.py +0 -168
- package/tools/ardy/gvhmr_trajectory.py +0 -240
- package/tools/ardy/merge-text-encoder.py +0 -160
- package/tools/ardy/mocap-metrics.mjs +0 -200
- package/tools/ardy/mocap-quality-gate.mjs +0 -110
- package/tools/ardy/motion-stabilize.mjs +0 -348
- package/tools/ardy/npz.mjs +0 -414
- package/tools/ardy/pose-to-npz.mjs +0 -106
- package/tools/ardy/prompt-limits.mjs +0 -5
- package/tools/ardy/run-edit-on-box.sh +0 -73
- package/tools/ardy/run-local.mjs +0 -233
- package/tools/ardy/run-on-box.sh +0 -593
- package/tools/ardy/run-sequence-on-box.sh +0 -177
- package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
- package/tools/ardy/runners/index.mjs +0 -18
- package/tools/ardy/runners/local.mjs +0 -455
- package/tools/ardy/runners/proc.mjs +0 -125
- package/tools/ardy/runners/remote.mjs +0 -252
- package/tools/ardy/setup-local.mjs +0 -189
- package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
- package/tools/ardy/setup-text-encoder.py +0 -306
- package/tools/ardy/smpl-cskel27.mjs +0 -178
- package/tools/ardy/visual-qa.mjs +0 -196
- package/tools/ardy/vq-car.mjs +0 -52
- package/tools/bench/EXP3.md +0 -61
- package/tools/bench/cclay_bench_extract_incam.py +0 -125
- package/tools/bench/cclay_bench_extract_obs.py +0 -204
- package/tools/bench/cclay_bench_runner.py +0 -42
- package/tools/bench/cube-contact.mjs +0 -162
- package/tools/bench/exp3.mjs +0 -152
- package/tools/bench/extract-bench-lib.mjs +0 -216
- package/tools/bench/extract-bench.mjs +0 -302
- package/tools/bench/fal-generate.mjs +0 -88
- package/tools/bench/fit/README.md +0 -189
- package/tools/bench/fit/camera.mjs +0 -59
- package/tools/bench/fit/contact.mjs +0 -419
- package/tools/bench/fit/footlock.mjs +0 -234
- package/tools/bench/fit/motion.mjs +0 -85
- package/tools/bench/fit/pin.mjs +0 -27
- package/tools/bench/fit/remote.mjs +0 -105
- package/tools/bench/fit-bench.mjs +0 -126
- package/tools/bench/fit-sanity.mjs +0 -45
- package/tools/bench/metrics.mjs +0 -313
- package/tools/bench/obs/depth.mjs +0 -258
- package/tools/bench/obs/extrinsics.mjs +0 -175
- package/tools/bench/obs/fit_mannequin_betas.py +0 -340
- package/tools/bench/obs/ground.mjs +0 -294
- package/tools/bench/obs/heading.mjs +0 -151
- package/tools/bench/obs/ladder.mjs +0 -555
- package/tools/bench/obs/mannequin-betas.json +0 -124
- package/tools/bench/obs/remote.mjs +0 -83
- package/tools/bench/obs/rest_joints.py +0 -52
- package/tools/bench/obs/ybot-targets.mjs +0 -49
- package/tools/bench/obs-bench.mjs +0 -520
- package/tools/bench/score.mjs +0 -343
- package/tools/bench/summarize.mjs +0 -86
- package/tools/demo-worker/README.md +0 -137
- package/tools/demo-worker/api-client.mjs +0 -233
- package/tools/demo-worker/generate.mjs +0 -301
- package/tools/demo-worker/index.mjs +0 -466
- package/tools/demo-worker/sign-core.mjs +0 -112
- package/tools/demo-worker/sign.mjs +0 -161
- package/tools/gt-render/browser.mjs +0 -182
- package/tools/gt-render/camera-math.mjs +0 -276
- package/tools/gt-render/page.mjs +0 -267
- package/tools/gt-render/render.mjs +0 -514
- package/tools/gt-render/scene-box.mjs +0 -31
- package/tools/gt-render/take-transform.mjs +0 -87
- package/tools/kimodo/compare-seam.mjs +0 -108
- package/tools/kimodo/constraints.mjs +0 -244
- package/tools/kimodo/edit.mjs +0 -259
- package/tools/kimodo/effector-constraints.mjs +0 -200
- package/tools/kimodo/generate.mjs +0 -646
- package/tools/kimodo/local-output.mjs +0 -265
- package/tools/kimodo/measure-pose.mjs +0 -65
- package/tools/kimodo/measure-preserve.mjs +0 -899
- package/tools/kimodo/measure-waypoints.mjs +0 -55
- package/tools/kimodo/mlx-generate.py +0 -111
- package/tools/kimodo/pose-constraints.mjs +0 -248
- package/tools/kimodo/preserve-mask.mjs +0 -543
- package/tools/kimodo/read-npz.mjs +0 -189
- package/tools/kimodo/run-edit-on-box.mjs +0 -155
- package/tools/kimodo/run-sequence-on-box.mjs +0 -178
- package/tools/kimodo/runner.mjs +0 -284
- package/tools/kimodo/seam-batch.mjs +0 -77
- package/tools/kimodo/setup-local.mjs +0 -30
- package/tools/kimodo/setup-on-box.sh +0 -339
- package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
- package/tools/morphgs/assets/gen_truth.py +0 -39
- package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
- package/tools/morphgs/assets/playback-check.mjs +0 -30
- package/tools/morphgs/demo-gate.sh +0 -26
- package/tools/morphgs/fbx2morphgs.mjs +0 -68
- package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
- package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
- package/tools/morphgs/setup-on-cluster.sh +0 -126
- package/tools/projflow/driver.py +0 -1069
- package/tools/projflow/generate.mjs +0 -805
- package/tools/projflow/hml22-to-cskel27.mjs +0 -735
- package/tools/projflow/line-edit-job.mjs +0 -547
- package/tools/projflow/replay.mjs +0 -452
- package/tools/projflow/runner.mjs +0 -269
- package/tools/projflow/service.mjs +0 -742
- package/tools/track/DESIGN.md +0 -513
- package/tools/track/backfill-provenance.mjs +0 -116
- package/tools/track/budget.mjs +0 -8
- package/tools/track/check-rig.mjs +0 -209
- package/tools/track/diagnostics.schema.json +0 -60
- package/tools/track/export-rig.mjs +0 -256
- package/tools/track/fallback.mjs +0 -7
- package/tools/track/fk-parity-fixture.mjs +0 -161
- package/tools/track/gate.mjs +0 -349
- package/tools/track/masks.mjs +0 -318
- package/tools/track/metrics.mjs +0 -201
- package/tools/track/publish-obs.mjs +0 -108
- package/tools/track/py/check_env.py +0 -61
- package/tools/track/py/eval_lr.py +0 -229
- package/tools/track/py/lr_viterbi.py +0 -294
- package/tools/track/py/masks.py +0 -405
- package/tools/track/py/objective.py +0 -382
- package/tools/track/py/rig.py +0 -508
- package/tools/track/py/scene.py +0 -326
- package/tools/track/py/test_joint_indices.py +0 -123
- package/tools/track/py/test_lr_viterbi.py +0 -195
- package/tools/track/py/test_masks.py +0 -229
- package/tools/track/py/test_rig.py +0 -288
- package/tools/track/py/test_scene.py +0 -354
- package/tools/track/py/test_track.py +0 -328
- package/tools/track/py/track.py +0 -411
- package/tools/track/remote.mjs +0 -140
- package/tools/track/rig-dump.mjs +0 -227
- package/tools/track/run-box-tests.mjs +0 -14
- package/tools/track/run-box.mjs +0 -168
- package/tools/track/setup-box.sh +0 -59
- package/tools/track/study-2d.mjs +0 -728
- /package/src/{ardy → motion}/collision-blockers.js +0 -0
- /package/src/{ardy → motion}/ground.js +0 -0
- /package/src/{ardy → motion}/ik-drag.js +0 -0
- /package/src/{ardy → motion}/ik-key-json.js +0 -0
- /package/src/{ardy → motion}/ik.js +0 -0
- /package/src/{ardy → motion}/physics-surface.js +0 -0
- /package/src/{ardy → motion}/playback-clock.js +0 -0
- /package/src/{ardy → motion}/range-pin.js +0 -0
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser extraction boundary shared by uploaded video and webcam frames.
|
|
3
|
-
* The UI supplies an async iterable of `{ image, timeS }` and an already
|
|
4
|
-
* configured detector (for MediaPipe: `detectForVideo(image, timeS * 1000)`).
|
|
5
|
-
* Keeping model creation outside this module makes model URL/revision,
|
|
6
|
-
* telemetry gating and first-use caching explicit at the App integration site.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
function confidenceOf(landmarks) {
|
|
10
|
-
if (!Array.isArray(landmarks) || landmarks.length === 0) return -Infinity;
|
|
11
|
-
return landmarks.reduce((sum, point) =>
|
|
12
|
-
sum + (Number.isFinite(point?.visibility) ? point.visibility : 1), 0
|
|
13
|
-
) / landmarks.length;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function selectMostConfidentPerson(result) {
|
|
17
|
-
const candidates = result?.worldLandmarks ?? result?.landmarks ?? [];
|
|
18
|
-
if (!Array.isArray(candidates) || candidates.length === 0) return null;
|
|
19
|
-
return candidates.reduce((best, candidate) =>
|
|
20
|
-
confidenceOf(candidate) > confidenceOf(best) ? candidate : best
|
|
21
|
-
, null);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export async function collectLandmarkTrack({ frames, detect, onProgress = () => {} }) {
|
|
25
|
-
if (!frames || typeof frames[Symbol.asyncIterator] !== "function") {
|
|
26
|
-
throw new Error("collectLandmarkTrack: frames must be an async iterable");
|
|
27
|
-
}
|
|
28
|
-
if (typeof detect !== "function") throw new Error("collectLandmarkTrack: detect must be a function");
|
|
29
|
-
const samples = [];
|
|
30
|
-
let processed = 0;
|
|
31
|
-
for await (const frame of frames) {
|
|
32
|
-
if (!frame || !Number.isFinite(frame.timeS) || frame.timeS < 0 || !("image" in frame)) {
|
|
33
|
-
throw new Error("collectLandmarkTrack: each frame needs image and non-negative timeS");
|
|
34
|
-
}
|
|
35
|
-
const result = await detect(frame.image, frame.timeS * 1000);
|
|
36
|
-
const landmarks = selectMostConfidentPerson(result);
|
|
37
|
-
if (landmarks) samples.push({ timeS: frame.timeS, landmarks });
|
|
38
|
-
processed += 1;
|
|
39
|
-
onProgress({ processed, accepted: samples.length, timeS: frame.timeS });
|
|
40
|
-
}
|
|
41
|
-
return samples;
|
|
42
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
// detector.js — the App-side loader for MediaPipe's PoseLandmarker. Model
|
|
2
|
-
// creation stays out of browser.js on purpose (see that header): this module
|
|
3
|
-
// is where the engine version, the download sources and the failure names
|
|
4
|
-
// live, so "extraction is broken" always decomposes into which piece failed.
|
|
5
|
-
//
|
|
6
|
-
// Both the wasm engine and the .task weights are runtime downloads. The wasm
|
|
7
|
-
// must match the npm bundle compiled into this build byte-for-byte, so its
|
|
8
|
-
// URL is pinned to the same version; the weights are ~9 MB of Google binaries
|
|
9
|
-
// that belong in neither this repo nor its npm package. Offline is therefore
|
|
10
|
-
// a NAMED failure, never a hang or a silent fallback.
|
|
11
|
-
|
|
12
|
-
export const TASKS_VISION_VERSION = "0.10.17"; // must equal the @mediapipe/tasks-vision entry in package.json — the JS bundle and the wasm ABI move together
|
|
13
|
-
export const POSE_WASM_BASE = `https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@${TASKS_VISION_VERSION}/wasm`;
|
|
14
|
-
// "full", not "lite": every caller here is an offline pass over a still photo or
|
|
15
|
-
// already-recorded footage, so the ~9 MB download and the slower per-frame cost
|
|
16
|
-
// buy landmark accuracy that nothing downstream can recover once it is lost.
|
|
17
|
-
// Only switch back if a realtime (camera-preview) caller ever appears.
|
|
18
|
-
export const POSE_MODEL_URL =
|
|
19
|
-
"https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_full/float16/1/pose_landmarker_full.task";
|
|
20
|
-
// "heavy" is the same landmark contract at the top of the accuracy ladder, paid
|
|
21
|
-
// for with a ~25 MB one-time download and several times the per-frame cost. That
|
|
22
|
-
// price is per FRAME, so it is only affordable where there is one: a single
|
|
23
|
-
// photograph. A clip of a few hundred frames stays on "full".
|
|
24
|
-
export const POSE_MODEL_URL_HEAVY =
|
|
25
|
-
"https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_heavy/float16/1/pose_landmarker_heavy.task";
|
|
26
|
-
// The choice is a name at the call site, not a URL: callers say what they are
|
|
27
|
-
// extracting from, and this table decides what that costs.
|
|
28
|
-
export const POSE_MODELS = Object.freeze({
|
|
29
|
-
full: POSE_MODEL_URL,
|
|
30
|
-
heavy: POSE_MODEL_URL_HEAVY,
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Create a `{ detect, close }` pair for collectLandmarkTrack. Every failure
|
|
35
|
-
* mode has its own name:
|
|
36
|
-
* pose-model-unknown — `model` is not a key of POSE_MODELS (caller bug)
|
|
37
|
-
* pose-runtime-unavailable — the JS bundle did not load (broken build)
|
|
38
|
-
* pose-model-download-failed — the .task weights were unreachable
|
|
39
|
-
* pose-engine-download-failed— the wasm engine was unreachable
|
|
40
|
-
* pose-engine-init-failed — the engine loaded but refused to start
|
|
41
|
-
* The GPU delegate is tried first and CPU is the retry: WebGPU blocklists
|
|
42
|
-
* are common and a slower extraction beats a refused one.
|
|
43
|
-
*/
|
|
44
|
-
export async function createPoseDetector({
|
|
45
|
-
wasmBase = POSE_WASM_BASE,
|
|
46
|
-
model = "full", // "heavy" for a single still; see POSE_MODELS
|
|
47
|
-
modelUrl, // an explicit URL wins over `model` — pinning a revision stays possible
|
|
48
|
-
fetchImpl,
|
|
49
|
-
loadRuntime,
|
|
50
|
-
numPoses = 2, // the footage guidance allows two performers in frame; selectMostConfidentPerson picks one
|
|
51
|
-
runningMode = "VIDEO", // "IMAGE" for a still: MediaPipe rejects detectForVideo outside VIDEO mode
|
|
52
|
-
} = {}) {
|
|
53
|
-
const doFetch = fetchImpl ?? globalThis.fetch;
|
|
54
|
-
const resolvedModelUrl = modelUrl ?? POSE_MODELS[model];
|
|
55
|
-
if (!resolvedModelUrl) throw new Error("pose-model-unknown");
|
|
56
|
-
let runtime;
|
|
57
|
-
try {
|
|
58
|
-
runtime = await (loadRuntime ?? (() => import("@mediapipe/tasks-vision")))();
|
|
59
|
-
} catch {
|
|
60
|
-
throw new Error("pose-runtime-unavailable");
|
|
61
|
-
}
|
|
62
|
-
let modelAssetBuffer;
|
|
63
|
-
try {
|
|
64
|
-
const response = await doFetch(resolvedModelUrl);
|
|
65
|
-
if (!response.ok) throw new Error(`http-${response.status}`);
|
|
66
|
-
modelAssetBuffer = new Uint8Array(await response.arrayBuffer());
|
|
67
|
-
} catch {
|
|
68
|
-
throw new Error("pose-model-download-failed");
|
|
69
|
-
}
|
|
70
|
-
let fileset;
|
|
71
|
-
try {
|
|
72
|
-
fileset = await runtime.FilesetResolver.forVisionTasks(wasmBase);
|
|
73
|
-
} catch {
|
|
74
|
-
throw new Error("pose-engine-download-failed");
|
|
75
|
-
}
|
|
76
|
-
const optionsFor = (delegate) => ({
|
|
77
|
-
baseOptions: { modelAssetBuffer, delegate },
|
|
78
|
-
runningMode,
|
|
79
|
-
numPoses,
|
|
80
|
-
outputSegmentationMasks: false,
|
|
81
|
-
});
|
|
82
|
-
let landmarker;
|
|
83
|
-
try {
|
|
84
|
-
landmarker = await runtime.PoseLandmarker.createFromOptions(fileset, optionsFor("GPU"));
|
|
85
|
-
} catch {
|
|
86
|
-
try {
|
|
87
|
-
landmarker = await runtime.PoseLandmarker.createFromOptions(fileset, optionsFor("CPU"));
|
|
88
|
-
} catch {
|
|
89
|
-
throw new Error("pose-engine-init-failed");
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return {
|
|
93
|
-
// One `detect(image, timestampMs)` shape for both modes so the frame
|
|
94
|
-
// supply — a clip or a still — is the only thing that differs.
|
|
95
|
-
detect: runningMode === "IMAGE"
|
|
96
|
-
? (image) => landmarker.detect(image)
|
|
97
|
-
: (image, timestampMs) => landmarker.detectForVideo(image, Math.round(timestampMs)),
|
|
98
|
-
close: () => landmarker.close?.(),
|
|
99
|
-
};
|
|
100
|
-
}
|
package/src/pose-extract/fit.js
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
/** Landmark directions -> ordinary cozyclay.pose.v1 keys, with no runtime UI dependency. */
|
|
2
|
-
|
|
3
|
-
import { COZYCLAY_BONES, CSKEL27_JOINTS } from "../ardy/cskel27.js";
|
|
4
|
-
import { CSKEL27_NEUTRAL } from "../ardy/cskel27-neutral.js";
|
|
5
|
-
import { localToBasis, matMul, matToQuat, matTranspose } from "../ardy/convert.js";
|
|
6
|
-
import { POSE_LANDMARK as L } from "./landmarks.js";
|
|
7
|
-
|
|
8
|
-
function add(a, b) { return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; }
|
|
9
|
-
function sub(a, b) { return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; }
|
|
10
|
-
function scale(v, amount) { return [v[0] * amount, v[1] * amount, v[2] * amount]; }
|
|
11
|
-
function dot(a, b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; }
|
|
12
|
-
function cross(a, b) {
|
|
13
|
-
return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
14
|
-
}
|
|
15
|
-
function norm(v) { return Math.hypot(v[0], v[1], v[2]); }
|
|
16
|
-
function unit(v) { const n = norm(v); return n > 1e-8 ? scale(v, 1 / n) : null; }
|
|
17
|
-
function lerp(a, b, t) { return add(a, scale(sub(b, a), t)); }
|
|
18
|
-
function rotateY(v, cos, sin) { return [v[0] * cos + v[2] * sin, v[1], v[2] * cos - v[0] * sin]; }
|
|
19
|
-
|
|
20
|
-
function averageLandmarks(landmarks, indices) {
|
|
21
|
-
const available = indices.map((index) => landmarks[index]).filter(Boolean);
|
|
22
|
-
if (available.length === 0) return null;
|
|
23
|
-
const weight = available.reduce((sum, entry) => sum + entry.visibility, 0);
|
|
24
|
-
if (!(weight > 0)) return null;
|
|
25
|
-
return available.reduce(
|
|
26
|
-
(sum, entry) => add(sum, scale(entry.position, entry.visibility / weight)),
|
|
27
|
-
[0, 0, 0]
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function landmarkPoint(landmarks, index) {
|
|
32
|
-
return landmarks[index]?.position?.slice() ?? null;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function buildTargetPoints(landmarks) {
|
|
36
|
-
const leftHip = landmarkPoint(landmarks, L.LEFT_HIP);
|
|
37
|
-
const rightHip = landmarkPoint(landmarks, L.RIGHT_HIP);
|
|
38
|
-
const leftShoulder = landmarkPoint(landmarks, L.LEFT_SHOULDER);
|
|
39
|
-
const rightShoulder = landmarkPoint(landmarks, L.RIGHT_SHOULDER);
|
|
40
|
-
if (!leftHip || !rightHip || !leftShoulder || !rightShoulder) return null;
|
|
41
|
-
const hips = scale(add(leftHip, rightHip), 0.5);
|
|
42
|
-
const shoulders = scale(add(leftShoulder, rightShoulder), 0.5);
|
|
43
|
-
const head = averageLandmarks(landmarks, [L.NOSE, L.LEFT_EYE, L.RIGHT_EYE, L.LEFT_EAR, L.RIGHT_EAR]);
|
|
44
|
-
return {
|
|
45
|
-
Hips: hips,
|
|
46
|
-
Spine: lerp(hips, shoulders, 0.25),
|
|
47
|
-
Spine1: lerp(hips, shoulders, 0.5),
|
|
48
|
-
Spine2: lerp(hips, shoulders, 0.75),
|
|
49
|
-
Spine3: shoulders,
|
|
50
|
-
Neck: shoulders,
|
|
51
|
-
Head: head,
|
|
52
|
-
LeftShoulder: shoulders,
|
|
53
|
-
LeftArm: leftShoulder,
|
|
54
|
-
LeftForeArm: landmarkPoint(landmarks, L.LEFT_ELBOW),
|
|
55
|
-
LeftHand: landmarkPoint(landmarks, L.LEFT_WRIST),
|
|
56
|
-
LeftHandEnd: averageLandmarks(landmarks, [L.LEFT_PINKY, L.LEFT_INDEX]),
|
|
57
|
-
RightShoulder: shoulders,
|
|
58
|
-
RightArm: rightShoulder,
|
|
59
|
-
RightForeArm: landmarkPoint(landmarks, L.RIGHT_ELBOW),
|
|
60
|
-
RightHand: landmarkPoint(landmarks, L.RIGHT_WRIST),
|
|
61
|
-
RightHandEnd: averageLandmarks(landmarks, [L.RIGHT_PINKY, L.RIGHT_INDEX]),
|
|
62
|
-
LeftUpLeg: leftHip,
|
|
63
|
-
LeftLeg: landmarkPoint(landmarks, L.LEFT_KNEE),
|
|
64
|
-
LeftFoot: landmarkPoint(landmarks, L.LEFT_ANKLE),
|
|
65
|
-
LeftToeBase: landmarkPoint(landmarks, L.LEFT_FOOT_INDEX),
|
|
66
|
-
RightUpLeg: rightHip,
|
|
67
|
-
RightLeg: landmarkPoint(landmarks, L.RIGHT_KNEE),
|
|
68
|
-
RightFoot: landmarkPoint(landmarks, L.RIGHT_ANKLE),
|
|
69
|
-
RightToeBase: landmarkPoint(landmarks, L.RIGHT_FOOT_INDEX),
|
|
70
|
-
_faceForward: head && averageLandmarks(landmarks, [L.LEFT_EAR, L.RIGHT_EAR])
|
|
71
|
-
? sub(landmarkPoint(landmarks, L.NOSE) ?? head, averageLandmarks(landmarks, [L.LEFT_EAR, L.RIGHT_EAR]))
|
|
72
|
-
: null,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function buildNeutralPoints() {
|
|
77
|
-
return Object.fromEntries(CSKEL27_JOINTS.map((name, index) => [name, CSKEL27_NEUTRAL[index].slice()]));
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const CHILD = {
|
|
81
|
-
Hips: "Spine", Spine: "Spine1", Spine1: "Spine2", Neck: "Head",
|
|
82
|
-
LeftShoulder: "LeftArm", LeftArm: "LeftForeArm", LeftForeArm: "LeftHand", LeftHand: "LeftHandEnd",
|
|
83
|
-
RightShoulder: "RightArm", RightArm: "RightForeArm", RightForeArm: "RightHand", RightHand: "RightHandEnd",
|
|
84
|
-
LeftUpLeg: "LeftLeg", LeftLeg: "LeftFoot", LeftFoot: "LeftToeBase",
|
|
85
|
-
RightUpLeg: "RightLeg", RightLeg: "RightFoot", RightFoot: "RightToeBase",
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const EFFECTIVE_PARENT = {
|
|
89
|
-
Hips: null, Spine: "Hips", Spine1: "Spine", Neck: "Spine1", Head: "Neck",
|
|
90
|
-
LeftShoulder: "Spine1", LeftArm: "LeftShoulder", LeftForeArm: "LeftArm", LeftHand: "LeftForeArm",
|
|
91
|
-
RightShoulder: "Spine1", RightArm: "RightShoulder", RightForeArm: "RightArm", RightHand: "RightForeArm",
|
|
92
|
-
LeftUpLeg: "Hips", LeftLeg: "LeftUpLeg", LeftFoot: "LeftLeg",
|
|
93
|
-
RightUpLeg: "Hips", RightLeg: "RightUpLeg", RightFoot: "RightLeg",
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
function bodyAxes(points) {
|
|
97
|
-
const lateral = points.LeftUpLeg && points.RightUpLeg ? unit(sub(points.LeftUpLeg, points.RightUpLeg)) : null;
|
|
98
|
-
const up = points.Spine3 && points.Hips ? unit(sub(points.Spine3, points.Hips)) : null;
|
|
99
|
-
const forward = lateral && up ? unit(cross(lateral, up)) : null;
|
|
100
|
-
return { lateral, up, forward };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Below this the forward axis is too close to vertical to carry a yaw: the
|
|
105
|
-
* subject is lying down or shot straight from above. Yaw comes out of
|
|
106
|
-
* atan2 over the leftover horizontal components, so its error scales as
|
|
107
|
-
* 1/horizontal — at 0.12 (about 7 deg off vertical) landmark jitter of 0.01
|
|
108
|
-
* already swings the angle ~5 deg, and it diverges below that.
|
|
109
|
-
*/
|
|
110
|
-
const YAW_HORIZONTAL_MIN = 0.12;
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Drop the photograph's horizontal facing: yaw belongs to the Character
|
|
114
|
-
* group's `rot`, not to the saved pose, so every target point is spun about Y
|
|
115
|
-
* until the subject's forward matches the neutral skeleton's forward. Returns
|
|
116
|
-
* the points unchanged when either forward is too vertical to yield a yaw.
|
|
117
|
-
*/
|
|
118
|
-
function yawAlignPoints(points, targetForward, neutralForward) {
|
|
119
|
-
if (!targetForward || !neutralForward) return points;
|
|
120
|
-
const targetHorizontal = Math.hypot(targetForward[0], targetForward[2]);
|
|
121
|
-
const neutralHorizontal = Math.hypot(neutralForward[0], neutralForward[2]);
|
|
122
|
-
if (targetHorizontal < YAW_HORIZONTAL_MIN || neutralHorizontal < YAW_HORIZONTAL_MIN) return points;
|
|
123
|
-
const angle = Math.atan2(neutralForward[0], neutralForward[2])
|
|
124
|
-
- Math.atan2(targetForward[0], targetForward[2]);
|
|
125
|
-
const cos = Math.cos(angle);
|
|
126
|
-
const sin = Math.sin(angle);
|
|
127
|
-
// _faceForward is a difference, not a position; rotation is linear, so the same spin applies.
|
|
128
|
-
return Object.fromEntries(
|
|
129
|
-
Object.entries(points).map(([name, value]) => [name, value ? rotateY(value, cos, sin) : value])
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function frameFrom(primary, secondary) {
|
|
134
|
-
const x = unit(primary);
|
|
135
|
-
if (!x || !secondary) return null;
|
|
136
|
-
let y = sub(secondary, scale(x, dot(secondary, x)));
|
|
137
|
-
y = unit(y);
|
|
138
|
-
if (!y) {
|
|
139
|
-
const fallback = Math.abs(x[1]) < 0.9 ? [0, 1, 0] : [0, 0, 1];
|
|
140
|
-
y = unit(sub(fallback, scale(x, dot(fallback, x))));
|
|
141
|
-
}
|
|
142
|
-
if (!y) return null;
|
|
143
|
-
const z = unit(cross(x, y));
|
|
144
|
-
if (!z) return null;
|
|
145
|
-
y = unit(cross(z, x));
|
|
146
|
-
return [
|
|
147
|
-
[x[0], y[0], z[0]],
|
|
148
|
-
[x[1], y[1], z[1]],
|
|
149
|
-
[x[2], y[2], z[2]],
|
|
150
|
-
];
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function jointFrame(name, points, axes) {
|
|
154
|
-
let primary;
|
|
155
|
-
if (name === "Head") primary = points.Head && points.Neck ? sub(points.Head, points.Neck) : null;
|
|
156
|
-
else {
|
|
157
|
-
const child = CHILD[name];
|
|
158
|
-
primary = child && points[name] && points[child] ? sub(points[child], points[name]) : null;
|
|
159
|
-
}
|
|
160
|
-
if (!primary) return null;
|
|
161
|
-
let secondary = axes.forward;
|
|
162
|
-
if (["Hips", "Spine", "Spine1", "Neck"].includes(name)) secondary = axes.lateral;
|
|
163
|
-
else if (name === "Head") secondary = points._faceForward ?? axes.forward;
|
|
164
|
-
else if (name.endsWith("Shoulder") || name.endsWith("Foot")) secondary = axes.up;
|
|
165
|
-
return frameFrom(primary, secondary);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function restByName(rest) {
|
|
169
|
-
if (!rest || !Array.isArray(rest.joints)) {
|
|
170
|
-
throw new Error("fitLandmarksToPose: rest must be parsed cskel27-rest.json");
|
|
171
|
-
}
|
|
172
|
-
return new Map(rest.joints.map((entry) => [entry.name, entry.rest]));
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function meanConfidence(landmarks) {
|
|
176
|
-
const visible = landmarks.filter(Boolean);
|
|
177
|
-
return visible.length ? visible.reduce((sum, entry) => sum + entry.visibility, 0) / visible.length : 0;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/** Fit one normalised landmark sample into the exact pose schema consumed by poseToCskel27. */
|
|
181
|
-
export function fitLandmarksToPose({ sample, rest, createdMs = 0 }) {
|
|
182
|
-
if (!sample?.valid || !Array.isArray(sample.landmarks)) {
|
|
183
|
-
throw new Error("fitLandmarksToPose: sample must be a valid normalized landmark sample");
|
|
184
|
-
}
|
|
185
|
-
const measuredPoints = buildTargetPoints(sample.landmarks);
|
|
186
|
-
if (!measuredPoints) throw new Error("fitLandmarksToPose: shoulders and hips must be visible");
|
|
187
|
-
const neutralPoints = buildNeutralPoints();
|
|
188
|
-
const neutralAxes = bodyAxes(neutralPoints);
|
|
189
|
-
// Neutral owns the forward direction; reading it off CSKEL27_NEUTRAL keeps the axis unhardcoded.
|
|
190
|
-
const targetPoints = yawAlignPoints(measuredPoints, bodyAxes(measuredPoints).forward, neutralAxes.forward);
|
|
191
|
-
const targetAxes = bodyAxes(targetPoints);
|
|
192
|
-
const rests = restByName(rest);
|
|
193
|
-
const desiredGlobal = new Map();
|
|
194
|
-
const bones = {};
|
|
195
|
-
const releasedBones = [];
|
|
196
|
-
const identity = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
|
|
197
|
-
|
|
198
|
-
for (const name of COZYCLAY_BONES) {
|
|
199
|
-
const targetFrame = jointFrame(name, targetPoints, targetAxes);
|
|
200
|
-
const neutralFrame = jointFrame(name, neutralPoints, neutralAxes);
|
|
201
|
-
const parentName = EFFECTIVE_PARENT[name];
|
|
202
|
-
const parentGlobal = parentName ? desiredGlobal.get(parentName) : identity;
|
|
203
|
-
let global = parentGlobal;
|
|
204
|
-
let local = identity;
|
|
205
|
-
if (targetFrame && neutralFrame) {
|
|
206
|
-
global = matMul(targetFrame, matTranspose(neutralFrame));
|
|
207
|
-
local = matMul(matTranspose(parentGlobal), global);
|
|
208
|
-
} else {
|
|
209
|
-
releasedBones.push(name);
|
|
210
|
-
}
|
|
211
|
-
desiredGlobal.set(name, global);
|
|
212
|
-
const restRotation = rests.get(name);
|
|
213
|
-
if (!restRotation) throw new Error(`fitLandmarksToPose: rest rotation missing for ${name}`);
|
|
214
|
-
bones[name] = matToQuat(localToBasis(local, restRotation));
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return {
|
|
218
|
-
pose: {
|
|
219
|
-
schema: "cozyclay.pose.v1",
|
|
220
|
-
created_ms: createdMs,
|
|
221
|
-
source: { app: "cozyclay", rig: "cskel27-video-fit", time_s: sample.timeS },
|
|
222
|
-
bones,
|
|
223
|
-
},
|
|
224
|
-
releasedBones,
|
|
225
|
-
confidence: meanConfidence(sample.landmarks),
|
|
226
|
-
};
|
|
227
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
// image-frame.js — single-still frame supply for pose extraction. A photo is
|
|
2
|
-
// the degenerate footage case: no duration, no seeking, one frame at t=0.
|
|
3
|
-
// Shaped as the same async iterable videoFrames yields so collectLandmarkTrack
|
|
4
|
-
// consumes a still and a clip through one code path.
|
|
5
|
-
|
|
6
|
-
function waitForDecode(image, timeoutMs) {
|
|
7
|
-
// A cached image can already be complete before a listener is attached;
|
|
8
|
-
// waiting for a load event that will never fire again would hang.
|
|
9
|
-
if (image.complete && (image.naturalWidth ?? 0) > 0) return Promise.resolve();
|
|
10
|
-
return new Promise((resolve, reject) => {
|
|
11
|
-
let settled = false;
|
|
12
|
-
const timer = setTimeout(() => finish(new Error("image-load-timeout")), timeoutMs);
|
|
13
|
-
const onLoad = () => finish(null);
|
|
14
|
-
const onError = () => finish(new Error("decode-failed"));
|
|
15
|
-
function finish(error) {
|
|
16
|
-
if (settled) return;
|
|
17
|
-
settled = true;
|
|
18
|
-
clearTimeout(timer);
|
|
19
|
-
image.removeEventListener?.("load", onLoad);
|
|
20
|
-
image.removeEventListener?.("error", onError);
|
|
21
|
-
if (error) reject(error);
|
|
22
|
-
else resolve();
|
|
23
|
-
}
|
|
24
|
-
image.addEventListener("load", onLoad);
|
|
25
|
-
image.addEventListener("error", onError);
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Decode one still and hand back the element itself — what the IMAGE-mode
|
|
31
|
-
* landmarker accepts, so no canvas copy sits between the decoder and the
|
|
32
|
-
* detector. Split out of imageFrames because a caller that runs its own
|
|
33
|
-
* detection passes over the still (mirror.js) needs the decode without the
|
|
34
|
-
* frame-supply shape, and the load/timeout rules must not be written twice.
|
|
35
|
-
* Failures are named: `image-load-timeout`, `decode-failed`.
|
|
36
|
-
*/
|
|
37
|
-
export async function decodeImage(objectUrl, { createImage, timeoutMs = 8000 } = {}) {
|
|
38
|
-
if (typeof createImage !== "function") throw new Error("imageFrames: createImage is required");
|
|
39
|
-
const image = createImage();
|
|
40
|
-
image.src = objectUrl;
|
|
41
|
-
await waitForDecode(image, timeoutMs);
|
|
42
|
-
return image;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Async-iterate the one `{ image, timeS }` frame a still supplies. Shaped like
|
|
47
|
-
* videoFrames so collectLandmarkTrack consumes a still and a clip alike.
|
|
48
|
-
*/
|
|
49
|
-
export async function* imageFrames(objectUrl, options = {}) {
|
|
50
|
-
yield { image: await decodeImage(objectUrl, options), timeS: 0 };
|
|
51
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public video-to-pose-keys contract.
|
|
3
|
-
*
|
|
4
|
-
* App wiring should pass MediaPipe world-landmark samples here, then merge
|
|
5
|
-
* `result.keys` into the same `{ frame, pose }[]` list currently sent as
|
|
6
|
-
* `body.poses`. The output is intentionally ordinary cozyclay.pose.v1 data:
|
|
7
|
-
* no bridge endpoint, npz path or root-recovery schema is introduced.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { fitLandmarksToPose } from "./fit.js";
|
|
11
|
-
import { selectPoseKeyframes } from "./keyframes.js";
|
|
12
|
-
import { filterLandmarkTrack, normalizeLandmarkTrack } from "./landmarks.js";
|
|
13
|
-
|
|
14
|
-
export { collectLandmarkTrack, selectMostConfidentPerson } from "./browser.js";
|
|
15
|
-
export { createPoseDetector, POSE_MODELS, POSE_MODEL_URL, POSE_MODEL_URL_HEAVY, POSE_WASM_BASE, TASKS_VISION_VERSION } from "./detector.js";
|
|
16
|
-
export { sampleTimes, videoFrames } from "./video-frames.js";
|
|
17
|
-
export { decodeImage, imageFrames } from "./image-frame.js";
|
|
18
|
-
export { averageLandmarkSets, detectMirrorAveraged, LEFT_RIGHT_PAIRS, mirrorLandmarks } from "./mirror.js";
|
|
19
|
-
export { bakeExtractedTake, bakePoseFrame } from "./take.js";
|
|
20
|
-
export { fitLandmarksToPose } from "./fit.js";
|
|
21
|
-
export { poseChangeScores, selectPoseKeyframes } from "./keyframes.js";
|
|
22
|
-
export { filterLandmarkTrack, normalizeLandmarkSample, normalizeLandmarkTrack, POSE_LANDMARK } from "./landmarks.js";
|
|
23
|
-
|
|
24
|
-
export function videoLandmarksToPoseKeys({ samples, rest, fps = 20, ...options }) {
|
|
25
|
-
if (!Number.isFinite(fps) || fps <= 0) {
|
|
26
|
-
throw new Error("videoLandmarksToPoseKeys: fps must be a positive finite number");
|
|
27
|
-
}
|
|
28
|
-
const normalized = normalizeLandmarkTrack(samples, options);
|
|
29
|
-
const filtered = filterLandmarkTrack(normalized, options.filter);
|
|
30
|
-
const { indices, scores } = selectPoseKeyframes(filtered, options);
|
|
31
|
-
const keys = [];
|
|
32
|
-
const diagnostics = [];
|
|
33
|
-
for (const sampleIndex of indices) {
|
|
34
|
-
const sample = filtered[sampleIndex];
|
|
35
|
-
const frame = Math.max(0, Math.round(sample.timeS * fps));
|
|
36
|
-
const fitted = fitLandmarksToPose({ sample, rest, createdMs: options.createdMs ?? 0 });
|
|
37
|
-
const entry = { frame, pose: fitted.pose };
|
|
38
|
-
const diagnostic = {
|
|
39
|
-
frame,
|
|
40
|
-
sampleIndex,
|
|
41
|
-
sourceTimeS: sample.timeS,
|
|
42
|
-
score: scores[sampleIndex],
|
|
43
|
-
confidence: fitted.confidence,
|
|
44
|
-
releasedBones: fitted.releasedBones,
|
|
45
|
-
};
|
|
46
|
-
const prior = keys[keys.length - 1];
|
|
47
|
-
if (prior?.frame === frame) {
|
|
48
|
-
if (diagnostic.score > diagnostics[diagnostics.length - 1].score) {
|
|
49
|
-
keys[keys.length - 1] = entry;
|
|
50
|
-
diagnostics[diagnostics.length - 1] = diagnostic;
|
|
51
|
-
}
|
|
52
|
-
} else {
|
|
53
|
-
keys.push(entry);
|
|
54
|
-
diagnostics.push(diagnostic);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return { keys, diagnostics };
|
|
58
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
/** Pure pose-change scoring and keyframe selection. */
|
|
2
|
-
|
|
3
|
-
function landmarkDistance(a, b) {
|
|
4
|
-
let sum = 0;
|
|
5
|
-
let weight = 0;
|
|
6
|
-
const count = Math.min(a.landmarks.length, b.landmarks.length);
|
|
7
|
-
for (let index = 0; index < count; index += 1) {
|
|
8
|
-
const pa = a.landmarks[index];
|
|
9
|
-
const pb = b.landmarks[index];
|
|
10
|
-
if (!pa || !pb) continue;
|
|
11
|
-
const confidence = Math.min(pa.visibility, pb.visibility);
|
|
12
|
-
const delta = Math.hypot(
|
|
13
|
-
pa.position[0] - pb.position[0],
|
|
14
|
-
pa.position[1] - pb.position[1],
|
|
15
|
-
pa.position[2] - pb.position[2]
|
|
16
|
-
);
|
|
17
|
-
sum += delta * confidence;
|
|
18
|
-
weight += confidence;
|
|
19
|
-
}
|
|
20
|
-
return weight > 0 ? sum / weight : 0;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function poseChangeScores(samples) {
|
|
24
|
-
if (!Array.isArray(samples)) throw new Error("poseChangeScores: samples must be an array");
|
|
25
|
-
return samples.map((sample, index) => {
|
|
26
|
-
if (!sample.valid || index === 0 || index === samples.length - 1) return 0;
|
|
27
|
-
const before = samples[index - 1];
|
|
28
|
-
const after = samples[index + 1];
|
|
29
|
-
if (!before.valid || !after.valid) return 0;
|
|
30
|
-
const dtBefore = Math.max(sample.timeS - before.timeS, 1e-6);
|
|
31
|
-
const dtAfter = Math.max(after.timeS - sample.timeS, 1e-6);
|
|
32
|
-
return (
|
|
33
|
-
landmarkDistance(before, sample) / dtBefore +
|
|
34
|
-
landmarkDistance(sample, after) / dtAfter
|
|
35
|
-
);
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Keep valid endpoints plus the strongest local pose-change peaks. Returned
|
|
41
|
-
* indices are source ordered and respect `minSpacingS` wherever possible.
|
|
42
|
-
*/
|
|
43
|
-
export function selectPoseKeyframes(samples, options = {}) {
|
|
44
|
-
if (!Array.isArray(samples) || samples.length === 0) {
|
|
45
|
-
throw new Error("selectPoseKeyframes: samples must be a non-empty array");
|
|
46
|
-
}
|
|
47
|
-
const maxKeys = Math.max(1, Math.floor(options.maxKeys ?? 12));
|
|
48
|
-
const minSpacingS = Math.max(0, options.minSpacingS ?? 0.25);
|
|
49
|
-
const valid = samples.map((sample, index) => sample.valid ? index : -1).filter((index) => index >= 0);
|
|
50
|
-
if (valid.length === 0) return { indices: [], scores: poseChangeScores(samples) };
|
|
51
|
-
if (maxKeys === 1) return { indices: [valid[0]], scores: poseChangeScores(samples) };
|
|
52
|
-
|
|
53
|
-
const scores = poseChangeScores(samples);
|
|
54
|
-
const selected = new Set([valid[0], valid[valid.length - 1]]);
|
|
55
|
-
const peaks = valid
|
|
56
|
-
.filter((index) => index > 0 && index < samples.length - 1)
|
|
57
|
-
.filter((index) => scores[index] >= scores[index - 1] && scores[index] >= scores[index + 1])
|
|
58
|
-
.sort((a, b) => scores[b] - scores[a] || a - b);
|
|
59
|
-
const candidates = [
|
|
60
|
-
...peaks,
|
|
61
|
-
...valid
|
|
62
|
-
.filter((index) => index > 0 && index < samples.length - 1 && !peaks.includes(index))
|
|
63
|
-
.sort((a, b) => scores[b] - scores[a] || a - b),
|
|
64
|
-
];
|
|
65
|
-
|
|
66
|
-
for (const index of candidates) {
|
|
67
|
-
if (selected.size >= maxKeys) break;
|
|
68
|
-
const time = samples[index].timeS;
|
|
69
|
-
if ([...selected].every((other) => Math.abs(samples[other].timeS - time) >= minSpacingS)) {
|
|
70
|
-
selected.add(index);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return { indices: [...selected].sort((a, b) => a - b), scores };
|
|
74
|
-
}
|