cozyclay 2.1.0 → 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 (583) hide show
  1. package/CHANGELOG.md +65 -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 +22 -6
  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-BlT33CGf.js → first-shot-handoff-CC4xe4HM.js} +1 -1
  33. package/dist/assets/{landing-BSesrCpL.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/llms.txt +14 -0
  40. package/dist/sw.js +3 -6
  41. package/dist/workflow/index.html +5 -4
  42. package/mcp/LIVE-PROTOCOL.md +7 -47
  43. package/mcp/README.md +16 -26
  44. package/mcp/live-hub.mjs +6 -10
  45. package/mcp/package.json +1 -3
  46. package/mcp/tool-handlers.mjs +4 -168
  47. package/package.json +3 -22
  48. package/src/App.jsx +174 -2639
  49. package/src/agent-progress.js +79 -0
  50. package/src/agent-turn-mark.js +30 -0
  51. package/src/analytics.js +1 -19
  52. package/src/app-context.js +5 -0
  53. package/src/app-stage.jsx +2 -34
  54. package/src/asset-pane.jsx +6 -5
  55. package/src/camera-block.js +21 -0
  56. package/src/camera-direction.js +120 -0
  57. package/src/character-kind.js +3 -9
  58. package/src/command-bus.js +8 -6
  59. package/src/commands/cast.js +5 -34
  60. package/src/commands/elements/character.js +1 -1
  61. package/src/commands/elements/motion.js +1 -6
  62. package/src/commands/elements/object.js +6 -2
  63. package/src/commands/elements/shot.js +4 -1
  64. package/src/commands/elements.js +38 -14
  65. package/src/commands/index.js +3 -1
  66. package/src/commands/kits.js +37 -0
  67. package/src/commands/motion-timing.js +57 -0
  68. package/src/commands/motion.js +9 -152
  69. package/src/commands/motion3-compile.js +65 -0
  70. package/src/commands/motion3.js +207 -0
  71. package/src/commands/objects.js +6 -4
  72. package/src/commands/shot.js +49 -11
  73. package/src/commands/stage.js +14 -1
  74. package/src/commands/view.js +4 -1
  75. package/src/document-mirrors.js +42 -0
  76. package/src/document-store.js +4 -0
  77. package/src/domains/cast.js +19 -194
  78. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  79. package/src/domains/motion/take-source.js +51 -0
  80. package/src/domains/motion.js +60 -1838
  81. package/src/domains/scenes.js +12 -55
  82. package/src/domains/shots.js +2 -2
  83. package/src/domains/stage.js +4 -2
  84. package/src/draft-ghost.js +46 -0
  85. package/src/draft-ghost.jsx +129 -0
  86. package/src/dualview.jsx +4 -3
  87. package/src/effects/EffectsLayer.jsx +112 -0
  88. package/src/effects/effect-meshes.js +232 -0
  89. package/src/effects/effect-record.js +103 -0
  90. package/src/effects/emitter.js +59 -0
  91. package/src/effects/simulate.js +261 -0
  92. package/src/effects/state.js +59 -0
  93. package/src/execution-telemetry.js +2 -3
  94. package/src/first-success-guide.jsx +5 -5
  95. package/src/ghost-set.js +257 -0
  96. package/src/held-object-edit.jsx +113 -0
  97. package/src/held-object-pose.js +175 -0
  98. package/src/hierarchy-model.js +1 -1
  99. package/src/hierarchy-panel.jsx +1 -1
  100. package/src/imported-mesh.jsx +103 -0
  101. package/src/kits/held.js +84 -0
  102. package/src/kits/index.js +70 -0
  103. package/src/kits/smoking.js +57 -0
  104. package/src/light-blockers.js +93 -0
  105. package/src/motion/beat-board-model.js +84 -0
  106. package/src/motion/beat-board.css +124 -0
  107. package/src/motion/beat-board.jsx +59 -0
  108. package/src/motion/center-of-mass.js +66 -0
  109. package/src/motion/fix-collisions.js +850 -0
  110. package/src/motion/physics-review.js +118 -0
  111. package/src/{ardy → motion}/physics-support.js +1 -76
  112. package/src/motion/rig-snapshot.js +22 -0
  113. package/src/motion/take-segments.jsx +140 -0
  114. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  115. package/src/{ardy → motion}/timeline.css +1 -2
  116. package/src/{ardy → motion}/timeline.jsx +48 -419
  117. package/src/motion/waypoints.js +76 -0
  118. package/src/motion3/beats.js +83 -0
  119. package/src/motion3/body.js +158 -0
  120. package/src/motion3/bones.js +47 -0
  121. package/src/motion3/carried.js +47 -0
  122. package/src/motion3/compile-job.js +75 -0
  123. package/src/motion3/compile-worker.js +10 -0
  124. package/src/motion3/compiler/anchors.js +105 -0
  125. package/src/motion3/compiler/arm-orient.js +128 -0
  126. package/src/motion3/compiler/channels.js +125 -0
  127. package/src/motion3/compiler/compile.js +205 -0
  128. package/src/motion3/compiler/gait-rhythm.js +97 -0
  129. package/src/motion3/compiler/grip.js +171 -0
  130. package/src/motion3/compiler/head-steady.js +48 -0
  131. package/src/motion3/compiler/hold.js +134 -0
  132. package/src/motion3/compiler/idle.js +113 -0
  133. package/src/motion3/compiler/limbs.js +170 -0
  134. package/src/motion3/compiler/math.js +72 -0
  135. package/src/motion3/compiler/pose.js +162 -0
  136. package/src/motion3/compiler/quat.js +49 -0
  137. package/src/motion3/compiler/skeleton.js +74 -0
  138. package/src/motion3/compiler/validate-common.js +32 -0
  139. package/src/motion3/compiler/validate-goals.js +118 -0
  140. package/src/motion3/compiler/validate-objects.js +143 -0
  141. package/src/motion3/compiler/validate-parts.js +116 -0
  142. package/src/motion3/compiler/validate.js +122 -0
  143. package/src/motion3/compiler/vocab.js +73 -0
  144. package/src/motion3/compiler/walk-pelvis.js +275 -0
  145. package/src/motion3/compiler/walk.js +207 -0
  146. package/src/motion3/compiler/wrist-clamp.js +32 -0
  147. package/src/motion3/delta.js +47 -0
  148. package/src/motion3/describe.js +174 -0
  149. package/src/motion3/docs.js +27 -0
  150. package/src/motion3/edit-carry.js +21 -0
  151. package/src/motion3/fix/ease.js +16 -0
  152. package/src/motion3/fix/ground.js +37 -0
  153. package/src/motion3/fix/index.js +52 -0
  154. package/src/motion3/fix/push.js +71 -0
  155. package/src/motion3/fix/reach.js +46 -0
  156. package/src/motion3/fix/retime.js +79 -0
  157. package/src/motion3/fix/self.js +211 -0
  158. package/src/motion3/fix/slide.js +70 -0
  159. package/src/motion3/fix/turn.js +51 -0
  160. package/src/motion3/follow.js +39 -0
  161. package/src/motion3/free-motion.js +173 -0
  162. package/src/motion3/grip-choice.js +130 -0
  163. package/src/motion3/hold-plan.js +157 -0
  164. package/src/motion3/json-patch.js +77 -0
  165. package/src/motion3/key-aim.js +105 -0
  166. package/src/motion3/key-edit.js +82 -0
  167. package/src/motion3/lenient.js +127 -0
  168. package/src/motion3/object-keys.js +236 -0
  169. package/src/motion3/object-pose.js +182 -0
  170. package/src/motion3/object-tracks.js +12 -0
  171. package/src/motion3/pipeline.js +102 -0
  172. package/src/motion3/playback.js +51 -0
  173. package/src/motion3/probes.js +91 -0
  174. package/src/motion3/qa/body-checks.js +34 -0
  175. package/src/motion3/qa/check-names.js +14 -0
  176. package/src/motion3/qa/checks.js +121 -0
  177. package/src/motion3/qa/contact-checks.js +51 -0
  178. package/src/motion3/qa/explain.js +80 -0
  179. package/src/motion3/qa/fmt.js +5 -0
  180. package/src/motion3/qa/foot-checks.js +72 -0
  181. package/src/motion3/qa/geometry.js +77 -0
  182. package/src/motion3/qa/goals.js +191 -0
  183. package/src/motion3/qa/ground.js +60 -0
  184. package/src/motion3/qa/hints-contact.js +179 -0
  185. package/src/motion3/qa/hints-goal.js +160 -0
  186. package/src/motion3/qa/hints-knee.js +24 -0
  187. package/src/motion3/qa/hints-motion.js +209 -0
  188. package/src/motion3/qa/hints-object.js +91 -0
  189. package/src/motion3/qa/index.js +42 -0
  190. package/src/motion3/qa/key-checks.js +82 -0
  191. package/src/motion3/qa/near.js +96 -0
  192. package/src/motion3/qa/object-checks.js +135 -0
  193. package/src/motion3/qa/patch.js +161 -0
  194. package/src/motion3/qa/severity.js +38 -0
  195. package/src/motion3/qa/spec-checks.js +65 -0
  196. package/src/motion3/qa/style.js +218 -0
  197. package/src/motion3/qa/wrist-checks.js +69 -0
  198. package/src/motion3/qa/wrist-obs.js +60 -0
  199. package/src/motion3/refs.js +120 -0
  200. package/src/motion3/repair-tune.js +121 -0
  201. package/src/motion3/repair.js +83 -0
  202. package/src/motion3/report.js +67 -0
  203. package/src/motion3/rigs/index.js +14 -0
  204. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  205. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  206. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  207. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  208. package/src/motion3/scene.js +60 -0
  209. package/src/motion3/schema/brief.js +89 -0
  210. package/src/motion3/schema/fields.js +44 -0
  211. package/src/motion3/schema/spec-examples.js +46 -0
  212. package/src/motion3/schema/topics.js +111 -0
  213. package/src/motion3/schema/types.js +147 -0
  214. package/src/motion3/search.js +117 -0
  215. package/src/motion3/second-hand.js +112 -0
  216. package/src/motion3/skills/basic.js +82 -0
  217. package/src/motion3/skills/book.js +76 -0
  218. package/src/motion3/skills/car.js +145 -0
  219. package/src/motion3/skills/frames-auto.js +29 -0
  220. package/src/motion3/skills/index.js +83 -0
  221. package/src/motion3/skills/info.js +65 -0
  222. package/src/motion3/skills/smoke-kit.js +109 -0
  223. package/src/motion3/skills/smoke.js +160 -0
  224. package/src/motion3/spec-store.js +59 -0
  225. package/src/motion3/take-edit.js +155 -0
  226. package/src/motion3/take.js +63 -0
  227. package/src/motion3/timeline.js +127 -0
  228. package/src/motion3/views.js +121 -0
  229. package/src/motion3/walk-plan.js +119 -0
  230. package/src/object-catalog.jsx +7 -1
  231. package/src/object-colors.js +98 -0
  232. package/src/object-gizmo-parts.js +115 -0
  233. package/src/object-gizmo.jsx +37 -122
  234. package/src/object-glow.js +139 -0
  235. package/src/object-hidden.js +33 -0
  236. package/src/object-look.js +196 -0
  237. package/src/object-size.js +10 -0
  238. package/src/object-travel.js +71 -6
  239. package/src/panels/CameraPanel.jsx +7 -7
  240. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  241. package/src/panels/LightPanel.jsx +25 -4
  242. package/src/panels/ObjectLookPanel.jsx +88 -0
  243. package/src/panels/PoseImagePanel.jsx +1 -1
  244. package/src/panels/PosePanel.jsx +2 -17
  245. package/src/panels/RigControlPanel.jsx +5 -53
  246. package/src/panels/SubjectBox.jsx +4 -1
  247. package/src/panels/details.css +14 -0
  248. package/src/posestudio.jsx +4 -27
  249. package/src/project-browser.jsx +3 -4
  250. package/src/project-resources.js +18 -31
  251. package/src/project.js +13 -104
  252. package/src/props.jsx +25 -280
  253. package/src/rail-dot-drag.js +23 -0
  254. package/src/render-passes.js +10 -0
  255. package/src/resource-status.jsx +1 -0
  256. package/src/result-modal.jsx +4 -19
  257. package/src/room.jsx +20 -12
  258. package/src/scene-assets.js +8 -6
  259. package/src/scene-light-place.js +72 -0
  260. package/src/scene-lights.jsx +148 -0
  261. package/src/scene-objects.js +31 -139
  262. package/src/scenes.js +15 -105
  263. package/src/semantic-edit.js +2 -5
  264. package/src/set-pieces.jsx +183 -0
  265. package/src/settings-menu.jsx +3 -11
  266. package/src/shell/BottomDock.jsx +11 -53
  267. package/src/shell/CompositionGuideButton.jsx +20 -0
  268. package/src/shell/DetailsSlot.jsx +16 -200
  269. package/src/shell/GhostsMenuGroup.jsx +74 -0
  270. package/src/shell/LibrarySlot.jsx +1 -3
  271. package/src/shell/MenuBar.jsx +3 -3
  272. package/src/shell/PreferencesDialog.jsx +15 -264
  273. package/src/shell/PreferencesSlot.jsx +2 -8
  274. package/src/shell/ShotPreviewHeader.jsx +17 -0
  275. package/src/shell/TopBar.jsx +5 -83
  276. package/src/shell/ViewportToolbar.jsx +23 -93
  277. package/src/shell/agent-glass.css +19 -6
  278. package/src/shell/details-selection.js +42 -0
  279. package/src/shell/ghosts-menu.css +22 -0
  280. package/src/shell/glass-regions.css +29 -3
  281. package/src/shell/preferences.css +6 -1
  282. package/src/shell/viewport.css +12 -0
  283. package/src/shot-authoring.js +4 -7
  284. package/src/soft-shadows.js +37 -0
  285. package/src/stage-attach-frames.js +12 -12
  286. package/src/stage-canvas-rigs.jsx +3 -3
  287. package/src/stage-capture.jsx +42 -8
  288. package/src/stage-characters.jsx +22 -216
  289. package/src/stage-editor-gizmos.jsx +6 -6
  290. package/src/stage-environment.js +93 -0
  291. package/src/stage-hdr-builtins.js +107 -0
  292. package/src/stage-hdr-image.js +116 -0
  293. package/src/stage-hdr-library.js +70 -0
  294. package/src/stage-hdr-map.js +94 -0
  295. package/src/stage-hdr.js +130 -0
  296. package/src/stage-lighting.js +149 -0
  297. package/src/stage-occlusion.js +210 -0
  298. package/src/stage-sky.jsx +159 -0
  299. package/src/stage-startup-defaults.js +5 -9
  300. package/src/studio-actions.js +12 -14
  301. package/src/studio-agent-commands.js +46 -16
  302. package/src/studio-agent-motion.js +9 -9
  303. package/src/studio-agent-protocol.js +46 -73
  304. package/src/studio-app-binding.js +29 -45
  305. package/src/studio-asset-list.js +17 -0
  306. package/src/studio-contact-sheet.js +37 -5
  307. package/src/studio-elements.js +27 -7
  308. package/src/studio-entity-bounds.js +18 -0
  309. package/src/studio-error-codes.js +8 -0
  310. package/src/studio-export-framing.js +152 -0
  311. package/src/studio-lighting-check.js +103 -0
  312. package/src/studio-motion-visual.js +150 -0
  313. package/src/studio-schema-errors.js +80 -0
  314. package/src/studio-support-gap.js +74 -0
  315. package/src/styles.css +1 -19
  316. package/src/timeline-defaults.js +5 -0
  317. package/src/timeline-extent.js +8 -20
  318. package/src/tutorial-walk.js +17 -0
  319. package/src/ui-scrub.js +1 -1
  320. package/src/use-shot-camera-tracks.js +12 -0
  321. package/src/view-ghosts.js +97 -0
  322. package/src/view-ghosts.jsx +49 -0
  323. package/src/workflow/AgentPanel.jsx +47 -166
  324. package/src/workflow/AgentProgressCard.jsx +107 -0
  325. package/src/workflow/AgentReplyText.jsx +37 -0
  326. package/src/workflow/AgentTurnSteps.jsx +84 -0
  327. package/src/workflow/LoginPanel.jsx +140 -0
  328. package/src/workflow/WorkflowBuilder.jsx +7 -23
  329. package/src/workflow/agent-client.js +54 -51
  330. package/src/workflow/agent-panel.css +71 -1
  331. package/src/workflow/agent-progress-card.css +167 -0
  332. package/src/workflow/agent-progress-view.js +47 -0
  333. package/src/workflow/agent-turn-steps.css +113 -0
  334. package/src/workflow/agent-turn.js +191 -0
  335. package/src/workflow/login-client.js +34 -0
  336. package/src/workflow/motion-input.js +18 -31
  337. package/src/workflow/node-schema.js +1 -1
  338. package/src/workflow/reply-markdown.js +69 -0
  339. package/src/workflow/scene-asset-sync.js +8 -8
  340. package/tools/agent-session-stats.mjs +61 -0
  341. package/tools/dev-full.mjs +5 -56
  342. package/tools/eslint-baseline.json +12 -13
  343. package/tools/headless-studio.mjs +85 -0
  344. package/tools/knip-baseline.json +0 -179
  345. package/tools/loc-baseline.json +33 -83
  346. package/tools/motion3/extract-body.mjs +103 -0
  347. package/tools/motion3/extract-rig.mjs +85 -0
  348. package/tools/process-supervisor.mjs +0 -88
  349. package/tools/run-tests.mjs +9 -124
  350. package/bin/agent/motion-runtime.mjs +0 -76
  351. package/dist/ardy/cskel27-rest.json +0 -557
  352. package/dist/assets/analytics-B41FmPYI.js +0 -1
  353. package/dist/assets/app-Do-Og5Pu.css +0 -1
  354. package/dist/assets/app-DwP-tl2x.js +0 -4920
  355. package/dist/assets/config-Bx7pYsD9.js +0 -1
  356. package/dist/assets/config-DeoFTYzb.css +0 -1
  357. package/dist/assets/demo-Dt0qZfS7.js +0 -1
  358. package/dist/assets/scene-asset-sync-BCWDJB1d.js +0 -19
  359. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  360. package/dist/assets/ticket-jqMhqL88.js +0 -1
  361. package/dist/assets/workflow-Br-K7Srs.js +0 -179
  362. package/dist/demo/walk-then-stop.npz +0 -0
  363. package/mcp/ardy-prompts.mjs +0 -218
  364. package/src/ardy/auto-fix-panel.css +0 -108
  365. package/src/ardy/auto-physics.js +0 -1194
  366. package/src/ardy/client.js +0 -153
  367. package/src/ardy/convert.js +0 -313
  368. package/src/ardy/cskel27-neutral.js +0 -39
  369. package/src/ardy/cskel27.js +0 -77
  370. package/src/ardy/export.js +0 -165
  371. package/src/ardy/fix-collisions.js +0 -2257
  372. package/src/ardy/foot-lock.js +0 -230
  373. package/src/ardy/motion-calibration.js +0 -118
  374. package/src/ardy/motion-edit.js +0 -213
  375. package/src/ardy/motion-url.js +0 -55
  376. package/src/ardy/mp4-duration.js +0 -102
  377. package/src/ardy/npz.js +0 -595
  378. package/src/ardy/physics-panel.css +0 -21
  379. package/src/ardy/physics-panel.jsx +0 -89
  380. package/src/ardy/physics-refine.js +0 -111
  381. package/src/ardy/physics-review.js +0 -567
  382. package/src/ardy/physics-temporal.js +0 -54
  383. package/src/ardy/platform-fit-panel.css +0 -64
  384. package/src/ardy/platform-fit-panel.jsx +0 -41
  385. package/src/ardy/platform-fit.js +0 -326
  386. package/src/ardy/playback.js +0 -711
  387. package/src/ardy/pose-pin.js +0 -97
  388. package/src/ardy/prompt-clips.js +0 -30
  389. package/src/ardy/retime.js +0 -211
  390. package/src/ardy/root-drop.js +0 -369
  391. package/src/ardy/timeline-resize.js +0 -13
  392. package/src/ardy/to-cskel27.js +0 -225
  393. package/src/ardy/trim.js +0 -33
  394. package/src/ardy/waypoints.js +0 -369
  395. package/src/ardy-bridge-contract.js +0 -281
  396. package/src/domains/motion/motion-video-capture.js +0 -664
  397. package/src/i2v-motion-client.js +0 -77
  398. package/src/i2v-motion-studio.jsx +0 -180
  399. package/src/line-edit.js +0 -1348
  400. package/src/motion/generation.js +0 -105
  401. package/src/motion-readiness-ui.jsx +0 -67
  402. package/src/motion-readiness.js +0 -40
  403. package/src/motion-resources.js +0 -177
  404. package/src/motion-store.js +0 -65
  405. package/src/motion-trail.js +0 -601
  406. package/src/multimodel-ingest.js +0 -394
  407. package/src/panels/PromptBlocksPanel.jsx +0 -444
  408. package/src/panels/VideoCapturePanel.jsx +0 -181
  409. package/src/pose-extract/browser.js +0 -42
  410. package/src/pose-extract/detector.js +0 -100
  411. package/src/pose-extract/fit.js +0 -227
  412. package/src/pose-extract/image-frame.js +0 -51
  413. package/src/pose-extract/index.js +0 -58
  414. package/src/pose-extract/keyframes.js +0 -74
  415. package/src/pose-extract/landmarks.js +0 -162
  416. package/src/pose-extract/mirror.js +0 -153
  417. package/src/pose-extract/take.js +0 -131
  418. package/src/pose-extract/video-frames.js +0 -91
  419. package/src/take-recipe.js +0 -165
  420. package/src/trail-key-conflicts.js +0 -39
  421. package/src/trail-pick.js +0 -59
  422. package/src/use-i2v-capture-modal.js +0 -15
  423. package/tools/ardy/BRIDGE.md +0 -37
  424. package/tools/ardy/GVHMR-NPZ.md +0 -68
  425. package/tools/ardy/README.md +0 -124
  426. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  427. package/tools/ardy/artifacts.mjs +0 -34
  428. package/tools/ardy/bridge.mjs +0 -1732
  429. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  430. package/tools/ardy/cclay_constrained_generate.py +0 -561
  431. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  432. package/tools/ardy/cclay_motion_edit.py +0 -510
  433. package/tools/ardy/cclay_sequence_generate.py +0 -705
  434. package/tools/ardy/cclay_worker.py +0 -304
  435. package/tools/ardy/dump-npz.py +0 -205
  436. package/tools/ardy/extract-rest.mjs +0 -299
  437. package/tools/ardy/extract.mjs +0 -507
  438. package/tools/ardy/footage.mjs +0 -467
  439. package/tools/ardy/gvhmr-floor.mjs +0 -102
  440. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  441. package/tools/ardy/gvhmr_fastpath.py +0 -168
  442. package/tools/ardy/gvhmr_trajectory.py +0 -240
  443. package/tools/ardy/merge-text-encoder.py +0 -160
  444. package/tools/ardy/mocap-metrics.mjs +0 -200
  445. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  446. package/tools/ardy/motion-stabilize.mjs +0 -348
  447. package/tools/ardy/npz.mjs +0 -414
  448. package/tools/ardy/pose-to-npz.mjs +0 -106
  449. package/tools/ardy/prompt-limits.mjs +0 -5
  450. package/tools/ardy/run-edit-on-box.sh +0 -73
  451. package/tools/ardy/run-local.mjs +0 -233
  452. package/tools/ardy/run-on-box.sh +0 -593
  453. package/tools/ardy/run-sequence-on-box.sh +0 -177
  454. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  455. package/tools/ardy/runners/index.mjs +0 -18
  456. package/tools/ardy/runners/local.mjs +0 -455
  457. package/tools/ardy/runners/proc.mjs +0 -125
  458. package/tools/ardy/runners/remote.mjs +0 -252
  459. package/tools/ardy/setup-local.mjs +0 -189
  460. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  461. package/tools/ardy/setup-text-encoder.py +0 -306
  462. package/tools/ardy/smpl-cskel27.mjs +0 -178
  463. package/tools/ardy/visual-qa.mjs +0 -196
  464. package/tools/ardy/vq-car.mjs +0 -52
  465. package/tools/bench/EXP3.md +0 -61
  466. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  467. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  468. package/tools/bench/cclay_bench_runner.py +0 -42
  469. package/tools/bench/cube-contact.mjs +0 -162
  470. package/tools/bench/exp3.mjs +0 -152
  471. package/tools/bench/extract-bench-lib.mjs +0 -216
  472. package/tools/bench/extract-bench.mjs +0 -302
  473. package/tools/bench/fal-generate.mjs +0 -88
  474. package/tools/bench/fit/README.md +0 -189
  475. package/tools/bench/fit/camera.mjs +0 -59
  476. package/tools/bench/fit/contact.mjs +0 -419
  477. package/tools/bench/fit/footlock.mjs +0 -234
  478. package/tools/bench/fit/motion.mjs +0 -85
  479. package/tools/bench/fit/pin.mjs +0 -27
  480. package/tools/bench/fit/remote.mjs +0 -105
  481. package/tools/bench/fit-bench.mjs +0 -126
  482. package/tools/bench/fit-sanity.mjs +0 -45
  483. package/tools/bench/metrics.mjs +0 -313
  484. package/tools/bench/obs/depth.mjs +0 -258
  485. package/tools/bench/obs/extrinsics.mjs +0 -175
  486. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  487. package/tools/bench/obs/ground.mjs +0 -294
  488. package/tools/bench/obs/heading.mjs +0 -151
  489. package/tools/bench/obs/ladder.mjs +0 -555
  490. package/tools/bench/obs/mannequin-betas.json +0 -124
  491. package/tools/bench/obs/remote.mjs +0 -83
  492. package/tools/bench/obs/rest_joints.py +0 -52
  493. package/tools/bench/obs/ybot-targets.mjs +0 -49
  494. package/tools/bench/obs-bench.mjs +0 -520
  495. package/tools/bench/score.mjs +0 -343
  496. package/tools/bench/summarize.mjs +0 -86
  497. package/tools/demo-worker/README.md +0 -137
  498. package/tools/demo-worker/api-client.mjs +0 -233
  499. package/tools/demo-worker/generate.mjs +0 -301
  500. package/tools/demo-worker/index.mjs +0 -466
  501. package/tools/demo-worker/sign-core.mjs +0 -112
  502. package/tools/demo-worker/sign.mjs +0 -161
  503. package/tools/gt-render/browser.mjs +0 -159
  504. package/tools/gt-render/camera-math.mjs +0 -276
  505. package/tools/gt-render/page.mjs +0 -267
  506. package/tools/gt-render/render.mjs +0 -514
  507. package/tools/gt-render/scene-box.mjs +0 -31
  508. package/tools/gt-render/take-transform.mjs +0 -87
  509. package/tools/kimodo/compare-seam.mjs +0 -108
  510. package/tools/kimodo/constraints.mjs +0 -244
  511. package/tools/kimodo/edit.mjs +0 -259
  512. package/tools/kimodo/effector-constraints.mjs +0 -200
  513. package/tools/kimodo/generate.mjs +0 -646
  514. package/tools/kimodo/local-output.mjs +0 -265
  515. package/tools/kimodo/measure-pose.mjs +0 -65
  516. package/tools/kimodo/measure-preserve.mjs +0 -899
  517. package/tools/kimodo/measure-waypoints.mjs +0 -55
  518. package/tools/kimodo/mlx-generate.py +0 -111
  519. package/tools/kimodo/pose-constraints.mjs +0 -248
  520. package/tools/kimodo/preserve-mask.mjs +0 -543
  521. package/tools/kimodo/read-npz.mjs +0 -189
  522. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  523. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  524. package/tools/kimodo/runner.mjs +0 -284
  525. package/tools/kimodo/seam-batch.mjs +0 -77
  526. package/tools/kimodo/setup-local.mjs +0 -30
  527. package/tools/kimodo/setup-on-box.sh +0 -339
  528. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  529. package/tools/morphgs/assets/gen_truth.py +0 -39
  530. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  531. package/tools/morphgs/assets/playback-check.mjs +0 -30
  532. package/tools/morphgs/demo-gate.sh +0 -26
  533. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  534. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  535. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  536. package/tools/morphgs/setup-on-cluster.sh +0 -126
  537. package/tools/projflow/driver.py +0 -1069
  538. package/tools/projflow/generate.mjs +0 -805
  539. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  540. package/tools/projflow/line-edit-job.mjs +0 -547
  541. package/tools/projflow/replay.mjs +0 -452
  542. package/tools/projflow/runner.mjs +0 -269
  543. package/tools/projflow/service.mjs +0 -742
  544. package/tools/track/DESIGN.md +0 -513
  545. package/tools/track/backfill-provenance.mjs +0 -116
  546. package/tools/track/budget.mjs +0 -8
  547. package/tools/track/check-rig.mjs +0 -209
  548. package/tools/track/diagnostics.schema.json +0 -60
  549. package/tools/track/export-rig.mjs +0 -256
  550. package/tools/track/fallback.mjs +0 -7
  551. package/tools/track/fk-parity-fixture.mjs +0 -161
  552. package/tools/track/gate.mjs +0 -349
  553. package/tools/track/masks.mjs +0 -318
  554. package/tools/track/metrics.mjs +0 -201
  555. package/tools/track/publish-obs.mjs +0 -108
  556. package/tools/track/py/check_env.py +0 -61
  557. package/tools/track/py/eval_lr.py +0 -229
  558. package/tools/track/py/lr_viterbi.py +0 -294
  559. package/tools/track/py/masks.py +0 -405
  560. package/tools/track/py/objective.py +0 -382
  561. package/tools/track/py/rig.py +0 -508
  562. package/tools/track/py/scene.py +0 -326
  563. package/tools/track/py/test_joint_indices.py +0 -123
  564. package/tools/track/py/test_lr_viterbi.py +0 -195
  565. package/tools/track/py/test_masks.py +0 -229
  566. package/tools/track/py/test_rig.py +0 -288
  567. package/tools/track/py/test_scene.py +0 -354
  568. package/tools/track/py/test_track.py +0 -328
  569. package/tools/track/py/track.py +0 -411
  570. package/tools/track/remote.mjs +0 -140
  571. package/tools/track/rig-dump.mjs +0 -227
  572. package/tools/track/run-box-tests.mjs +0 -14
  573. package/tools/track/run-box.mjs +0 -168
  574. package/tools/track/setup-box.sh +0 -59
  575. package/tools/track/study-2d.mjs +0 -728
  576. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  577. /package/src/{ardy → motion}/ground.js +0 -0
  578. /package/src/{ardy → motion}/ik-drag.js +0 -0
  579. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  580. /package/src/{ardy → motion}/ik.js +0 -0
  581. /package/src/{ardy → motion}/physics-surface.js +0 -0
  582. /package/src/{ardy → motion}/playback-clock.js +0 -0
  583. /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"