cozyclay 2.1.1 → 3.0.0

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.
Files changed (577) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +40 -113
  4. package/bin/agent/agent-runner.mjs +9 -10
  5. package/bin/agent/cliproxy-provider.mjs +135 -0
  6. package/bin/agent/credential-store.mjs +30 -7
  7. package/bin/agent/pi-tools.mjs +4 -1
  8. package/bin/agent/provider-keys.mjs +32 -0
  9. package/bin/agent/provider-login.mjs +110 -0
  10. package/bin/agent/provider-routes.mjs +99 -0
  11. package/bin/agent/providers.mjs +19 -97
  12. package/bin/agent/script-tool.mjs +17 -4
  13. package/bin/agent/studio-motion-reference.mjs +23 -0
  14. package/bin/agent/studio-prompt.mjs +1 -1
  15. package/bin/agent/studio-rebase-text.mjs +32 -0
  16. package/bin/agent/studio-rebase.mjs +26 -39
  17. package/bin/agent/studio-tools.mjs +9 -28
  18. package/bin/agent/tool-arguments.mjs +49 -0
  19. package/bin/agent/turn-frames.mjs +66 -0
  20. package/bin/codex-auth.mjs +9 -2
  21. package/bin/cozyclay.mjs +10 -116
  22. package/bin/live/cli.mjs +3 -3
  23. package/bin/mcp-runtime.mjs +2 -12
  24. package/bin/package-signature.mjs +0 -4
  25. package/dist/app/index.html +7 -6
  26. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  27. package/dist/assets/analytics-C9sUnndd.js +1 -0
  28. package/dist/assets/app-CCh--1Hc.js +5087 -0
  29. package/dist/assets/app-f-fkV5uP.css +1 -0
  30. package/dist/assets/compile-worker-CkxiD6lH.js +5 -0
  31. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  32. package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-CC4xe4HM.js} +1 -1
  33. package/dist/assets/{landing-ChUvB_zL.js → landing-DjHD8u7k.js} +1 -1
  34. package/dist/assets/scene-asset-sync-Hx_FzdOH.js +20 -0
  35. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  36. package/dist/assets/workflow-C4yKH1p4.js +179 -0
  37. package/dist/cozyclay-package.json +1 -1
  38. package/dist/index.html +3 -3
  39. package/dist/sw.js +3 -6
  40. package/dist/workflow/index.html +5 -4
  41. package/mcp/LIVE-PROTOCOL.md +7 -47
  42. package/mcp/README.md +16 -26
  43. package/mcp/live-hub.mjs +6 -10
  44. package/mcp/package.json +1 -3
  45. package/mcp/tool-handlers.mjs +4 -168
  46. package/package.json +3 -22
  47. package/src/App.jsx +150 -2624
  48. package/src/agent-progress.js +79 -0
  49. package/src/agent-turn-mark.js +30 -0
  50. package/src/analytics.js +1 -19
  51. package/src/app-context.js +5 -0
  52. package/src/app-stage.jsx +2 -34
  53. package/src/asset-pane.jsx +6 -5
  54. package/src/camera-block.js +21 -0
  55. package/src/camera-direction.js +120 -0
  56. package/src/character-kind.js +3 -9
  57. package/src/command-bus.js +8 -6
  58. package/src/commands/cast.js +5 -34
  59. package/src/commands/elements/character.js +1 -1
  60. package/src/commands/elements/motion.js +1 -6
  61. package/src/commands/elements/object.js +6 -2
  62. package/src/commands/elements/shot.js +4 -1
  63. package/src/commands/elements.js +38 -14
  64. package/src/commands/index.js +3 -1
  65. package/src/commands/kits.js +37 -0
  66. package/src/commands/motion-timing.js +57 -0
  67. package/src/commands/motion.js +9 -152
  68. package/src/commands/motion3-compile.js +65 -0
  69. package/src/commands/motion3.js +207 -0
  70. package/src/commands/objects.js +6 -4
  71. package/src/commands/shot.js +49 -11
  72. package/src/commands/stage.js +14 -1
  73. package/src/commands/view.js +4 -1
  74. package/src/document-mirrors.js +42 -0
  75. package/src/document-store.js +4 -0
  76. package/src/domains/cast.js +19 -194
  77. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  78. package/src/domains/motion/take-source.js +51 -0
  79. package/src/domains/motion.js +60 -1838
  80. package/src/domains/scenes.js +12 -55
  81. package/src/domains/shots.js +2 -2
  82. package/src/domains/stage.js +4 -2
  83. package/src/draft-ghost.js +46 -0
  84. package/src/draft-ghost.jsx +129 -0
  85. package/src/dualview.jsx +4 -3
  86. package/src/effects/EffectsLayer.jsx +112 -0
  87. package/src/effects/effect-meshes.js +232 -0
  88. package/src/effects/effect-record.js +103 -0
  89. package/src/effects/emitter.js +59 -0
  90. package/src/effects/simulate.js +261 -0
  91. package/src/effects/state.js +59 -0
  92. package/src/execution-telemetry.js +2 -3
  93. package/src/first-success-guide.jsx +5 -5
  94. package/src/ghost-set.js +257 -0
  95. package/src/held-object-edit.jsx +113 -0
  96. package/src/held-object-pose.js +175 -0
  97. package/src/hierarchy-model.js +1 -1
  98. package/src/hierarchy-panel.jsx +1 -1
  99. package/src/imported-mesh.jsx +103 -0
  100. package/src/kits/held.js +84 -0
  101. package/src/kits/index.js +70 -0
  102. package/src/kits/smoking.js +57 -0
  103. package/src/light-blockers.js +93 -0
  104. package/src/motion/beat-board-model.js +84 -0
  105. package/src/motion/beat-board.css +124 -0
  106. package/src/motion/beat-board.jsx +59 -0
  107. package/src/motion/center-of-mass.js +66 -0
  108. package/src/motion/fix-collisions.js +850 -0
  109. package/src/motion/physics-review.js +118 -0
  110. package/src/{ardy → motion}/physics-support.js +1 -76
  111. package/src/motion/rig-snapshot.js +22 -0
  112. package/src/motion/take-segments.jsx +140 -0
  113. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  114. package/src/{ardy → motion}/timeline.css +1 -2
  115. package/src/{ardy → motion}/timeline.jsx +42 -413
  116. package/src/motion/waypoints.js +76 -0
  117. package/src/motion3/beats.js +83 -0
  118. package/src/motion3/body.js +158 -0
  119. package/src/motion3/bones.js +47 -0
  120. package/src/motion3/carried.js +47 -0
  121. package/src/motion3/compile-job.js +75 -0
  122. package/src/motion3/compile-worker.js +10 -0
  123. package/src/motion3/compiler/anchors.js +105 -0
  124. package/src/motion3/compiler/arm-orient.js +128 -0
  125. package/src/motion3/compiler/channels.js +125 -0
  126. package/src/motion3/compiler/compile.js +205 -0
  127. package/src/motion3/compiler/gait-rhythm.js +97 -0
  128. package/src/motion3/compiler/grip.js +171 -0
  129. package/src/motion3/compiler/head-steady.js +48 -0
  130. package/src/motion3/compiler/hold.js +134 -0
  131. package/src/motion3/compiler/idle.js +113 -0
  132. package/src/motion3/compiler/limbs.js +170 -0
  133. package/src/motion3/compiler/math.js +72 -0
  134. package/src/motion3/compiler/pose.js +162 -0
  135. package/src/motion3/compiler/quat.js +49 -0
  136. package/src/motion3/compiler/skeleton.js +74 -0
  137. package/src/motion3/compiler/validate-common.js +32 -0
  138. package/src/motion3/compiler/validate-goals.js +118 -0
  139. package/src/motion3/compiler/validate-objects.js +143 -0
  140. package/src/motion3/compiler/validate-parts.js +116 -0
  141. package/src/motion3/compiler/validate.js +122 -0
  142. package/src/motion3/compiler/vocab.js +73 -0
  143. package/src/motion3/compiler/walk-pelvis.js +275 -0
  144. package/src/motion3/compiler/walk.js +207 -0
  145. package/src/motion3/compiler/wrist-clamp.js +32 -0
  146. package/src/motion3/delta.js +47 -0
  147. package/src/motion3/describe.js +174 -0
  148. package/src/motion3/docs.js +27 -0
  149. package/src/motion3/edit-carry.js +21 -0
  150. package/src/motion3/fix/ease.js +16 -0
  151. package/src/motion3/fix/ground.js +37 -0
  152. package/src/motion3/fix/index.js +52 -0
  153. package/src/motion3/fix/push.js +71 -0
  154. package/src/motion3/fix/reach.js +46 -0
  155. package/src/motion3/fix/retime.js +79 -0
  156. package/src/motion3/fix/self.js +211 -0
  157. package/src/motion3/fix/slide.js +70 -0
  158. package/src/motion3/fix/turn.js +51 -0
  159. package/src/motion3/follow.js +39 -0
  160. package/src/motion3/free-motion.js +173 -0
  161. package/src/motion3/grip-choice.js +130 -0
  162. package/src/motion3/hold-plan.js +157 -0
  163. package/src/motion3/json-patch.js +77 -0
  164. package/src/motion3/key-aim.js +105 -0
  165. package/src/motion3/key-edit.js +82 -0
  166. package/src/motion3/lenient.js +127 -0
  167. package/src/motion3/object-keys.js +236 -0
  168. package/src/motion3/object-pose.js +182 -0
  169. package/src/motion3/object-tracks.js +12 -0
  170. package/src/motion3/pipeline.js +102 -0
  171. package/src/motion3/playback.js +51 -0
  172. package/src/motion3/probes.js +91 -0
  173. package/src/motion3/qa/body-checks.js +34 -0
  174. package/src/motion3/qa/check-names.js +14 -0
  175. package/src/motion3/qa/checks.js +121 -0
  176. package/src/motion3/qa/contact-checks.js +51 -0
  177. package/src/motion3/qa/explain.js +80 -0
  178. package/src/motion3/qa/fmt.js +5 -0
  179. package/src/motion3/qa/foot-checks.js +72 -0
  180. package/src/motion3/qa/geometry.js +77 -0
  181. package/src/motion3/qa/goals.js +191 -0
  182. package/src/motion3/qa/ground.js +60 -0
  183. package/src/motion3/qa/hints-contact.js +179 -0
  184. package/src/motion3/qa/hints-goal.js +160 -0
  185. package/src/motion3/qa/hints-knee.js +24 -0
  186. package/src/motion3/qa/hints-motion.js +209 -0
  187. package/src/motion3/qa/hints-object.js +91 -0
  188. package/src/motion3/qa/index.js +42 -0
  189. package/src/motion3/qa/key-checks.js +82 -0
  190. package/src/motion3/qa/near.js +96 -0
  191. package/src/motion3/qa/object-checks.js +135 -0
  192. package/src/motion3/qa/patch.js +161 -0
  193. package/src/motion3/qa/severity.js +38 -0
  194. package/src/motion3/qa/spec-checks.js +65 -0
  195. package/src/motion3/qa/style.js +218 -0
  196. package/src/motion3/qa/wrist-checks.js +69 -0
  197. package/src/motion3/qa/wrist-obs.js +60 -0
  198. package/src/motion3/refs.js +120 -0
  199. package/src/motion3/repair-tune.js +121 -0
  200. package/src/motion3/repair.js +83 -0
  201. package/src/motion3/report.js +67 -0
  202. package/src/motion3/rigs/index.js +14 -0
  203. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  204. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  205. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  206. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  207. package/src/motion3/scene.js +60 -0
  208. package/src/motion3/schema/brief.js +89 -0
  209. package/src/motion3/schema/fields.js +44 -0
  210. package/src/motion3/schema/spec-examples.js +46 -0
  211. package/src/motion3/schema/topics.js +111 -0
  212. package/src/motion3/schema/types.js +147 -0
  213. package/src/motion3/search.js +117 -0
  214. package/src/motion3/second-hand.js +112 -0
  215. package/src/motion3/skills/basic.js +82 -0
  216. package/src/motion3/skills/book.js +76 -0
  217. package/src/motion3/skills/car.js +145 -0
  218. package/src/motion3/skills/frames-auto.js +29 -0
  219. package/src/motion3/skills/index.js +83 -0
  220. package/src/motion3/skills/info.js +65 -0
  221. package/src/motion3/skills/smoke-kit.js +109 -0
  222. package/src/motion3/skills/smoke.js +160 -0
  223. package/src/motion3/spec-store.js +59 -0
  224. package/src/motion3/take-edit.js +155 -0
  225. package/src/motion3/take.js +63 -0
  226. package/src/motion3/timeline.js +127 -0
  227. package/src/motion3/views.js +121 -0
  228. package/src/motion3/walk-plan.js +119 -0
  229. package/src/object-catalog.jsx +7 -1
  230. package/src/object-colors.js +98 -0
  231. package/src/object-gizmo-parts.js +115 -0
  232. package/src/object-gizmo.jsx +37 -122
  233. package/src/object-glow.js +139 -0
  234. package/src/object-hidden.js +33 -0
  235. package/src/object-look.js +196 -0
  236. package/src/object-size.js +10 -0
  237. package/src/object-travel.js +71 -6
  238. package/src/panels/CameraPanel.jsx +7 -7
  239. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  240. package/src/panels/LightPanel.jsx +25 -4
  241. package/src/panels/ObjectLookPanel.jsx +88 -0
  242. package/src/panels/PosePanel.jsx +2 -17
  243. package/src/panels/RigControlPanel.jsx +5 -53
  244. package/src/panels/SubjectBox.jsx +4 -1
  245. package/src/panels/details.css +14 -0
  246. package/src/posestudio.jsx +4 -27
  247. package/src/project-browser.jsx +3 -4
  248. package/src/project-resources.js +18 -31
  249. package/src/project.js +13 -104
  250. package/src/props.jsx +25 -280
  251. package/src/rail-dot-drag.js +23 -0
  252. package/src/render-passes.js +10 -0
  253. package/src/resource-status.jsx +1 -0
  254. package/src/result-modal.jsx +4 -19
  255. package/src/room.jsx +20 -12
  256. package/src/scene-assets.js +8 -6
  257. package/src/scene-light-place.js +72 -0
  258. package/src/scene-lights.jsx +148 -0
  259. package/src/scene-objects.js +31 -139
  260. package/src/scenes.js +15 -105
  261. package/src/semantic-edit.js +2 -5
  262. package/src/set-pieces.jsx +183 -0
  263. package/src/settings-menu.jsx +3 -11
  264. package/src/shell/BottomDock.jsx +9 -51
  265. package/src/shell/DetailsSlot.jsx +16 -200
  266. package/src/shell/GhostsMenuGroup.jsx +74 -0
  267. package/src/shell/LibrarySlot.jsx +1 -3
  268. package/src/shell/MenuBar.jsx +3 -3
  269. package/src/shell/PreferencesDialog.jsx +15 -264
  270. package/src/shell/PreferencesSlot.jsx +2 -8
  271. package/src/shell/TopBar.jsx +5 -84
  272. package/src/shell/ViewportToolbar.jsx +10 -70
  273. package/src/shell/agent-glass.css +19 -6
  274. package/src/shell/details-selection.js +42 -0
  275. package/src/shell/ghosts-menu.css +22 -0
  276. package/src/shell/glass-regions.css +29 -3
  277. package/src/shell/preferences.css +6 -1
  278. package/src/shot-authoring.js +4 -7
  279. package/src/soft-shadows.js +37 -0
  280. package/src/stage-attach-frames.js +12 -12
  281. package/src/stage-canvas-rigs.jsx +3 -3
  282. package/src/stage-capture.jsx +42 -8
  283. package/src/stage-characters.jsx +22 -216
  284. package/src/stage-editor-gizmos.jsx +6 -6
  285. package/src/stage-environment.js +93 -0
  286. package/src/stage-hdr-builtins.js +107 -0
  287. package/src/stage-hdr-image.js +116 -0
  288. package/src/stage-hdr-library.js +70 -0
  289. package/src/stage-hdr-map.js +94 -0
  290. package/src/stage-hdr.js +130 -0
  291. package/src/stage-lighting.js +149 -0
  292. package/src/stage-occlusion.js +210 -0
  293. package/src/stage-sky.jsx +159 -0
  294. package/src/stage-startup-defaults.js +5 -9
  295. package/src/studio-actions.js +12 -14
  296. package/src/studio-agent-commands.js +19 -16
  297. package/src/studio-agent-motion.js +9 -9
  298. package/src/studio-agent-protocol.js +41 -71
  299. package/src/studio-app-binding.js +25 -45
  300. package/src/studio-asset-list.js +17 -0
  301. package/src/studio-contact-sheet.js +37 -5
  302. package/src/studio-elements.js +27 -7
  303. package/src/studio-entity-bounds.js +18 -0
  304. package/src/studio-error-codes.js +8 -0
  305. package/src/studio-export-framing.js +152 -0
  306. package/src/studio-lighting-check.js +103 -0
  307. package/src/studio-motion-visual.js +150 -0
  308. package/src/studio-schema-errors.js +80 -0
  309. package/src/studio-support-gap.js +74 -0
  310. package/src/styles.css +1 -19
  311. package/src/timeline-defaults.js +5 -0
  312. package/src/timeline-extent.js +8 -20
  313. package/src/tutorial-walk.js +17 -0
  314. package/src/ui-scrub.js +1 -1
  315. package/src/use-shot-camera-tracks.js +12 -0
  316. package/src/view-ghosts.js +97 -0
  317. package/src/view-ghosts.jsx +49 -0
  318. package/src/workflow/AgentPanel.jsx +47 -166
  319. package/src/workflow/AgentProgressCard.jsx +107 -0
  320. package/src/workflow/AgentReplyText.jsx +37 -0
  321. package/src/workflow/AgentTurnSteps.jsx +84 -0
  322. package/src/workflow/LoginPanel.jsx +140 -0
  323. package/src/workflow/WorkflowBuilder.jsx +7 -23
  324. package/src/workflow/agent-client.js +54 -51
  325. package/src/workflow/agent-panel.css +71 -1
  326. package/src/workflow/agent-progress-card.css +167 -0
  327. package/src/workflow/agent-progress-view.js +47 -0
  328. package/src/workflow/agent-turn-steps.css +113 -0
  329. package/src/workflow/agent-turn.js +191 -0
  330. package/src/workflow/login-client.js +34 -0
  331. package/src/workflow/motion-input.js +18 -31
  332. package/src/workflow/node-schema.js +1 -1
  333. package/src/workflow/reply-markdown.js +69 -0
  334. package/src/workflow/scene-asset-sync.js +8 -8
  335. package/tools/agent-session-stats.mjs +61 -0
  336. package/tools/dev-full.mjs +5 -56
  337. package/tools/eslint-baseline.json +12 -13
  338. package/tools/knip-baseline.json +0 -179
  339. package/tools/loc-baseline.json +31 -81
  340. package/tools/motion3/extract-body.mjs +103 -0
  341. package/tools/motion3/extract-rig.mjs +85 -0
  342. package/tools/process-supervisor.mjs +0 -88
  343. package/tools/run-tests.mjs +8 -124
  344. package/bin/agent/motion-runtime.mjs +0 -76
  345. package/dist/ardy/cskel27-rest.json +0 -557
  346. package/dist/assets/analytics-C9pMpW7s.js +0 -1
  347. package/dist/assets/app-B_2XB9rw.css +0 -1
  348. package/dist/assets/app-DAuvKPP8.js +0 -4920
  349. package/dist/assets/config-Bx7pYsD9.js +0 -1
  350. package/dist/assets/config-DeoFTYzb.css +0 -1
  351. package/dist/assets/demo-CvKuBgy5.js +0 -1
  352. package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
  353. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  354. package/dist/assets/ticket-BPk2Ni95.js +0 -1
  355. package/dist/assets/workflow-jJRBR94Z.js +0 -179
  356. package/dist/demo/walk-then-stop.npz +0 -0
  357. package/mcp/ardy-prompts.mjs +0 -218
  358. package/src/ardy/auto-fix-panel.css +0 -108
  359. package/src/ardy/auto-physics.js +0 -1194
  360. package/src/ardy/client.js +0 -153
  361. package/src/ardy/convert.js +0 -313
  362. package/src/ardy/cskel27-neutral.js +0 -39
  363. package/src/ardy/cskel27.js +0 -77
  364. package/src/ardy/export.js +0 -165
  365. package/src/ardy/fix-collisions.js +0 -2257
  366. package/src/ardy/foot-lock.js +0 -230
  367. package/src/ardy/motion-calibration.js +0 -118
  368. package/src/ardy/motion-edit.js +0 -213
  369. package/src/ardy/motion-url.js +0 -55
  370. package/src/ardy/mp4-duration.js +0 -102
  371. package/src/ardy/npz.js +0 -595
  372. package/src/ardy/physics-panel.css +0 -21
  373. package/src/ardy/physics-panel.jsx +0 -89
  374. package/src/ardy/physics-refine.js +0 -111
  375. package/src/ardy/physics-review.js +0 -567
  376. package/src/ardy/physics-temporal.js +0 -54
  377. package/src/ardy/platform-fit-panel.css +0 -64
  378. package/src/ardy/platform-fit-panel.jsx +0 -41
  379. package/src/ardy/platform-fit.js +0 -326
  380. package/src/ardy/playback.js +0 -711
  381. package/src/ardy/pose-pin.js +0 -97
  382. package/src/ardy/prompt-clips.js +0 -30
  383. package/src/ardy/retime.js +0 -211
  384. package/src/ardy/root-drop.js +0 -369
  385. package/src/ardy/timeline-resize.js +0 -13
  386. package/src/ardy/to-cskel27.js +0 -225
  387. package/src/ardy/trim.js +0 -33
  388. package/src/ardy/waypoints.js +0 -369
  389. package/src/ardy-bridge-contract.js +0 -281
  390. package/src/domains/motion/motion-video-capture.js +0 -664
  391. package/src/i2v-motion-client.js +0 -77
  392. package/src/i2v-motion-studio.jsx +0 -180
  393. package/src/line-edit.js +0 -1348
  394. package/src/motion/generation.js +0 -105
  395. package/src/motion-readiness-ui.jsx +0 -67
  396. package/src/motion-readiness.js +0 -40
  397. package/src/motion-resources.js +0 -177
  398. package/src/motion-store.js +0 -65
  399. package/src/motion-trail.js +0 -601
  400. package/src/multimodel-ingest.js +0 -394
  401. package/src/panels/PromptBlocksPanel.jsx +0 -444
  402. package/src/panels/VideoCapturePanel.jsx +0 -181
  403. package/src/pose-extract/browser.js +0 -42
  404. package/src/pose-extract/detector.js +0 -100
  405. package/src/pose-extract/fit.js +0 -227
  406. package/src/pose-extract/image-frame.js +0 -51
  407. package/src/pose-extract/index.js +0 -58
  408. package/src/pose-extract/keyframes.js +0 -74
  409. package/src/pose-extract/landmarks.js +0 -162
  410. package/src/pose-extract/mirror.js +0 -153
  411. package/src/pose-extract/take.js +0 -131
  412. package/src/pose-extract/video-frames.js +0 -91
  413. package/src/take-recipe.js +0 -165
  414. package/src/trail-key-conflicts.js +0 -39
  415. package/src/trail-pick.js +0 -59
  416. package/src/use-i2v-capture-modal.js +0 -15
  417. package/tools/ardy/BRIDGE.md +0 -37
  418. package/tools/ardy/GVHMR-NPZ.md +0 -68
  419. package/tools/ardy/README.md +0 -124
  420. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  421. package/tools/ardy/artifacts.mjs +0 -34
  422. package/tools/ardy/bridge.mjs +0 -1732
  423. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  424. package/tools/ardy/cclay_constrained_generate.py +0 -561
  425. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  426. package/tools/ardy/cclay_motion_edit.py +0 -510
  427. package/tools/ardy/cclay_sequence_generate.py +0 -705
  428. package/tools/ardy/cclay_worker.py +0 -304
  429. package/tools/ardy/dump-npz.py +0 -205
  430. package/tools/ardy/extract-rest.mjs +0 -299
  431. package/tools/ardy/extract.mjs +0 -507
  432. package/tools/ardy/footage.mjs +0 -467
  433. package/tools/ardy/gvhmr-floor.mjs +0 -102
  434. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  435. package/tools/ardy/gvhmr_fastpath.py +0 -168
  436. package/tools/ardy/gvhmr_trajectory.py +0 -240
  437. package/tools/ardy/merge-text-encoder.py +0 -160
  438. package/tools/ardy/mocap-metrics.mjs +0 -200
  439. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  440. package/tools/ardy/motion-stabilize.mjs +0 -348
  441. package/tools/ardy/npz.mjs +0 -414
  442. package/tools/ardy/pose-to-npz.mjs +0 -106
  443. package/tools/ardy/prompt-limits.mjs +0 -5
  444. package/tools/ardy/run-edit-on-box.sh +0 -73
  445. package/tools/ardy/run-local.mjs +0 -233
  446. package/tools/ardy/run-on-box.sh +0 -593
  447. package/tools/ardy/run-sequence-on-box.sh +0 -177
  448. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  449. package/tools/ardy/runners/index.mjs +0 -18
  450. package/tools/ardy/runners/local.mjs +0 -455
  451. package/tools/ardy/runners/proc.mjs +0 -125
  452. package/tools/ardy/runners/remote.mjs +0 -252
  453. package/tools/ardy/setup-local.mjs +0 -189
  454. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  455. package/tools/ardy/setup-text-encoder.py +0 -306
  456. package/tools/ardy/smpl-cskel27.mjs +0 -178
  457. package/tools/ardy/visual-qa.mjs +0 -196
  458. package/tools/ardy/vq-car.mjs +0 -52
  459. package/tools/bench/EXP3.md +0 -61
  460. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  461. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  462. package/tools/bench/cclay_bench_runner.py +0 -42
  463. package/tools/bench/cube-contact.mjs +0 -162
  464. package/tools/bench/exp3.mjs +0 -152
  465. package/tools/bench/extract-bench-lib.mjs +0 -216
  466. package/tools/bench/extract-bench.mjs +0 -302
  467. package/tools/bench/fal-generate.mjs +0 -88
  468. package/tools/bench/fit/README.md +0 -189
  469. package/tools/bench/fit/camera.mjs +0 -59
  470. package/tools/bench/fit/contact.mjs +0 -419
  471. package/tools/bench/fit/footlock.mjs +0 -234
  472. package/tools/bench/fit/motion.mjs +0 -85
  473. package/tools/bench/fit/pin.mjs +0 -27
  474. package/tools/bench/fit/remote.mjs +0 -105
  475. package/tools/bench/fit-bench.mjs +0 -126
  476. package/tools/bench/fit-sanity.mjs +0 -45
  477. package/tools/bench/metrics.mjs +0 -313
  478. package/tools/bench/obs/depth.mjs +0 -258
  479. package/tools/bench/obs/extrinsics.mjs +0 -175
  480. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  481. package/tools/bench/obs/ground.mjs +0 -294
  482. package/tools/bench/obs/heading.mjs +0 -151
  483. package/tools/bench/obs/ladder.mjs +0 -555
  484. package/tools/bench/obs/mannequin-betas.json +0 -124
  485. package/tools/bench/obs/remote.mjs +0 -83
  486. package/tools/bench/obs/rest_joints.py +0 -52
  487. package/tools/bench/obs/ybot-targets.mjs +0 -49
  488. package/tools/bench/obs-bench.mjs +0 -520
  489. package/tools/bench/score.mjs +0 -343
  490. package/tools/bench/summarize.mjs +0 -86
  491. package/tools/demo-worker/README.md +0 -137
  492. package/tools/demo-worker/api-client.mjs +0 -233
  493. package/tools/demo-worker/generate.mjs +0 -301
  494. package/tools/demo-worker/index.mjs +0 -466
  495. package/tools/demo-worker/sign-core.mjs +0 -112
  496. package/tools/demo-worker/sign.mjs +0 -161
  497. package/tools/gt-render/browser.mjs +0 -182
  498. package/tools/gt-render/camera-math.mjs +0 -276
  499. package/tools/gt-render/page.mjs +0 -267
  500. package/tools/gt-render/render.mjs +0 -514
  501. package/tools/gt-render/scene-box.mjs +0 -31
  502. package/tools/gt-render/take-transform.mjs +0 -87
  503. package/tools/kimodo/compare-seam.mjs +0 -108
  504. package/tools/kimodo/constraints.mjs +0 -244
  505. package/tools/kimodo/edit.mjs +0 -259
  506. package/tools/kimodo/effector-constraints.mjs +0 -200
  507. package/tools/kimodo/generate.mjs +0 -646
  508. package/tools/kimodo/local-output.mjs +0 -265
  509. package/tools/kimodo/measure-pose.mjs +0 -65
  510. package/tools/kimodo/measure-preserve.mjs +0 -899
  511. package/tools/kimodo/measure-waypoints.mjs +0 -55
  512. package/tools/kimodo/mlx-generate.py +0 -111
  513. package/tools/kimodo/pose-constraints.mjs +0 -248
  514. package/tools/kimodo/preserve-mask.mjs +0 -543
  515. package/tools/kimodo/read-npz.mjs +0 -189
  516. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  517. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  518. package/tools/kimodo/runner.mjs +0 -284
  519. package/tools/kimodo/seam-batch.mjs +0 -77
  520. package/tools/kimodo/setup-local.mjs +0 -30
  521. package/tools/kimodo/setup-on-box.sh +0 -339
  522. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  523. package/tools/morphgs/assets/gen_truth.py +0 -39
  524. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  525. package/tools/morphgs/assets/playback-check.mjs +0 -30
  526. package/tools/morphgs/demo-gate.sh +0 -26
  527. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  528. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  529. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  530. package/tools/morphgs/setup-on-cluster.sh +0 -126
  531. package/tools/projflow/driver.py +0 -1069
  532. package/tools/projflow/generate.mjs +0 -805
  533. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  534. package/tools/projflow/line-edit-job.mjs +0 -547
  535. package/tools/projflow/replay.mjs +0 -452
  536. package/tools/projflow/runner.mjs +0 -269
  537. package/tools/projflow/service.mjs +0 -742
  538. package/tools/track/DESIGN.md +0 -513
  539. package/tools/track/backfill-provenance.mjs +0 -116
  540. package/tools/track/budget.mjs +0 -8
  541. package/tools/track/check-rig.mjs +0 -209
  542. package/tools/track/diagnostics.schema.json +0 -60
  543. package/tools/track/export-rig.mjs +0 -256
  544. package/tools/track/fallback.mjs +0 -7
  545. package/tools/track/fk-parity-fixture.mjs +0 -161
  546. package/tools/track/gate.mjs +0 -349
  547. package/tools/track/masks.mjs +0 -318
  548. package/tools/track/metrics.mjs +0 -201
  549. package/tools/track/publish-obs.mjs +0 -108
  550. package/tools/track/py/check_env.py +0 -61
  551. package/tools/track/py/eval_lr.py +0 -229
  552. package/tools/track/py/lr_viterbi.py +0 -294
  553. package/tools/track/py/masks.py +0 -405
  554. package/tools/track/py/objective.py +0 -382
  555. package/tools/track/py/rig.py +0 -508
  556. package/tools/track/py/scene.py +0 -326
  557. package/tools/track/py/test_joint_indices.py +0 -123
  558. package/tools/track/py/test_lr_viterbi.py +0 -195
  559. package/tools/track/py/test_masks.py +0 -229
  560. package/tools/track/py/test_rig.py +0 -288
  561. package/tools/track/py/test_scene.py +0 -354
  562. package/tools/track/py/test_track.py +0 -328
  563. package/tools/track/py/track.py +0 -411
  564. package/tools/track/remote.mjs +0 -140
  565. package/tools/track/rig-dump.mjs +0 -227
  566. package/tools/track/run-box-tests.mjs +0 -14
  567. package/tools/track/run-box.mjs +0 -168
  568. package/tools/track/setup-box.sh +0 -59
  569. package/tools/track/study-2d.mjs +0 -728
  570. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  571. /package/src/{ardy → motion}/ground.js +0 -0
  572. /package/src/{ardy → motion}/ik-drag.js +0 -0
  573. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  574. /package/src/{ardy → motion}/ik.js +0 -0
  575. /package/src/{ardy → motion}/physics-surface.js +0 -0
  576. /package/src/{ardy → motion}/playback-clock.js +0 -0
  577. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -1,326 +0,0 @@
1
- """Scene terms for the known-character tracker: boxes, floor, stance, occlusion.
2
-
3
- World frame: Studio / three.js, metres, +Y up, floor plane y = floor_y (0).
4
- Boxes are oriented about +Y only (props stay upright, `src/ardy/collision-blockers.js`):
5
- a box point is `center + Ry(yaw) @ local` with `Ry` the three.js rotation
6
- (`tools/gt-render/scene-box.mjs` boxCorners), `|local| <= halfExtents`.
7
-
8
- Accepted box records (`parse_scene`):
9
- - today's bench scene.json: `{min, max}` (plus optional `centre`, `halfExtents`, `yawDeg`)
10
- - shot context: `{center|centre, halfExtents, yaw (radians) | yawDeg, perFrame?}` where
11
- `perFrame` is a list of per-frame centres `[x, y, z]` or `{center|centre, yaw|yawDeg}`
12
- for props that travel or turn.
13
- - a list of such records, or `{boxes: [...]}`.
14
-
15
- Everything that feeds a loss takes plain torch tensors (vertices or joints) and is
16
- differentiable; the HMM and ray tests are discrete and run without gradients.
17
- """
18
- from __future__ import annotations
19
-
20
- import json
21
- import math
22
- from pathlib import Path
23
- from typing import NamedTuple
24
-
25
- import numpy as np
26
- import torch
27
-
28
- # cskel27 indices: LeftFoot, LeftToeBase / RightFoot, RightToeBase (src/ardy/cskel27.js).
29
- FOOT_NAMES = ("left", "right")
30
- FOOT_JOINTS = ((25, 26), (21, 22))
31
-
32
-
33
- class SceneError(ValueError):
34
- """Malformed scene input. `code` is the machine-readable reason."""
35
-
36
- def __init__(self, code: str, detail: str):
37
- super().__init__(f"{code}: {detail}")
38
- self.code = code
39
-
40
-
41
- class Box(NamedTuple):
42
- center: np.ndarray # (3,)
43
- half_extents: np.ndarray # (3,)
44
- yaw: float # radians about +Y
45
- per_frame_center: np.ndarray | None # (T, 3)
46
- per_frame_yaw: np.ndarray | None # (T,)
47
-
48
-
49
- class SceneBoxes(NamedTuple):
50
- centers: torch.Tensor # (B, T, 3)
51
- half: torch.Tensor # (B, 3)
52
- yaw: torch.Tensor # (B, T)
53
-
54
-
55
- def _vec3(value, what: str) -> np.ndarray:
56
- try:
57
- out = np.asarray(value, dtype=np.float64)
58
- except (TypeError, ValueError):
59
- raise SceneError("bad-box", f"{what} must be 3 numbers, got {value!r}") from None
60
- if out.shape != (3,) or not np.all(np.isfinite(out)):
61
- raise SceneError("bad-box", f"{what} must be 3 finite numbers, got {value!r}")
62
- return out
63
-
64
-
65
- def _yaw(record: dict, default: float, what: str) -> float:
66
- if "yaw" in record:
67
- value = record["yaw"]
68
- elif "yawDeg" in record:
69
- value = record["yawDeg"] * math.pi / 180 if isinstance(record["yawDeg"], (int, float)) else record["yawDeg"]
70
- else:
71
- return default
72
- if not isinstance(value, (int, float)) or not math.isfinite(value):
73
- raise SceneError("bad-box", f"{what} yaw must be a finite number, got {value!r}")
74
- return float(value)
75
-
76
-
77
- def parse_box(record, index: int = 0) -> Box:
78
- what = f"box {index}"
79
- if not isinstance(record, dict):
80
- raise SceneError("bad-box", f"{what} must be an object, got {type(record).__name__}")
81
- center = record.get("center", record.get("centre"))
82
- if center is not None or "halfExtents" in record:
83
- if center is None or "halfExtents" not in record:
84
- raise SceneError("bad-box", f"{what} needs both center and halfExtents")
85
- center, half = _vec3(center, f"{what} center"), _vec3(record["halfExtents"], f"{what} halfExtents")
86
- elif "min" in record and "max" in record:
87
- lo, hi = _vec3(record["min"], f"{what} min"), _vec3(record["max"], f"{what} max")
88
- center, half = (lo + hi) / 2, (hi - lo) / 2
89
- else:
90
- raise SceneError("bad-box", f"{what} needs {{center, halfExtents}} or {{min, max}}")
91
- if not np.all(half > 0):
92
- raise SceneError("degenerate-box", f"{what} has non-positive size {(2 * half).tolist()}")
93
- yaw = _yaw(record, 0.0, what)
94
- per_center = per_yaw = None
95
- frames = record.get("perFrame")
96
- if frames is not None:
97
- if not isinstance(frames, list) or not frames:
98
- raise SceneError("bad-box", f"{what} perFrame must be a non-empty list")
99
- per_center, per_yaw = np.empty((len(frames), 3)), np.empty(len(frames))
100
- for t, entry in enumerate(frames):
101
- if isinstance(entry, dict):
102
- per_center[t] = _vec3(entry.get("center", entry.get("centre")), f"{what} perFrame[{t}] center")
103
- per_yaw[t] = _yaw(entry, yaw, f"{what} perFrame[{t}]")
104
- else:
105
- per_center[t], per_yaw[t] = _vec3(entry, f"{what} perFrame[{t}]"), yaw
106
- return Box(center, half, yaw, per_center, per_yaw)
107
-
108
-
109
- def parse_scene(scene) -> list[Box]:
110
- """Boxes from a scene.json / shot-context value (None -> no boxes)."""
111
- if scene is None:
112
- return []
113
- if isinstance(scene, dict) and "boxes" in scene:
114
- scene = scene["boxes"]
115
- records = scene if isinstance(scene, list) else [scene]
116
- return [parse_box(record, i) for i, record in enumerate(records)]
117
-
118
-
119
- def load_scene(path) -> list[Box]:
120
- return parse_scene(json.loads(Path(path).read_text()))
121
-
122
-
123
- def box_tensors(boxes: list[Box], frames: int, *, device=None, dtype=torch.float32) -> SceneBoxes:
124
- """Per-frame box tensors; static boxes are repeated over `frames`."""
125
- centers, yaws = [], []
126
- for i, box in enumerate(boxes):
127
- if box.per_frame_center is not None:
128
- if len(box.per_frame_center) != frames:
129
- raise SceneError("box-frames-mismatch", f"box {i} has {len(box.per_frame_center)} perFrame entries for {frames} frames")
130
- centers.append(box.per_frame_center)
131
- yaws.append(box.per_frame_yaw)
132
- else:
133
- centers.append(np.broadcast_to(box.center, (frames, 3)))
134
- yaws.append(np.full(frames, box.yaw))
135
- as_t = lambda a, shape: torch.as_tensor(np.asarray(a, dtype=np.float64).reshape(shape), dtype=dtype, device=device)
136
- b = len(boxes)
137
- return SceneBoxes(
138
- centers=as_t(centers, (b, frames, 3)),
139
- half=as_t([box.half_extents for box in boxes], (b, 3)),
140
- yaw=as_t(yaws, (b, frames)),
141
- )
142
-
143
-
144
- def _boxes_like(boxes: SceneBoxes, points: torch.Tensor) -> SceneBoxes:
145
- """Boxes on the points' device and dtype (differentiable; a no-op when they already match)."""
146
- return SceneBoxes(*(v.to(device=points.device, dtype=points.dtype) for v in boxes))
147
-
148
-
149
- def _cpu64(value) -> torch.Tensor:
150
- """Detached float64 CPU tensor from a tensor on any device, an array or a list."""
151
- if isinstance(value, torch.Tensor):
152
- return value.detach().to(device="cpu", dtype=torch.float64)
153
- return torch.as_tensor(np.asarray(value, dtype=np.float64))
154
-
155
-
156
- def to_box_local(points: torch.Tensor, boxes: SceneBoxes) -> torch.Tensor:
157
- """points (T, N, 3) -> box-local coordinates (T, N, B, 3), on the points' device."""
158
- boxes = _boxes_like(boxes, points)
159
- d = points[:, :, None, :] - boxes.centers.transpose(0, 1)[:, None, :, :]
160
- c, s = torch.cos(boxes.yaw).T[:, None, :], torch.sin(boxes.yaw).T[:, None, :]
161
- dx, dy, dz = d.unbind(-1)
162
- return torch.stack((c * dx - s * dz, dy, s * dx + c * dz), dim=-1)
163
-
164
-
165
- def box_sdf(points: torch.Tensor, boxes: SceneBoxes) -> torch.Tensor:
166
- """Signed distance (T, N, B) of points (T, N, 3) to each box; positive outside."""
167
- q = to_box_local(points, boxes).abs() - boxes.half[None, None].to(device=points.device, dtype=points.dtype)
168
- sq = torch.relu(q).square().sum(-1)
169
- # sqrt has an infinite derivative at 0: route inside points through a constant.
170
- outside = torch.where(sq > 0, torch.sqrt(torch.where(sq > 0, sq, torch.ones_like(sq))), torch.zeros_like(sq))
171
- return outside + torch.clamp(q.max(-1).values, max=0)
172
-
173
-
174
- def floor_sdf(points: torch.Tensor, floor_y: float = 0.0) -> torch.Tensor:
175
- return points[..., 1] - floor_y
176
-
177
-
178
- def penetration_loss(points: torch.Tensor, boxes: SceneBoxes, *, floor_y: float = 0.0, margin: float = 0.0) -> dict[str, torch.Tensor]:
179
- """{"box", "floor"}: sum relu(margin - sd)^2 over points (T, N, 3) and every box / the floor."""
180
- return {
181
- "box": torch.relu(margin - box_sdf(points, boxes)).square().sum(),
182
- "floor": torch.relu(margin - floor_sdf(points, floor_y)).square().sum(),
183
- }
184
-
185
-
186
- @torch.no_grad()
187
- def penetration_stats(points: torch.Tensor, boxes: SceneBoxes, *, floor_y: float = 0.0) -> dict:
188
- """Diagnostics: deepest box / floor penetration in cm and the frames with any penetration."""
189
- box_depth = torch.relu(-box_sdf(points, boxes)).amax(dim=(1, 2)) if boxes.half.shape[0] else points.new_zeros(points.shape[0])
190
- floor_depth = torch.relu(-floor_sdf(points, floor_y)).amax(dim=1)
191
- worst = torch.maximum(box_depth, floor_depth)
192
- return {
193
- "maxBoxCm": float(box_depth.max()) * 100 if box_depth.numel() else 0.0,
194
- "maxFloorCm": float(floor_depth.max()) * 100 if floor_depth.numel() else 0.0,
195
- "frames": [int(t) for t in torch.nonzero(worst > 0).flatten()],
196
- }
197
-
198
-
199
- @torch.no_grad()
200
- def support_height(points: torch.Tensor, boxes: SceneBoxes, *, floor_y: float = 0.0, step_tol: float = 0.1) -> torch.Tensor:
201
- """(T, N) height of the surface under each point: the floor or the top of a box whose
202
- footprint contains the point and whose top is at most `step_tol` above it."""
203
- support = torch.full(points.shape[:2], float(floor_y), dtype=points.dtype, device=points.device)
204
- if not boxes.half.shape[0]:
205
- return support
206
- boxes = _boxes_like(boxes, points)
207
- local = to_box_local(points, boxes)
208
- half = boxes.half[None, None]
209
- over = (local[..., 0].abs() <= half[..., 0]) & (local[..., 2].abs() <= half[..., 2])
210
- top = (boxes.centers[..., 1] + boxes.half[:, 1:2]).T[:, None, :] # (T, 1, B)
211
- eligible = over & (top <= points[..., 1:2] + step_tol)
212
- tops = torch.where(eligible, top.expand_as(eligible), torch.full_like(eligible, -math.inf, dtype=points.dtype))
213
- return torch.maximum(support, tops.amax(-1))
214
-
215
-
216
- def foot_points(joints: torch.Tensor) -> torch.Tensor:
217
- """cskel27 joints (T, 27, 3) -> (T, 2, 2, 3): feet (left, right) x (ankle, toe)."""
218
- return joints[:, torch.as_tensor(FOOT_JOINTS, device=joints.device)]
219
-
220
-
221
- @torch.no_grad()
222
- def stance_hmm(
223
- feet,
224
- fps: float,
225
- boxes: SceneBoxes | None = None,
226
- *,
227
- floor_y: float = 0.0,
228
- contact_height=None,
229
- stay: float = 0.95,
230
- height_scale: float = 0.05,
231
- speed_scale: float = 0.3,
232
- sharpness: float = 4.0,
233
- step_tol: float = 0.1,
234
- ) -> np.ndarray:
235
- """Per-foot 2-state (swing/contact) Viterbi. Returns bool (T, F), True = contact.
236
-
237
- feet: (T, F, 3) or (T, F, K, 3) world positions of the current estimate (K contact
238
- joints per foot, e.g. `foot_points`). Per joint, h = height above its support minus
239
- its planted height (`contact_height`, broadcastable to (F, K); default: the joint's
240
- 5th-percentile height over the clip), v = horizontal speed. Normalised evidence
241
- z = min_k sqrt((max(h, 0) / height_scale)^2 + (v / speed_scale)^2) (any planted joint
242
- means contact); p(contact) = sigmoid(sharpness * (1 - z)); stay probability `stay`.
243
- """
244
- # Discrete pass: everything (feet, boxes, contact_height) crosses to float64 CPU here.
245
- x = _cpu64(feet)
246
- if x.ndim == 3:
247
- x = x[:, :, None, :]
248
- if x.ndim != 4 or x.shape[-1] != 3:
249
- raise SceneError("bad-feet", f"feet must be (T, F, 3) or (T, F, K, 3), got {tuple(x.shape)}")
250
- if not torch.isfinite(x).all():
251
- raise SceneError("non-finite", "foot positions contain NaN or inf")
252
- t_len, f_len, k_len, _ = x.shape
253
- flat = x.reshape(t_len, f_len * k_len, 3)
254
- if boxes is None:
255
- support = torch.full(flat.shape[:2], float(floor_y), dtype=torch.float64)
256
- else:
257
- support = support_height(flat, SceneBoxes(*(_cpu64(v) for v in boxes)), floor_y=floor_y, step_tol=step_tol)
258
- height = (flat[..., 1] - support).reshape(t_len, f_len, k_len).numpy()
259
- if contact_height is None:
260
- ref = np.percentile(height, 5, axis=0)
261
- else:
262
- ref = np.broadcast_to(_cpu64(contact_height).numpy(), (f_len, k_len))
263
- xz = x[..., [0, 2]].numpy()
264
- speed = np.linalg.norm(np.gradient(xz, axis=0), axis=-1) * fps if t_len > 1 else np.zeros((t_len, f_len, k_len))
265
- z = np.sqrt((np.maximum(height - ref, 0) / height_scale) ** 2 + (speed / speed_scale) ** 2).min(-1) # (T, F)
266
- logit = sharpness * (1 - z)
267
- # -log sigmoid(l) = softplus(-l); cost[..., 0] swing, cost[..., 1] contact.
268
- cost = np.stack((np.logaddexp(0, logit), np.logaddexp(0, -logit)), axis=-1)
269
- switch = -math.log(1 - stay) + math.log(stay)
270
- out = np.zeros((t_len, f_len), dtype=bool)
271
- for f in range(f_len):
272
- acc, back = cost[0, f].copy(), np.zeros((t_len, 2), dtype=np.int64)
273
- for t in range(1, t_len):
274
- nxt = np.empty(2)
275
- for s in range(2):
276
- via = acc + np.where(np.arange(2) == s, 0.0, switch)
277
- back[t, s] = int(np.argmin(via))
278
- nxt[s] = via[back[t, s]] + cost[t, f, s]
279
- acc = nxt
280
- state = int(np.argmin(acc))
281
- for t in range(t_len - 1, -1, -1):
282
- out[t, f] = state == 1
283
- state = back[t, state]
284
- return out
285
-
286
-
287
- def skate_loss(feet: torch.Tensor, stance, fps: float) -> torch.Tensor:
288
- """Sum of squared horizontal velocity (m/s) of foot joints between consecutive
289
- frames that are both stance. feet (T, F, 3) or (T, F, K, 3), stance bool (T, F)."""
290
- if feet.ndim == 3:
291
- feet = feet[:, :, None, :]
292
- stance = torch.as_tensor(stance, dtype=torch.bool, device=feet.device)
293
- both = (stance[1:] & stance[:-1])[..., None] # (T-1, F, 1)
294
- vel = (feet[1:, ..., [0, 2]] - feet[:-1, ..., [0, 2]]) * fps
295
- return (vel.square().sum(-1) * both).sum()
296
-
297
-
298
- def camera_center(camera) -> np.ndarray:
299
- """World camera position from camera.json (`worldToCamera`, OpenCV 4x4) or a 4x4 array."""
300
- matrix = _cpu64(camera["worldToCamera"] if isinstance(camera, dict) else camera).numpy()
301
- if matrix.shape != (4, 4) or not np.all(np.isfinite(matrix)):
302
- raise SceneError("bad-camera", "worldToCamera must be a finite 4x4 matrix")
303
- return -matrix[:3, :3].T @ matrix[:3, 3]
304
-
305
-
306
- @torch.no_grad()
307
- def ray_occlusion(points: torch.Tensor, camera_pos, boxes: SceneBoxes, *, eps: float = 1e-6) -> torch.Tensor:
308
- """bool (T, N): the segment camera -> point passes through a box interior (the point is
309
- hidden behind a box, or inside one). A point on a box's camera-facing face is visible."""
310
- t_len, n = points.shape[:2]
311
- if not boxes.half.shape[0]:
312
- return torch.zeros((t_len, n), dtype=torch.bool, device=points.device)
313
- boxes = _boxes_like(boxes, points)
314
- cam = _cpu64(camera_pos).to(device=points.device, dtype=points.dtype)
315
- origin = to_box_local(cam.expand(t_len, 1, 3), boxes) # (T, 1, B, 3)
316
- d = to_box_local(points, boxes) - origin # (T, N, B, 3)
317
- half = boxes.half[None, None].to(points.dtype)
318
- flat = d.abs() < 1e-12
319
- safe = torch.where(flat, torch.ones_like(d), d)
320
- t0, t1 = (-half - origin) / safe, (half - origin) / safe
321
- inside_slab = origin.abs() < half
322
- lo = torch.where(flat, torch.where(inside_slab, -math.inf, math.inf), torch.minimum(t0, t1))
323
- hi = torch.where(flat, torch.where(inside_slab, math.inf, -math.inf), torch.maximum(t0, t1))
324
- near = torch.clamp(lo.amax(-1), min=0)
325
- far = torch.clamp(hi.amin(-1), max=1)
326
- return (far - near > eps).any(-1)
@@ -1,123 +0,0 @@
1
- """Independent name contracts for cskel27 versus COCO index spaces.
2
-
3
- The synthetic recovery fixture imports the objective's map, so it cannot by
4
- itself catch a shared off-by-one. These assertions use exported rig names and
5
- an independent anatomical correspondence instead of regenerating that map.
6
- """
7
- import json
8
- from pathlib import Path
9
- import warnings
10
-
11
- import numpy as np
12
- import pytest
13
- import torch
14
-
15
- from lr_viterbi import ARM_PAIRS, COCO_NAMES, FACE_PAIRS, LEG_PAIRS, state_permutation
16
- from masks import ANKLES, EXTREMITIES, TORSO_PAIRS, UPPER
17
- from objective import (ARM_TRUST_JOINTS, COCO_JOINTS, JOINT_CAPS_DEG,
18
- articulation_reliability, trust_region_loss)
19
- from rig import State
20
- from scene import FOOT_JOINTS, FOOT_NAMES, foot_points
21
-
22
-
23
- @pytest.fixture(scope='module')
24
- def joint_names():
25
- path = Path.home() / 'cclay-ingest/cozyfit/testdata/rig-y-bot-tpose.npz'
26
- with np.load(path, allow_pickle=False) as rig:
27
- meta = json.loads(bytes(rig['meta_json'].astype(np.uint8)))
28
- names = meta['cskel27Joints']
29
- assert len(names) == 27 and names[0] == 'Hips'
30
- return names
31
-
32
-
33
- def test_coco_correspondence_and_visibility_indices_by_name(joint_names):
34
- expected = ['LeftArm', 'RightArm', 'LeftForeArm', 'RightForeArm', 'LeftHand', 'RightHand',
35
- 'LeftUpLeg', 'RightUpLeg', 'LeftLeg', 'RightLeg', 'LeftFoot', 'RightFoot']
36
- assert [joint_names[j] for j in COCO_JOINTS[5:]] == expected
37
- # Visibility is anatomical: selecting it by this map must hide the matching
38
- # detector-side body landmark, not the next joint along the right arm.
39
- for coco, name in enumerate(expected, start=5):
40
- hidden = torch.zeros(1, 27, dtype=torch.bool)
41
- hidden[0, joint_names.index(name)] = True
42
- assert hidden[:, COCO_JOINTS][0, 5:].nonzero().flatten().tolist() == [coco - 5]
43
-
44
-
45
- def test_trust_weighting_by_exported_bilateral_joint_names(joint_names):
46
- strong = {side + part for side in ('Left', 'Right') for part in ('Shoulder', 'Arm', 'ForeArm', 'Hand')}
47
- assert {joint_names[j] for j in ARM_TRUST_JOINTS} == strong
48
- state = State.identity(1)
49
- initial = state.local_rot_mats()
50
- angle = 0.4
51
- c, s = np.cos(angle), np.sin(angle)
52
- turn = torch.tensor([[c, 0, s], [0, 1, 0], [-s, 0, c]], dtype=torch.float32)
53
- leg = initial.clone(); leg[:, joint_names.index('RightUpLeg')] = turn
54
- unit = trust_region_loss(state.transl, leg, state.transl, initial).item()
55
- assert unit > 0
56
- for joint, name in enumerate(joint_names):
57
- rotations = initial.clone(); rotations[:, joint] = turn
58
- actual = trust_region_loss(state.transl, rotations, state.transl, initial).item()
59
- multiplier = 2 if name == 'Hips' else 4 if name in strong else 1
60
- assert actual == pytest.approx(multiplier * unit), name
61
-
62
-
63
- def test_joint_limit_caps_are_anatomically_symmetric(joint_names):
64
- assert len(JOINT_CAPS_DEG) == len(joint_names)
65
- caps = dict(zip(joint_names, JOINT_CAPS_DEG))
66
- expected = {'Shoulder': 90, 'Arm': 175, 'ForeArm': 175, 'Hand': 110,
67
- 'HandEnd': 150, 'HandThumb1': 180, 'UpLeg': 160, 'Leg': 175, 'Foot': 100, 'ToeBase': 80}
68
- for part, degrees in expected.items():
69
- assert caps['Left' + part] == caps['Right' + part] == degrees
70
-
71
-
72
- def test_foot_and_lr_selections_use_their_own_index_spaces(joint_names):
73
- assert FOOT_NAMES == ('left', 'right')
74
- expected_feet = [['LeftFoot', 'LeftToeBase'], ['RightFoot', 'RightToeBase']]
75
- assert [[joint_names[j] for j in foot] for foot in FOOT_JOINTS] == expected_feet
76
- tagged = torch.arange(27)[None, :, None].expand(1, -1, 3)
77
- selected = foot_points(tagged)[0, :, :, 0].tolist()
78
- assert [[joint_names[j] for j in foot] for foot in selected] == expected_feet
79
- for pairs, parts in [(ARM_PAIRS, ['Shoulder', 'Elbow', 'Wrist']),
80
- (LEG_PAIRS, ['Hip', 'Knee', 'Ankle']), (FACE_PAIRS, ['Eye', 'Ear'])]:
81
- assert [(COCO_NAMES[a], COCO_NAMES[b]) for a, b in pairs] == [('left' + p, 'right' + p) for p in parts]
82
- full = state_permutation('full_swap')
83
- assert all(full[a] == b and full[b] == a for a, b in ARM_PAIRS + LEG_PAIRS + FACE_PAIRS)
84
-
85
-
86
- def test_mask_prompt_indices_by_coco_name():
87
- assert [[COCO_NAMES[j] for j in pair] for pair in TORSO_PAIRS] == [
88
- ['leftShoulder', 'rightShoulder'], ['leftHip', 'rightHip']]
89
- assert [COCO_NAMES[j] for j in EXTREMITIES] == ['nose', 'leftWrist', 'rightWrist', 'leftAnkle', 'rightAnkle']
90
- assert [COCO_NAMES[j] for j in UPPER] == ['nose', 'leftWrist', 'rightWrist']
91
- assert [COCO_NAMES[j] for j in ANKLES] == ['leftAnkle', 'rightAnkle']
92
-
93
-
94
- def test_reliability_lower_median_strict_cuda_determinism():
95
- assert torch.cuda.is_available(), 'Box acceptance requires CUDA'
96
- projected = np.arange(4 * 17 * 2, dtype=np.float32).reshape(4, 17, 2) / 3
97
- assigned = np.zeros((4, 17, 3), np.float32)
98
- assigned[..., 2] = 1
99
- assigned[:, :5, 2] = 0
100
- assigned[1, 6::2, 2] = 0.2
101
- assigned[2, 10:, 2] = 0
102
- assigned[3, :, 2] = 0
103
- seen = np.ones((4, 17), bool)
104
- seen[2, 5] = False
105
- expected = []
106
- for uv, obs, visible in zip(projected, assigned, seen):
107
- valid = visible[5:] & (obs[5:, 2] >= 0.5)
108
- body = (uv - obs[:, :2])[5:][valid]
109
- center = np.sort(body, axis=0)[(len(body) - 1) // 2] if len(body) else np.zeros(2)
110
- excess = np.maximum(np.linalg.norm(uv - obs[:, :2] - center, axis=-1) / 3 - 1, 0) / 2
111
- expected.append(obs[:, 2] * visible / (1 + excess ** 2) ** 2)
112
- enabled = torch.are_deterministic_algorithms_enabled()
113
- warn_only = torch.is_deterministic_algorithms_warn_only_enabled()
114
- try:
115
- torch.use_deterministic_algorithms(True)
116
- with warnings.catch_warnings():
117
- warnings.simplefilter('error', UserWarning)
118
- actual = articulation_reliability(torch.tensor(projected, device='cuda'),
119
- torch.tensor(assigned, device='cuda'),
120
- torch.tensor(seen, device='cuda'), 3)
121
- np.testing.assert_allclose(actual.cpu().numpy(), np.asarray(expected), atol=1e-6, rtol=1e-5)
122
- finally:
123
- torch.use_deterministic_algorithms(enabled, warn_only=warn_only)
@@ -1,195 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- import sys
5
- from pathlib import Path
6
-
7
- import numpy as np
8
-
9
- from eval_lr import evaluate_item, main as eval_main, truth_reference_error
10
- from lr_viterbi import (
11
- ARM_PAIRS,
12
- BODY_INDICES,
13
- LEG_PAIRS,
14
- STATE_NAMES,
15
- solve_lr_viterbi,
16
- state_permutation,
17
- )
18
-
19
-
20
- def synthetic_clip(frames: int = 64, seed: int = 17):
21
- rng = np.random.default_rng(seed)
22
- base = np.zeros((frames, 17, 2), dtype=np.float64)
23
- # A moving torso plus distinct, smooth limbs. The labels are deliberately
24
- # geometric only; no colour or appearance signal is present.
25
- for t in range(frames):
26
- phase = 0.13 * t
27
- base[t, 0] = [120 + 1.5 * t, 70]
28
- base[t, 1:5] = [[119, 63], [121, 63], [117, 66], [123, 66]]
29
- base[t, 5] = [100 + 2 * np.sin(phase), 120 + 0.3 * t]
30
- base[t, 6] = [140 - 2 * np.sin(phase), 120 + 0.3 * t]
31
- base[t, 7] = [82 + 3 * np.sin(phase), 155 + 2 * np.cos(phase)]
32
- base[t, 8] = [158 - 3 * np.sin(phase), 155 + 2 * np.cos(phase)]
33
- base[t, 9] = [70 + 4 * np.sin(phase), 190 + 2 * np.cos(phase)]
34
- base[t, 10] = [170 - 4 * np.sin(phase), 190 + 2 * np.cos(phase)]
35
- base[t, 11] = [108 + 1.5 * np.sin(phase), 220 + 0.4 * t]
36
- base[t, 12] = [132 - 1.5 * np.sin(phase), 220 + 0.4 * t]
37
- base[t, 13] = [102 + 2 * np.sin(phase), 270 + 1.5 * np.cos(phase)]
38
- base[t, 14] = [138 - 2 * np.sin(phase), 270 + 1.5 * np.cos(phase)]
39
- base[t, 15] = [96 + 2 * np.sin(phase), 320 + 1.2 * np.cos(phase)]
40
- base[t, 16] = [144 - 2 * np.sin(phase), 320 + 1.2 * np.cos(phase)]
41
- kp = np.concatenate([base, np.ones((frames, 17, 1), dtype=np.float64)], axis=2)
42
- kp[..., :2] += rng.normal(0, 0.15, kp[..., :2].shape)
43
- return base, kp
44
-
45
-
46
- def swap_observations(kp, groups, start, end):
47
- out = kp.copy()
48
- for t in range(start, end + 1):
49
- for left, right in groups:
50
- out[t, [left, right]] = out[t, [right, left]]
51
- return out
52
-
53
-
54
- def assert_corrected(result, projected, observed, start, end, groups):
55
- assignments = result["assignments"]
56
- for t in range(projected.shape[0]):
57
- for left, right in groups:
58
- expected = right if start <= t <= end else left
59
- assert assignments[t, left] == expected
60
- expected = left if start <= t <= end else right
61
- assert assignments[t, right] == expected
62
- corrected = np.take_along_axis(observed[..., :2], assignments[:, :, None], axis=1)
63
- raw_error = np.linalg.norm(observed[..., :2] - projected, axis=2)[:, list(BODY_INDICES)].mean()
64
- corrected_error = np.linalg.norm(corrected - projected, axis=2)[:, list(BODY_INDICES)].mean()
65
- # The detector noise remains after assignment correction; the swap error
66
- # should nevertheless be removed rather than requiring a denoiser here.
67
- assert corrected_error < raw_error * 0.4
68
-
69
-
70
- def test_one_three_seven_frame_swap_runs_are_recovered():
71
- projected, clean = synthetic_clip()
72
- # Use separate clips so each expected state is unambiguous.
73
- for length, state_groups in ((1, ARM_PAIRS), (3, LEG_PAIRS), (7, ARM_PAIRS)):
74
- start = 20
75
- observed = swap_observations(clean, state_groups, start, start + length - 1)
76
- result = solve_lr_viterbi(observed, projected, deltaPx=2.0)
77
- assert_corrected(result, projected, observed, start, start + length - 1, state_groups)
78
- expected_name = "arms_swap" if state_groups is ARM_PAIRS else "legs_swap"
79
- assert all(result["state_names"][s] == expected_name for s in result["path"][start : start + length])
80
-
81
-
82
- def test_iid_noise_has_no_switches():
83
- projected, clean = synthetic_clip(seed=21)
84
- rng = np.random.default_rng(99)
85
- noisy = clean.copy()
86
- noisy[..., :2] += rng.normal(0, 1.0, noisy[..., :2].shape)
87
- result = solve_lr_viterbi(noisy, projected, deltaPx=3.0)
88
- assert result["switches"] == 0
89
- assert np.all(result["path"] == STATE_NAMES.index("identity"))
90
-
91
-
92
- def test_slow_180_degree_turn_is_not_a_swap():
93
- projected, _ = synthetic_clip(frames=80)
94
- fixture = next((parent / "test/fixtures/heading-orient.json" for parent in Path(__file__).resolve().parents if (parent / "test/fixtures/heading-orient.json").exists()), None)
95
- if fixture is not None:
96
- raw = json.loads(fixture.read_text())["items"]["gt/turn"]
97
- angles = []
98
- for x, y, z in raw:
99
- theta = np.linalg.norm([x, y, z])
100
- qw = np.cos(theta / 2)
101
- qy = (y / theta) * np.sin(theta / 2) if theta > 1e-12 else y / 2
102
- angles.append(2 * np.arctan2(qy, qw))
103
- angles = np.unwrap(np.asarray(angles))
104
- projected = np.resize(projected, (len(angles), 17, 2))
105
- else:
106
- # The box runner receives only tools/track/py, so retain a deterministic
107
- # equivalent when the repository fixture is not uploaded.
108
- angles = np.linspace(0, np.pi, projected.shape[0])
109
- turned = projected.copy()
110
- for t, angle in enumerate(angles):
111
- c, s = np.cos(angle), np.sin(angle)
112
- xy = projected[t] - [125, 210]
113
- turned[t] = xy @ np.array([[c, s], [-s, c]]) + [125, 210]
114
- kp = np.concatenate([turned, np.ones((turned.shape[0], 17, 1))], axis=2)
115
- kp[..., :2] += np.random.default_rng(123).normal(0, 0.2, kp[..., :2].shape)
116
- result = solve_lr_viterbi(kp, turned, deltaPx=2.0)
117
- assert result["switches"] == 0
118
- assert np.all(result["path"] == STATE_NAMES.index("identity"))
119
-
120
-
121
- def test_malformed_and_no_evidence_inputs_are_safe():
122
- projected = np.zeros((4, 17, 2), dtype=np.float64)
123
- kp = np.zeros((4, 17, 3), dtype=np.float64)
124
- result = solve_lr_viterbi(kp, projected, deltaPx=2.0)
125
- assert np.all(result["path"] == 0)
126
- assert np.all(result["ambiguous"])
127
- assert np.all(result["margins"] == 0)
128
-
129
- nan_kp = kp.copy()
130
- nan_kp[..., :2] = np.nan
131
- nan_result = solve_lr_viterbi(nan_kp, projected, deltaPx=2.0)
132
- assert np.all(nan_result["path"] == 0)
133
- empty = solve_lr_viterbi(np.empty((0, 17, 3)), np.empty((0, 17, 2)), deltaPx=2.0)
134
- assert empty["path"].size == 0
135
-
136
-
137
- def test_permutations_are_restricted_and_deterministic():
138
- assert np.array_equal(state_permutation("identity"), np.arange(17))
139
- assert state_permutation("arms_swap")[11] == 11
140
- assert state_permutation("legs_swap")[5] == 5
141
- assert state_permutation("full_swap")[1] == 2
142
- assert len(set(tuple(state_permutation(name)) for name in STATE_NAMES)) == 4
143
-
144
-
145
- def test_visibility_is_anatomical_not_permuted_with_detector_labels():
146
- projected = np.zeros((1, 17, 2), dtype=np.float64)
147
- observed = np.zeros((1, 17, 3), dtype=np.float64)
148
- visibility = np.zeros((1, 17), dtype=bool)
149
- # Only anatomical left shoulder is visible. Detector left is wrong by 100px;
150
- # detector right is exactly at the visible anatomical projection.
151
- projected[0, 5] = [0, 0]
152
- observed[0, 5] = [100, 0, 1]
153
- observed[0, 6] = [0, 0, 1]
154
- visibility[0, 5] = True
155
- paths = []
156
- for hidden_right_x in (0, 10000):
157
- projected[0, 6] = [hidden_right_x, 0]
158
- result = solve_lr_viterbi(observed, projected, visibility=visibility, deltaPx=2.0)
159
- paths.append(int(result["path"][0]))
160
- assert int(np.argmin(result["emission"][0])) == STATE_NAMES.index("arms_swap")
161
- assert paths == [STATE_NAMES.index("arms_swap")] * 2
162
-
163
-
164
- def test_hysteresis_keeps_clear_swap_when_unrelated_states_tie():
165
- projected = np.zeros((5, 17, 2), dtype=np.float64)
166
- observed = np.zeros((5, 17, 3), dtype=np.float64)
167
- for left, right in ARM_PAIRS:
168
- projected[:, right, 0] = 100
169
- observed[:, left, 2] = observed[:, right, 2] = 1
170
- observed[:, :, :2] = projected
171
- for left, right in ARM_PAIRS:
172
- observed[2, [left, right]] = observed[2, [right, left]]
173
- result = solve_lr_viterbi(observed, projected, deltaPx=2.0, hysteresis=0.1)
174
- assert result["raw_path"][2] in (STATE_NAMES.index("full_swap"), STATE_NAMES.index("arms_swap"))
175
- assert result["assignments"][2, 5] == 6
176
- assert result["assignments"][2, 6] == 5
177
- assert result["margins"][2] > 0.1
178
-
179
-
180
- def test_eval_rejects_missing_truth_assignment_reference(tmp_path, monkeypatch):
181
- item = {"set": "gt", "name": "walk", "variant": "shaded", "dir": str(tmp_path), "source": "none"}
182
- assert truth_reference_error({}, "gt/walk") is not None
183
- row = evaluate_item(item, {}, tmp_path, 2.0)
184
- assert row["status"] == "missing-truth-reference"
185
-
186
- summary = tmp_path / "summary.json"
187
- approved = tmp_path / "approved.json"
188
- out = tmp_path / "result.json"
189
- summary.write_text(json.dumps({"deltaPx": 2.0}))
190
- approved.write_text(json.dumps({"items": [item]}))
191
- monkeypatch.setattr(sys, "argv", ["eval_lr.py", "--summary", str(summary), "--approved", str(approved), "--obs-root", str(tmp_path), "--out", str(out)])
192
- assert eval_main() == 1
193
- result = json.loads(out.read_text())
194
- assert result["pass"] is False
195
- assert result["items"][0]["status"] == "missing-truth-reference"