framefields 2.0.2

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 (408) hide show
  1. package/LICENCE +202 -0
  2. package/README.md +740 -0
  3. package/dist/audio/index.d.mts +5 -0
  4. package/dist/audio/index.mjs +14 -0
  5. package/dist/audio-Dvwwz8xU.d.mts +37 -0
  6. package/dist/audio-Dvwwz8xU.d.mts.map +1 -0
  7. package/dist/audio-TAvPwzLR.mjs +214 -0
  8. package/dist/audio-TAvPwzLR.mjs.map +1 -0
  9. package/dist/audio-x28ZgQBP.mjs +85 -0
  10. package/dist/audio-x28ZgQBP.mjs.map +1 -0
  11. package/dist/chunk-GMkBE123.mjs +47 -0
  12. package/dist/client-C74hb7fy.mjs +66 -0
  13. package/dist/client-C74hb7fy.mjs.map +1 -0
  14. package/dist/dist-1iUYuPct.mjs +1859 -0
  15. package/dist/dist-1iUYuPct.mjs.map +1 -0
  16. package/dist/dist-Bx6J0VcM.mjs +1859 -0
  17. package/dist/dist-Bx6J0VcM.mjs.map +1 -0
  18. package/dist/dist-C_hjvkdN.mjs +2220 -0
  19. package/dist/dist-C_hjvkdN.mjs.map +1 -0
  20. package/dist/dist-DKm2IyEJ.mjs +94 -0
  21. package/dist/dist-DKm2IyEJ.mjs.map +1 -0
  22. package/dist/dist-cjs-B5EVqDFp.mjs +85 -0
  23. package/dist/dist-cjs-B5EVqDFp.mjs.map +1 -0
  24. package/dist/dist-cjs-BGJkdexX.mjs +437 -0
  25. package/dist/dist-cjs-BGJkdexX.mjs.map +1 -0
  26. package/dist/dist-cjs-BKmGCgtm.mjs +9069 -0
  27. package/dist/dist-cjs-BKmGCgtm.mjs.map +1 -0
  28. package/dist/dist-cjs-BUc0_kg-.mjs +1091 -0
  29. package/dist/dist-cjs-BUc0_kg-.mjs.map +1 -0
  30. package/dist/dist-cjs-BUwe2XhR.mjs +153 -0
  31. package/dist/dist-cjs-BUwe2XhR.mjs.map +1 -0
  32. package/dist/dist-cjs-BWgoUCXJ.mjs +137 -0
  33. package/dist/dist-cjs-BWgoUCXJ.mjs.map +1 -0
  34. package/dist/dist-cjs-CmZEMtO6.mjs +6 -0
  35. package/dist/dist-cjs-D7kEM-Mj.mjs +181 -0
  36. package/dist/dist-cjs-D7kEM-Mj.mjs.map +1 -0
  37. package/dist/dist-cjs-DFUmy89O.mjs +48 -0
  38. package/dist/dist-cjs-DFUmy89O.mjs.map +1 -0
  39. package/dist/dist-cjs-Df7144zM.mjs +209 -0
  40. package/dist/dist-cjs-Df7144zM.mjs.map +1 -0
  41. package/dist/dist-cjs-DgxjcvBN.mjs +98 -0
  42. package/dist/dist-cjs-DgxjcvBN.mjs.map +1 -0
  43. package/dist/dist-cjs-DsMdld8l.mjs +5015 -0
  44. package/dist/dist-cjs-DsMdld8l.mjs.map +1 -0
  45. package/dist/dist-cjs-Dzs5P3K0.mjs +51 -0
  46. package/dist/dist-cjs-Dzs5P3K0.mjs.map +1 -0
  47. package/dist/dist-cjs-ECMuIF57.mjs +323 -0
  48. package/dist/dist-cjs-ECMuIF57.mjs.map +1 -0
  49. package/dist/dist-cjs-lYvO8aDo.mjs +64 -0
  50. package/dist/dist-cjs-lYvO8aDo.mjs.map +1 -0
  51. package/dist/dist-cjs-qEJrwjGk.mjs +66 -0
  52. package/dist/dist-cjs-qEJrwjGk.mjs.map +1 -0
  53. package/dist/dist-cjs-x5cwlqA5.mjs +43 -0
  54. package/dist/dist-cjs-x5cwlqA5.mjs.map +1 -0
  55. package/dist/dist-gjHmorxL.mjs +27479 -0
  56. package/dist/dist-gjHmorxL.mjs.map +1 -0
  57. package/dist/effects/index.d.mts +5 -0
  58. package/dist/effects/index.mjs +35 -0
  59. package/dist/effects-CHWORCYd.mjs +811 -0
  60. package/dist/effects-CHWORCYd.mjs.map +1 -0
  61. package/dist/event-streams-D-APZ7Ke.mjs +204 -0
  62. package/dist/event-streams-D-APZ7Ke.mjs.map +1 -0
  63. package/dist/fonts/index.d.mts +2 -0
  64. package/dist/fonts/index.mjs +3 -0
  65. package/dist/fonts-DrGTBJJk.mjs +188 -0
  66. package/dist/fonts-DrGTBJJk.mjs.map +1 -0
  67. package/dist/index-B0BNPEoH.d.mts +2117 -0
  68. package/dist/index-B0BNPEoH.d.mts.map +1 -0
  69. package/dist/index-BMVVmc4G.d.mts +72 -0
  70. package/dist/index-BMVVmc4G.d.mts.map +1 -0
  71. package/dist/index-D1ZsMpke.d.mts +264 -0
  72. package/dist/index-D1ZsMpke.d.mts.map +1 -0
  73. package/dist/index-D2jDC7O5.d.mts +3643 -0
  74. package/dist/index-D2jDC7O5.d.mts.map +1 -0
  75. package/dist/index-D2wJqSl5.d.mts +114 -0
  76. package/dist/index-D2wJqSl5.d.mts.map +1 -0
  77. package/dist/index-DKFGNyMB.d.mts +309 -0
  78. package/dist/index-DKFGNyMB.d.mts.map +1 -0
  79. package/dist/index-DiSKzDGR.d.mts +65 -0
  80. package/dist/index-DiSKzDGR.d.mts.map +1 -0
  81. package/dist/index.d.mts +4425 -0
  82. package/dist/index.d.mts.map +1 -0
  83. package/dist/index.mjs +2623 -0
  84. package/dist/index.mjs.map +1 -0
  85. package/dist/package-C58KmGKw.mjs +6 -0
  86. package/dist/package-C58KmGKw.mjs.map +1 -0
  87. package/dist/preview/index.d.mts +90 -0
  88. package/dist/preview/index.d.mts.map +1 -0
  89. package/dist/preview/index.mjs +15 -0
  90. package/dist/preview-engine/audio.mjs +1 -0
  91. package/dist/preview-engine/child_process-AIL2NBWS.mjs +1 -0
  92. package/dist/preview-engine/chunk-24P6DAB3.mjs +1 -0
  93. package/dist/preview-engine/chunk-37EFVZWT.mjs +1 -0
  94. package/dist/preview-engine/chunk-3O5OKR6D.mjs +223 -0
  95. package/dist/preview-engine/chunk-47VT677S.mjs +8 -0
  96. package/dist/preview-engine/chunk-4BEJJKEU.mjs +315 -0
  97. package/dist/preview-engine/chunk-4GEWSUX5.mjs +1 -0
  98. package/dist/preview-engine/chunk-55CCFPUX.mjs +237 -0
  99. package/dist/preview-engine/chunk-5JD65JSJ.mjs +110 -0
  100. package/dist/preview-engine/chunk-5UP7FSGT.mjs +294 -0
  101. package/dist/preview-engine/chunk-5XDHHOF2.mjs +367 -0
  102. package/dist/preview-engine/chunk-6YJP57MZ.mjs +729 -0
  103. package/dist/preview-engine/chunk-6ZOXGAYA.mjs +112 -0
  104. package/dist/preview-engine/chunk-AOLAHYVV.mjs +529 -0
  105. package/dist/preview-engine/chunk-B6PLCMBM.mjs +1 -0
  106. package/dist/preview-engine/chunk-BJKH76OM.mjs +264 -0
  107. package/dist/preview-engine/chunk-BUIQ5TUH.mjs +104 -0
  108. package/dist/preview-engine/chunk-BVF64D6H.mjs +1 -0
  109. package/dist/preview-engine/chunk-CA5N7JRZ.mjs +138 -0
  110. package/dist/preview-engine/chunk-CNSHAFEA.mjs +1 -0
  111. package/dist/preview-engine/chunk-CURR5X7D.mjs +1 -0
  112. package/dist/preview-engine/chunk-CVGM2U4J.mjs +47 -0
  113. package/dist/preview-engine/chunk-CYY366IE.mjs +1 -0
  114. package/dist/preview-engine/chunk-D5PHI6ET.mjs +109 -0
  115. package/dist/preview-engine/chunk-D7MZHP2Y.mjs +275 -0
  116. package/dist/preview-engine/chunk-DPPH3NK2.mjs +182 -0
  117. package/dist/preview-engine/chunk-DPST2XLC.mjs +418 -0
  118. package/dist/preview-engine/chunk-DRLEOHN5.mjs +1 -0
  119. package/dist/preview-engine/chunk-EDIZNO3P.mjs +1 -0
  120. package/dist/preview-engine/chunk-EHIIT4ZR.mjs +52 -0
  121. package/dist/preview-engine/chunk-FKZ4XXPE.mjs +1 -0
  122. package/dist/preview-engine/chunk-G3HW4VER.mjs +60 -0
  123. package/dist/preview-engine/chunk-G76HSKOS.mjs +184 -0
  124. package/dist/preview-engine/chunk-GYG2IIBR.mjs +1 -0
  125. package/dist/preview-engine/chunk-H2F5NVQ6.mjs +63 -0
  126. package/dist/preview-engine/chunk-H5BPCY7N.mjs +188 -0
  127. package/dist/preview-engine/chunk-HAXK42HG.mjs +997 -0
  128. package/dist/preview-engine/chunk-HXMDPMLB.mjs +1 -0
  129. package/dist/preview-engine/chunk-HYBAPT6I.mjs +1 -0
  130. package/dist/preview-engine/chunk-I4FH6HYU.mjs +73 -0
  131. package/dist/preview-engine/chunk-IYVN2QEL.mjs +1 -0
  132. package/dist/preview-engine/chunk-JPXQPP6J.mjs +1 -0
  133. package/dist/preview-engine/chunk-JWNHTRED.mjs +164 -0
  134. package/dist/preview-engine/chunk-KZOLCZIV.mjs +103 -0
  135. package/dist/preview-engine/chunk-L62MRHGZ.mjs +89 -0
  136. package/dist/preview-engine/chunk-LEEZXHQO.mjs +117 -0
  137. package/dist/preview-engine/chunk-LKYN3BM2.mjs +1 -0
  138. package/dist/preview-engine/chunk-LP3EP33S.mjs +610 -0
  139. package/dist/preview-engine/chunk-M2GRYRLM.mjs +111 -0
  140. package/dist/preview-engine/chunk-MC7TZZLD.mjs +134 -0
  141. package/dist/preview-engine/chunk-MT2DZQHO.mjs +1 -0
  142. package/dist/preview-engine/chunk-MTJHQATU.mjs +1 -0
  143. package/dist/preview-engine/chunk-N4LHIPHZ.mjs +340 -0
  144. package/dist/preview-engine/chunk-N4PWLBZ7.mjs +5 -0
  145. package/dist/preview-engine/chunk-NQZKBFC6.mjs +40 -0
  146. package/dist/preview-engine/chunk-NWRSVVTF.mjs +186 -0
  147. package/dist/preview-engine/chunk-P3HBP725.mjs +1 -0
  148. package/dist/preview-engine/chunk-PIAJWALT.mjs +1 -0
  149. package/dist/preview-engine/chunk-PJ3DVG45.mjs +1 -0
  150. package/dist/preview-engine/chunk-QP2NI4XD.mjs +82 -0
  151. package/dist/preview-engine/chunk-SA3JUR6D.mjs +52 -0
  152. package/dist/preview-engine/chunk-SRT2OCQW.mjs +1 -0
  153. package/dist/preview-engine/chunk-SWD76532.mjs +98 -0
  154. package/dist/preview-engine/chunk-SXX7HX34.mjs +191 -0
  155. package/dist/preview-engine/chunk-SZ7YRPKO.mjs +3 -0
  156. package/dist/preview-engine/chunk-TMRLZV5X.mjs +1 -0
  157. package/dist/preview-engine/chunk-U446SQP3.mjs +292 -0
  158. package/dist/preview-engine/chunk-UDNCEHUI.mjs +141 -0
  159. package/dist/preview-engine/chunk-V3H7WWOT.mjs +271 -0
  160. package/dist/preview-engine/chunk-V7ZVVUQU.mjs +108 -0
  161. package/dist/preview-engine/chunk-VJGK7MLJ.mjs +3 -0
  162. package/dist/preview-engine/chunk-VSSB73UU.mjs +58 -0
  163. package/dist/preview-engine/chunk-VXUJWPIK.mjs +1 -0
  164. package/dist/preview-engine/chunk-XD4GKD6H.mjs +5 -0
  165. package/dist/preview-engine/chunk-XKO4RYKN.mjs +3 -0
  166. package/dist/preview-engine/chunk-XOEEGC4Z.mjs +1 -0
  167. package/dist/preview-engine/chunk-YAQFFQPG.mjs +1 -0
  168. package/dist/preview-engine/chunk-YKMBRUAE.mjs +1 -0
  169. package/dist/preview-engine/chunk-YSTLMWCG.mjs +1 -0
  170. package/dist/preview-engine/chunk-Z4ERCLNF.mjs +1 -0
  171. package/dist/preview-engine/chunk-ZEYUXJIV.mjs +1 -0
  172. package/dist/preview-engine/chunk-ZX6APG6E.mjs +117 -0
  173. package/dist/preview-engine/effects.mjs +1 -0
  174. package/dist/preview-engine/esbuild-GWV7CM7D.mjs +1 -0
  175. package/dist/preview-engine/fonts.mjs +1 -0
  176. package/dist/preview-engine/framefields.mjs +1959 -0
  177. package/dist/preview-engine/onnxruntime-node-56GSZEH3.mjs +1 -0
  178. package/dist/preview-engine/player.mjs +155 -0
  179. package/dist/preview-engine/renderers-2AUSIYZN.mjs +1 -0
  180. package/dist/preview-engine/renderers-2QN6KZJP.mjs +1 -0
  181. package/dist/preview-engine/renderers-32SKW7ML.mjs +1 -0
  182. package/dist/preview-engine/renderers-5QYK6766.mjs +1 -0
  183. package/dist/preview-engine/renderers-6BJA6MSU.mjs +251 -0
  184. package/dist/preview-engine/renderers-6QAF3LQ2.mjs +1 -0
  185. package/dist/preview-engine/renderers-7AO5746S.mjs +296 -0
  186. package/dist/preview-engine/renderers-AFYKNM6N.mjs +1 -0
  187. package/dist/preview-engine/renderers-AGMH5CRH.mjs +1 -0
  188. package/dist/preview-engine/renderers-AHSGP6LY.mjs +126 -0
  189. package/dist/preview-engine/renderers-ASTBICLA.mjs +1 -0
  190. package/dist/preview-engine/renderers-BHVPLYNN.mjs +1 -0
  191. package/dist/preview-engine/renderers-D7PVQFVU.mjs +1 -0
  192. package/dist/preview-engine/renderers-DB4ABTNA.mjs +1 -0
  193. package/dist/preview-engine/renderers-F2NFJI5L.mjs +1 -0
  194. package/dist/preview-engine/renderers-FI7YHNLY.mjs +1 -0
  195. package/dist/preview-engine/renderers-GBHULMPH.mjs +1 -0
  196. package/dist/preview-engine/renderers-GUKQUMGG.mjs +55 -0
  197. package/dist/preview-engine/renderers-HGT7QDY3.mjs +1 -0
  198. package/dist/preview-engine/renderers-HOYLA7ZK.mjs +1 -0
  199. package/dist/preview-engine/renderers-KKI2D3MZ.mjs +1 -0
  200. package/dist/preview-engine/renderers-KRAJNV4E.mjs +1 -0
  201. package/dist/preview-engine/renderers-KUGZKSVE.mjs +1 -0
  202. package/dist/preview-engine/renderers-L2EQQIOC.mjs +1 -0
  203. package/dist/preview-engine/renderers-LK3NRMGR.mjs +1 -0
  204. package/dist/preview-engine/renderers-LX654SZ3.mjs +1 -0
  205. package/dist/preview-engine/renderers-MCFQFYEL.mjs +1 -0
  206. package/dist/preview-engine/renderers-O6HOYIPS.mjs +1 -0
  207. package/dist/preview-engine/renderers-OEVXCGE2.mjs +1 -0
  208. package/dist/preview-engine/renderers-QVD6C4CS.mjs +1 -0
  209. package/dist/preview-engine/renderers-R3XEYD5V.mjs +248 -0
  210. package/dist/preview-engine/renderers-RBFWJWVM.mjs +1 -0
  211. package/dist/preview-engine/renderers-RX3VLVLO.mjs +1 -0
  212. package/dist/preview-engine/renderers-SM6JP64K.mjs +1 -0
  213. package/dist/preview-engine/renderers-TKMAATVS.mjs +1 -0
  214. package/dist/preview-engine/renderers-UGZUPONY.mjs +1 -0
  215. package/dist/preview-engine/renderers-UTFXZE3G.mjs +1 -0
  216. package/dist/preview-engine/renderers-UWPH7HBH.mjs +1 -0
  217. package/dist/preview-engine/renderers-UZGECFVO.mjs +1 -0
  218. package/dist/preview-engine/renderers-VGU3LNLT.mjs +1 -0
  219. package/dist/preview-engine/renderers-W3VVM5HB.mjs +1 -0
  220. package/dist/preview-engine/renderers-WJUTT5BB.mjs +1 -0
  221. package/dist/preview-engine/renderers-XA2M4YXK.mjs +1 -0
  222. package/dist/preview-engine/renderers-XIHRFEFQ.mjs +1 -0
  223. package/dist/preview-engine/renderers-XJTA5MFG.mjs +1 -0
  224. package/dist/preview-engine/renderers-XLTY5APF.mjs +1 -0
  225. package/dist/preview-engine/renderers-XZ3VXFGZ.mjs +1 -0
  226. package/dist/preview-engine/renderers-YKUQUCTO.mjs +124 -0
  227. package/dist/preview-engine/renderers-YVN3EIOP.mjs +1 -0
  228. package/dist/preview-engine/renderers-YWRP23SZ.mjs +125 -0
  229. package/dist/preview-engine/renderers-ZPWOUWZJ.mjs +120 -0
  230. package/dist/preview-engine/resvg-js-VMU4A77S.mjs +1 -0
  231. package/dist/preview-engine/server-utils-2FAIV45N.mjs +1 -0
  232. package/dist/preview-engine/sharp-V6ZTBNDW.mjs +1 -0
  233. package/dist/preview-engine/shims/fs-promises.mjs +1 -0
  234. package/dist/preview-engine/shims/fs.mjs +1 -0
  235. package/dist/preview-engine/shims/module.mjs +1 -0
  236. package/dist/preview-engine/shims/os.mjs +1 -0
  237. package/dist/preview-engine/shims/path.mjs +1 -0
  238. package/dist/preview-engine/shims/url.mjs +1 -0
  239. package/dist/preview-engine/signals.mjs +1 -0
  240. package/dist/preview-engine/webgpu-NSSANM4F.mjs +1 -0
  241. package/dist/preview-uQM210iy.mjs +2149 -0
  242. package/dist/preview-uQM210iy.mjs.map +1 -0
  243. package/dist/program-Dl510wmI.mjs +5183 -0
  244. package/dist/program-Dl510wmI.mjs.map +1 -0
  245. package/dist/renderer/index.d.mts +3 -0
  246. package/dist/renderer/index.mjs +6 -0
  247. package/dist/renderer-3BNskNuJ.mjs +3 -0
  248. package/dist/renderer-7DOcIxFG.mjs +288 -0
  249. package/dist/renderer-7DOcIxFG.mjs.map +1 -0
  250. package/dist/renderer-9dHlMQGX.mjs +3 -0
  251. package/dist/renderer-B1CvhW4p.mjs +677 -0
  252. package/dist/renderer-B1CvhW4p.mjs.map +1 -0
  253. package/dist/renderer-B2XzjK0z.mjs +3 -0
  254. package/dist/renderer-B80s47yo.mjs +166 -0
  255. package/dist/renderer-B80s47yo.mjs.map +1 -0
  256. package/dist/renderer-B9-QSJcG.mjs +479 -0
  257. package/dist/renderer-B9-QSJcG.mjs.map +1 -0
  258. package/dist/renderer-BAQmR0Zo.mjs +3 -0
  259. package/dist/renderer-BDLnExoP.mjs +127 -0
  260. package/dist/renderer-BDLnExoP.mjs.map +1 -0
  261. package/dist/renderer-BGXU6vcW.mjs +270 -0
  262. package/dist/renderer-BGXU6vcW.mjs.map +1 -0
  263. package/dist/renderer-BGvORwkQ.mjs +3 -0
  264. package/dist/renderer-BLikd6BK.mjs +3 -0
  265. package/dist/renderer-BMovYRoR.mjs +3 -0
  266. package/dist/renderer-BQHbp-Sp.mjs +243 -0
  267. package/dist/renderer-BQHbp-Sp.mjs.map +1 -0
  268. package/dist/renderer-BREIjsUU.mjs +252 -0
  269. package/dist/renderer-BREIjsUU.mjs.map +1 -0
  270. package/dist/renderer-BSbTJBWc.mjs +3 -0
  271. package/dist/renderer-BTu2EVhF.mjs +3 -0
  272. package/dist/renderer-BU1rwNRT.mjs +3 -0
  273. package/dist/renderer-BXMmISWG.mjs +499 -0
  274. package/dist/renderer-BXMmISWG.mjs.map +1 -0
  275. package/dist/renderer-BZJm3m4f.mjs +3 -0
  276. package/dist/renderer-B_NuVaIP.mjs +744 -0
  277. package/dist/renderer-B_NuVaIP.mjs.map +1 -0
  278. package/dist/renderer-BfJSUjPN.mjs +3 -0
  279. package/dist/renderer-BqyAgNt3.mjs +3 -0
  280. package/dist/renderer-BryTM2g7.mjs +3 -0
  281. package/dist/renderer-BsgIcBKH.mjs +391 -0
  282. package/dist/renderer-BsgIcBKH.mjs.map +1 -0
  283. package/dist/renderer-ByDhfrPE.mjs +422 -0
  284. package/dist/renderer-ByDhfrPE.mjs.map +1 -0
  285. package/dist/renderer-C-H680_l.mjs +3 -0
  286. package/dist/renderer-C17wL9QX.mjs +181 -0
  287. package/dist/renderer-C17wL9QX.mjs.map +1 -0
  288. package/dist/renderer-C2n-EHxg.mjs +1 -0
  289. package/dist/renderer-CB5aWAaf.mjs +343 -0
  290. package/dist/renderer-CB5aWAaf.mjs.map +1 -0
  291. package/dist/renderer-CB7lH5K8.mjs +3 -0
  292. package/dist/renderer-CDLrNgfo.mjs +3 -0
  293. package/dist/renderer-CDOUAN7X.mjs +454 -0
  294. package/dist/renderer-CDOUAN7X.mjs.map +1 -0
  295. package/dist/renderer-CH8sdfYX.mjs +387 -0
  296. package/dist/renderer-CH8sdfYX.mjs.map +1 -0
  297. package/dist/renderer-CIvaRnP_.mjs +261 -0
  298. package/dist/renderer-CIvaRnP_.mjs.map +1 -0
  299. package/dist/renderer-CMzEjdzi.mjs +369 -0
  300. package/dist/renderer-CMzEjdzi.mjs.map +1 -0
  301. package/dist/renderer-COkcU3db.mjs +3302 -0
  302. package/dist/renderer-COkcU3db.mjs.map +1 -0
  303. package/dist/renderer-CQCoggIa.mjs +3 -0
  304. package/dist/renderer-CQdVj2JS.mjs +3 -0
  305. package/dist/renderer-CQiTodg9.mjs +337 -0
  306. package/dist/renderer-CQiTodg9.mjs.map +1 -0
  307. package/dist/renderer-CV2m6k1V.mjs +3 -0
  308. package/dist/renderer-CYY-poLe.mjs +3 -0
  309. package/dist/renderer-CZgB5st8.mjs +3 -0
  310. package/dist/renderer-C_Ki91kc.mjs +3 -0
  311. package/dist/renderer-CclIvsLO.mjs +3 -0
  312. package/dist/renderer-Cj-whdVr.mjs +655 -0
  313. package/dist/renderer-Cj-whdVr.mjs.map +1 -0
  314. package/dist/renderer-CkykdR_D.mjs +3 -0
  315. package/dist/renderer-Cl1oUXMi.mjs +1143 -0
  316. package/dist/renderer-Cl1oUXMi.mjs.map +1 -0
  317. package/dist/renderer-CmApuKjS.mjs +3 -0
  318. package/dist/renderer-CpkiHRhh.mjs +269 -0
  319. package/dist/renderer-CpkiHRhh.mjs.map +1 -0
  320. package/dist/renderer-CszOjcRW.mjs +3 -0
  321. package/dist/renderer-CtVkU_GA.mjs +44 -0
  322. package/dist/renderer-CtVkU_GA.mjs.map +1 -0
  323. package/dist/renderer-Cu074POw.mjs +3 -0
  324. package/dist/renderer-CwQhzcUn.mjs +644 -0
  325. package/dist/renderer-CwQhzcUn.mjs.map +1 -0
  326. package/dist/renderer-CzPWB0Ss.mjs +273 -0
  327. package/dist/renderer-CzPWB0Ss.mjs.map +1 -0
  328. package/dist/renderer-D4qLmox-.mjs +3 -0
  329. package/dist/renderer-D6bWQtr-.mjs +3 -0
  330. package/dist/renderer-D6cJ6MSq.mjs +3 -0
  331. package/dist/renderer-D7EX8IjU.mjs +240 -0
  332. package/dist/renderer-D7EX8IjU.mjs.map +1 -0
  333. package/dist/renderer-DDp7_TUi.d.mts +10 -0
  334. package/dist/renderer-DDp7_TUi.d.mts.map +1 -0
  335. package/dist/renderer-DFKsLrUs.mjs +256 -0
  336. package/dist/renderer-DFKsLrUs.mjs.map +1 -0
  337. package/dist/renderer-DPSsf8-t.mjs +3 -0
  338. package/dist/renderer-DQLGnEFc.mjs +3 -0
  339. package/dist/renderer-DY2SVYUW.mjs +3 -0
  340. package/dist/renderer-DZGtBUCx.mjs +792 -0
  341. package/dist/renderer-DZGtBUCx.mjs.map +1 -0
  342. package/dist/renderer-DabG62XS.mjs +406 -0
  343. package/dist/renderer-DabG62XS.mjs.map +1 -0
  344. package/dist/renderer-DbaQLI4z.mjs +771 -0
  345. package/dist/renderer-DbaQLI4z.mjs.map +1 -0
  346. package/dist/renderer-DjI45OYC.mjs +71 -0
  347. package/dist/renderer-DjI45OYC.mjs.map +1 -0
  348. package/dist/renderer-Dm08-hAU.mjs +323 -0
  349. package/dist/renderer-Dm08-hAU.mjs.map +1 -0
  350. package/dist/renderer-DoAY5JbB.mjs +4 -0
  351. package/dist/renderer-Dp_FfvF-.mjs +401 -0
  352. package/dist/renderer-Dp_FfvF-.mjs.map +1 -0
  353. package/dist/renderer-Dt6fzgX5.mjs +756 -0
  354. package/dist/renderer-Dt6fzgX5.mjs.map +1 -0
  355. package/dist/renderer-DuplLRbp.mjs +3 -0
  356. package/dist/renderer-DvfUbZ31.mjs +3 -0
  357. package/dist/renderer-DxjVQV6f.mjs +311 -0
  358. package/dist/renderer-DxjVQV6f.mjs.map +1 -0
  359. package/dist/renderer-Fco4EmAu.mjs +23868 -0
  360. package/dist/renderer-Fco4EmAu.mjs.map +1 -0
  361. package/dist/renderer-HFfcJlmR.mjs +981 -0
  362. package/dist/renderer-HFfcJlmR.mjs.map +1 -0
  363. package/dist/renderer-I-Sw3JBB.mjs +145 -0
  364. package/dist/renderer-I-Sw3JBB.mjs.map +1 -0
  365. package/dist/renderer-OWQmmg4Z.mjs +697 -0
  366. package/dist/renderer-OWQmmg4Z.mjs.map +1 -0
  367. package/dist/renderer-Q_uUQipO.mjs +3 -0
  368. package/dist/renderer-Ryo2CEFD.mjs +586 -0
  369. package/dist/renderer-Ryo2CEFD.mjs.map +1 -0
  370. package/dist/renderer-Tp6OXWNZ.mjs +257 -0
  371. package/dist/renderer-Tp6OXWNZ.mjs.map +1 -0
  372. package/dist/renderer-UQFCgeNx.mjs +3 -0
  373. package/dist/renderer-WkVrkCVA.mjs +83 -0
  374. package/dist/renderer-WkVrkCVA.mjs.map +1 -0
  375. package/dist/renderer-XcmYTd6P.mjs +3818 -0
  376. package/dist/renderer-XcmYTd6P.mjs.map +1 -0
  377. package/dist/renderer-_uvdg48N.mjs +333 -0
  378. package/dist/renderer-_uvdg48N.mjs.map +1 -0
  379. package/dist/renderer-bZU_ndxf.mjs +8 -0
  380. package/dist/renderer-bZU_ndxf.mjs.map +1 -0
  381. package/dist/renderer-eMsnzJg9.mjs +3 -0
  382. package/dist/renderer-ho_jXJ0H.mjs +254 -0
  383. package/dist/renderer-ho_jXJ0H.mjs.map +1 -0
  384. package/dist/renderer-k2JL79kM.mjs +306 -0
  385. package/dist/renderer-k2JL79kM.mjs.map +1 -0
  386. package/dist/renderer-pHiz78e0.mjs +3 -0
  387. package/dist/renderer-pxXf9dvk.mjs +394 -0
  388. package/dist/renderer-pxXf9dvk.mjs.map +1 -0
  389. package/dist/renderer-vCFTm0oe.mjs +226 -0
  390. package/dist/renderer-vCFTm0oe.mjs.map +1 -0
  391. package/dist/renderer-vvpdx1vk.mjs +379 -0
  392. package/dist/renderer-vvpdx1vk.mjs.map +1 -0
  393. package/dist/renderer-x2s9LmVL.mjs +1121 -0
  394. package/dist/renderer-x2s9LmVL.mjs.map +1 -0
  395. package/dist/renderers-BWg16W-V-BXG_cjhM.mjs +49 -0
  396. package/dist/renderers-BWg16W-V-BXG_cjhM.mjs.map +1 -0
  397. package/dist/signals/index.d.mts +7 -0
  398. package/dist/signals/index.mjs +10 -0
  399. package/dist/signals-BwbdO3AN.mjs +224 -0
  400. package/dist/signals-BwbdO3AN.mjs.map +1 -0
  401. package/dist/signin-CZj4Oc5s.mjs +865 -0
  402. package/dist/signin-CZj4Oc5s.mjs.map +1 -0
  403. package/dist/sso-oidc-C0_31ThY.mjs +1051 -0
  404. package/dist/sso-oidc-C0_31ThY.mjs.map +1 -0
  405. package/dist/sts-DD4ggEXC.mjs +6 -0
  406. package/dist/sts-nXw8RDJI.mjs +1395 -0
  407. package/dist/sts-nXw8RDJI.mjs.map +1 -0
  408. package/package.json +173 -0
@@ -0,0 +1,3818 @@
1
+ import { At as programmaticSignal, vt as frameSignal } from "./dist-Bx6J0VcM.mjs";
2
+ import { n as defineRenderer } from "./renderer-CtVkU_GA.mjs";
3
+ import { a as PoseSkeletonComputePipeline } from "./dist-C_hjvkdN.mjs";
4
+ import { existsSync, mkdirSync, statSync } from "node:fs";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { readFile, rename, unlink, writeFile } from "node:fs/promises";
7
+ import { createHash } from "node:crypto";
8
+ import { homedir } from "node:os";
9
+
10
+ //#region ../vision/dist/registry-CSFimt7C.mjs
11
+ const hf = (repo, revision, path$1) => `https://huggingface.co/${repo}/resolve/${revision}/${path$1}`;
12
+ const RTMDET_INS_REPO = "tori29umai/rtmdet-ins-onnx-person-masks";
13
+ const RTMDET_INS_REV = "e5218e8d47d7c50077f27f9893a28e0ef2ad5656";
14
+ const RTMDET_INS_SOURCE = "RTMDet-Ins (OpenMMLab mmdetection, Apache-2.0), mmdeploy ONNX export";
15
+ const RTMO_SOURCE = "RTMO (OpenMMLab mmpose, Apache-2.0), ONNX export";
16
+ const SELFIE_SOURCE = "MediaPipe Selfie Segmenter (Google, Apache-2.0), ONNX export";
17
+ const VISION_MODELS = {
18
+ "rtmdet-ins-t": {
19
+ key: "rtmdet-ins-t",
20
+ family: "rtmdet-ins",
21
+ filename: "rtmdet-ins-t.onnx",
22
+ url: hf(RTMDET_INS_REPO, RTMDET_INS_REV, "models/tiny/rtmdet-ins_tiny_640x640.onnx"),
23
+ bytes: 24032356,
24
+ sha256: "99ee670820c4493aa13e20578b6a8e39781c319846dc41796a77765734beac5c",
25
+ input: [640, 640],
26
+ source: RTMDET_INS_SOURCE
27
+ },
28
+ "rtmdet-ins-s": {
29
+ key: "rtmdet-ins-s",
30
+ family: "rtmdet-ins",
31
+ filename: "rtmdet-ins-s.onnx",
32
+ url: hf(RTMDET_INS_REPO, RTMDET_INS_REV, "models/s/rtmdet-ins_s_640x640.onnx"),
33
+ bytes: 43236976,
34
+ sha256: "9cd1787fbf3eb2bd64cc2d8154f41c036f5484b85599e1cbe0227a2dc5a4a08b",
35
+ input: [640, 640],
36
+ source: RTMDET_INS_SOURCE
37
+ },
38
+ "rtmdet-ins-m": {
39
+ key: "rtmdet-ins-m",
40
+ family: "rtmdet-ins",
41
+ filename: "rtmdet-ins-m.onnx",
42
+ url: hf(RTMDET_INS_REPO, RTMDET_INS_REV, "models/m/rtmdet-ins_m_640x640.onnx"),
43
+ bytes: 115735979,
44
+ sha256: "a003bce80316e03467c2c2b483df2d5c0398220c2357a44f9bf985929f7eb052",
45
+ input: [640, 640],
46
+ source: RTMDET_INS_SOURCE
47
+ },
48
+ "rtmo-t": {
49
+ key: "rtmo-t",
50
+ family: "rtmo",
51
+ filename: "rtmo-t.onnx",
52
+ url: hf("Xenova/RTMO-t", "f29d13ce5ee291fbe505e9d06a049992f05699dc", "onnx/model.onnx"),
53
+ bytes: 27360569,
54
+ sha256: "cb54dd042bf997df86d938721c94238940f2bff98ba8d2ccc834430de5c65c13",
55
+ input: [416, 416],
56
+ source: RTMO_SOURCE
57
+ },
58
+ "rtmo-s": {
59
+ key: "rtmo-s",
60
+ family: "rtmo",
61
+ filename: "rtmo-s.onnx",
62
+ url: hf("Xenova/RTMO-s", "d8c526187f341d287753831c9c8b1ecc4855bba1", "onnx/model.onnx"),
63
+ bytes: 39636400,
64
+ sha256: "1cd6a3517658903233e9ccb78666554862f57f0944b6886e2048093594aef4e7",
65
+ input: [640, 640],
66
+ source: RTMO_SOURCE
67
+ },
68
+ "rtmo-m": {
69
+ key: "rtmo-m",
70
+ family: "rtmo",
71
+ filename: "rtmo-m.onnx",
72
+ url: hf("Xenova/RTMO-m", "3aba1280472b98ee2bf663482e27e243038d23fe", "onnx/model.onnx"),
73
+ bytes: 89291929,
74
+ sha256: "76d82c45e5c4810baf587ecf2638d15cbf7ed196afb2055e977382532021b59e",
75
+ input: [640, 640],
76
+ source: RTMO_SOURCE
77
+ },
78
+ "selfie-square": {
79
+ key: "selfie-square",
80
+ family: "selfie",
81
+ filename: "selfie-square.onnx",
82
+ url: hf("onnx-community/mediapipe_selfie_segmentation", "be49485c8e027524be38591817fc5cd31bd9d00e", "onnx/model.onnx"),
83
+ bytes: 462352,
84
+ sha256: "3241ac4ad8aa35bdaf33946776db29f7c283a413aa0b0dacb9483594b4531aad",
85
+ input: [256, 256],
86
+ source: SELFIE_SOURCE
87
+ },
88
+ "selfie-landscape": {
89
+ key: "selfie-landscape",
90
+ family: "selfie",
91
+ filename: "selfie-landscape.onnx",
92
+ url: hf("onnx-community/mediapipe_selfie_segmentation_landscape", "2497d5bec26c626c7b3c4edc6e1fefc21b64f6c3", "onnx/model.onnx"),
93
+ bytes: 462338,
94
+ sha256: "7a0adcfdb1715d3b0ff0f61486d8a181c33f4a208343a5abfccbc6540e872d24",
95
+ input: [256, 144],
96
+ source: SELFIE_SOURCE
97
+ }
98
+ };
99
+ /**
100
+ * Resolves the model for a task. `detect` and `segment` share RTMDet-Ins (one forward pass
101
+ * yields both); `matte` picks the landscape Selfie Segmenter for wide frames.
102
+ */
103
+ function modelKeyFor(task, variant = "s", aspect = 1) {
104
+ if (task === "pose") return `rtmo-${variant}`;
105
+ if (task === "matte") return aspect > 1.3 ? "selfie-landscape" : "selfie-square";
106
+ return `rtmdet-ins-${variant}`;
107
+ }
108
+ /** COCO 80 class names — index order matches the RTMDet-Ins `labels` output. */
109
+ const COCO_CLASSES = [
110
+ "person",
111
+ "bicycle",
112
+ "car",
113
+ "motorcycle",
114
+ "airplane",
115
+ "bus",
116
+ "train",
117
+ "truck",
118
+ "boat",
119
+ "traffic light",
120
+ "fire hydrant",
121
+ "stop sign",
122
+ "parking meter",
123
+ "bench",
124
+ "bird",
125
+ "cat",
126
+ "dog",
127
+ "horse",
128
+ "sheep",
129
+ "cow",
130
+ "elephant",
131
+ "bear",
132
+ "zebra",
133
+ "giraffe",
134
+ "backpack",
135
+ "umbrella",
136
+ "handbag",
137
+ "tie",
138
+ "suitcase",
139
+ "frisbee",
140
+ "skis",
141
+ "snowboard",
142
+ "sports ball",
143
+ "kite",
144
+ "baseball bat",
145
+ "baseball glove",
146
+ "skateboard",
147
+ "surfboard",
148
+ "tennis racket",
149
+ "bottle",
150
+ "wine glass",
151
+ "cup",
152
+ "fork",
153
+ "knife",
154
+ "spoon",
155
+ "bowl",
156
+ "banana",
157
+ "apple",
158
+ "sandwich",
159
+ "orange",
160
+ "broccoli",
161
+ "carrot",
162
+ "hot dog",
163
+ "pizza",
164
+ "donut",
165
+ "cake",
166
+ "chair",
167
+ "couch",
168
+ "potted plant",
169
+ "bed",
170
+ "dining table",
171
+ "toilet",
172
+ "tv",
173
+ "laptop",
174
+ "mouse",
175
+ "remote",
176
+ "keyboard",
177
+ "cell phone",
178
+ "microwave",
179
+ "oven",
180
+ "toaster",
181
+ "sink",
182
+ "refrigerator",
183
+ "book",
184
+ "clock",
185
+ "vase",
186
+ "scissors",
187
+ "teddy bear",
188
+ "hair drier",
189
+ "toothbrush"
190
+ ];
191
+
192
+ //#endregion
193
+ //#region ../vision/dist/src-DbPyIIat.mjs
194
+ function computeIoU(boxA, boxB) {
195
+ const xA = Math.max(boxA.originX, boxB.originX);
196
+ const yA = Math.max(boxA.originY, boxB.originY);
197
+ const xB = Math.min(boxA.originX + boxA.width, boxB.originX + boxB.width);
198
+ const yB = Math.min(boxA.originY + boxA.height, boxB.originY + boxB.height);
199
+ const interArea = Math.max(0, xB - xA) * Math.max(0, yB - yA);
200
+ if (interArea <= 0) return 0;
201
+ const unionArea = boxA.width * boxA.height + boxB.width * boxB.height - interArea;
202
+ return unionArea > 0 ? interArea / unionArea : 0;
203
+ }
204
+ var TemporalObjectTracker = class {
205
+ _nextTrackId = 1;
206
+ _tracks = [];
207
+ _iouThreshold;
208
+ _maxMissedFrames;
209
+ _minHits;
210
+ _positionSmoothing;
211
+ _activeDuringCoast;
212
+ constructor(options = {}) {
213
+ this._iouThreshold = options.iouThreshold ?? .25;
214
+ this._maxMissedFrames = options.maxMissedFrames ?? 15;
215
+ this._minHits = options.minHits ?? 1;
216
+ this._positionSmoothing = Math.max(.1, Math.min(1, options.positionSmoothing ?? .75));
217
+ this._activeDuringCoast = options.activeDuringCoast !== false;
218
+ }
219
+ /**
220
+ * Updates the multi-object tracker with new detections for the current frame.
221
+ *
222
+ * @param detections Raw detections found on the current frame
223
+ * @param frame Current frame index
224
+ * @param fps Video framerate (used for physical velocity estimation)
225
+ * @returns Stable, temporal list of TrackedObjects
226
+ */
227
+ update(detections, _frame, fps = 24) {
228
+ const dt = fps > 0 ? 1 / fps : .0416;
229
+ for (const track of this._tracks) {
230
+ track.age++;
231
+ track.timeSinceUpdate++;
232
+ if (track.timeSinceUpdate > 0) {
233
+ const predCenterX = track.centerX + track.vx * dt;
234
+ const predCenterY = track.centerY + track.vy * dt;
235
+ track.centerX = predCenterX;
236
+ track.centerY = predCenterY;
237
+ const w = track.box.width;
238
+ const h = track.box.height;
239
+ const normW = track.box.normalizedWidth;
240
+ const normH = track.box.normalizedHeight;
241
+ const normCenterX = track.box.normalizedX + normW / 2 + track.vx * dt / Math.max(1, track.box.width / normW);
242
+ const normCenterY = track.box.normalizedY + normH / 2 + track.vy * dt / Math.max(1, track.box.height / normH);
243
+ track.box = {
244
+ originX: predCenterX - w / 2,
245
+ originY: predCenterY - h / 2,
246
+ width: w,
247
+ height: h,
248
+ normalizedX: normCenterX - normW / 2,
249
+ normalizedY: normCenterY - normH / 2,
250
+ normalizedWidth: normW,
251
+ normalizedHeight: normH
252
+ };
253
+ track.vx *= .92;
254
+ track.vy *= .92;
255
+ track.isCoasting = true;
256
+ }
257
+ }
258
+ const matchedDetections = /* @__PURE__ */ new Set();
259
+ const matchedTracks = /* @__PURE__ */ new Set();
260
+ const candidateMatches = [];
261
+ for (let t = 0; t < this._tracks.length; t++) {
262
+ const trk = this._tracks[t];
263
+ for (let d = 0; d < detections.length; d++) {
264
+ const det = detections[d];
265
+ if (trk.category !== det.category) continue;
266
+ const iou = computeIoU(trk.box, det.boundingBox);
267
+ if (iou >= this._iouThreshold) candidateMatches.push({
268
+ trackIdx: t,
269
+ detIdx: d,
270
+ iou
271
+ });
272
+ }
273
+ }
274
+ candidateMatches.sort((a, b) => b.iou - a.iou);
275
+ for (const match of candidateMatches) {
276
+ if (matchedTracks.has(match.trackIdx) || matchedDetections.has(match.detIdx)) continue;
277
+ matchedTracks.add(match.trackIdx);
278
+ matchedDetections.add(match.detIdx);
279
+ const trk = this._tracks[match.trackIdx];
280
+ const det = detections[match.detIdx];
281
+ const detBox = det.boundingBox;
282
+ const detCenterX = detBox.originX + detBox.width / 2;
283
+ const detCenterY = detBox.originY + detBox.height / 2;
284
+ const instVx = (detCenterX - trk.centerX) / dt;
285
+ const instVy = (detCenterY - trk.centerY) / dt;
286
+ trk.vx = trk.vx * .4 + instVx * .6;
287
+ trk.vy = trk.vy * .4 + instVy * .6;
288
+ const alpha = this._positionSmoothing;
289
+ const smoothW = trk.box.width * (1 - alpha) + detBox.width * alpha;
290
+ const smoothH = trk.box.height * (1 - alpha) + detBox.height * alpha;
291
+ const smoothCenterX = trk.centerX * (1 - alpha) + detCenterX * alpha;
292
+ const smoothCenterY = trk.centerY * (1 - alpha) + detCenterY * alpha;
293
+ trk.centerX = smoothCenterX;
294
+ trk.centerY = smoothCenterY;
295
+ trk.score = det.score;
296
+ trk.timeSinceUpdate = 0;
297
+ trk.hits++;
298
+ trk.isCoasting = false;
299
+ if (trk.hits >= this._minHits) trk.active = true;
300
+ trk.box = {
301
+ originX: smoothCenterX - smoothW / 2,
302
+ originY: smoothCenterY - smoothH / 2,
303
+ width: smoothW,
304
+ height: smoothH,
305
+ normalizedX: detBox.normalizedX,
306
+ normalizedY: detBox.normalizedY,
307
+ normalizedWidth: detBox.normalizedWidth,
308
+ normalizedHeight: detBox.normalizedHeight
309
+ };
310
+ }
311
+ for (let d = 0; d < detections.length; d++) {
312
+ if (matchedDetections.has(d)) continue;
313
+ const det = detections[d];
314
+ const detBox = det.boundingBox;
315
+ const centerX = detBox.originX + detBox.width / 2;
316
+ const centerY = detBox.originY + detBox.height / 2;
317
+ this._tracks.push({
318
+ trackId: this._nextTrackId++,
319
+ category: det.category,
320
+ score: det.score,
321
+ box: detBox,
322
+ centerX,
323
+ centerY,
324
+ vx: 0,
325
+ vy: 0,
326
+ age: 1,
327
+ hits: 1,
328
+ timeSinceUpdate: 0,
329
+ active: this._minHits <= 1,
330
+ isCoasting: false
331
+ });
332
+ }
333
+ this._tracks = this._tracks.filter((t) => t.timeSinceUpdate <= this._maxMissedFrames);
334
+ return this._tracks.filter((t) => t.active).map((t) => {
335
+ const normW = t.box.normalizedWidth;
336
+ const normH = t.box.normalizedHeight;
337
+ const normCenterX = t.box.normalizedX + normW / 2;
338
+ const normCenterY = t.box.normalizedY + normH / 2;
339
+ const speed = Math.sqrt(t.vx * t.vx + t.vy * t.vy);
340
+ return {
341
+ trackId: t.trackId,
342
+ category: t.category,
343
+ score: t.score,
344
+ boundingBox: t.box,
345
+ centerX: t.centerX,
346
+ centerY: t.centerY,
347
+ normalizedCenterX: normCenterX,
348
+ normalizedCenterY: normCenterY,
349
+ velocity: {
350
+ vx: t.vx,
351
+ vy: t.vy
352
+ },
353
+ speed,
354
+ age: t.age,
355
+ hits: t.hits,
356
+ active: this._activeDuringCoast ? true : t.timeSinceUpdate === 0,
357
+ isCoasting: t.isCoasting
358
+ };
359
+ });
360
+ }
361
+ /**
362
+ * Tracks objects across a full sequence of frames with gap interpolation,
363
+ * boundary extrapolation (ensuring frame 0 through end are tracked),
364
+ * and temporal Gaussian smoothing.
365
+ *
366
+ * Guarantees zero flicker, zero drift, and frame-accurate stability across the entire video.
367
+ */
368
+ trackSequence(perFrameDetections, totalFrames, fps = 24) {
369
+ this.reset();
370
+ const dt = fps > 0 ? 1 / fps : .0416;
371
+ const trackHistory = /* @__PURE__ */ new Map();
372
+ for (let f = 0; f < totalFrames; f++) {
373
+ const dets = perFrameDetections[f] ?? [];
374
+ const stepTracks = this.update(dets, f, fps);
375
+ for (const trk of stepTracks) {
376
+ if (!trackHistory.has(trk.trackId)) trackHistory.set(trk.trackId, {
377
+ category: trk.category,
378
+ observations: []
379
+ });
380
+ if (!trk.isCoasting) trackHistory.get(trk.trackId).observations.push({
381
+ frame: f,
382
+ box: trk.boundingBox,
383
+ score: trk.score
384
+ });
385
+ }
386
+ }
387
+ const fullTrajectories = /* @__PURE__ */ new Map();
388
+ for (const [trackId, info] of trackHistory.entries()) {
389
+ const obs = info.observations;
390
+ if (obs.length === 0) continue;
391
+ obs.sort((a, b) => a.frame - b.frame);
392
+ const frameData = new Array(totalFrames).fill(null);
393
+ for (const o of obs) frameData[o.frame] = {
394
+ box: o.box,
395
+ score: o.score
396
+ };
397
+ const firstObs = obs[0];
398
+ for (let f = 0; f < firstObs.frame; f++) frameData[f] = {
399
+ box: { ...firstObs.box },
400
+ score: firstObs.score * .9
401
+ };
402
+ for (let i = 0; i < obs.length - 1; i++) {
403
+ const startObs = obs[i];
404
+ const endObs = obs[i + 1];
405
+ const gap = endObs.frame - startObs.frame;
406
+ if (gap <= 1) continue;
407
+ for (let f = startObs.frame + 1; f < endObs.frame; f++) {
408
+ const t = (f - startObs.frame) / gap;
409
+ frameData[f] = {
410
+ box: {
411
+ originX: startObs.box.originX + (endObs.box.originX - startObs.box.originX) * t,
412
+ originY: startObs.box.originY + (endObs.box.originY - startObs.box.originY) * t,
413
+ width: startObs.box.width + (endObs.box.width - startObs.box.width) * t,
414
+ height: startObs.box.height + (endObs.box.height - startObs.box.height) * t,
415
+ normalizedX: startObs.box.normalizedX + (endObs.box.normalizedX - startObs.box.normalizedX) * t,
416
+ normalizedY: startObs.box.normalizedY + (endObs.box.normalizedY - startObs.box.normalizedY) * t,
417
+ normalizedWidth: startObs.box.normalizedWidth + (endObs.box.normalizedWidth - startObs.box.normalizedWidth) * t,
418
+ normalizedHeight: startObs.box.normalizedHeight + (endObs.box.normalizedHeight - startObs.box.normalizedHeight) * t
419
+ },
420
+ score: startObs.score * (1 - t) + endObs.score * t
421
+ };
422
+ }
423
+ }
424
+ const lastObs = obs[obs.length - 1];
425
+ for (let f = lastObs.frame + 1; f < totalFrames; f++) frameData[f] = {
426
+ box: { ...lastObs.box },
427
+ score: lastObs.score * .9
428
+ };
429
+ const smoothedFrames = [];
430
+ const radius = 2;
431
+ const weights = [
432
+ .06136,
433
+ .24477,
434
+ .38774,
435
+ .24477,
436
+ .06136
437
+ ];
438
+ for (let f = 0; f < totalFrames; f++) {
439
+ let sumWeight = 0;
440
+ let sumX = 0;
441
+ let sumY = 0;
442
+ let sumW = 0;
443
+ let sumH = 0;
444
+ let sumNormX = 0;
445
+ let sumNormY = 0;
446
+ let sumNormW = 0;
447
+ let sumNormH = 0;
448
+ let sumScore = 0;
449
+ for (let r = -radius; r <= radius; r++) {
450
+ const pt = frameData[Math.max(0, Math.min(totalFrames - 1, f + r))];
451
+ if (pt) {
452
+ const w = weights[r + radius];
453
+ sumWeight += w;
454
+ sumX += pt.box.originX * w;
455
+ sumY += pt.box.originY * w;
456
+ sumW += pt.box.width * w;
457
+ sumH += pt.box.height * w;
458
+ sumNormX += pt.box.normalizedX * w;
459
+ sumNormY += pt.box.normalizedY * w;
460
+ sumNormW += pt.box.normalizedWidth * w;
461
+ sumNormH += pt.box.normalizedHeight * w;
462
+ sumScore += pt.score * w;
463
+ }
464
+ }
465
+ const invW = sumWeight > 0 ? 1 / sumWeight : 1;
466
+ smoothedFrames.push({
467
+ box: {
468
+ originX: sumX * invW,
469
+ originY: sumY * invW,
470
+ width: sumW * invW,
471
+ height: sumH * invW,
472
+ normalizedX: sumNormX * invW,
473
+ normalizedY: sumNormY * invW,
474
+ normalizedWidth: sumNormW * invW,
475
+ normalizedHeight: sumNormH * invW
476
+ },
477
+ score: sumScore * invW
478
+ });
479
+ }
480
+ fullTrajectories.set(trackId, {
481
+ category: info.category,
482
+ frames: smoothedFrames
483
+ });
484
+ }
485
+ const result = [];
486
+ for (let f = 0; f < totalFrames; f++) {
487
+ const frameObjects = [];
488
+ for (const [trackId, traj] of fullTrajectories.entries()) {
489
+ const current = traj.frames[f];
490
+ if (!current) continue;
491
+ const prev = traj.frames[Math.max(0, f - 1)] ?? current;
492
+ const next = traj.frames[Math.min(totalFrames - 1, f + 1)] ?? current;
493
+ const curCenterX = current.box.originX + current.box.width / 2;
494
+ const curCenterY = current.box.originY + current.box.height / 2;
495
+ const prevCenterX = prev.box.originX + prev.box.width / 2;
496
+ const prevCenterY = prev.box.originY + prev.box.height / 2;
497
+ const nextCenterX = next.box.originX + next.box.width / 2;
498
+ const nextCenterY = next.box.originY + next.box.height / 2;
499
+ const timeDelta = f === 0 || f === totalFrames - 1 ? dt : 2 * dt;
500
+ const vx = (nextCenterX - prevCenterX) / timeDelta;
501
+ const vy = (nextCenterY - prevCenterY) / timeDelta;
502
+ const speed = Math.sqrt(vx * vx + vy * vy);
503
+ const normW = current.box.normalizedWidth;
504
+ const normH = current.box.normalizedHeight;
505
+ frameObjects.push({
506
+ trackId,
507
+ category: traj.category,
508
+ score: current.score,
509
+ boundingBox: current.box,
510
+ centerX: curCenterX,
511
+ centerY: curCenterY,
512
+ normalizedCenterX: current.box.normalizedX + normW / 2,
513
+ normalizedCenterY: current.box.normalizedY + normH / 2,
514
+ velocity: {
515
+ vx,
516
+ vy
517
+ },
518
+ speed,
519
+ age: f + 1,
520
+ hits: 10,
521
+ active: true,
522
+ isCoasting: false
523
+ });
524
+ }
525
+ result.push(frameObjects);
526
+ }
527
+ return result;
528
+ }
529
+ /**
530
+ * Resets all internal track state.
531
+ */
532
+ reset() {
533
+ this._tracks = [];
534
+ this._nextTrackId = 1;
535
+ }
536
+ };
537
+ const now = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
538
+ async function analyzeSequence(frames, options) {
539
+ const { runner } = options;
540
+ const fps = options.fps ?? 24;
541
+ const tasks = options.tasks && options.tasks.length > 0 ? options.tasks : ["detect"];
542
+ const taskSet = new Set(tasks);
543
+ const categoryFilter = options.categories ? new Set(options.categories.map((c) => c.toLowerCase())) : null;
544
+ const includeMasks = options.includeMasks === true && taskSet.has("segment");
545
+ const tracksObjects = taskSet.has("detect") || taskSet.has("segment");
546
+ const perFrameDetections = [];
547
+ const modelDownloads = {};
548
+ const maskCoverage = /* @__PURE__ */ new Map();
549
+ let inferenceMs = 0;
550
+ let frameIndex = 0;
551
+ const accepts = (category) => !categoryFilter || categoryFilter.has(category.toLowerCase());
552
+ const runTask = async (task, frame, fn) => {
553
+ const key = runner.modelKeyFor(task, frame.width / frame.height);
554
+ const wasReady = runner.downloadStatus.get(key) === "ready";
555
+ const start = now();
556
+ const result = await fn();
557
+ const elapsed = now() - start;
558
+ inferenceMs += elapsed;
559
+ if (!wasReady && !modelDownloads[key]) modelDownloads[key] = {
560
+ bytes: VISION_MODELS[key].bytes,
561
+ ms: elapsed
562
+ };
563
+ return result;
564
+ };
565
+ const recordMasks = (masks$1) => {
566
+ for (const m of masks$1) {
567
+ if (!accepts(m.category)) continue;
568
+ const entry = maskCoverage.get(m.category) ?? {
569
+ sum: 0,
570
+ count: 0
571
+ };
572
+ entry.sum += m.coverage;
573
+ entry.count++;
574
+ maskCoverage.set(m.category, entry);
575
+ }
576
+ };
577
+ for await (const frame of frames) {
578
+ let detections = [];
579
+ if (includeMasks) {
580
+ const seg = await runTask("segment", frame, () => runner.segment(frame));
581
+ recordMasks(seg.masks);
582
+ detections = seg.detections;
583
+ } else if (tracksObjects) detections = await runTask("detect", frame, () => runner.detect(frame));
584
+ if (taskSet.has("pose")) await runTask("pose", frame, () => runner.pose(frame));
585
+ if (taskSet.has("matte")) await runTask("matte", frame, () => runner.matte(frame));
586
+ perFrameDetections.push(categoryFilter ? detections.filter((d) => accepts(d.category)) : detections);
587
+ frameIndex++;
588
+ }
589
+ const totalFrames = frameIndex;
590
+ const trackedPerFrame = new TemporalObjectTracker({
591
+ iouThreshold: options.iouThreshold ?? .25,
592
+ maxMissedFrames: options.maxMissedFrames ?? 15
593
+ }).trackSequence(perFrameDetections, totalFrames, fps);
594
+ const pathStride = options.pathStride ?? Math.max(1, Math.ceil(totalFrames / 50));
595
+ const tracks = /* @__PURE__ */ new Map();
596
+ const classPresentFrames = /* @__PURE__ */ new Map();
597
+ const classMaxConfidence = /* @__PURE__ */ new Map();
598
+ const classTrackIds = /* @__PURE__ */ new Map();
599
+ const getOrCreate = (map, key, make) => {
600
+ let value = map.get(key);
601
+ if (value === void 0) {
602
+ value = make();
603
+ map.set(key, value);
604
+ }
605
+ return value;
606
+ };
607
+ for (let f = 0; f < totalFrames; f++) for (const obj of trackedPerFrame[f] ?? []) {
608
+ if (!accepts(obj.category)) continue;
609
+ let acc = tracks.get(obj.trackId);
610
+ if (!acc) {
611
+ acc = {
612
+ category: obj.category,
613
+ firstFrame: f,
614
+ lastFrame: f,
615
+ speedSum: 0,
616
+ speedCount: 0,
617
+ centers: []
618
+ };
619
+ tracks.set(obj.trackId, acc);
620
+ }
621
+ acc.lastFrame = f;
622
+ if (Number.isFinite(obj.speed)) {
623
+ acc.speedSum += obj.speed;
624
+ acc.speedCount++;
625
+ }
626
+ if ((f - acc.firstFrame) % pathStride === 0) acc.centers.push({
627
+ frame: f,
628
+ x: obj.centerX,
629
+ y: obj.centerY
630
+ });
631
+ getOrCreate(classPresentFrames, obj.category, () => /* @__PURE__ */ new Set()).add(f);
632
+ classMaxConfidence.set(obj.category, Math.max(classMaxConfidence.get(obj.category) ?? 0, obj.score));
633
+ getOrCreate(classTrackIds, obj.category, () => /* @__PURE__ */ new Set()).add(obj.trackId);
634
+ }
635
+ const trackSummaries = Array.from(tracks.entries()).map(([trackId, acc]) => ({
636
+ trackId,
637
+ category: acc.category,
638
+ frames: [acc.firstFrame, acc.lastFrame],
639
+ speedMeanPxS: acc.speedCount > 0 ? acc.speedSum / acc.speedCount : 0,
640
+ centerPath: acc.centers.map((c) => [c.x, c.y])
641
+ })).sort((a, b) => a.trackId - b.trackId);
642
+ const classes = {};
643
+ for (const [category, frameSet] of classPresentFrames) classes[category] = {
644
+ framesPresent: frameSet.size,
645
+ totalFrames,
646
+ maxConfidence: classMaxConfidence.get(category) ?? 0,
647
+ tracks: classTrackIds.get(category)?.size ?? 0
648
+ };
649
+ const masks = {};
650
+ for (const [category, entry] of maskCoverage) masks[category] = { meanCoverage: entry.count > 0 ? entry.sum / entry.count : 0 };
651
+ return {
652
+ source: options.source ?? "frames",
653
+ totalFrames,
654
+ fps,
655
+ durationSec: fps > 0 ? totalFrames / fps : 0,
656
+ tasks: Array.from(taskSet),
657
+ modelDownloads,
658
+ inferenceMs,
659
+ trackCount: trackSummaries.length,
660
+ classes,
661
+ tracks: trackSummaries,
662
+ masks
663
+ };
664
+ }
665
+ const IMAGENET_BGR_MEAN = [
666
+ 103.53,
667
+ 116.28,
668
+ 123.675
669
+ ];
670
+ const IMAGENET_BGR_STD = [
671
+ 57.375,
672
+ 57.12,
673
+ 58.395
674
+ ];
675
+ /** Per-family normalization; the input size always comes from the model's registry entry. */
676
+ const PREPROCESS_BY_FAMILY = {
677
+ "rtmdet-ins": {
678
+ fit: "topLeft",
679
+ channels: "bgr",
680
+ mean: IMAGENET_BGR_MEAN,
681
+ std: IMAGENET_BGR_STD,
682
+ padValue: 114
683
+ },
684
+ rtmo: {
685
+ fit: "center",
686
+ channels: "bgr",
687
+ mean: [
688
+ 0,
689
+ 0,
690
+ 0
691
+ ],
692
+ std: [
693
+ 1,
694
+ 1,
695
+ 1
696
+ ],
697
+ padValue: 114
698
+ },
699
+ selfie: {
700
+ fit: "stretch",
701
+ channels: "rgb",
702
+ mean: [
703
+ 0,
704
+ 0,
705
+ 0
706
+ ],
707
+ std: [
708
+ 255,
709
+ 255,
710
+ 255
711
+ ],
712
+ padValue: 0
713
+ }
714
+ };
715
+ function preprocessSpec(family, input) {
716
+ return {
717
+ ...PREPROCESS_BY_FAMILY[family],
718
+ width: input[0],
719
+ height: input[1]
720
+ };
721
+ }
722
+ function computeInputTransform(srcW, srcH, spec) {
723
+ if (spec.fit === "stretch") return {
724
+ scaleX: spec.width / srcW,
725
+ scaleY: spec.height / srcH,
726
+ offsetX: 0,
727
+ offsetY: 0,
728
+ inputWidth: spec.width,
729
+ inputHeight: spec.height
730
+ };
731
+ const scale = Math.min(spec.width / srcW, spec.height / srcH);
732
+ const contentW = Math.round(srcW * scale);
733
+ const contentH = Math.round(srcH * scale);
734
+ const center = spec.fit === "center";
735
+ return {
736
+ scaleX: scale,
737
+ scaleY: scale,
738
+ offsetX: center ? (spec.width - contentW) / 2 : 0,
739
+ offsetY: center ? (spec.height - contentH) / 2 : 0,
740
+ inputWidth: spec.width,
741
+ inputHeight: spec.height
742
+ };
743
+ }
744
+ /** Model-input point → source pixel point. */
745
+ function toSourcePoint(x, y, t) {
746
+ return {
747
+ x: (x - t.offsetX) / t.scaleX,
748
+ y: (y - t.offsetY) / t.scaleY
749
+ };
750
+ }
751
+ /**
752
+ * Resizes RGBA → NCHW float32 `[1, 3, height, width]` with bilinear sampling, padding,
753
+ * channel reordering and normalization per `spec`. Reuses `out` when large enough
754
+ * (hot-path frame loop).
755
+ */
756
+ function imageToTensor(image, spec, out) {
757
+ const { width: srcW, height: srcH, data: src } = image;
758
+ const { width: W, height: H } = spec;
759
+ const transform = computeInputTransform(srcW, srcH, spec);
760
+ const { scaleX, scaleY, offsetX, offsetY } = transform;
761
+ const plane = W * H;
762
+ if (!out || out.length < 3 * plane) out = new Float32Array(3 * plane);
763
+ const order = spec.channels === "bgr" ? [
764
+ 2,
765
+ 1,
766
+ 0
767
+ ] : [
768
+ 0,
769
+ 1,
770
+ 2
771
+ ];
772
+ const [m0, m1, m2] = spec.mean;
773
+ const inv0 = 1 / spec.std[0];
774
+ const inv1 = 1 / spec.std[1];
775
+ const inv2 = 1 / spec.std[2];
776
+ const pad0 = (spec.padValue - m0) * inv0;
777
+ const pad1 = (spec.padValue - m1) * inv1;
778
+ const pad2 = (spec.padValue - m2) * inv2;
779
+ const [c0, c1, c2] = order;
780
+ const x0 = Math.ceil(offsetX);
781
+ const y0 = Math.ceil(offsetY);
782
+ const x1 = Math.min(W, Math.floor(offsetX + srcW * scaleX)) - 1;
783
+ const y1 = Math.min(H, Math.floor(offsetY + srcH * scaleY)) - 1;
784
+ for (let y = 0; y < H; y++) {
785
+ const rowBase = y * W;
786
+ const paddedRow = y < y0 || y > y1;
787
+ const sy = Math.max(0, (y + .5 - offsetY) / scaleY - .5);
788
+ const iy0 = Math.min(srcH - 1, Math.floor(sy));
789
+ const iy1 = Math.min(srcH - 1, iy0 + 1);
790
+ const fy = sy - iy0;
791
+ for (let x = 0; x < W; x++) {
792
+ const o = rowBase + x;
793
+ if (paddedRow || x < x0 || x > x1) {
794
+ out[o] = pad0;
795
+ out[plane + o] = pad1;
796
+ out[2 * plane + o] = pad2;
797
+ continue;
798
+ }
799
+ const sx = Math.max(0, (x + .5 - offsetX) / scaleX - .5);
800
+ const ix0 = Math.min(srcW - 1, Math.floor(sx));
801
+ const ix1 = Math.min(srcW - 1, ix0 + 1);
802
+ const fx = sx - ix0;
803
+ const i00 = (iy0 * srcW + ix0) * 4;
804
+ const i10 = (iy0 * srcW + ix1) * 4;
805
+ const i01 = (iy1 * srcW + ix0) * 4;
806
+ const i11 = (iy1 * srcW + ix1) * 4;
807
+ const w00 = (1 - fx) * (1 - fy);
808
+ const w10 = fx * (1 - fy);
809
+ const w01 = (1 - fx) * fy;
810
+ const w11 = fx * fy;
811
+ out[o] = (src[i00 + c0] * w00 + src[i10 + c0] * w10 + src[i01 + c0] * w01 + src[i11 + c0] * w11 - m0) * inv0;
812
+ out[plane + o] = (src[i00 + c1] * w00 + src[i10 + c1] * w10 + src[i01 + c1] * w01 + src[i11 + c1] * w11 - m1) * inv1;
813
+ out[2 * plane + o] = (src[i00 + c2] * w00 + src[i10 + c2] * w10 + src[i01 + c2] * w01 + src[i11 + c2] * w11 - m2) * inv2;
814
+ }
815
+ }
816
+ return {
817
+ tensor: out,
818
+ transform
819
+ };
820
+ }
821
+ /** Source pixels a mask may extend past its box (instance masks are not box-clipped). */
822
+ const MASK_BOX_PAD_PX = 8;
823
+ function decodeRtmdetIns(out, opts) {
824
+ const { sourceWidth: sw, sourceHeight: sh, transform } = opts;
825
+ const classFilter = opts.classes ? new Set(opts.classes.map((c) => c.toLowerCase())) : null;
826
+ const kept = [];
827
+ for (let i = 0; i < out.count; i++) {
828
+ const score = out.dets[i * 5 + 4];
829
+ if (!(score >= opts.confidence)) continue;
830
+ const classIndex = Number(out.labels[i]);
831
+ const category = opts.classNames[classIndex] ?? `class_${classIndex}`;
832
+ if (classFilter && !classFilter.has(category.toLowerCase())) continue;
833
+ const p0 = toSourcePoint(out.dets[i * 5], out.dets[i * 5 + 1], transform);
834
+ const p1 = toSourcePoint(out.dets[i * 5 + 2], out.dets[i * 5 + 3], transform);
835
+ const x0 = clamp$1(p0.x, 0, sw);
836
+ const y0 = clamp$1(p0.y, 0, sh);
837
+ const width = clamp$1(p1.x, 0, sw) - x0;
838
+ const height = clamp$1(p1.y, 0, sh) - y0;
839
+ if (width <= 0 || height <= 0) continue;
840
+ kept.push({
841
+ index: i,
842
+ detection: {
843
+ category,
844
+ score,
845
+ boundingBox: {
846
+ originX: x0,
847
+ originY: y0,
848
+ width,
849
+ height,
850
+ normalizedX: x0 / sw,
851
+ normalizedY: y0 / sh,
852
+ normalizedWidth: width / sw,
853
+ normalizedHeight: height / sh
854
+ }
855
+ }
856
+ });
857
+ }
858
+ kept.sort((a, b) => b.detection.score - a.detection.score);
859
+ const detections = kept.map((k) => k.detection);
860
+ const masks = [];
861
+ if (!out.masks) return {
862
+ detections,
863
+ masks
864
+ };
865
+ const alphaOf = probabilityToAlpha(opts.maskThreshold ?? .5, opts.featherRadius ?? .05);
866
+ const { maskWidth: mw, maskHeight: mh } = out;
867
+ const plane = mw * mh;
868
+ const gx = mw / transform.inputWidth;
869
+ const gy = mh / transform.inputHeight;
870
+ for (let d = 0; d < kept.length; d++) {
871
+ const { index, detection } = kept[d];
872
+ const box = detection.boundingBox;
873
+ const base = index * plane;
874
+ const bx0 = Math.max(0, Math.floor(box.originX) - MASK_BOX_PAD_PX);
875
+ const by0 = Math.max(0, Math.floor(box.originY) - MASK_BOX_PAD_PX);
876
+ const bx1 = Math.min(sw - 1, Math.ceil(box.originX + box.width) + MASK_BOX_PAD_PX);
877
+ const by1 = Math.min(sh - 1, Math.ceil(box.originY + box.height) + MASK_BOX_PAD_PX);
878
+ const mask = new Uint8Array(sw * sh);
879
+ let area = 0;
880
+ for (let sy = by0; sy <= by1; sy++) {
881
+ const my = ((sy + .5) * transform.scaleY + transform.offsetY) * gy - .5;
882
+ const my0 = clamp$1(Math.floor(my), 0, mh - 1);
883
+ const my1 = Math.min(mh - 1, my0 + 1);
884
+ const fy = clamp$1(my - my0, 0, 1);
885
+ const row = sy * sw;
886
+ for (let sx = bx0; sx <= bx1; sx++) {
887
+ const mx = ((sx + .5) * transform.scaleX + transform.offsetX) * gx - .5;
888
+ const mx0 = clamp$1(Math.floor(mx), 0, mw - 1);
889
+ const mx1 = Math.min(mw - 1, mx0 + 1);
890
+ const fx = clamp$1(mx - mx0, 0, 1);
891
+ const alpha = alphaOf(out.masks[base + my0 * mw + mx0] * (1 - fx) * (1 - fy) + out.masks[base + my0 * mw + mx1] * fx * (1 - fy) + out.masks[base + my1 * mw + mx0] * (1 - fx) * fy + out.masks[base + my1 * mw + mx1] * fx * fy);
892
+ if (alpha > 0) {
893
+ mask[row + sx] = alpha;
894
+ area += alpha / 255;
895
+ }
896
+ }
897
+ }
898
+ area = Math.round(area);
899
+ if (area === 0) continue;
900
+ masks.push({
901
+ category: detection.category,
902
+ mask,
903
+ width: sw,
904
+ height: sh,
905
+ area,
906
+ coverage: area / (sw * sh),
907
+ detectionIndex: d
908
+ });
909
+ }
910
+ return {
911
+ detections,
912
+ masks
913
+ };
914
+ }
915
+ /**
916
+ * Probability → 0..255 alpha. A hard threshold when `feather` is 0, otherwise a linear ramp
917
+ * across `[threshold − feather, threshold + feather]` for anti-aliased edges.
918
+ */
919
+ function probabilityToAlpha(threshold, feather) {
920
+ if (feather <= 0) return (p) => p > threshold ? 255 : 0;
921
+ const lo = threshold - feather;
922
+ const inv = 255 / (2 * feather);
923
+ return (p) => p <= lo ? 0 : p >= threshold + feather ? 255 : (p - lo) * inv;
924
+ }
925
+ function clamp$1(v, lo, hi) {
926
+ return v < lo ? lo : v > hi ? hi : v;
927
+ }
928
+ const COCO17_KEYPOINT_COUNT = 17;
929
+ /** Keypoint visibility at which a joint counts as seen. */
930
+ const VISIBLE = .3;
931
+ function decodeRtmo(out, opts) {
932
+ const { sourceWidth: sw, sourceHeight: sh, transform } = opts;
933
+ const stride = out.keypointStride;
934
+ const people = [];
935
+ for (let i = 0; i < out.count; i++) {
936
+ const score = out.dets[i * 5 + 4];
937
+ if (!(score >= opts.confidence)) continue;
938
+ const p0 = toSourcePoint(out.dets[i * 5], out.dets[i * 5 + 1], transform);
939
+ const p1 = toSourcePoint(out.dets[i * 5 + 2], out.dets[i * 5 + 3], transform);
940
+ const x0 = clamp(p0.x, 0, sw);
941
+ const y0 = clamp(p0.y, 0, sh);
942
+ const width = clamp(p1.x, 0, sw) - x0;
943
+ const height = clamp(p1.y, 0, sh) - y0;
944
+ if (width <= 0 || height <= 0) continue;
945
+ const keypoints = [];
946
+ const base = i * COCO17_KEYPOINT_COUNT * stride;
947
+ for (let k = 0; k < COCO17_KEYPOINT_COUNT; k++) {
948
+ const o = base + k * stride;
949
+ const p = toSourcePoint(out.keypoints[o], out.keypoints[o + 1], transform);
950
+ keypoints.push({
951
+ x: clamp(p.x, 0, sw),
952
+ y: clamp(p.y, 0, sh),
953
+ visibility: clamp(out.keypoints[o + 2], 0, 1)
954
+ });
955
+ }
956
+ let visible = 0;
957
+ for (const k of keypoints) if (k.visibility >= VISIBLE) visible++;
958
+ if (visible < (opts.minVisibleKeypoints ?? 3)) continue;
959
+ people.push({
960
+ score,
961
+ boundingBox: {
962
+ originX: x0,
963
+ originY: y0,
964
+ width,
965
+ height,
966
+ normalizedX: x0 / sw,
967
+ normalizedY: y0 / sh,
968
+ normalizedWidth: width / sw,
969
+ normalizedHeight: height / sh
970
+ },
971
+ keypoints
972
+ });
973
+ }
974
+ people.sort((a, b) => b.score - a.score);
975
+ const iou = opts.iouThreshold ?? .6;
976
+ const kept = [];
977
+ for (const p of people) if (!kept.some((k) => isDuplicate(k, p, iou))) kept.push(p);
978
+ return { people: kept };
979
+ }
980
+ function clamp(v, lo, hi) {
981
+ return v < lo ? lo : v > hi ? hi : v;
982
+ }
983
+ /**
984
+ * Same person twice: boxes overlap above `iou`, or the joints both detections can see sit
985
+ * within 10% of the stronger detection's box size of each other (boxes may differ when one
986
+ * of them also covers a flowing garment or a shadow).
987
+ */
988
+ function isDuplicate(a, b, iou) {
989
+ if (computeIoU(a.boundingBox, b.boundingBox) > iou) return true;
990
+ const scale = Math.sqrt(a.boundingBox.width * a.boundingBox.height);
991
+ let sum = 0;
992
+ let n = 0;
993
+ for (let k = 0; k < a.keypoints.length; k++) {
994
+ const ka = a.keypoints[k];
995
+ const kb = b.keypoints[k];
996
+ if (ka.visibility < VISIBLE || kb.visibility < VISIBLE) continue;
997
+ sum += Math.hypot(ka.x - kb.x, ka.y - kb.y);
998
+ n++;
999
+ }
1000
+ return n >= 3 && sum / n < .1 * scale;
1001
+ }
1002
+ function decodeSelfie(alphas, matteWidth, matteHeight, opts) {
1003
+ const { sourceWidth: sw, sourceHeight: sh } = opts;
1004
+ const alphaOf = probabilityToAlpha(opts.maskThreshold ?? .5, opts.featherRadius ?? .2);
1005
+ const mask = new Uint8Array(sw * sh);
1006
+ const kx = matteWidth / sw;
1007
+ const ky = matteHeight / sh;
1008
+ let sum = 0;
1009
+ for (let y = 0; y < sh; y++) {
1010
+ const my = Math.max(0, (y + .5) * ky - .5);
1011
+ const my0 = Math.min(matteHeight - 1, Math.floor(my));
1012
+ const my1 = Math.min(matteHeight - 1, my0 + 1);
1013
+ const fy = my - my0;
1014
+ for (let x = 0; x < sw; x++) {
1015
+ const mx = Math.max(0, (x + .5) * kx - .5);
1016
+ const mx0 = Math.min(matteWidth - 1, Math.floor(mx));
1017
+ const mx1 = Math.min(matteWidth - 1, mx0 + 1);
1018
+ const fx = mx - mx0;
1019
+ const a = alphaOf(alphas[my0 * matteWidth + mx0] * (1 - fx) * (1 - fy) + alphas[my0 * matteWidth + mx1] * fx * (1 - fy) + alphas[my1 * matteWidth + mx0] * (1 - fx) * fy + alphas[my1 * matteWidth + mx1] * fx * fy);
1020
+ mask[y * sw + x] = a;
1021
+ sum += a;
1022
+ }
1023
+ }
1024
+ return {
1025
+ mask,
1026
+ width: sw,
1027
+ height: sh,
1028
+ coverage: sw * sh > 0 ? sum / (255 * sw * sh) : 0
1029
+ };
1030
+ }
1031
+ /** COCO-17 keypoint indices (RTMO output order). */
1032
+ const COCO17_KEYPOINTS = {
1033
+ NOSE: 0,
1034
+ LEFT_EYE: 1,
1035
+ RIGHT_EYE: 2,
1036
+ LEFT_EAR: 3,
1037
+ RIGHT_EAR: 4,
1038
+ LEFT_SHOULDER: 5,
1039
+ RIGHT_SHOULDER: 6,
1040
+ LEFT_ELBOW: 7,
1041
+ RIGHT_ELBOW: 8,
1042
+ LEFT_WRIST: 9,
1043
+ RIGHT_WRIST: 10,
1044
+ LEFT_HIP: 11,
1045
+ RIGHT_HIP: 12,
1046
+ LEFT_KNEE: 13,
1047
+ RIGHT_KNEE: 14,
1048
+ LEFT_ANKLE: 15,
1049
+ RIGHT_ANKLE: 16
1050
+ };
1051
+ const COCO17_KEYPOINT_NAMES = [
1052
+ "nose",
1053
+ "leftEye",
1054
+ "rightEye",
1055
+ "leftEar",
1056
+ "rightEar",
1057
+ "leftShoulder",
1058
+ "rightShoulder",
1059
+ "leftElbow",
1060
+ "rightElbow",
1061
+ "leftWrist",
1062
+ "rightWrist",
1063
+ "leftHip",
1064
+ "rightHip",
1065
+ "leftKnee",
1066
+ "rightKnee",
1067
+ "leftAnkle",
1068
+ "rightAnkle"
1069
+ ];
1070
+ /** COCO-17 skeleton bones for the WebGPU skeleton renderer (indices above). */
1071
+ const COCO17_BONES = [
1072
+ {
1073
+ from: COCO17_KEYPOINTS.NOSE,
1074
+ to: COCO17_KEYPOINTS.LEFT_SHOULDER,
1075
+ color: [
1076
+ 0,
1077
+ 1,
1078
+ 1,
1079
+ 1
1080
+ ]
1081
+ },
1082
+ {
1083
+ from: COCO17_KEYPOINTS.NOSE,
1084
+ to: COCO17_KEYPOINTS.RIGHT_SHOULDER,
1085
+ color: [
1086
+ 0,
1087
+ 1,
1088
+ 1,
1089
+ 1
1090
+ ]
1091
+ },
1092
+ {
1093
+ from: COCO17_KEYPOINTS.LEFT_SHOULDER,
1094
+ to: COCO17_KEYPOINTS.RIGHT_SHOULDER,
1095
+ color: [
1096
+ 1,
1097
+ 0,
1098
+ 0,
1099
+ 1
1100
+ ]
1101
+ },
1102
+ {
1103
+ from: COCO17_KEYPOINTS.LEFT_SHOULDER,
1104
+ to: COCO17_KEYPOINTS.LEFT_ELBOW,
1105
+ color: [
1106
+ 1,
1107
+ .333,
1108
+ 0,
1109
+ 1
1110
+ ]
1111
+ },
1112
+ {
1113
+ from: COCO17_KEYPOINTS.LEFT_ELBOW,
1114
+ to: COCO17_KEYPOINTS.LEFT_WRIST,
1115
+ color: [
1116
+ 1,
1117
+ .667,
1118
+ 0,
1119
+ 1
1120
+ ]
1121
+ },
1122
+ {
1123
+ from: COCO17_KEYPOINTS.RIGHT_SHOULDER,
1124
+ to: COCO17_KEYPOINTS.RIGHT_ELBOW,
1125
+ color: [
1126
+ 1,
1127
+ 1,
1128
+ 0,
1129
+ 1
1130
+ ]
1131
+ },
1132
+ {
1133
+ from: COCO17_KEYPOINTS.RIGHT_ELBOW,
1134
+ to: COCO17_KEYPOINTS.RIGHT_WRIST,
1135
+ color: [
1136
+ .667,
1137
+ 1,
1138
+ 0,
1139
+ 1
1140
+ ]
1141
+ },
1142
+ {
1143
+ from: COCO17_KEYPOINTS.LEFT_SHOULDER,
1144
+ to: COCO17_KEYPOINTS.LEFT_HIP,
1145
+ color: [
1146
+ .333,
1147
+ 1,
1148
+ 0,
1149
+ 1
1150
+ ]
1151
+ },
1152
+ {
1153
+ from: COCO17_KEYPOINTS.RIGHT_SHOULDER,
1154
+ to: COCO17_KEYPOINTS.RIGHT_HIP,
1155
+ color: [
1156
+ 0,
1157
+ 1,
1158
+ 0,
1159
+ 1
1160
+ ]
1161
+ },
1162
+ {
1163
+ from: COCO17_KEYPOINTS.LEFT_HIP,
1164
+ to: COCO17_KEYPOINTS.RIGHT_HIP,
1165
+ color: [
1166
+ 0,
1167
+ 1,
1168
+ .333,
1169
+ 1
1170
+ ]
1171
+ },
1172
+ {
1173
+ from: COCO17_KEYPOINTS.LEFT_HIP,
1174
+ to: COCO17_KEYPOINTS.LEFT_KNEE,
1175
+ color: [
1176
+ 0,
1177
+ 1,
1178
+ .667,
1179
+ 1
1180
+ ]
1181
+ },
1182
+ {
1183
+ from: COCO17_KEYPOINTS.LEFT_KNEE,
1184
+ to: COCO17_KEYPOINTS.LEFT_ANKLE,
1185
+ color: [
1186
+ 0,
1187
+ 1,
1188
+ 1,
1189
+ 1
1190
+ ]
1191
+ },
1192
+ {
1193
+ from: COCO17_KEYPOINTS.RIGHT_HIP,
1194
+ to: COCO17_KEYPOINTS.RIGHT_KNEE,
1195
+ color: [
1196
+ 0,
1197
+ .667,
1198
+ 1,
1199
+ 1
1200
+ ]
1201
+ },
1202
+ {
1203
+ from: COCO17_KEYPOINTS.RIGHT_KNEE,
1204
+ to: COCO17_KEYPOINTS.RIGHT_ANKLE,
1205
+ color: [
1206
+ 0,
1207
+ .333,
1208
+ 1,
1209
+ 1
1210
+ ]
1211
+ }
1212
+ ];
1213
+ /**
1214
+ * WebGPU skeleton renderer for pose results.
1215
+ *
1216
+ * Rasterizes the primary person's COCO-17 keypoints into an OpenPose-style
1217
+ * conditioning texture natively in VRAM, using the COCO-17 bone set.
1218
+ */
1219
+ var PoseSkeletonRenderer = class {
1220
+ pipeline;
1221
+ constructor(device) {
1222
+ this.pipeline = new PoseSkeletonComputePipeline(device);
1223
+ }
1224
+ renderToTexture(keypoints, options, bones = COCO17_BONES) {
1225
+ return this.pipeline.execute(keypoints, options, bones);
1226
+ }
1227
+ destroy() {
1228
+ this.pipeline.destroy();
1229
+ }
1230
+ };
1231
+ var SegmentationTexturePool = class {
1232
+ device;
1233
+ pool = /* @__PURE__ */ new Map();
1234
+ constructor(device) {
1235
+ this.device = device;
1236
+ }
1237
+ getOrCreateTexture(key, options) {
1238
+ const existing = this.pool.get(key);
1239
+ if (existing && existing.width === options.width && existing.height === options.height) return existing;
1240
+ if (existing) existing.destroy();
1241
+ const format = options.format ?? "rgba8unorm";
1242
+ const texture = this.device.createTexture({
1243
+ label: options.label ?? `vision_segmentation_${key}`,
1244
+ size: [
1245
+ options.width,
1246
+ options.height,
1247
+ 1
1248
+ ],
1249
+ format,
1250
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
1251
+ });
1252
+ this.pool.set(key, texture);
1253
+ return texture;
1254
+ }
1255
+ uploadMask(key, maskData, width, height) {
1256
+ const isRgba = maskData.length >= width * height * 4;
1257
+ const format = isRgba ? "rgba8unorm" : "r8unorm";
1258
+ const texture = this.getOrCreateTexture(key, {
1259
+ width,
1260
+ height,
1261
+ format
1262
+ });
1263
+ let uploadBytes;
1264
+ if (maskData instanceof Float32Array) {
1265
+ uploadBytes = new Uint8Array(maskData.length);
1266
+ for (let i = 0; i < maskData.length; i++) uploadBytes[i] = Math.round(Math.max(0, Math.min(1, maskData[i])) * 255);
1267
+ } else uploadBytes = maskData;
1268
+ const bytesPerRow = width * (isRgba ? 4 : 1);
1269
+ const alignedBytesPerRow = Math.ceil(bytesPerRow / 256) * 256;
1270
+ let finalBuffer;
1271
+ if (alignedBytesPerRow === bytesPerRow) finalBuffer = uploadBytes;
1272
+ else {
1273
+ finalBuffer = new Uint8Array(alignedBytesPerRow * height);
1274
+ for (let y = 0; y < height; y++) finalBuffer.set(uploadBytes.subarray(y * bytesPerRow, (y + 1) * bytesPerRow), y * alignedBytesPerRow);
1275
+ }
1276
+ this.device.queue.writeTexture({ texture }, finalBuffer.buffer, {
1277
+ offset: finalBuffer.byteOffset,
1278
+ bytesPerRow: alignedBytesPerRow,
1279
+ rowsPerImage: height
1280
+ }, {
1281
+ width,
1282
+ height
1283
+ });
1284
+ return texture;
1285
+ }
1286
+ destroy() {
1287
+ for (const tex of this.pool.values()) tex.destroy();
1288
+ this.pool.clear();
1289
+ }
1290
+ };
1291
+ /**
1292
+ * Resolves the default model cache directory:
1293
+ * 1. `FRAMEFIELDS_MODELS_DIR`
1294
+ * 2. `~/.cache/framefields/models`
1295
+ */
1296
+ function getDefaultModelsDir() {
1297
+ if (process.env.FRAMEFIELDS_MODELS_DIR) return resolve(process.env.FRAMEFIELDS_MODELS_DIR);
1298
+ return resolve(homedir(), ".cache/framefields/models");
1299
+ }
1300
+ /**
1301
+ * Lazy model store. The constructor performs ZERO I/O; the only entry that can hit the
1302
+ * network is `ensure(key)`, called by an inference function the first time it runs.
1303
+ * Concurrent callers of the same key share a single in-flight download (promise dedupe),
1304
+ * downloads are written atomically (temp + rename), and verified files on disk are reused
1305
+ * across processes.
1306
+ */
1307
+ var VisionModelStore = class {
1308
+ _modelsDir;
1309
+ _baseUrl;
1310
+ _timeoutMs;
1311
+ _retries;
1312
+ _retryDelayMs;
1313
+ _verify;
1314
+ _fetch;
1315
+ _onProgress;
1316
+ _inflight = /* @__PURE__ */ new Map();
1317
+ _buffers = /* @__PURE__ */ new Map();
1318
+ _status = /* @__PURE__ */ new Map();
1319
+ constructor(options = {}) {
1320
+ this._modelsDir = options.modelsDir ? resolve(options.modelsDir) : getDefaultModelsDir();
1321
+ this._baseUrl = options.baseUrl ?? process.env.FRAMEFIELDS_MODELS_BASE_URL ?? void 0;
1322
+ this._timeoutMs = options.timeoutMs;
1323
+ this._retries = Math.max(0, options.retries ?? 2);
1324
+ this._retryDelayMs = options.retryDelayMs ?? 1e3;
1325
+ this._verify = options.verify !== false;
1326
+ this._fetch = options.fetchImpl ?? globalThis.fetch;
1327
+ this._onProgress = options.onProgress;
1328
+ }
1329
+ get modelsDir() {
1330
+ return this._modelsDir;
1331
+ }
1332
+ pathFor(key) {
1333
+ return join(this._modelsDir, VISION_MODELS[key].filename);
1334
+ }
1335
+ descriptor(key) {
1336
+ return VISION_MODELS[key];
1337
+ }
1338
+ /** The URL `ensure(key)` downloads from (mirror when `baseUrl` is set). */
1339
+ urlFor(key) {
1340
+ const desc = VISION_MODELS[key];
1341
+ if (!this._baseUrl) return desc.url;
1342
+ return `${this._baseUrl.replace(/\/+$/, "")}/${desc.filename}`;
1343
+ }
1344
+ /** True when a complete cached file exists (exact registry size when verifying). */
1345
+ has(key) {
1346
+ const targetPath = this.pathFor(key);
1347
+ if (!existsSync(targetPath)) return false;
1348
+ try {
1349
+ const size = statSync(targetPath).size;
1350
+ return this._verify ? size === VISION_MODELS[key].bytes : size > 0;
1351
+ } catch {
1352
+ return false;
1353
+ }
1354
+ }
1355
+ /** Drops a cached model (file + memory) so the next `ensure` re-downloads it. */
1356
+ async evict(key) {
1357
+ this._buffers.delete(key);
1358
+ this._status.set(key, "pending");
1359
+ await unlink(this.pathFor(key)).catch(() => void 0);
1360
+ }
1361
+ status(key) {
1362
+ return this._status.get(key) ?? "pending";
1363
+ }
1364
+ /** Snapshot of every tracked model's status (empty until anything is fetched). */
1365
+ statuses() {
1366
+ return new Map(this._status);
1367
+ }
1368
+ /** True when the model is resident in memory (loaded or downloaded this process). */
1369
+ isLoaded(key) {
1370
+ return this._buffers.has(key);
1371
+ }
1372
+ /**
1373
+ * THE only download entry point. Returns the model bytes from memory (cached),
1374
+ * loading from disk or fetching from the registry URL as needed.
1375
+ */
1376
+ async ensure(key) {
1377
+ const inMemory = this._buffers.get(key);
1378
+ if (inMemory) return inMemory;
1379
+ const inflight = this._inflight.get(key);
1380
+ if (inflight) return inflight;
1381
+ const promise = this.load(key).finally(() => this._inflight.delete(key));
1382
+ this._inflight.set(key, promise);
1383
+ return promise;
1384
+ }
1385
+ /** Explicit warm-up — fetch multiple models ahead of use (agents, renderers). */
1386
+ async preload(keys) {
1387
+ await Promise.all([...new Set(keys)].map((key) => this.ensure(key)));
1388
+ }
1389
+ async load(key) {
1390
+ const targetPath = this.pathFor(key);
1391
+ if (this.has(key)) {
1392
+ this._status.set(key, "ready");
1393
+ return this.readBuffer(key, targetPath);
1394
+ }
1395
+ const downloadUrl = this.urlFor(key);
1396
+ this._status.set(key, "downloading");
1397
+ if (!existsSync(dirname(targetPath))) mkdirSync(dirname(targetPath), { recursive: true });
1398
+ const tempPath = `${targetPath}.tmp.${Date.now()}`;
1399
+ try {
1400
+ const { buffer, totalBytes } = await this.download(key, downloadUrl);
1401
+ if (buffer.byteLength === 0) throw new Error(`Downloaded vision model '${key}' is empty (0 bytes).`);
1402
+ if (this._verify) this.verifyBytes(key, buffer, downloadUrl);
1403
+ this._onProgress?.(key, buffer.byteLength, totalBytes || buffer.byteLength);
1404
+ await writeFile(tempPath, buffer);
1405
+ await rename(tempPath, targetPath);
1406
+ this._buffers.set(key, buffer);
1407
+ this._status.set(key, "ready");
1408
+ return buffer;
1409
+ } catch (error) {
1410
+ this._status.set(key, "error");
1411
+ if (existsSync(tempPath)) await unlink(tempPath).catch(() => void 0);
1412
+ throw error;
1413
+ }
1414
+ }
1415
+ /** Fetches with retries on transient failures (network errors, HTTP 429/5xx). */
1416
+ async download(key, url) {
1417
+ for (let attempt = 0;; attempt++) {
1418
+ let transient;
1419
+ try {
1420
+ const response = await this._fetch(url, { signal: this._timeoutMs ? AbortSignal.timeout(this._timeoutMs) : void 0 });
1421
+ if (response.ok) {
1422
+ const totalBytes = Number(response.headers.get("content-length") ?? 0);
1423
+ return {
1424
+ buffer: new Uint8Array(await response.arrayBuffer()),
1425
+ totalBytes
1426
+ };
1427
+ }
1428
+ const error = /* @__PURE__ */ new Error(`Failed to download vision model '${key}' from ${url}: HTTP ${response.status} ${response.statusText}`);
1429
+ if (response.status !== 429 && response.status < 500) throw error;
1430
+ transient = error;
1431
+ } catch (error) {
1432
+ if (!(error instanceof TypeError)) throw error;
1433
+ transient = error;
1434
+ }
1435
+ if (attempt >= this._retries) throw new Error(`Failed to download vision model '${key}' from ${url} after ${attempt + 1} attempts: ${String(transient)}`, { cause: transient });
1436
+ await new Promise((r) => setTimeout(r, this._retryDelayMs * 2 ** attempt));
1437
+ }
1438
+ }
1439
+ verifyBytes(key, buffer, url) {
1440
+ const desc = VISION_MODELS[key];
1441
+ if (buffer.byteLength !== desc.bytes) throw new Error(`Vision model '${key}' from ${url} is ${buffer.byteLength} bytes, expected ${desc.bytes}.`);
1442
+ const digest = createHash("sha256").update(buffer).digest("hex");
1443
+ if (digest !== desc.sha256) throw new Error(`Vision model '${key}' from ${url} failed its SHA-256 check (got ${digest}, expected ${desc.sha256}).`);
1444
+ }
1445
+ async readBuffer(key, targetPath) {
1446
+ const fileBuffer = await readFile(targetPath);
1447
+ const buffer = new Uint8Array(fileBuffer.buffer, fileBuffer.byteOffset, fileBuffer.byteLength);
1448
+ this._buffers.set(key, buffer);
1449
+ return buffer;
1450
+ }
1451
+ /** Drops in-memory buffers (model stays on disk for the next run). */
1452
+ clearMemoryCache() {
1453
+ this._buffers.clear();
1454
+ }
1455
+ };
1456
+ /** Neutral fallbacks so signal evaluators always have a well-formed frame to read. */
1457
+ function createNeutralObjectResult() {
1458
+ return {
1459
+ objects: [],
1460
+ rawDetections: []
1461
+ };
1462
+ }
1463
+ function createNeutralPoseResult() {
1464
+ return { people: [] };
1465
+ }
1466
+ /** 17 neutral COCO keypoints at frame center — used by pose signals when nobody is detected. */
1467
+ function createNeutralLandmarks() {
1468
+ const landmarks = [];
1469
+ for (let i = 0; i < 17; i++) landmarks.push({
1470
+ x: .5,
1471
+ y: .5,
1472
+ z: 0,
1473
+ visibility: 0
1474
+ });
1475
+ return landmarks;
1476
+ }
1477
+ let defaultProvider;
1478
+ /** The provider for a runner created without one. */
1479
+ function createDefaultSessionProvider() {
1480
+ return defaultProvider?.() ?? new NodeSessionProvider();
1481
+ }
1482
+ /** onnxruntime-node provider. The native module is imported lazily on first session. */
1483
+ var NodeSessionProvider = class {
1484
+ kind = "node";
1485
+ ortPromise;
1486
+ async ort() {
1487
+ if (!this.ortPromise) this.ortPromise = import("onnxruntime-node").catch((err) => {
1488
+ this.ortPromise = void 0;
1489
+ throw new Error(`onnxruntime-node failed to load (${String(err)}). Install it in the environment or provide a custom SessionProvider.`);
1490
+ });
1491
+ return this.ortPromise;
1492
+ }
1493
+ async createSession(modelBytes) {
1494
+ const ort = await this.ort();
1495
+ const session = await ort.InferenceSession.create(modelBytes, {
1496
+ executionProviders: ["cpu"],
1497
+ graphOptimizationLevel: "all"
1498
+ });
1499
+ return {
1500
+ run: async (input) => {
1501
+ const feeds = { [session.inputNames.includes(input.name) ? input.name : session.inputNames[0] ?? input.name]: new ort.Tensor("float32", input.data, [...input.dims]) };
1502
+ const results = await session.run(feeds);
1503
+ const outputs = {};
1504
+ for (const [name, tensor] of Object.entries(results)) outputs[name] = {
1505
+ data: tensor.data,
1506
+ dims: [...tensor.dims]
1507
+ };
1508
+ return outputs;
1509
+ },
1510
+ release: () => {
1511
+ session.release();
1512
+ }
1513
+ };
1514
+ }
1515
+ };
1516
+ /**
1517
+ * Lazy vision runner.
1518
+ *
1519
+ * `create()` performs ZERO I/O: no downloads, no sessions. Each inference method downloads
1520
+ * its model and warms its session on FIRST USE ONLY, then reuses them for the process
1521
+ * lifetime. `detect()` and `segment()` share one RTMDet-Ins forward pass per frame.
1522
+ * Call `close()` to release sessions.
1523
+ */
1524
+ var VisionRunner = class VisionRunner$1 {
1525
+ variant;
1526
+ confidence;
1527
+ classes;
1528
+ maskThreshold;
1529
+ featherRadius;
1530
+ _store;
1531
+ _provider;
1532
+ _sessions = /* @__PURE__ */ new Map();
1533
+ _tensorScratch = /* @__PURE__ */ new Map();
1534
+ /** One RTMDet-Ins pass per frame buffer, shared by detect() and segment(). */
1535
+ _instancePass;
1536
+ constructor(options) {
1537
+ this.variant = options.variant ?? "s";
1538
+ this.confidence = options.confidence ?? .3;
1539
+ this.classes = options.classes;
1540
+ this.maskThreshold = options.maskThreshold ?? .5;
1541
+ this.featherRadius = options.featherRadius;
1542
+ this._store = options.store ?? new VisionModelStore({
1543
+ modelsDir: options.modelsDir,
1544
+ baseUrl: options.baseUrl,
1545
+ timeoutMs: options.timeoutMs,
1546
+ onProgress: options.onProgress
1547
+ });
1548
+ this._provider = options.provider ?? createDefaultSessionProvider();
1549
+ }
1550
+ /** Cheap: no downloads, no sessions, no side effects. */
1551
+ static create(options = {}) {
1552
+ return new VisionRunner$1(options);
1553
+ }
1554
+ get modelsDir() {
1555
+ return this._store.modelsDir;
1556
+ }
1557
+ /** Every registry model keyed to its current download state (downloaded → "ready"). */
1558
+ get downloadStatus() {
1559
+ const statuses = /* @__PURE__ */ new Map();
1560
+ for (const key of Object.keys(VISION_MODELS)) statuses.set(key, "pending");
1561
+ for (const [key, status] of this._store.statuses()) statuses.set(key, status);
1562
+ return statuses;
1563
+ }
1564
+ /** The model serving `task` (matte depends on frame aspect; defaults to square). */
1565
+ modelKeyFor(task, aspect = 1) {
1566
+ return modelKeyFor(task, this.variant, aspect);
1567
+ }
1568
+ /**
1569
+ * Explicit warm-up: downloads + sessions for the given tasks, ahead of first use.
1570
+ * `matte` warms both aspect variants (the frame aspect is unknown until inference).
1571
+ */
1572
+ async preload(tasks = ["detect"]) {
1573
+ const keys = /* @__PURE__ */ new Set();
1574
+ for (const task of tasks) if (task === "matte") {
1575
+ keys.add("selfie-square");
1576
+ keys.add("selfie-landscape");
1577
+ } else keys.add(this.modelKeyFor(task));
1578
+ await this._store.preload([...keys]);
1579
+ await Promise.all([...keys].map((key) => this.session(key)));
1580
+ }
1581
+ /** COCO-80 boxes (sorted by score, source pixels). Masks are not decoded. */
1582
+ async detect(image) {
1583
+ const pass = await this.instancePass(image);
1584
+ return decodeRtmdetIns({
1585
+ ...pass.outputs,
1586
+ masks: void 0
1587
+ }, this.instanceDecodeOptions(image, pass.transform)).detections;
1588
+ }
1589
+ /** COCO-80 boxes plus a frame-aligned soft mask per instance. */
1590
+ async segment(image) {
1591
+ const pass = await this.instancePass(image);
1592
+ return decodeRtmdetIns(pass.outputs, this.instanceDecodeOptions(image, pass.transform));
1593
+ }
1594
+ /** COCO-17 keypoints per person (source pixels), highest score first. */
1595
+ async pose(image) {
1596
+ const key = this.modelKeyFor("pose");
1597
+ const { outputs, transform } = await this.infer(key, image);
1598
+ const dets = output(outputs, "dets", key);
1599
+ const keypoints = output(outputs, "keypoints", key);
1600
+ return decodeRtmo({
1601
+ dets: dets.data,
1602
+ keypoints: keypoints.data,
1603
+ count: dets.dims[1] ?? 0,
1604
+ keypointStride: keypoints.dims[3] ?? 3
1605
+ }, {
1606
+ confidence: this.confidence,
1607
+ transform,
1608
+ sourceWidth: image.width,
1609
+ sourceHeight: image.height
1610
+ });
1611
+ }
1612
+ /** Person-vs-background alpha for the whole frame (Selfie Segmenter). */
1613
+ async matte(image) {
1614
+ const key = this.modelKeyFor("matte", image.width / image.height);
1615
+ const [w, h] = VISION_MODELS[key].input;
1616
+ const { outputs } = await this.infer(key, image);
1617
+ return decodeSelfie(output(outputs, "alphas", key).data, w, h, {
1618
+ sourceWidth: image.width,
1619
+ sourceHeight: image.height,
1620
+ maskThreshold: this.maskThreshold,
1621
+ ...this.featherRadius !== void 0 ? { featherRadius: this.featherRadius } : {}
1622
+ });
1623
+ }
1624
+ close() {
1625
+ for (const promise of this._sessions.values()) promise.then((session) => session.release()).catch(() => void 0);
1626
+ this._sessions.clear();
1627
+ this._instancePass = void 0;
1628
+ this._store.clearMemoryCache();
1629
+ }
1630
+ instancePass(image) {
1631
+ if (this._instancePass?.frame === image.data) return this._instancePass.result;
1632
+ const key = this.modelKeyFor("segment");
1633
+ const result = this.infer(key, image).then(({ outputs, transform }) => {
1634
+ const dets = output(outputs, "dets", key);
1635
+ const masks = output(outputs, "masks", key);
1636
+ return {
1637
+ transform,
1638
+ outputs: {
1639
+ dets: dets.data,
1640
+ labels: output(outputs, "labels", key).data,
1641
+ masks: masks.data,
1642
+ count: dets.dims[1] ?? 0,
1643
+ maskHeight: masks.dims[masks.dims.length - 2] ?? 0,
1644
+ maskWidth: masks.dims[masks.dims.length - 1] ?? 0
1645
+ }
1646
+ };
1647
+ });
1648
+ this._instancePass = {
1649
+ frame: image.data,
1650
+ result
1651
+ };
1652
+ result.catch(() => {
1653
+ if (this._instancePass?.result === result) this._instancePass = void 0;
1654
+ });
1655
+ return result;
1656
+ }
1657
+ instanceDecodeOptions(image, transform) {
1658
+ return {
1659
+ confidence: this.confidence,
1660
+ classes: this.classes,
1661
+ classNames: COCO_CLASSES,
1662
+ transform,
1663
+ sourceWidth: image.width,
1664
+ sourceHeight: image.height,
1665
+ maskThreshold: this.maskThreshold,
1666
+ ...this.featherRadius !== void 0 ? { featherRadius: this.featherRadius } : {}
1667
+ };
1668
+ }
1669
+ async infer(key, image) {
1670
+ const session = await this.session(key);
1671
+ const desc = VISION_MODELS[key];
1672
+ const spec = preprocessSpec(desc.family, desc.input);
1673
+ const size = 3 * spec.width * spec.height;
1674
+ let scratch = this._tensorScratch.get(key);
1675
+ if (!scratch || scratch.length < size) {
1676
+ scratch = new Float32Array(size);
1677
+ this._tensorScratch.set(key, scratch);
1678
+ }
1679
+ const { tensor, transform } = imageToTensor(image, spec, scratch);
1680
+ return {
1681
+ outputs: await session.run({
1682
+ name: "input",
1683
+ data: tensor,
1684
+ dims: [
1685
+ 1,
1686
+ 3,
1687
+ spec.height,
1688
+ spec.width
1689
+ ]
1690
+ }),
1691
+ transform
1692
+ };
1693
+ }
1694
+ session(key) {
1695
+ let promise = this._sessions.get(key);
1696
+ if (!promise) {
1697
+ promise = this._store.ensure(key).then((bytes) => this._provider.createSession(bytes)).catch((err) => {
1698
+ this._sessions.delete(key);
1699
+ this._store.evict(key);
1700
+ throw err;
1701
+ });
1702
+ this._sessions.set(key, promise);
1703
+ }
1704
+ return promise;
1705
+ }
1706
+ };
1707
+ function output(outputs, name, key) {
1708
+ const tensor = outputs[name];
1709
+ if (!tensor) throw new Error(`Vision model '${key}' returned no '${name}' output (got: ${Object.keys(outputs).join(", ") || "none"}). Expected the ${VISION_MODELS[key].source}.`);
1710
+ return tensor;
1711
+ }
1712
+ /**
1713
+ * The frame's primary instance: the largest person when present, else the most confident
1714
+ * instance (lowest `detectionIndex` — detections are score-sorted). Size alone is a poor
1715
+ * signal without a person: the largest instance is usually a backdrop ("dining table").
1716
+ */
1717
+ function selectSubjectMask(masks) {
1718
+ let best;
1719
+ for (const m of masks) {
1720
+ if (!best) {
1721
+ best = m;
1722
+ continue;
1723
+ }
1724
+ const mIsPerson = isPerson(m);
1725
+ if (mIsPerson !== isPerson(best)) {
1726
+ if (mIsPerson) best = m;
1727
+ } else if (mIsPerson ? m.area > best.area : m.detectionIndex < best.detectionIndex) best = m;
1728
+ }
1729
+ return best;
1730
+ }
1731
+ function isPerson(mask) {
1732
+ return mask.category.toLowerCase() === "person";
1733
+ }
1734
+ /** Tight pixel bounds of a mask's non-zero alpha, or null when empty. */
1735
+ function maskBounds(mask) {
1736
+ const { mask: data, width, height } = mask;
1737
+ let x0 = width;
1738
+ let y0 = height;
1739
+ let x1 = -1;
1740
+ let y1 = -1;
1741
+ for (let y = 0; y < height; y++) {
1742
+ const row = y * width;
1743
+ for (let x = 0; x < width; x++) {
1744
+ if (data[row + x] === 0) continue;
1745
+ if (x < x0) x0 = x;
1746
+ if (x > x1) x1 = x;
1747
+ if (y < y0) y0 = y;
1748
+ if (y > y1) y1 = y;
1749
+ }
1750
+ }
1751
+ return x1 < 0 ? null : {
1752
+ x0,
1753
+ y0,
1754
+ x1,
1755
+ y1
1756
+ };
1757
+ }
1758
+ /**
1759
+ * Builds the full subject silhouette: the primary instance plus every comparably sized
1760
+ * instance whose bounds sit mostly inside or across it (`overlap` = intersection / smaller
1761
+ * box area; `maxGrowth` caps a part's box area relative to the subject's).
1762
+ *
1763
+ * COCO has no "clothing" class, so a flowing dress, a held guitar or a ridden bike comes back
1764
+ * as its own instance (often mislabeled) — merging them keeps the whole figure in the cutout.
1765
+ * The size cap keeps containers out: a small figure inside a tunnel or window detected as a
1766
+ * huge "clock" must not drag the whole frame into the subject.
1767
+ */
1768
+ function mergeSubjectMask(masks, overlap = .5, maxGrowth = 2) {
1769
+ const subject = selectSubjectMask(masks);
1770
+ if (!subject) return void 0;
1771
+ const sb = maskBounds(subject);
1772
+ if (!sb) return subject;
1773
+ const subjectArea = boundsArea(sb);
1774
+ const parts = masks.filter((m) => {
1775
+ if (m === subject) return false;
1776
+ const b = maskBounds(m);
1777
+ return b !== null && boundsArea(b) <= subjectArea * maxGrowth && boundsOverlap(sb, b) >= overlap;
1778
+ });
1779
+ if (parts.length === 0) return subject;
1780
+ const merged = subject.mask.slice();
1781
+ for (const part of parts) {
1782
+ const data = part.mask;
1783
+ for (let i = 0; i < merged.length; i++) if (data[i] > merged[i]) merged[i] = data[i];
1784
+ }
1785
+ let area = 0;
1786
+ for (let i = 0; i < merged.length; i++) area += merged[i];
1787
+ area = Math.round(area / 255);
1788
+ return {
1789
+ ...subject,
1790
+ mask: merged,
1791
+ area,
1792
+ coverage: area / (subject.width * subject.height)
1793
+ };
1794
+ }
1795
+ function boundsArea(b) {
1796
+ return (b.x1 - b.x0 + 1) * (b.y1 - b.y0 + 1);
1797
+ }
1798
+ function boundsOverlap(a, b) {
1799
+ const iw = Math.min(a.x1, b.x1) - Math.max(a.x0, b.x0) + 1;
1800
+ const ih = Math.min(a.y1, b.y1) - Math.max(a.y0, b.y0) + 1;
1801
+ if (iw <= 0 || ih <= 0) return 0;
1802
+ return iw * ih / Math.min(boundsArea(a), boundsArea(b));
1803
+ }
1804
+ var SpatialLandmarkTransformer = class {
1805
+ width;
1806
+ height;
1807
+ fovRad;
1808
+ focalDistance;
1809
+ constructor(camera) {
1810
+ this.width = camera.width;
1811
+ this.height = camera.height;
1812
+ this.fovRad = (camera.fov ?? 60) * Math.PI / 180;
1813
+ this.focalDistance = camera.cameraDistance ?? this.height / 2 / Math.tan(this.fovRad / 2);
1814
+ }
1815
+ /**
1816
+ * Projects a normalized landmark [0, 1] into 2D/3D canvas coordinates.
1817
+ */
1818
+ projectNormalizedLandmark(landmark, offsetZ = 0) {
1819
+ const screenX = landmark.x * this.width;
1820
+ const screenY = landmark.y * this.height;
1821
+ const rawZ = landmark.z * this.width + offsetZ;
1822
+ const effectiveZ = Math.min(rawZ, this.focalDistance - 10);
1823
+ const scale = this.focalDistance / (this.focalDistance - effectiveZ);
1824
+ return {
1825
+ x: screenX,
1826
+ y: screenY,
1827
+ z: rawZ,
1828
+ scale: Math.max(.1, scale)
1829
+ };
1830
+ }
1831
+ /**
1832
+ * Projects a metric world landmark [X_w, Y_w, Z_w] in meters into canvas space.
1833
+ */
1834
+ projectWorldLandmark(worldLandmark, subjectDistanceMeters = 2, scaleMetersToPixels = 800) {
1835
+ const centerX = this.width / 2;
1836
+ const centerY = this.height / 2;
1837
+ const worldX = worldLandmark.x * scaleMetersToPixels;
1838
+ const worldY = worldLandmark.y * scaleMetersToPixels;
1839
+ const worldZ = (worldLandmark.z + subjectDistanceMeters) * scaleMetersToPixels;
1840
+ const effectiveZ = Math.min(worldZ, this.focalDistance - 10);
1841
+ const scale = this.focalDistance / (this.focalDistance - effectiveZ);
1842
+ return {
1843
+ x: centerX + worldX * scale,
1844
+ y: centerY + worldY * scale,
1845
+ z: worldZ,
1846
+ scale: Math.max(.1, scale)
1847
+ };
1848
+ }
1849
+ };
1850
+ function getPersonBoundingBox(person) {
1851
+ if (person.boundingBox && person.boundingBox.width > 0 && person.boundingBox.height > 0) return {
1852
+ originX: person.boundingBox.originX,
1853
+ originY: person.boundingBox.originY,
1854
+ width: person.boundingBox.width,
1855
+ height: person.boundingBox.height
1856
+ };
1857
+ let minX = Infinity;
1858
+ let minY = Infinity;
1859
+ let maxX = -Infinity;
1860
+ let maxY = -Infinity;
1861
+ let validCount = 0;
1862
+ for (const kp of person.keypoints) if (kp.visibility > .1) {
1863
+ validCount++;
1864
+ if (kp.x < minX) minX = kp.x;
1865
+ if (kp.y < minY) minY = kp.y;
1866
+ if (kp.x > maxX) maxX = kp.x;
1867
+ if (kp.y > maxY) maxY = kp.y;
1868
+ }
1869
+ if (validCount === 0 || minX >= maxX || minY >= maxY) return {
1870
+ originX: person.boundingBox?.originX ?? 0,
1871
+ originY: person.boundingBox?.originY ?? 0,
1872
+ width: person.boundingBox?.width ?? 0,
1873
+ height: person.boundingBox?.height ?? 0
1874
+ };
1875
+ return {
1876
+ originX: minX,
1877
+ originY: minY,
1878
+ width: maxX - minX,
1879
+ height: maxY - minY
1880
+ };
1881
+ }
1882
+ /**
1883
+ * Matches detected pose people with active person tracks using greedy bipartite IoU.
1884
+ *
1885
+ * Annotates each person with the matched trackId.
1886
+ */
1887
+ function matchPoseToTracks(people, trackedObjects, minIoU = .15) {
1888
+ if (people.length === 0) return [];
1889
+ const personTracks = trackedObjects.filter((o) => o.category.toLowerCase() === "person" && o.active);
1890
+ if (personTracks.length === 0) return [...people];
1891
+ const candidateMatches = [];
1892
+ for (let p = 0; p < people.length; p++) {
1893
+ const person = people[p];
1894
+ if (person.trackId !== void 0) continue;
1895
+ const pBox = getPersonBoundingBox(person);
1896
+ const isNormalized = pBox.width <= 1.05 && pBox.height <= 1.05;
1897
+ for (const trk of personTracks) {
1898
+ const iou = computeIoU(pBox, isNormalized && trk.boundingBox.normalizedWidth > 0 ? {
1899
+ originX: trk.boundingBox.normalizedX,
1900
+ originY: trk.boundingBox.normalizedY,
1901
+ width: trk.boundingBox.normalizedWidth,
1902
+ height: trk.boundingBox.normalizedHeight
1903
+ } : trk.boundingBox);
1904
+ if (iou >= minIoU) candidateMatches.push({
1905
+ personIdx: p,
1906
+ trackId: trk.trackId,
1907
+ iou
1908
+ });
1909
+ }
1910
+ }
1911
+ candidateMatches.sort((a, b) => b.iou - a.iou);
1912
+ const assignedPeople = /* @__PURE__ */ new Set();
1913
+ const assignedTracks = /* @__PURE__ */ new Set();
1914
+ const personTrackMap = /* @__PURE__ */ new Map();
1915
+ for (const match of candidateMatches) {
1916
+ if (assignedPeople.has(match.personIdx) || assignedTracks.has(match.trackId)) continue;
1917
+ assignedPeople.add(match.personIdx);
1918
+ assignedTracks.add(match.trackId);
1919
+ personTrackMap.set(match.personIdx, match.trackId);
1920
+ }
1921
+ if (people.length === 1 && personTracks.length === 1 && !personTrackMap.has(0) && people[0].trackId === void 0) personTrackMap.set(0, personTracks[0].trackId);
1922
+ return people.map((person, idx) => {
1923
+ if (person.trackId !== void 0) return person;
1924
+ const trackId = personTrackMap.get(idx);
1925
+ return trackId !== void 0 ? {
1926
+ ...person,
1927
+ trackId
1928
+ } : person;
1929
+ });
1930
+ }
1931
+ var VisionBundle = class {
1932
+ objects;
1933
+ poseLandmarks;
1934
+ masks;
1935
+ segmentation;
1936
+ classes;
1937
+ poseLandmarksTensor;
1938
+ objectsTensor;
1939
+ masksTensor;
1940
+ /** Per-class detection histogram [nc] — top-level convenience mirror of `classes.histogram`. */
1941
+ histogramTensor;
1942
+ _totalFrames;
1943
+ _fps;
1944
+ _width;
1945
+ _height;
1946
+ _transformer;
1947
+ _stencilTexture;
1948
+ _poseCache = /* @__PURE__ */ new Map();
1949
+ _objectCache = /* @__PURE__ */ new Map();
1950
+ _maskCache = /* @__PURE__ */ new Map();
1951
+ _matteCache = /* @__PURE__ */ new Map();
1952
+ _poseLmCache = /* @__PURE__ */ new Map();
1953
+ _trackCache = /* @__PURE__ */ new Map();
1954
+ _categoryCache = /* @__PURE__ */ new Map();
1955
+ _classCache = /* @__PURE__ */ new Map();
1956
+ _maskTrackCache = /* @__PURE__ */ new Map();
1957
+ _registeredSignals = /* @__PURE__ */ new Set();
1958
+ constructor(options = {}) {
1959
+ this._totalFrames = options.totalFrames ?? 120;
1960
+ this._fps = options.fps ?? 24;
1961
+ this._width = options.width ?? 1920;
1962
+ this._height = options.height ?? 1080;
1963
+ this._transformer = new SpatialLandmarkTransformer({
1964
+ width: this._width,
1965
+ height: this._height,
1966
+ fov: options.cameraFov ?? 60
1967
+ });
1968
+ const self = this;
1969
+ const programmaticSignal$1 = (evaluator, sigOptions) => {
1970
+ const sig = programmaticSignal(evaluator, sigOptions);
1971
+ this._registeredSignals.add(sig);
1972
+ return sig;
1973
+ };
1974
+ const getPrimaryLandmarks = (frame) => {
1975
+ return this.getPoseResult(frame).people[0]?.keypoints ?? createNeutralLandmarks();
1976
+ };
1977
+ const createPoseCoord = (index, label) => {
1978
+ const getLm = (frame) => getPrimaryLandmarks(frame)[index] ?? {
1979
+ x: .5,
1980
+ y: .5,
1981
+ z: 0
1982
+ };
1983
+ return {
1984
+ x: programmaticSignal$1((ctx) => getLm(ctx.frame).x, {
1985
+ fps: this._fps,
1986
+ label: `${label}_x`
1987
+ }),
1988
+ y: programmaticSignal$1((ctx) => getLm(ctx.frame).y, {
1989
+ fps: this._fps,
1990
+ label: `${label}_y`
1991
+ }),
1992
+ z: programmaticSignal$1((ctx) => getLm(ctx.frame).z ?? 0, {
1993
+ fps: this._fps,
1994
+ label: `${label}_z`
1995
+ }),
1996
+ screenX: programmaticSignal$1((ctx) => this._transformer.projectNormalizedLandmark(getLm(ctx.frame)).x, {
1997
+ fps: this._fps,
1998
+ label: `${label}_screenX`
1999
+ }),
2000
+ screenY: programmaticSignal$1((ctx) => this._transformer.projectNormalizedLandmark(getLm(ctx.frame)).y, {
2001
+ fps: this._fps,
2002
+ label: `${label}_screenY`
2003
+ })
2004
+ };
2005
+ };
2006
+ const getOrCreatePoseLm = (index) => {
2007
+ let sig = this._poseLmCache.get(index);
2008
+ if (!sig) {
2009
+ sig = createPoseCoord(index, COCO17_KEYPOINT_NAMES[index] ?? `kpt_${index}`);
2010
+ this._poseLmCache.set(index, sig);
2011
+ }
2012
+ return sig;
2013
+ };
2014
+ const poseNamed = {
2015
+ nose: getOrCreatePoseLm(COCO17_KEYPOINTS.NOSE),
2016
+ leftEye: getOrCreatePoseLm(COCO17_KEYPOINTS.LEFT_EYE),
2017
+ rightEye: getOrCreatePoseLm(COCO17_KEYPOINTS.RIGHT_EYE),
2018
+ leftEar: getOrCreatePoseLm(COCO17_KEYPOINTS.LEFT_EAR),
2019
+ rightEar: getOrCreatePoseLm(COCO17_KEYPOINTS.RIGHT_EAR),
2020
+ shoulder: getOrCreatePoseLm(COCO17_KEYPOINTS.LEFT_SHOULDER),
2021
+ leftShoulder: getOrCreatePoseLm(COCO17_KEYPOINTS.LEFT_SHOULDER),
2022
+ rightShoulder: getOrCreatePoseLm(COCO17_KEYPOINTS.RIGHT_SHOULDER),
2023
+ leftElbow: getOrCreatePoseLm(COCO17_KEYPOINTS.LEFT_ELBOW),
2024
+ rightElbow: getOrCreatePoseLm(COCO17_KEYPOINTS.RIGHT_ELBOW),
2025
+ leftWrist: getOrCreatePoseLm(COCO17_KEYPOINTS.LEFT_WRIST),
2026
+ rightWrist: getOrCreatePoseLm(COCO17_KEYPOINTS.RIGHT_WRIST),
2027
+ leftHip: getOrCreatePoseLm(COCO17_KEYPOINTS.LEFT_HIP),
2028
+ rightHip: getOrCreatePoseLm(COCO17_KEYPOINTS.RIGHT_HIP),
2029
+ leftKnee: getOrCreatePoseLm(COCO17_KEYPOINTS.LEFT_KNEE),
2030
+ rightKnee: getOrCreatePoseLm(COCO17_KEYPOINTS.RIGHT_KNEE),
2031
+ leftAnkle: getOrCreatePoseLm(COCO17_KEYPOINTS.LEFT_ANKLE),
2032
+ rightAnkle: getOrCreatePoseLm(COCO17_KEYPOINTS.RIGHT_ANKLE),
2033
+ get: getOrCreatePoseLm
2034
+ };
2035
+ this.poseLandmarks = new Proxy(poseNamed, { get(target, prop, receiver) {
2036
+ if (typeof prop === "string" && /^\d+$/.test(prop)) return target.get(Number(prop));
2037
+ if (typeof prop === "number") return target.get(prop);
2038
+ return Reflect.get(target, prop, receiver);
2039
+ } });
2040
+ const neutralTrack = (trackId, category = "unknown") => ({
2041
+ trackId,
2042
+ category,
2043
+ score: 0,
2044
+ boundingBox: {
2045
+ originX: this._width / 2,
2046
+ originY: this._height / 2,
2047
+ width: 0,
2048
+ height: 0,
2049
+ normalizedX: .5,
2050
+ normalizedY: .5,
2051
+ normalizedWidth: 0,
2052
+ normalizedHeight: 0
2053
+ },
2054
+ centerX: this._width / 2,
2055
+ centerY: this._height / 2,
2056
+ normalizedCenterX: .5,
2057
+ normalizedCenterY: .5,
2058
+ velocity: {
2059
+ vx: 0,
2060
+ vy: 0
2061
+ },
2062
+ speed: 0,
2063
+ age: 0,
2064
+ hits: 0,
2065
+ active: false,
2066
+ isCoasting: false
2067
+ });
2068
+ const createAnchor = (getPos, label) => {
2069
+ const screenX = programmaticSignal$1((ctx) => getPos(ctx.frame).sx, {
2070
+ fps: this._fps,
2071
+ label: `${label}_screenX`
2072
+ });
2073
+ const screenY = programmaticSignal$1((ctx) => getPos(ctx.frame).sy, {
2074
+ fps: this._fps,
2075
+ label: `${label}_screenY`
2076
+ });
2077
+ return {
2078
+ x: programmaticSignal$1((ctx) => this._width > 0 ? getPos(ctx.frame).sx / this._width : 0, {
2079
+ fps: this._fps,
2080
+ label: `${label}_x`
2081
+ }),
2082
+ y: programmaticSignal$1((ctx) => this._height > 0 ? getPos(ctx.frame).sy / this._height : 0, {
2083
+ fps: this._fps,
2084
+ label: `${label}_y`
2085
+ }),
2086
+ z: programmaticSignal$1(() => 0, {
2087
+ fps: this._fps,
2088
+ label: `${label}_z`
2089
+ }),
2090
+ screenX,
2091
+ screenY
2092
+ };
2093
+ };
2094
+ const buildTrackPoseSignals = (resolveTrack, label, fixedId) => {
2095
+ const resolvePerson = (frame) => {
2096
+ const poseRes = self.getPoseResult(frame);
2097
+ if (poseRes.people.length === 0) return void 0;
2098
+ const trk = resolveTrack(frame);
2099
+ const targetId = fixedId ?? trk.trackId;
2100
+ const byId = poseRes.people.find((p) => p.trackId === targetId);
2101
+ if (byId) return byId;
2102
+ if (!trk.active && poseRes.people.length > 1) return void 0;
2103
+ if (poseRes.people.length === 1) return poseRes.people[0];
2104
+ let bestPerson;
2105
+ let bestIoU = .1;
2106
+ for (const p of poseRes.people) {
2107
+ const pBox = getPersonBoundingBox(p);
2108
+ const iou = computeIoU(pBox, pBox.width <= 1.05 && pBox.height <= 1.05 && trk.boundingBox.normalizedWidth > 0 ? {
2109
+ originX: trk.boundingBox.normalizedX,
2110
+ originY: trk.boundingBox.normalizedY,
2111
+ width: trk.boundingBox.normalizedWidth,
2112
+ height: trk.boundingBox.normalizedHeight
2113
+ } : trk.boundingBox);
2114
+ if (iou > bestIoU) {
2115
+ bestIoU = iou;
2116
+ bestPerson = p;
2117
+ }
2118
+ }
2119
+ return bestPerson;
2120
+ };
2121
+ const hasPose = programmaticSignal$1((ctx) => resolvePerson(ctx.frame) ? 1 : 0, {
2122
+ fps: this._fps,
2123
+ label: `${label}_hasPose`
2124
+ });
2125
+ const createCoord = (index, kptName) => {
2126
+ const getLm = (frame) => {
2127
+ const kp = resolvePerson(frame)?.keypoints[index];
2128
+ if (kp && kp.visibility > .05) return {
2129
+ x: kp.x,
2130
+ y: kp.y,
2131
+ z: 0
2132
+ };
2133
+ const trk = resolveTrack(frame);
2134
+ return {
2135
+ x: trk.normalizedCenterX,
2136
+ y: trk.normalizedCenterY,
2137
+ z: 0
2138
+ };
2139
+ };
2140
+ return {
2141
+ x: programmaticSignal$1((ctx) => getLm(ctx.frame).x, {
2142
+ fps: this._fps,
2143
+ label: `${label}_${kptName}_x`
2144
+ }),
2145
+ y: programmaticSignal$1((ctx) => getLm(ctx.frame).y, {
2146
+ fps: this._fps,
2147
+ label: `${label}_${kptName}_y`
2148
+ }),
2149
+ z: programmaticSignal$1((ctx) => getLm(ctx.frame).z ?? 0, {
2150
+ fps: this._fps,
2151
+ label: `${label}_${kptName}_z`
2152
+ }),
2153
+ screenX: programmaticSignal$1((ctx) => self._transformer.projectNormalizedLandmark(getLm(ctx.frame)).x, {
2154
+ fps: this._fps,
2155
+ label: `${label}_${kptName}_screenX`
2156
+ }),
2157
+ screenY: programmaticSignal$1((ctx) => self._transformer.projectNormalizedLandmark(getLm(ctx.frame)).y, {
2158
+ fps: this._fps,
2159
+ label: `${label}_${kptName}_screenY`
2160
+ })
2161
+ };
2162
+ };
2163
+ const coordsCache = /* @__PURE__ */ new Map();
2164
+ const getOrCreateCoord = (idx) => {
2165
+ let c = coordsCache.get(idx);
2166
+ if (!c) {
2167
+ c = createCoord(idx, COCO17_KEYPOINT_NAMES[idx] ?? `kpt_${idx}`);
2168
+ coordsCache.set(idx, c);
2169
+ }
2170
+ return c;
2171
+ };
2172
+ const namedPose = {
2173
+ hasPose,
2174
+ wristSpeed: programmaticSignal$1((ctx) => {
2175
+ const f = ctx.frame;
2176
+ const curr = resolvePerson(f);
2177
+ if (!curr) return 0;
2178
+ const prev = resolvePerson(Math.max(0, f - 1));
2179
+ const currKp = curr.keypoints;
2180
+ const prevKp = prev?.keypoints;
2181
+ const rwCurr = currKp[COCO17_KEYPOINTS.RIGHT_WRIST];
2182
+ const rwPrev = prevKp?.[COCO17_KEYPOINTS.RIGHT_WRIST];
2183
+ let rwSpeed = 0;
2184
+ if (rwCurr && rwPrev && rwCurr.visibility > .1 && rwPrev.visibility > .1) {
2185
+ const dx = (rwCurr.x - rwPrev.x) * this._width;
2186
+ const dy = (rwCurr.y - rwPrev.y) * this._height;
2187
+ rwSpeed = Math.sqrt(dx * dx + dy * dy) * this._fps;
2188
+ }
2189
+ const lwCurr = currKp[COCO17_KEYPOINTS.LEFT_WRIST];
2190
+ const lwPrev = prevKp?.[COCO17_KEYPOINTS.LEFT_WRIST];
2191
+ let lwSpeed = 0;
2192
+ if (lwCurr && lwPrev && lwCurr.visibility > .1 && lwPrev.visibility > .1) {
2193
+ const dx = (lwCurr.x - lwPrev.x) * this._width;
2194
+ const dy = (lwCurr.y - lwPrev.y) * this._height;
2195
+ lwSpeed = Math.sqrt(dx * dx + dy * dy) * this._fps;
2196
+ }
2197
+ return Math.max(rwSpeed, lwSpeed);
2198
+ }, {
2199
+ fps: this._fps,
2200
+ label: `${label}_wristSpeed`
2201
+ }),
2202
+ handRaised: programmaticSignal$1((ctx) => {
2203
+ const p = resolvePerson(ctx.frame);
2204
+ if (!p) return 0;
2205
+ const k = p.keypoints;
2206
+ const lw = k[COCO17_KEYPOINTS.LEFT_WRIST];
2207
+ const ls = k[COCO17_KEYPOINTS.LEFT_SHOULDER];
2208
+ const rw = k[COCO17_KEYPOINTS.RIGHT_WRIST];
2209
+ const rs = k[COCO17_KEYPOINTS.RIGHT_SHOULDER];
2210
+ const lRaised = lw && ls && lw.visibility > .1 && ls.visibility > .1 && lw.y < ls.y;
2211
+ const rRaised = rw && rs && rw.visibility > .1 && rs.visibility > .1 && rw.y < rs.y;
2212
+ return lRaised || rRaised ? 1 : 0;
2213
+ }, {
2214
+ fps: this._fps,
2215
+ label: `${label}_handRaised`
2216
+ }),
2217
+ bodyTiltAngle: programmaticSignal$1((ctx) => {
2218
+ const p = resolvePerson(ctx.frame);
2219
+ if (!p) return 0;
2220
+ const k = p.keypoints;
2221
+ const ls = k[COCO17_KEYPOINTS.LEFT_SHOULDER];
2222
+ const rs = k[COCO17_KEYPOINTS.RIGHT_SHOULDER];
2223
+ const lh = k[COCO17_KEYPOINTS.LEFT_HIP];
2224
+ const rh = k[COCO17_KEYPOINTS.RIGHT_HIP];
2225
+ if (!ls || !rs || !lh || !rh) return 0;
2226
+ const sx = (ls.x + rs.x) / 2 * this._width;
2227
+ const sy = (ls.y + rs.y) / 2 * this._height;
2228
+ const hx = (lh.x + rh.x) / 2 * this._width;
2229
+ const hy = (lh.y + rh.y) / 2 * this._height;
2230
+ const dx = sx - hx;
2231
+ const dy = sy - hy;
2232
+ if (dx === 0 && dy === 0) return 0;
2233
+ return Math.atan2(dx, -dy);
2234
+ }, {
2235
+ fps: this._fps,
2236
+ label: `${label}_bodyTiltAngle`
2237
+ }),
2238
+ nose: getOrCreateCoord(COCO17_KEYPOINTS.NOSE),
2239
+ leftEye: getOrCreateCoord(COCO17_KEYPOINTS.LEFT_EYE),
2240
+ rightEye: getOrCreateCoord(COCO17_KEYPOINTS.RIGHT_EYE),
2241
+ leftEar: getOrCreateCoord(COCO17_KEYPOINTS.LEFT_EAR),
2242
+ rightEar: getOrCreateCoord(COCO17_KEYPOINTS.RIGHT_EAR),
2243
+ shoulder: getOrCreateCoord(COCO17_KEYPOINTS.LEFT_SHOULDER),
2244
+ leftShoulder: getOrCreateCoord(COCO17_KEYPOINTS.LEFT_SHOULDER),
2245
+ rightShoulder: getOrCreateCoord(COCO17_KEYPOINTS.RIGHT_SHOULDER),
2246
+ leftElbow: getOrCreateCoord(COCO17_KEYPOINTS.LEFT_ELBOW),
2247
+ rightElbow: getOrCreateCoord(COCO17_KEYPOINTS.RIGHT_ELBOW),
2248
+ leftWrist: getOrCreateCoord(COCO17_KEYPOINTS.LEFT_WRIST),
2249
+ rightWrist: getOrCreateCoord(COCO17_KEYPOINTS.RIGHT_WRIST),
2250
+ leftHip: getOrCreateCoord(COCO17_KEYPOINTS.LEFT_HIP),
2251
+ rightHip: getOrCreateCoord(COCO17_KEYPOINTS.RIGHT_HIP),
2252
+ leftKnee: getOrCreateCoord(COCO17_KEYPOINTS.LEFT_KNEE),
2253
+ rightKnee: getOrCreateCoord(COCO17_KEYPOINTS.RIGHT_KNEE),
2254
+ leftAnkle: getOrCreateCoord(COCO17_KEYPOINTS.LEFT_ANKLE),
2255
+ rightAnkle: getOrCreateCoord(COCO17_KEYPOINTS.RIGHT_ANKLE),
2256
+ get: getOrCreateCoord
2257
+ };
2258
+ return new Proxy(namedPose, { get(target, prop, receiver) {
2259
+ if (typeof prop === "string" && /^\d+$/.test(prop)) return target.get(Number(prop));
2260
+ if (typeof prop === "number") return target.get(prop);
2261
+ return Reflect.get(target, prop, receiver);
2262
+ } });
2263
+ };
2264
+ const buildTrackSignals = (resolve$1, label, fixedCategory, fixedId) => {
2265
+ const getBox = (f) => resolve$1(f).boundingBox;
2266
+ const pose = buildTrackPoseSignals(resolve$1, `${label}_pose`, fixedId);
2267
+ const anchor = (name, sx, sy) => createAnchor((f) => ({
2268
+ sx: sx(getBox(f)),
2269
+ sy: sy(getBox(f))
2270
+ }), `${label}_${name}`);
2271
+ const center = anchor("center", (b) => b.originX + b.width / 2, (b) => b.originY + b.height / 2);
2272
+ const speed = programmaticSignal$1((ctx) => resolve$1(ctx.frame).speed, {
2273
+ fps: this._fps,
2274
+ label: `${label}_speed`
2275
+ });
2276
+ return {
2277
+ get trackId() {
2278
+ return fixedId ?? resolve$1(0).trackId;
2279
+ },
2280
+ get category() {
2281
+ return fixedCategory ?? resolve$1(0).category;
2282
+ },
2283
+ bounds: {
2284
+ x: programmaticSignal$1((ctx) => getBox(ctx.frame).normalizedX, {
2285
+ fps: this._fps,
2286
+ label: `${label}_normX`
2287
+ }),
2288
+ y: programmaticSignal$1((ctx) => getBox(ctx.frame).normalizedY, {
2289
+ fps: this._fps,
2290
+ label: `${label}_normY`
2291
+ }),
2292
+ width: programmaticSignal$1((ctx) => getBox(ctx.frame).normalizedWidth, {
2293
+ fps: this._fps,
2294
+ label: `${label}_normWidth`
2295
+ }),
2296
+ height: programmaticSignal$1((ctx) => getBox(ctx.frame).normalizedHeight, {
2297
+ fps: this._fps,
2298
+ label: `${label}_normHeight`
2299
+ }),
2300
+ screenX: programmaticSignal$1((ctx) => getBox(ctx.frame).originX, {
2301
+ fps: this._fps,
2302
+ label: `${label}_screenX`
2303
+ }),
2304
+ screenY: programmaticSignal$1((ctx) => getBox(ctx.frame).originY, {
2305
+ fps: this._fps,
2306
+ label: `${label}_screenY`
2307
+ }),
2308
+ screenWidth: programmaticSignal$1((ctx) => getBox(ctx.frame).width, {
2309
+ fps: this._fps,
2310
+ label: `${label}_screenWidth`
2311
+ }),
2312
+ screenHeight: programmaticSignal$1((ctx) => getBox(ctx.frame).height, {
2313
+ fps: this._fps,
2314
+ label: `${label}_screenHeight`
2315
+ }),
2316
+ aspectRatio: programmaticSignal$1((ctx) => {
2317
+ const b = getBox(ctx.frame);
2318
+ return b.height > 0 ? b.width / b.height : 1;
2319
+ }, {
2320
+ fps: this._fps,
2321
+ label: `${label}_aspectRatio`
2322
+ }),
2323
+ area: programmaticSignal$1((ctx) => {
2324
+ const b = getBox(ctx.frame);
2325
+ return b.width * b.height;
2326
+ }, {
2327
+ fps: this._fps,
2328
+ label: `${label}_area`
2329
+ })
2330
+ },
2331
+ anchors: {
2332
+ topLeft: anchor("topLeft", (b) => b.originX, (b) => b.originY),
2333
+ topCenter: anchor("topCenter", (b) => b.originX + b.width / 2, (b) => b.originY),
2334
+ topRight: anchor("topRight", (b) => b.originX + b.width, (b) => b.originY),
2335
+ centerLeft: anchor("centerLeft", (b) => b.originX, (b) => b.originY + b.height / 2),
2336
+ center,
2337
+ centerRight: anchor("centerRight", (b) => b.originX + b.width, (b) => b.originY + b.height / 2),
2338
+ bottomLeft: anchor("bottomLeft", (b) => b.originX, (b) => b.originY + b.height),
2339
+ bottomCenter: anchor("bottomCenter", (b) => b.originX + b.width / 2, (b) => b.originY + b.height),
2340
+ bottomRight: anchor("bottomRight", (b) => b.originX + b.width, (b) => b.originY + b.height)
2341
+ },
2342
+ kinematics: {
2343
+ vx: programmaticSignal$1((ctx) => resolve$1(ctx.frame).velocity.vx, {
2344
+ fps: this._fps,
2345
+ label: `${label}_vx`
2346
+ }),
2347
+ vy: programmaticSignal$1((ctx) => resolve$1(ctx.frame).velocity.vy, {
2348
+ fps: this._fps,
2349
+ label: `${label}_vy`
2350
+ }),
2351
+ speed,
2352
+ acceleration: speed.velocity(1),
2353
+ headingRad: programmaticSignal$1((ctx) => {
2354
+ const v = resolve$1(ctx.frame).velocity;
2355
+ return Math.atan2(v.vy, v.vx);
2356
+ }, {
2357
+ fps: this._fps,
2358
+ label: `${label}_headingRad`
2359
+ }),
2360
+ headingDeg: programmaticSignal$1((ctx) => {
2361
+ const v = resolve$1(ctx.frame).velocity;
2362
+ return Math.atan2(v.vy, v.vx) * 180 / Math.PI;
2363
+ }, {
2364
+ fps: this._fps,
2365
+ label: `${label}_headingDeg`
2366
+ })
2367
+ },
2368
+ confidence: programmaticSignal$1((ctx) => resolve$1(ctx.frame).score, {
2369
+ fps: this._fps,
2370
+ label: `${label}_score`
2371
+ }),
2372
+ active: programmaticSignal$1((ctx) => resolve$1(ctx.frame).active ? 1 : 0, {
2373
+ fps: this._fps,
2374
+ label: `${label}_active`
2375
+ }),
2376
+ isCoasting: programmaticSignal$1((ctx) => resolve$1(ctx.frame).isCoasting ? 1 : 0, {
2377
+ fps: this._fps,
2378
+ label: `${label}_isCoasting`
2379
+ }),
2380
+ age: programmaticSignal$1((ctx) => resolve$1(ctx.frame).age, {
2381
+ fps: this._fps,
2382
+ label: `${label}_age`
2383
+ }),
2384
+ pose,
2385
+ center,
2386
+ topCenter: this[`__pending`],
2387
+ bottomCenter: center,
2388
+ topLeft: center,
2389
+ bottomLeft: center
2390
+ };
2391
+ };
2392
+ const buildTrack = (resolve$1, label, fixedCategory, fixedId) => {
2393
+ const sig = buildTrackSignals(resolve$1, label, fixedCategory, fixedId);
2394
+ const anchors = sig.anchors;
2395
+ Object.assign(sig, {
2396
+ topCenter: anchors.topCenter,
2397
+ bottomCenter: anchors.bottomCenter,
2398
+ topLeft: anchors.topLeft,
2399
+ bottomLeft: anchors.bottomLeft
2400
+ });
2401
+ return sig;
2402
+ };
2403
+ const getTrackById = (trackId) => {
2404
+ let sig = this._trackCache.get(trackId);
2405
+ if (!sig) {
2406
+ sig = buildTrack((frame) => this.getObjectResult(frame).objects.find((o) => o.trackId === trackId) ?? neutralTrack(trackId), `track_${trackId}`, void 0, trackId);
2407
+ this._trackCache.set(trackId, sig);
2408
+ }
2409
+ return sig;
2410
+ };
2411
+ const getTrackByCategory = (category, rank = 0) => {
2412
+ const cacheKey = `${category.toLowerCase()}_${rank}`;
2413
+ let sig = this._categoryCache.get(cacheKey);
2414
+ if (!sig) {
2415
+ sig = buildTrack((frame) => {
2416
+ return this.getObjectResult(frame).objects.filter((o) => o.category.toLowerCase() === category.toLowerCase() && o.active)[rank] ?? neutralTrack(0, category);
2417
+ }, `cat_${category}_${rank}`, category);
2418
+ this._categoryCache.set(cacheKey, sig);
2419
+ }
2420
+ return sig;
2421
+ };
2422
+ const primaryTrack = buildTrack((frame) => {
2423
+ const res = this.getObjectResult(frame);
2424
+ if (res.objects.length === 0) return neutralTrack(0);
2425
+ let best = res.objects[0];
2426
+ for (let i = 1; i < res.objects.length; i++) if (res.objects[i].score > best.score) best = res.objects[i];
2427
+ return best;
2428
+ }, "primary_object");
2429
+ const activeCategories = (frame) => {
2430
+ const set = /* @__PURE__ */ new Set();
2431
+ for (const o of this.getObjectResult(frame).objects) if (o.active) set.add(o.category);
2432
+ return Array.from(set).sort();
2433
+ };
2434
+ const buildClassSignals = (category) => {
2435
+ const lower = category.toLowerCase();
2436
+ let sig = this._classCache.get(lower);
2437
+ if (sig) return sig;
2438
+ sig = {
2439
+ count: programmaticSignal$1((ctx) => this.getObjectResult(ctx.frame).objects.filter((o) => o.active && o.category.toLowerCase() === lower).length, {
2440
+ fps: this._fps,
2441
+ label: `class_${lower}_count`
2442
+ }),
2443
+ maxConfidence: programmaticSignal$1((ctx) => {
2444
+ let max = 0;
2445
+ for (const o of this.getObjectResult(ctx.frame).objects) if (o.active && o.category.toLowerCase() === lower && o.score > max) max = o.score;
2446
+ return max;
2447
+ }, {
2448
+ fps: this._fps,
2449
+ label: `class_${lower}_maxConf`
2450
+ }),
2451
+ present: programmaticSignal$1((ctx) => this.getObjectResult(ctx.frame).objects.some((o) => o.active && o.category.toLowerCase() === lower) ? 1 : 0, {
2452
+ fps: this._fps,
2453
+ label: `class_${lower}_present`
2454
+ }),
2455
+ primary: getTrackByCategory(category)
2456
+ };
2457
+ this._classCache.set(lower, sig);
2458
+ return sig;
2459
+ };
2460
+ this.classes = {
2461
+ get: buildClassSignals,
2462
+ get names() {
2463
+ return activeCategories(frameSignal.value);
2464
+ },
2465
+ histogram: {
2466
+ get: (ctx) => self.getClassHistogramTensor(ctx?.frame ?? frameSignal.value),
2467
+ get value() {
2468
+ return self.getClassHistogramTensor(frameSignal.value);
2469
+ }
2470
+ }
2471
+ };
2472
+ this.objects = {
2473
+ get: getTrackById,
2474
+ byCategory: getTrackByCategory,
2475
+ primary: primaryTrack,
2476
+ count: programmaticSignal$1((ctx) => this.getObjectResult(ctx.frame).objects.filter((o) => o.active).length, {
2477
+ fps: this._fps,
2478
+ label: "tracked_objects_count"
2479
+ }),
2480
+ hasCategory: (category) => buildClassSignals(category).present,
2481
+ getActiveTracks: (frame) => this.getObjectResult(frame).objects.filter((o) => o.active),
2482
+ get detectedCategories() {
2483
+ return activeCategories(frameSignal.value);
2484
+ }
2485
+ };
2486
+ const maskForTrack = (frame, trackId) => this.getMaskResult(frame).find((m) => m.trackId === trackId);
2487
+ const subjectMask = (frame) => selectSubjectMask(this.getMaskResult(frame));
2488
+ const buildMaskTrackSignals = (resolve$1, label) => {
2489
+ const boxFor = (frame) => {
2490
+ const mask = resolve$1(frame);
2491
+ if (!mask || mask.trackId === void 0) return void 0;
2492
+ return this.getObjectResult(frame).objects.find((o) => o.trackId === mask.trackId);
2493
+ };
2494
+ return {
2495
+ get trackId() {
2496
+ return resolve$1(frameSignal.value)?.trackId ?? 0;
2497
+ },
2498
+ get category() {
2499
+ return resolve$1(frameSignal.value)?.category ?? "unknown";
2500
+ },
2501
+ area: programmaticSignal$1((ctx) => resolve$1(ctx.frame)?.area ?? 0, {
2502
+ fps: this._fps,
2503
+ label: `${label}_area`
2504
+ }),
2505
+ coverage: programmaticSignal$1((ctx) => resolve$1(ctx.frame)?.coverage ?? 0, {
2506
+ fps: this._fps,
2507
+ label: `${label}_coverage`
2508
+ }),
2509
+ solidity: programmaticSignal$1((ctx) => {
2510
+ const mask = resolve$1(ctx.frame);
2511
+ const box = boxFor(ctx.frame);
2512
+ if (!mask || !box) return 0;
2513
+ const bboxArea = box.boundingBox.width * box.boundingBox.height;
2514
+ return bboxArea > 0 ? mask.area / bboxArea : 0;
2515
+ }, {
2516
+ fps: this._fps,
2517
+ label: `${label}_solidity`
2518
+ }),
2519
+ bboxFill: programmaticSignal$1((ctx) => {
2520
+ const mask = resolve$1(ctx.frame);
2521
+ if (!mask || mask.width * mask.height === 0) return 0;
2522
+ const box = boxFor(ctx.frame);
2523
+ if (!box) return 0;
2524
+ return box.boundingBox.width * box.boundingBox.height / (mask.width * mask.height);
2525
+ }, {
2526
+ fps: this._fps,
2527
+ label: `${label}_bboxFill`
2528
+ })
2529
+ };
2530
+ };
2531
+ this.masks = {
2532
+ get: (trackId) => {
2533
+ let sig = this._maskTrackCache.get(trackId);
2534
+ if (!sig) {
2535
+ sig = buildMaskTrackSignals((frame) => maskForTrack(frame, trackId), `mask_${trackId}`);
2536
+ this._maskTrackCache.set(trackId, sig);
2537
+ }
2538
+ return sig;
2539
+ },
2540
+ subject: buildMaskTrackSignals(subjectMask, "subject"),
2541
+ count: programmaticSignal$1((ctx) => new Set(this.getMaskResult(ctx.frame).map((m) => m.trackId ?? -1)).size, {
2542
+ fps: this._fps,
2543
+ label: "mask_count"
2544
+ })
2545
+ };
2546
+ this.segmentation = {
2547
+ humanSilhouette: this.masks.subject,
2548
+ subject: this.masks.subject,
2549
+ instanceMasks: this.masks,
2550
+ matte: {
2551
+ coverage: programmaticSignal$1((ctx) => self.getMatteResult(ctx.frame)?.coverage ?? 0, {
2552
+ fps: this._fps,
2553
+ label: "matte_coverage"
2554
+ }),
2555
+ at: (frame) => self.getMatteResult(frame)
2556
+ },
2557
+ get stencilTexture() {
2558
+ return self._stencilTexture;
2559
+ }
2560
+ };
2561
+ this.poseLandmarksTensor = {
2562
+ get: (ctx) => self.getPoseLandmarksTensor(ctx?.frame ?? frameSignal.value),
2563
+ get value() {
2564
+ return self.getPoseLandmarksTensor(frameSignal.value);
2565
+ }
2566
+ };
2567
+ this.objectsTensor = {
2568
+ get: (ctx) => self.getObjectsTensor(ctx?.frame ?? frameSignal.value),
2569
+ get value() {
2570
+ return self.getObjectsTensor(frameSignal.value);
2571
+ }
2572
+ };
2573
+ this.masksTensor = {
2574
+ get: (ctx) => self.getMasksTensor(ctx?.frame ?? frameSignal.value),
2575
+ get value() {
2576
+ return self.getMasksTensor(frameSignal.value);
2577
+ }
2578
+ };
2579
+ this.histogramTensor = {
2580
+ get: (ctx) => self.getClassHistogramTensor(ctx?.frame ?? frameSignal.value),
2581
+ get value() {
2582
+ return self.getClassHistogramTensor(frameSignal.value);
2583
+ }
2584
+ };
2585
+ }
2586
+ setStencilTexture(texture) {
2587
+ this._stencilTexture = texture;
2588
+ }
2589
+ get stencilTexture() {
2590
+ return this._stencilTexture;
2591
+ }
2592
+ /**
2593
+ * Reads the most recent result at or before `frame`. Vision results are written one
2594
+ * frame behind the plate (the node renderer reads back the previous frame to infer),
2595
+ * and pinned-signal layout runs before the current frame's inference, so a strict
2596
+ * per-frame lookup would always miss. Walking back a few frames keeps signals live at
2597
+ * a stable one-frame lag instead of snapping to neutral defaults.
2598
+ */
2599
+ latestAtOrBefore(cache, frame, maxBack = 8) {
2600
+ const start = this.clamp(frame);
2601
+ const floor = Math.max(0, start - maxBack);
2602
+ for (let f = start; f >= floor; f--) {
2603
+ const value = cache.get(f);
2604
+ if (value !== void 0) return value;
2605
+ }
2606
+ }
2607
+ invalidateSignals() {
2608
+ for (const sig of this._registeredSignals) sig.invalidate();
2609
+ }
2610
+ setPoseResult(frame, result) {
2611
+ this._poseCache.set(this.clamp(frame), result);
2612
+ this.invalidateSignals();
2613
+ }
2614
+ getPoseResult(frame) {
2615
+ return this.latestAtOrBefore(this._poseCache, frame) ?? createNeutralPoseResult();
2616
+ }
2617
+ setObjectResult(frame, result) {
2618
+ const normalized = "objects" in result ? result : {
2619
+ objects: result,
2620
+ rawDetections: []
2621
+ };
2622
+ this._objectCache.set(this.clamp(frame), {
2623
+ objects: normalized.objects,
2624
+ rawDetections: normalized.rawDetections ?? []
2625
+ });
2626
+ this.invalidateSignals();
2627
+ }
2628
+ getObjectResult(frame) {
2629
+ return this.latestAtOrBefore(this._objectCache, frame) ?? createNeutralObjectResult();
2630
+ }
2631
+ setMaskResult(frame, masks) {
2632
+ this._maskCache.set(this.clamp(frame), masks);
2633
+ this.invalidateSignals();
2634
+ }
2635
+ getMaskResult(frame) {
2636
+ return this.latestAtOrBefore(this._maskCache, frame) ?? [];
2637
+ }
2638
+ setMatteResult(frame, matte) {
2639
+ this._matteCache.set(this.clamp(frame), matte);
2640
+ this.invalidateSignals();
2641
+ }
2642
+ getMatteResult(frame) {
2643
+ return this.latestAtOrBefore(this._matteCache, frame);
2644
+ }
2645
+ /**
2646
+ * Serializable snapshot of one frame's object / class / mask signals.
2647
+ * Synchronous — reads only the per-frame caches, never triggers inference.
2648
+ */
2649
+ summary(frame = frameSignal.value) {
2650
+ const f = this.clamp(frame);
2651
+ const res = this.getObjectResult(f);
2652
+ const objects = res.objects.map((o) => ({
2653
+ trackId: o.trackId,
2654
+ category: o.category,
2655
+ score: o.score,
2656
+ center: [o.centerX, o.centerY],
2657
+ speed: o.speed,
2658
+ active: o.active
2659
+ }));
2660
+ const classSet = /* @__PURE__ */ new Set();
2661
+ for (const o of res.objects) if (o.active) classSet.add(o.category);
2662
+ const masks = {};
2663
+ for (const m of this.getMaskResult(f)) masks[m.category] = (masks[m.category] ?? 0) + 1;
2664
+ return {
2665
+ frame: f,
2666
+ objects,
2667
+ classes: Array.from(classSet).sort(),
2668
+ masks
2669
+ };
2670
+ }
2671
+ /** Primary person's 17 keypoints as a [17, 3] float32 tensor. */
2672
+ getPoseLandmarksTensor(frame) {
2673
+ const kpts = this.getPoseResult(frame).people[0]?.keypoints ?? [];
2674
+ const data = new Float32Array(51);
2675
+ for (let i = 0; i < 17; i++) {
2676
+ data[i * 3] = kpts[i]?.x ?? .5;
2677
+ data[i * 3 + 1] = kpts[i]?.y ?? .5;
2678
+ data[i * 3 + 2] = kpts[i]?.visibility ?? 0;
2679
+ }
2680
+ return {
2681
+ data,
2682
+ shape: [17, 3],
2683
+ dtype: "float32"
2684
+ };
2685
+ }
2686
+ /** Up to 16 active tracks as [16, 8]: [active, categoryHash, x, y, w, h, vx, vy]. */
2687
+ getObjectsTensor(frame, maxObjects = 16) {
2688
+ const res = this.getObjectResult(frame);
2689
+ const data = new Float32Array(maxObjects * 8);
2690
+ const count = Math.min(maxObjects, res.objects.length);
2691
+ for (let i = 0; i < count; i++) {
2692
+ const obj = res.objects[i];
2693
+ const off = i * 8;
2694
+ let catHash = 0;
2695
+ for (let c = 0; c < obj.category.length; c++) catHash = catHash * 31 + obj.category.charCodeAt(c) & 65535;
2696
+ data[off] = obj.active ? 1 : 0;
2697
+ data[off + 1] = catHash;
2698
+ data[off + 2] = obj.centerX;
2699
+ data[off + 3] = obj.centerY;
2700
+ data[off + 4] = obj.boundingBox.width;
2701
+ data[off + 5] = obj.boundingBox.height;
2702
+ data[off + 6] = obj.velocity.vx;
2703
+ data[off + 7] = obj.velocity.vy;
2704
+ }
2705
+ return {
2706
+ data,
2707
+ shape: [maxObjects, 8],
2708
+ dtype: "float32"
2709
+ };
2710
+ }
2711
+ /** Per-mask coverage + solidity as [16, 2]. */
2712
+ getMasksTensor(frame, maxMasks = 16) {
2713
+ const masks = this.getMaskResult(frame);
2714
+ const data = new Float32Array(maxMasks * 2);
2715
+ const count = Math.min(maxMasks, masks.length);
2716
+ for (let i = 0; i < count; i++) {
2717
+ const m = masks[i];
2718
+ data[i * 2] = m.coverage;
2719
+ const box = this.getObjectResult(frame).objects.find((o) => o.trackId === m.trackId);
2720
+ const bboxArea = box ? box.boundingBox.width * box.boundingBox.height : 0;
2721
+ data[i * 2 + 1] = bboxArea > 0 ? m.area / bboxArea : 0;
2722
+ }
2723
+ return {
2724
+ data,
2725
+ shape: [maxMasks, 2],
2726
+ dtype: "float32"
2727
+ };
2728
+ }
2729
+ /** Per-class detection histogram for the frame as [nc] float32 (COCO 80 by default). */
2730
+ getClassHistogramTensor(frame, nc = 80) {
2731
+ const data = new Float32Array(nc);
2732
+ for (const o of this.getObjectResult(frame).objects) if (o.active) {
2733
+ const idx = COCO_CLASS_INDEX_BY_NAME.get(o.category.toLowerCase());
2734
+ if (idx !== void 0) data[idx] += 1;
2735
+ }
2736
+ return {
2737
+ data,
2738
+ shape: [nc],
2739
+ dtype: "float32"
2740
+ };
2741
+ }
2742
+ clamp(frame) {
2743
+ return Math.max(0, Math.min(this._totalFrames - 1, Math.round(frame)));
2744
+ }
2745
+ };
2746
+ const COCO_CLASS_INDEX_BY_NAME = new Map(COCO_CLASSES.map((name, index) => [name, index]));
2747
+ function pinNodeToLandmark(node, target, options = {}) {
2748
+ const offX = options.offsetX ?? 0;
2749
+ const offY = options.offsetY ?? 0;
2750
+ const offZ = options.offsetZ ?? 0;
2751
+ if (!target || typeof target !== "object") return node;
2752
+ if ("screenX" in target && target.screenX && typeof target.screenX === "object") {
2753
+ const signals = target;
2754
+ node.x = offX !== 0 ? signals.screenX.add(offX) : signals.screenX;
2755
+ node.y = offY !== 0 ? signals.screenY.add(offY) : signals.screenY;
2756
+ node.z = offZ !== 0 ? signals.z.add(offZ) : signals.z;
2757
+ } else if ("x" in target && typeof target.x === "object" && target.x !== null) {
2758
+ const signals = target;
2759
+ node.x = offX !== 0 ? signals.x.add(offX) : signals.x;
2760
+ node.y = offY !== 0 ? signals.y.add(offY) : signals.y;
2761
+ node.z = offZ !== 0 ? signals.z.add(offZ) : signals.z;
2762
+ } else {
2763
+ const staticCoord = target;
2764
+ node.x = (staticCoord.screenX ?? staticCoord.x) + offX;
2765
+ node.y = (staticCoord.screenY ?? staticCoord.y) + offY;
2766
+ node.z = (staticCoord.z ?? 0) + offZ;
2767
+ }
2768
+ return node;
2769
+ }
2770
+ function pinNodeToObject(node, target, options = {}) {
2771
+ if (!target || typeof target !== "object") return node;
2772
+ if ("anchors" in target && target.anchors) {
2773
+ const track = target;
2774
+ const anchorName = options.anchor ?? (options.matchWidth || options.matchHeight ? "topLeft" : "center");
2775
+ const rawAnchor = track.anchors[anchorName] ?? track.center;
2776
+ let resolvedAnchor = rawAnchor;
2777
+ if (options.smoothFrames && options.smoothFrames > 1) resolvedAnchor = {
2778
+ x: rawAnchor.x.smooth(options.smoothFrames),
2779
+ y: rawAnchor.y.smooth(options.smoothFrames),
2780
+ z: rawAnchor.z,
2781
+ screenX: rawAnchor.screenX.smooth(options.smoothFrames),
2782
+ screenY: rawAnchor.screenY.smooth(options.smoothFrames)
2783
+ };
2784
+ if (options.matchWidth) node.width = options.smoothFrames && options.smoothFrames > 1 ? track.bounds.screenWidth.smooth(options.smoothFrames) : track.bounds.screenWidth;
2785
+ if (options.matchHeight) node.height = options.smoothFrames && options.smoothFrames > 1 ? track.bounds.screenHeight.smooth(options.smoothFrames) : track.bounds.screenHeight;
2786
+ if (options.hideWhenLost) node.opacity = track.active;
2787
+ return pinNodeToLandmark(node, resolvedAnchor, options);
2788
+ }
2789
+ return pinNodeToLandmark(node, target, options);
2790
+ }
2791
+ /** Tasks a config turns on; detection is the default when nothing is enabled explicitly. */
2792
+ function enabledTasks(config) {
2793
+ const tasks = [];
2794
+ if (config.enableDetection === true) tasks.push("detect");
2795
+ if (config.enableSegmentation === true) tasks.push("segment");
2796
+ if (config.enablePose === true) tasks.push("pose");
2797
+ if (config.enableMatte === true) tasks.push("matte");
2798
+ return tasks.length > 0 ? tasks : ["detect"];
2799
+ }
2800
+ /**
2801
+ * Vision node.
2802
+ *
2803
+ * LAZY BY CONSTRUCTION: creating a VisionNode performs ZERO I/O — no model downloads, no
2804
+ * sessions, no file probes. The first inference call (or an explicit `await vision.ready()`)
2805
+ * downloads the required models.
2806
+ */
2807
+ var VisionNode = class VisionNode$1 {
2808
+ id;
2809
+ kind = "vision";
2810
+ source;
2811
+ config;
2812
+ vision;
2813
+ _runner;
2814
+ /** Runner construction options (modelsDir, provider, store, …) — injectable for tests/agents. */
2815
+ _runnerOptions;
2816
+ constructor(source, config = {}, bundleOptions = {}, runnerOptions = {}) {
2817
+ this.id = `vision-${Math.random().toString(36).slice(2, 9)}`;
2818
+ this.source = source;
2819
+ this.config = config;
2820
+ this._runnerOptions = runnerOptions;
2821
+ this.vision = new VisionBundle({
2822
+ ...bundleOptions,
2823
+ config
2824
+ });
2825
+ }
2826
+ /** Lazily-created runner; downloads happen on its first inference. */
2827
+ runner() {
2828
+ if (!this._runner) {
2829
+ const { variant, confidence, classes, modelsDir, baseUrl, maskThreshold, featherRadius } = this.config;
2830
+ this._runner = VisionRunner.create({
2831
+ ...this._runnerOptions,
2832
+ ...variant !== void 0 ? { variant } : {},
2833
+ ...confidence !== void 0 ? { confidence } : {},
2834
+ ...classes !== void 0 ? { classes } : {},
2835
+ ...modelsDir !== void 0 ? { modelsDir } : {},
2836
+ ...baseUrl !== void 0 ? { baseUrl } : {},
2837
+ ...maskThreshold !== void 0 ? { maskThreshold } : {},
2838
+ ...featherRadius !== void 0 ? { featherRadius } : {}
2839
+ });
2840
+ }
2841
+ return this._runner;
2842
+ }
2843
+ /** Explicit warm-up — downloads the models for the enabled tasks, ahead of first inference. */
2844
+ async ready() {
2845
+ const runner = this.runner();
2846
+ await runner.preload(enabledTasks(this.config));
2847
+ return runner;
2848
+ }
2849
+ close() {
2850
+ this._runner?.close();
2851
+ this._runner = void 0;
2852
+ }
2853
+ static attach(source, config = {}, bundleOptions = {}) {
2854
+ const node = new VisionNode$1(source, config, bundleOptions);
2855
+ const vision = node.vision;
2856
+ Object.defineProperties(vision, {
2857
+ node: {
2858
+ value: node.toNode(),
2859
+ enumerable: true
2860
+ },
2861
+ ready: { value: () => node.ready() },
2862
+ runner: { value: () => node.runner() },
2863
+ close: { value: () => node.close() }
2864
+ });
2865
+ return vision;
2866
+ }
2867
+ toNode() {
2868
+ return {
2869
+ id: this.id,
2870
+ kind: "vision",
2871
+ source: this.source,
2872
+ config: this.config
2873
+ };
2874
+ }
2875
+ };
2876
+
2877
+ //#endregion
2878
+ //#region ../../nodes/node-vision/dist/renderers-B24hckhO.mjs
2879
+ let scratchValues = new Uint8Array(0);
2880
+ let scratchLengths = new Uint32Array(0);
2881
+ function encodeMask(mask, width, height) {
2882
+ if (scratchValues.length < mask.length) {
2883
+ scratchValues = new Uint8Array(mask.length);
2884
+ scratchLengths = new Uint32Array(mask.length);
2885
+ }
2886
+ const values = scratchValues;
2887
+ const lengths = scratchLengths;
2888
+ let runs = 0;
2889
+ let start = 0;
2890
+ for (let i = 1; i <= mask.length; i++) if (i === mask.length || mask[i] !== mask[start]) {
2891
+ values[runs] = mask[start];
2892
+ lengths[runs] = i - start;
2893
+ runs++;
2894
+ start = i;
2895
+ }
2896
+ return {
2897
+ width,
2898
+ height,
2899
+ values: values.slice(0, runs),
2900
+ lengths: lengths.slice(0, runs)
2901
+ };
2902
+ }
2903
+ function decodeMask(rle) {
2904
+ const out = new Uint8Array(rle.width * rle.height);
2905
+ let at = 0;
2906
+ for (let r = 0; r < rle.values.length; r++) {
2907
+ const end = at + rle.lengths[r];
2908
+ if (rle.values[r] !== 0) out.fill(rle.values[r], at, end);
2909
+ at = end;
2910
+ }
2911
+ return out;
2912
+ }
2913
+ function rleBytes(rle) {
2914
+ return rle.values.byteLength + rle.lengths.byteLength;
2915
+ }
2916
+ function toCachedMask(mask) {
2917
+ const { mask: data, ...rest } = mask;
2918
+ return {
2919
+ ...rest,
2920
+ rle: encodeMask(data, mask.width, mask.height)
2921
+ };
2922
+ }
2923
+ function fromCachedMask(mask) {
2924
+ const { rle, ...rest } = mask;
2925
+ return {
2926
+ ...rest,
2927
+ mask: decodeMask(rle)
2928
+ };
2929
+ }
2930
+ function resultBytes(result) {
2931
+ let bytes = 512 + result.detections.length * 128 + result.objects.length * 256;
2932
+ for (const m of result.masks ?? []) bytes += rleBytes(m.rle);
2933
+ if (result.subject) bytes += rleBytes(result.subject.mask.rle);
2934
+ return bytes;
2935
+ }
2936
+ /** Results by source (node + inference settings) and frame, least recently used out first. */
2937
+ var VisionFrameCache = class {
2938
+ entries = /* @__PURE__ */ new Map();
2939
+ bytes = 0;
2940
+ constructor(maxBytes = 256 * 1024 * 1024) {
2941
+ this.maxBytes = maxBytes;
2942
+ }
2943
+ get(source, frame) {
2944
+ const key = `${source}#${frame}`;
2945
+ const hit = this.entries.get(key);
2946
+ if (hit) {
2947
+ this.entries.delete(key);
2948
+ this.entries.set(key, hit);
2949
+ }
2950
+ return hit;
2951
+ }
2952
+ set(source, frame, result) {
2953
+ const key = `${source}#${frame}`;
2954
+ const old = this.entries.get(key);
2955
+ if (old) {
2956
+ this.bytes -= resultBytes(old);
2957
+ this.entries.delete(key);
2958
+ }
2959
+ this.entries.set(key, result);
2960
+ this.bytes += resultBytes(result);
2961
+ for (const [k, v] of this.entries) {
2962
+ if (this.bytes <= this.maxBytes) break;
2963
+ this.entries.delete(k);
2964
+ this.bytes -= resultBytes(v);
2965
+ }
2966
+ }
2967
+ clear() {
2968
+ this.entries.clear();
2969
+ this.bytes = 0;
2970
+ }
2971
+ get size() {
2972
+ return this.entries.size;
2973
+ }
2974
+ };
2975
+ /**
2976
+ * GPU composite for the mask-family modes: the picture times the subject mask,
2977
+ * in one full-screen pass. Replaces building the RGBA result on the CPU and
2978
+ * uploading it; only the 8-bit mask goes to the GPU.
2979
+ *
2980
+ * - matte: picture × mask (premultiplied, so the background is transparent)
2981
+ * - mask: white silhouette (rgb = mask, a = 1)
2982
+ * - crop: matte, with the subject's bounding box stretched to the frame
2983
+ */
2984
+ const WGSL = `
2985
+ struct Params {
2986
+ mode: u32, // 0 matte, 1 mask, 2 crop
2987
+ _pad: u32,
2988
+ cropMin: vec2<f32>, // uv of the subject bounds (crop)
2989
+ cropMax: vec2<f32>,
2990
+ _pad2: vec2<f32>,
2991
+ };
2992
+
2993
+ @group(0) @binding(0) var<uniform> params: Params;
2994
+ @group(0) @binding(1) var picture: texture_2d<f32>;
2995
+ @group(0) @binding(2) var mask: texture_2d<f32>;
2996
+ @group(0) @binding(3) var samp: sampler;
2997
+
2998
+ struct VSOut {
2999
+ @builtin(position) pos: vec4<f32>,
3000
+ @location(0) uv: vec2<f32>,
3001
+ };
3002
+
3003
+ @vertex fn vs(@builtin(vertex_index) i: u32) -> VSOut {
3004
+ var p = array<vec2<f32>, 3>(vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0));
3005
+ var out: VSOut;
3006
+ out.pos = vec4(p[i], 0.0, 1.0);
3007
+ out.uv = vec2(p[i].x * 0.5 + 0.5, 0.5 - p[i].y * 0.5);
3008
+ return out;
3009
+ }
3010
+
3011
+ @fragment fn fs(in: VSOut) -> @location(0) vec4<f32> {
3012
+ var uv = in.uv;
3013
+ if (params.mode == 2u) {
3014
+ uv = mix(params.cropMin, params.cropMax, uv);
3015
+ }
3016
+ let m = textureSampleLevel(mask, samp, uv, 0.0).r;
3017
+ if (params.mode == 1u) {
3018
+ return vec4(m, m, m, 1.0);
3019
+ }
3020
+ return textureSampleLevel(picture, samp, uv, 0.0) * m;
3021
+ }
3022
+ `;
3023
+ var MatteCompositor = class {
3024
+ pipeline;
3025
+ sampler;
3026
+ uniforms = [];
3027
+ uniformIndex = 0;
3028
+ /** One 8-bit mask texture per node, rewritten each frame. */
3029
+ masks = /* @__PURE__ */ new Map();
3030
+ constructor(device, format) {
3031
+ this.device = device;
3032
+ const module = device.createShaderModule({
3033
+ label: "vision_matte_composite",
3034
+ code: WGSL
3035
+ });
3036
+ this.pipeline = device.createRenderPipeline({
3037
+ label: "vision_matte_composite",
3038
+ layout: "auto",
3039
+ vertex: {
3040
+ module,
3041
+ entryPoint: "vs"
3042
+ },
3043
+ fragment: {
3044
+ module,
3045
+ entryPoint: "fs",
3046
+ targets: [{ format }]
3047
+ },
3048
+ primitive: { topology: "triangle-list" }
3049
+ });
3050
+ this.sampler = device.createSampler({
3051
+ magFilter: "linear",
3052
+ minFilter: "linear"
3053
+ });
3054
+ }
3055
+ /** Uploads a frame-sized 0..255 mask for `key` and returns its texture. */
3056
+ uploadMask(key, mask, width, height) {
3057
+ let tex = this.masks.get(key);
3058
+ if (!tex || tex.width !== width || tex.height !== height) {
3059
+ tex?.destroy();
3060
+ tex = this.device.createTexture({
3061
+ label: `vision_mask_${key}`,
3062
+ size: [width, height],
3063
+ format: "r8unorm",
3064
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
3065
+ });
3066
+ this.masks.set(key, tex);
3067
+ }
3068
+ const aligned = Math.ceil(width / 256) * 256;
3069
+ let bytes = mask;
3070
+ if (aligned !== width) {
3071
+ bytes = new Uint8Array(aligned * height);
3072
+ for (let y = 0; y < height; y++) bytes.set(mask.subarray(y * width, (y + 1) * width), y * aligned);
3073
+ }
3074
+ this.device.queue.writeTexture({ texture: tex }, bytes.buffer, {
3075
+ offset: bytes.byteOffset,
3076
+ bytesPerRow: aligned,
3077
+ rowsPerImage: height
3078
+ }, {
3079
+ width,
3080
+ height
3081
+ });
3082
+ return tex;
3083
+ }
3084
+ /** Clears `target` and draws `picture` through `mask` into it. */
3085
+ draw(encoder, target, picture, mask, mode, bounds) {
3086
+ const w = mask.width;
3087
+ const h = mask.height;
3088
+ const data = new Float32Array(8);
3089
+ const view = new Uint32Array(data.buffer);
3090
+ view[0] = mode === "matte" ? 0 : mode === "mask" ? 1 : 2;
3091
+ if (mode === "crop" && bounds) {
3092
+ data[2] = bounds.x0 / w;
3093
+ data[3] = bounds.y0 / h;
3094
+ data[4] = (bounds.x1 + 1) / w;
3095
+ data[5] = (bounds.y1 + 1) / h;
3096
+ } else {
3097
+ data[2] = 0;
3098
+ data[3] = 0;
3099
+ data[4] = 1;
3100
+ data[5] = 1;
3101
+ if (mode === "crop") view[0] = 0;
3102
+ }
3103
+ if (this.uniformIndex >= this.uniforms.length) this.uniforms.push(this.device.createBuffer({
3104
+ size: data.byteLength,
3105
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
3106
+ }));
3107
+ const uniform = this.uniforms[this.uniformIndex];
3108
+ this.uniformIndex = (this.uniformIndex + 1) % 16;
3109
+ this.device.queue.writeBuffer(uniform, 0, data);
3110
+ const bindGroup = this.device.createBindGroup({
3111
+ layout: this.pipeline.getBindGroupLayout(0),
3112
+ entries: [
3113
+ {
3114
+ binding: 0,
3115
+ resource: { buffer: uniform }
3116
+ },
3117
+ {
3118
+ binding: 1,
3119
+ resource: picture.createView()
3120
+ },
3121
+ {
3122
+ binding: 2,
3123
+ resource: mask.createView()
3124
+ },
3125
+ {
3126
+ binding: 3,
3127
+ resource: this.sampler
3128
+ }
3129
+ ]
3130
+ });
3131
+ const pass = encoder.beginRenderPass({
3132
+ label: "vision_matte_composite",
3133
+ colorAttachments: [{
3134
+ view: target,
3135
+ loadOp: "clear",
3136
+ storeOp: "store",
3137
+ clearValue: {
3138
+ r: 0,
3139
+ g: 0,
3140
+ b: 0,
3141
+ a: 0
3142
+ }
3143
+ }]
3144
+ });
3145
+ pass.setPipeline(this.pipeline);
3146
+ pass.setBindGroup(0, bindGroup);
3147
+ pass.draw(3);
3148
+ pass.end();
3149
+ }
3150
+ };
3151
+ let sharedRunner = null;
3152
+ let sharedSkeletonRenderer = null;
3153
+ let sharedCompositor = null;
3154
+ let sharedCompositorFormat = null;
3155
+ /** Analysed frames, shared by every vision node in the process. */
3156
+ const frameCache = new VisionFrameCache();
3157
+ /** The frame each node's child texture holds (drawn on its last call). */
3158
+ const heldFrameByNode = /* @__PURE__ */ new Map();
3159
+ /** Per node: a copy of the held frame, and the composited matte. */
3160
+ const heldTextures = /* @__PURE__ */ new Map();
3161
+ const matteTextures = /* @__PURE__ */ new Map();
3162
+ /** One tracker per vision node — track ids must not mix across nodes/sources. */
3163
+ const objectTrackers = /* @__PURE__ */ new Map();
3164
+ /**
3165
+ * Persistent per-node child textures. The shared frame encoder is submitted at
3166
+ * the END of the frame, so a node renderer cannot read back the child it just
3167
+ * drew. We keep the texture around and read back the PREVIOUS frame's pixels at
3168
+ * the start of the next call — a stable one-frame delay instead of the
3169
+ * unpredictable multi-frame lag you get from pooled textures.
3170
+ *
3171
+ * Keyed on the stable Vision `Effect` instance (render ids change every frame),
3172
+ * with a size-keyed fallback for raw operation nodes.
3173
+ */
3174
+ const childTexturesByEffect = /* @__PURE__ */ new WeakMap();
3175
+ const childTexturesByKey = /* @__PURE__ */ new Map();
3176
+ /**
3177
+ * Last successful subject mask, reused for a few frames when the model misses —
3178
+ * a transient miss then holds the silhouette instead of flashing the raw plate.
3179
+ * Keyed per node/effect so multiple vision nodes do not collide.
3180
+ */
3181
+ const lastSubjectByNode = /* @__PURE__ */ new Map();
3182
+ const SUBJECT_HOLD_FRAMES = 3;
3183
+ /** A per-node texture shaped like `like`, recreated when the size changes. */
3184
+ function nodeTexture(textures, device, key, like, usage, label) {
3185
+ let tex = textures.get(key);
3186
+ if (!tex || tex.width !== like.width || tex.height !== like.height || tex.format !== like.format) {
3187
+ tex?.destroy();
3188
+ tex = device.createTexture({
3189
+ size: [like.width, like.height],
3190
+ format: like.format,
3191
+ usage,
3192
+ label
3193
+ });
3194
+ textures.set(key, tex);
3195
+ }
3196
+ return tex;
3197
+ }
3198
+ const heldTexture = (device, key, like) => nodeTexture(heldTextures, device, key, like, GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING, "vision_held_frame");
3199
+ const matteTexture = (device, key, like) => nodeTexture(matteTextures, device, key, like, GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC, "vision_matte");
3200
+ /** Everything that decides a frame's results besides the frame itself. */
3201
+ function cacheSource(nodeKey, child, op, width, height) {
3202
+ const c = child;
3203
+ return JSON.stringify([
3204
+ nodeKey,
3205
+ c?.operation?.id ?? c?.id ?? null,
3206
+ width,
3207
+ height,
3208
+ op.mode ?? "passthrough",
3209
+ op.variant,
3210
+ op.confidence,
3211
+ op.classes,
3212
+ op.maskThreshold,
3213
+ op.featherRadius,
3214
+ op.matteSource,
3215
+ op.keyBackground === true,
3216
+ op.maxMissedFrames
3217
+ ]);
3218
+ }
3219
+ /** The background-key threshold at `frame` (it may be animated). */
3220
+ function backgroundKeyThreshold(op, frame, fps) {
3221
+ const raw = op.backgroundKeyThreshold;
3222
+ if (typeof raw === "number") return raw;
3223
+ if (typeof raw?.get === "function") return Number(raw.get({
3224
+ frame,
3225
+ fps
3226
+ }));
3227
+ if (typeof raw?._value === "number") return Number(raw._value);
3228
+ return 70;
3229
+ }
3230
+ /**
3231
+ * The model's subject, or the last one through brief misses: a transient miss
3232
+ * then holds the silhouette instead of flashing the raw plate.
3233
+ */
3234
+ function selectSubject(selected, nodeKey, frame) {
3235
+ if (selected) {
3236
+ lastSubjectByNode.set(nodeKey, {
3237
+ mask: selected,
3238
+ frame
3239
+ });
3240
+ return selected;
3241
+ }
3242
+ const last = lastSubjectByNode.get(nodeKey);
3243
+ if (last && frame - last.frame <= SUBJECT_HOLD_FRAMES) return last.mask;
3244
+ }
3245
+ /**
3246
+ * Lazy shared runner — `create()` is a pure constructor (zero I/O); the first frame that
3247
+ * requests a task triggers that task's model download at inference time (never at init).
3248
+ * Recreated only when an option that changes inference changes.
3249
+ */
3250
+ function getSharedRunner(op) {
3251
+ const options = {
3252
+ variant: op.variant,
3253
+ confidence: op.confidence,
3254
+ classes: op.classes,
3255
+ maskThreshold: op.maskThreshold,
3256
+ featherRadius: op.featherRadius,
3257
+ modelsDir: op.modelsDir,
3258
+ baseUrl: op.baseUrl
3259
+ };
3260
+ const key = JSON.stringify(options);
3261
+ if (sharedRunner?.key !== key) {
3262
+ sharedRunner?.runner.close();
3263
+ sharedRunner = {
3264
+ key,
3265
+ runner: VisionRunner.create(options)
3266
+ };
3267
+ }
3268
+ return sharedRunner.runner;
3269
+ }
3270
+ function getObjectTracker(nodeKey, op) {
3271
+ let tracker = objectTrackers.get(nodeKey);
3272
+ if (!tracker) {
3273
+ tracker = new TemporalObjectTracker({ maxMissedFrames: op.maxMissedFrames ?? 15 });
3274
+ objectTrackers.set(nodeKey, tracker);
3275
+ }
3276
+ return tracker;
3277
+ }
3278
+ /**
3279
+ * Normalizes the runner's pose keypoints (plate pixel space) to [0, 1] landmarks, which is
3280
+ * the contract the signal bundle and the skeleton pipeline expect.
3281
+ */
3282
+ function normalizePoseKeypoints(result, width, height) {
3283
+ if (width <= 0 || height <= 0) return result;
3284
+ return { people: result.people.map((person) => ({
3285
+ ...person,
3286
+ keypoints: person.keypoints.map((k) => ({
3287
+ x: k.x / width,
3288
+ y: k.y / height,
3289
+ visibility: k.visibility
3290
+ }))
3291
+ })) };
3292
+ }
3293
+ /** Assigns each decoded instance mask to the temporal track whose box center is nearest the mask's centroid. */
3294
+ function assignMasksToTracks(masks, tracked) {
3295
+ return masks.map((mask) => {
3296
+ const { cx, cy } = maskCentroid(mask);
3297
+ let best = null;
3298
+ for (const obj of tracked) {
3299
+ const dx = obj.centerX - cx;
3300
+ const dy = obj.centerY - cy;
3301
+ const distance = dx * dx + dy * dy;
3302
+ if (!best || distance < best.distance) best = {
3303
+ trackId: obj.trackId,
3304
+ distance
3305
+ };
3306
+ }
3307
+ return best ? {
3308
+ ...mask,
3309
+ trackId: best.trackId
3310
+ } : mask;
3311
+ });
3312
+ }
3313
+ /**
3314
+ * Grows a subject mask into connected foreground pixels — seeding a flood fill
3315
+ * from the subject through pixels that differ from the sampled backdrop recovers
3316
+ * thin or fast-moving edges (hair, fabric) without adding a near-uniform studio
3317
+ * wall or its soft grey shadow.
3318
+ */
3319
+ function fillInternalHoles(mask, width, height) {
3320
+ const n = width * height;
3321
+ const exterior = new Uint8Array(n);
3322
+ const queue = new Int32Array(n);
3323
+ let tail = 0;
3324
+ const seed = (i) => {
3325
+ if (exterior[i] || mask[i] > 0) return;
3326
+ exterior[i] = 1;
3327
+ queue[tail++] = i;
3328
+ };
3329
+ for (let x = 0; x < width; x++) {
3330
+ seed(x);
3331
+ seed((height - 1) * width + x);
3332
+ }
3333
+ for (let y = 0; y < height; y++) {
3334
+ seed(y * width);
3335
+ seed(y * width + width - 1);
3336
+ }
3337
+ let head = 0;
3338
+ while (head < tail) {
3339
+ const i = queue[head++];
3340
+ const x = i % width;
3341
+ let j = i + 1;
3342
+ if (x + 1 < width && !exterior[j] && mask[j] === 0) {
3343
+ exterior[j] = 1;
3344
+ queue[tail++] = j;
3345
+ }
3346
+ j = i - 1;
3347
+ if (x > 0 && !exterior[j] && mask[j] === 0) {
3348
+ exterior[j] = 1;
3349
+ queue[tail++] = j;
3350
+ }
3351
+ j = i + width;
3352
+ if (j < n && !exterior[j] && mask[j] === 0) {
3353
+ exterior[j] = 1;
3354
+ queue[tail++] = j;
3355
+ }
3356
+ j = i - width;
3357
+ if (j >= 0 && !exterior[j] && mask[j] === 0) {
3358
+ exterior[j] = 1;
3359
+ queue[tail++] = j;
3360
+ }
3361
+ }
3362
+ for (let i = 0; i < n; i++) if (mask[i] === 0 && exterior[i] === 0) mask[i] = 255;
3363
+ }
3364
+ function smoothMaskBoundary(mask, width, height, radius = 2) {
3365
+ if (radius <= 0) return mask;
3366
+ const temp = new Uint8Array(width * height);
3367
+ const out = new Uint8Array(width * height);
3368
+ const div = 2 * radius + 1;
3369
+ for (let y = 0; y < height; y++) {
3370
+ const row = y * width;
3371
+ let sum = 0;
3372
+ for (let k = -radius; k <= radius; k++) {
3373
+ const x = Math.max(0, Math.min(width - 1, k));
3374
+ sum += mask[row + x];
3375
+ }
3376
+ for (let x = 0; x < width; x++) {
3377
+ temp[row + x] = Math.round(sum / div);
3378
+ const xOut = Math.max(0, x - radius);
3379
+ const xIn = Math.min(width - 1, x + radius + 1);
3380
+ sum += mask[row + xIn] - mask[row + xOut];
3381
+ }
3382
+ }
3383
+ const sums = new Float64Array(width);
3384
+ for (let k = -radius; k <= radius; k++) {
3385
+ const row = Math.max(0, Math.min(height - 1, k)) * width;
3386
+ for (let x = 0; x < width; x++) sums[x] += temp[row + x];
3387
+ }
3388
+ for (let y = 0; y < height; y++) {
3389
+ const row = y * width;
3390
+ const rowOut = Math.max(0, y - radius) * width;
3391
+ const rowIn = Math.min(height - 1, y + radius + 1) * width;
3392
+ for (let x = 0; x < width; x++) {
3393
+ out[row + x] = Math.round(sums[x] / div);
3394
+ sums[x] += temp[rowIn + x] - temp[rowOut + x];
3395
+ }
3396
+ }
3397
+ return out;
3398
+ }
3399
+ function growMaskIntoForeground(mask, width, height, pixels, threshold, featherRadius) {
3400
+ let br = 0;
3401
+ let bg = 0;
3402
+ let bb = 0;
3403
+ let count = 0;
3404
+ const sample = (x, y) => {
3405
+ const i = y * width + x;
3406
+ if (mask[i]) return;
3407
+ const p = i * 4;
3408
+ br += pixels[p];
3409
+ bg += pixels[p + 1];
3410
+ bb += pixels[p + 2];
3411
+ count++;
3412
+ };
3413
+ for (let x = 0; x < width; x += 4) {
3414
+ sample(x, 0);
3415
+ sample(x, height - 1);
3416
+ }
3417
+ for (let y = 0; y < height; y += 4) {
3418
+ sample(0, y);
3419
+ sample(width - 1, y);
3420
+ }
3421
+ if (count === 0) return mask;
3422
+ br /= count;
3423
+ bg /= count;
3424
+ bb /= count;
3425
+ const n = width * height;
3426
+ const fg = new Uint8Array(n);
3427
+ for (let i = 0, p = 0; i < n; i++, p += 4) fg[i] = Math.max(Math.abs(pixels[p] - br), Math.abs(pixels[p + 1] - bg), Math.abs(pixels[p + 2] - bb)) > threshold ? 1 : 0;
3428
+ const queue = new Int32Array(n);
3429
+ let tail = 0;
3430
+ for (let i = 0; i < n; i++) if (mask[i] > 0) {
3431
+ if (fg[i]) mask[i] = 255;
3432
+ queue[tail++] = i;
3433
+ }
3434
+ let head = 0;
3435
+ while (head < tail) {
3436
+ const i = queue[head++];
3437
+ const x = i % width;
3438
+ let j = i + 1;
3439
+ if (x + 1 < width && mask[j] !== 255 && fg[j]) {
3440
+ mask[j] = 255;
3441
+ queue[tail++] = j;
3442
+ }
3443
+ j = i - 1;
3444
+ if (x > 0 && mask[j] !== 255 && fg[j]) {
3445
+ mask[j] = 255;
3446
+ queue[tail++] = j;
3447
+ }
3448
+ j = i + width;
3449
+ if (j < n && mask[j] !== 255 && fg[j]) {
3450
+ mask[j] = 255;
3451
+ queue[tail++] = j;
3452
+ }
3453
+ j = i - width;
3454
+ if (j >= 0 && mask[j] !== 255 && fg[j]) {
3455
+ mask[j] = 255;
3456
+ queue[tail++] = j;
3457
+ }
3458
+ }
3459
+ fillInternalHoles(mask, width, height);
3460
+ return smoothMaskBoundary(mask, width, height, featherRadius !== void 0 && featherRadius > 0 ? Math.max(1, Math.min(8, Math.round(featherRadius * 50))) : 2);
3461
+ }
3462
+ function maskCentroid(mask) {
3463
+ const { mask: data, width, height } = mask;
3464
+ let sumX = 0;
3465
+ let sumY = 0;
3466
+ let count = 0;
3467
+ for (let y = 0; y < height; y++) {
3468
+ const row = y * width;
3469
+ for (let x = 0; x < width; x++) if (data[row + x] > 0) {
3470
+ sumX += x;
3471
+ sumY += y;
3472
+ count++;
3473
+ }
3474
+ }
3475
+ if (count === 0) return {
3476
+ cx: 0,
3477
+ cy: 0
3478
+ };
3479
+ return {
3480
+ cx: sumX / count,
3481
+ cy: sumY / count
3482
+ };
3483
+ }
3484
+ const VisionWebGPURenderer = async (args) => {
3485
+ const { ctx, encoder, pass, targetView, targetWidth, targetHeight, props, drawChild } = args;
3486
+ const { virtualMedia } = props;
3487
+ const rawOp = virtualMedia?.operation;
3488
+ if (rawOp?.op !== "Vision") return;
3489
+ const op = rawOp;
3490
+ pass.end();
3491
+ const childMedia = virtualMedia?.children?.[0];
3492
+ if (!childMedia) return;
3493
+ const effectKey = op.effect;
3494
+ const hasEffectKey = effectKey !== void 0 && effectKey !== null;
3495
+ const nodeKeyStr = virtualMedia?.id ?? rawOp?.id ?? `${targetWidth}x${targetHeight}_${op.mode}_${op.variant}_${op.keyBackground}_${op.backgroundKeyThreshold}`;
3496
+ const fallbackKey = `vision-${nodeKeyStr}`;
3497
+ let childTex = hasEffectKey ? childTexturesByEffect.get(effectKey) : childTexturesByKey.get(fallbackKey);
3498
+ if (childTex && (childTex.width !== targetWidth || childTex.height !== targetHeight)) {
3499
+ childTex.destroy();
3500
+ childTex = void 0;
3501
+ if (hasEffectKey) childTexturesByEffect.delete(effectKey);
3502
+ else childTexturesByKey.delete(fallbackKey);
3503
+ }
3504
+ const hasPreviousFrame = childTex !== void 0;
3505
+ if (!childTex) {
3506
+ childTex = ctx.device.createTexture({
3507
+ size: [targetWidth, targetHeight],
3508
+ format: ctx.renderer.format,
3509
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC,
3510
+ label: "vision_child_persistent"
3511
+ });
3512
+ if (hasEffectKey) childTexturesByEffect.set(effectKey, childTex);
3513
+ else childTexturesByKey.set(fallbackKey, childTex);
3514
+ if (childTexturesByKey.size > 4) {
3515
+ const oldest = childTexturesByKey.keys().next().value;
3516
+ if (oldest && oldest !== fallbackKey) {
3517
+ childTexturesByKey.get(oldest)?.destroy();
3518
+ childTexturesByKey.delete(oldest);
3519
+ }
3520
+ }
3521
+ }
3522
+ const frameIdx = props.frame ?? 0;
3523
+ const fps = props.fps ?? 24;
3524
+ const mode = op.mode ?? "passthrough";
3525
+ const isMatteMode = mode === "mask" || mode === "matte" || mode === "crop";
3526
+ const visionBundle = op.visionBundle ?? virtualMedia.visionBundle;
3527
+ const wantsDetection = op.enableDetection !== false || mode === "boxes" || mode === "tracking";
3528
+ const wantsSegmentation = op.enableSegmentation === true || isMatteMode && op.matteSource !== "selfie";
3529
+ const wantsSelfie = op.enableMatte === true || isMatteMode && op.matteSource === "selfie";
3530
+ const wantsPose = op.enablePose === true || mode === "skeleton";
3531
+ const cacheable = !wantsSelfie && !wantsPose && (wantsDetection || wantsSegmentation);
3532
+ const source = cacheable ? cacheSource(nodeKeyStr, childMedia, op, targetWidth, targetHeight) : "";
3533
+ const keyThresholdAt = (frame) => op.keyBackground === true ? backgroundKeyThreshold(op, frame, fps) : void 0;
3534
+ const lookup = (frame) => {
3535
+ if (!cacheable || frame === void 0) return void 0;
3536
+ const hit = frameCache.get(source, frame);
3537
+ if (!hit || hit.keyThreshold !== keyThresholdAt(frame)) return void 0;
3538
+ if (wantsSegmentation && visionBundle && !hit.masks) return void 0;
3539
+ return hit;
3540
+ };
3541
+ const heldFrame = hasPreviousFrame ? heldFrameByNode.get(nodeKeyStr) : void 0;
3542
+ let cached = lookup(frameIdx);
3543
+ const exact = cached !== void 0;
3544
+ if (!cached) cached = lookup(heldFrame);
3545
+ let heldTex;
3546
+ if (isMatteMode && hasPreviousFrame && !exact) {
3547
+ heldTex = heldTexture(ctx.device, nodeKeyStr, childTex);
3548
+ encoder.copyTextureToTexture({ texture: childTex }, { texture: heldTex }, [
3549
+ targetWidth,
3550
+ targetHeight,
3551
+ 1
3552
+ ]);
3553
+ }
3554
+ let framePixels = null;
3555
+ if (hasPreviousFrame && !cached) {
3556
+ const unalignedBytesPerRow = targetWidth * 4;
3557
+ const bytesPerRow = Math.ceil(unalignedBytesPerRow / 256) * 256;
3558
+ const bufferSize = bytesPerRow * targetHeight;
3559
+ const stagingBuffer = ctx.device.createBuffer({
3560
+ size: bufferSize,
3561
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
3562
+ label: "vision_frame_staging"
3563
+ });
3564
+ const readbackEncoder = ctx.device.createCommandEncoder({ label: "vision_readback_encoder" });
3565
+ readbackEncoder.copyTextureToBuffer({ texture: childTex }, {
3566
+ buffer: stagingBuffer,
3567
+ bytesPerRow,
3568
+ rowsPerImage: targetHeight
3569
+ }, [
3570
+ targetWidth,
3571
+ targetHeight,
3572
+ 1
3573
+ ]);
3574
+ ctx.device.queue.submit([readbackEncoder.finish()]);
3575
+ await stagingBuffer.mapAsync(GPUMapMode.READ);
3576
+ const mappedBytes = new Uint8Array(stagingBuffer.getMappedRange());
3577
+ framePixels = new Uint8ClampedArray(targetWidth * targetHeight * 4);
3578
+ if (bytesPerRow === unalignedBytesPerRow) framePixels.set(mappedBytes.subarray(0, framePixels.length));
3579
+ else for (let y = 0; y < targetHeight; y++) framePixels.set(mappedBytes.subarray(y * bytesPerRow, y * bytesPerRow + unalignedBytesPerRow), y * unalignedBytesPerRow);
3580
+ stagingBuffer.unmap();
3581
+ stagingBuffer.destroy();
3582
+ }
3583
+ const childView = childTex.createView();
3584
+ ctx.renderer.beginFrame(encoder, childView, {
3585
+ r: 0,
3586
+ g: 0,
3587
+ b: 0,
3588
+ a: 0
3589
+ }, targetWidth, targetHeight, "clear").end();
3590
+ await drawChild(childMedia, { ...props }, childView, childTex, targetWidth, targetHeight);
3591
+ heldFrameByNode.set(nodeKeyStr, frameIdx);
3592
+ let frameObjects = [];
3593
+ let frameMasks = [];
3594
+ let personMatte;
3595
+ let currentPoseRes;
3596
+ let subject;
3597
+ if (cached) {
3598
+ frameObjects = cached.objects;
3599
+ if (wantsDetection) visionBundle?.setObjectResult(frameIdx, {
3600
+ objects: cached.objects,
3601
+ rawDetections: cached.detections
3602
+ });
3603
+ if (wantsSegmentation && visionBundle && cached.masks) {
3604
+ frameMasks = cached.masks.map(fromCachedMask);
3605
+ visionBundle.setMaskResult(frameIdx, frameMasks);
3606
+ }
3607
+ if (cached.subject) {
3608
+ subject = fromCachedMask(cached.subject.mask);
3609
+ lastSubjectByNode.set(nodeKeyStr, {
3610
+ mask: subject,
3611
+ frame: frameIdx
3612
+ });
3613
+ }
3614
+ } else if (framePixels) {
3615
+ const runner = getSharedRunner(op);
3616
+ const image = {
3617
+ data: framePixels,
3618
+ width: targetWidth,
3619
+ height: targetHeight
3620
+ };
3621
+ let detections = [];
3622
+ if (wantsDetection) {
3623
+ detections = await runner.detect(image);
3624
+ const tracker = getObjectTracker(nodeKeyStr, op);
3625
+ if (frameIdx === 0) tracker.reset();
3626
+ frameObjects = tracker.update(detections, frameIdx, fps);
3627
+ visionBundle?.setObjectResult(frameIdx, {
3628
+ objects: frameObjects,
3629
+ rawDetections: detections
3630
+ });
3631
+ }
3632
+ if (wantsSegmentation) {
3633
+ frameMasks = assignMasksToTracks((await runner.segment(image)).masks, frameObjects);
3634
+ visionBundle?.setMaskResult(frameIdx, frameMasks);
3635
+ }
3636
+ if (wantsSelfie) {
3637
+ personMatte = await runner.matte(image);
3638
+ visionBundle?.setMatteResult(frameIdx, personMatte);
3639
+ }
3640
+ if (wantsPose) {
3641
+ currentPoseRes = { people: matchPoseToTracks(normalizePoseKeypoints(await runner.pose(image), image.width, image.height).people, frameObjects) };
3642
+ visionBundle?.setPoseResult(frameIdx, currentPoseRes);
3643
+ }
3644
+ if (isMatteMode) {
3645
+ subject = selectSubject(op.matteSource === "selfie" ? personMatteAsMask(personMatte) : mergeSubjectMask(frameMasks.length > 0 ? frameMasks : visionBundle?.getMaskResult(frameIdx) ?? []), nodeKeyStr, frameIdx);
3646
+ const threshold = keyThresholdAt(heldFrame ?? frameIdx);
3647
+ if (subject && threshold !== void 0) subject = {
3648
+ ...subject,
3649
+ mask: growMaskIntoForeground(subject.mask, subject.width, subject.height, framePixels, threshold, op.featherRadius)
3650
+ };
3651
+ }
3652
+ if (cacheable && heldFrame !== void 0) frameCache.set(source, heldFrame, {
3653
+ detections,
3654
+ objects: frameObjects,
3655
+ masks: visionBundle ? frameMasks.map(toCachedMask) : null,
3656
+ subject: subject ? { mask: toCachedMask(subject) } : null,
3657
+ keyThreshold: keyThresholdAt(heldFrame)
3658
+ });
3659
+ }
3660
+ if (isMatteMode) {
3661
+ const picture = exact ? childTex : heldTex;
3662
+ if (!subject || !picture) {
3663
+ ctx.renderer.beginFrame(encoder, targetView, {
3664
+ r: 0,
3665
+ g: 0,
3666
+ b: 0,
3667
+ a: 0
3668
+ }, targetWidth, targetHeight, "clear").end();
3669
+ return;
3670
+ }
3671
+ if (!sharedCompositor || sharedCompositorFormat !== ctx.renderer.format) {
3672
+ sharedCompositor = new MatteCompositor(ctx.device, ctx.renderer.format);
3673
+ sharedCompositorFormat = ctx.renderer.format;
3674
+ }
3675
+ const maskTex = sharedCompositor.uploadMask(nodeKeyStr, subject.mask, subject.width, subject.height);
3676
+ const matteTex = matteTexture(ctx.device, nodeKeyStr, childTex);
3677
+ sharedCompositor.draw(encoder, matteTex.createView(), picture, maskTex, mode, mode === "crop" ? maskBounds(subject) ?? void 0 : void 0);
3678
+ visionBundle?.setStencilTexture(matteTex);
3679
+ const outPass$1 = ctx.renderer.beginFrame(encoder, targetView, {
3680
+ r: 0,
3681
+ g: 0,
3682
+ b: 0,
3683
+ a: 0
3684
+ }, targetWidth, targetHeight, "clear");
3685
+ ctx.renderer.drawTexture(outPass$1, matteTex, {
3686
+ x: 0,
3687
+ y: 0,
3688
+ width: targetWidth,
3689
+ height: targetHeight
3690
+ });
3691
+ outPass$1.end();
3692
+ return;
3693
+ }
3694
+ if (mode === "skeleton") {
3695
+ if (!sharedSkeletonRenderer) sharedSkeletonRenderer = new PoseSkeletonRenderer(ctx.device);
3696
+ const person = (visionBundle?.getPoseResult(frameIdx) ?? currentPoseRes)?.people[0];
3697
+ if (person && person.keypoints.length > 0) {
3698
+ const skelTex = sharedSkeletonRenderer.renderToTexture(person.keypoints, {
3699
+ width: targetWidth,
3700
+ height: targetHeight
3701
+ });
3702
+ const skelPass = ctx.renderer.beginFrame(encoder, targetView, {
3703
+ r: 0,
3704
+ g: 0,
3705
+ b: 0,
3706
+ a: 0
3707
+ }, targetWidth, targetHeight, "clear");
3708
+ ctx.renderer.drawTexture(skelPass, skelTex, {
3709
+ x: 0,
3710
+ y: 0,
3711
+ width: targetWidth,
3712
+ height: targetHeight
3713
+ });
3714
+ skelPass.end();
3715
+ return;
3716
+ }
3717
+ }
3718
+ if (mode === "boxes" || mode === "tracking") {
3719
+ const outPass$1 = ctx.renderer.beginFrame(encoder, targetView, {
3720
+ r: 0,
3721
+ g: 0,
3722
+ b: 0,
3723
+ a: 0
3724
+ }, targetWidth, targetHeight, "clear");
3725
+ ctx.renderer.drawTexture(outPass$1, childTex, {
3726
+ x: 0,
3727
+ y: 0,
3728
+ width: targetWidth,
3729
+ height: targetHeight
3730
+ });
3731
+ const objects = frameObjects.length > 0 ? frameObjects : visionBundle?.getObjectResult(frameIdx).objects ?? [];
3732
+ if (objects.length > 0) {
3733
+ const boxColor = "#38bdf8";
3734
+ const stroke = 2;
3735
+ for (const obj of objects) {
3736
+ if (!obj.active) continue;
3737
+ const b = obj.boundingBox;
3738
+ ctx.renderer.drawRect(outPass$1, {
3739
+ x: b.originX,
3740
+ y: b.originY,
3741
+ width: b.width,
3742
+ height: stroke
3743
+ }, boxColor);
3744
+ ctx.renderer.drawRect(outPass$1, {
3745
+ x: b.originX,
3746
+ y: b.originY + b.height - stroke,
3747
+ width: b.width,
3748
+ height: stroke
3749
+ }, boxColor);
3750
+ ctx.renderer.drawRect(outPass$1, {
3751
+ x: b.originX,
3752
+ y: b.originY,
3753
+ width: stroke,
3754
+ height: b.height
3755
+ }, boxColor);
3756
+ ctx.renderer.drawRect(outPass$1, {
3757
+ x: b.originX + b.width - stroke,
3758
+ y: b.originY,
3759
+ width: stroke,
3760
+ height: b.height
3761
+ }, boxColor);
3762
+ const cornerLen = Math.min(16, b.width / 4, b.height / 4);
3763
+ ctx.renderer.drawRect(outPass$1, {
3764
+ x: b.originX,
3765
+ y: b.originY,
3766
+ width: cornerLen,
3767
+ height: stroke * 2
3768
+ }, "#ffffff");
3769
+ ctx.renderer.drawRect(outPass$1, {
3770
+ x: b.originX,
3771
+ y: b.originY,
3772
+ width: stroke * 2,
3773
+ height: cornerLen
3774
+ }, "#ffffff");
3775
+ if (mode === "tracking") ctx.renderer.drawRect(outPass$1, {
3776
+ x: obj.centerX - 3,
3777
+ y: obj.centerY - 3,
3778
+ width: 6,
3779
+ height: 6
3780
+ }, "#ef4444", 3);
3781
+ }
3782
+ }
3783
+ outPass$1.end();
3784
+ return;
3785
+ }
3786
+ const outPass = ctx.renderer.beginFrame(encoder, targetView, {
3787
+ r: 0,
3788
+ g: 0,
3789
+ b: 0,
3790
+ a: 0
3791
+ }, targetWidth, targetHeight, "clear");
3792
+ ctx.renderer.drawTexture(outPass, childTex, {
3793
+ x: 0,
3794
+ y: 0,
3795
+ width: targetWidth,
3796
+ height: targetHeight
3797
+ });
3798
+ outPass.end();
3799
+ };
3800
+ /** Adapts a Selfie Segmenter matte to the instance-mask shape the composite path takes. */
3801
+ function personMatteAsMask(matte) {
3802
+ if (!matte || matte.coverage <= 0) return void 0;
3803
+ const area = Math.round(matte.coverage * matte.width * matte.height);
3804
+ return {
3805
+ category: "person",
3806
+ mask: matte.mask,
3807
+ width: matte.width,
3808
+ height: matte.height,
3809
+ area,
3810
+ coverage: matte.coverage,
3811
+ detectionIndex: -1
3812
+ };
3813
+ }
3814
+ var renderers_default = defineRenderer({ WebGPURenderer: VisionWebGPURenderer });
3815
+
3816
+ //#endregion
3817
+ export { PoseSkeletonRenderer as a, TemporalObjectTracker as c, VisionRunner as d, analyzeSequence as f, COCO_CLASSES as h, COCO17_KEYPOINT_NAMES as i, VisionBundle as l, pinNodeToObject as m, COCO17_BONES as n, SegmentationTexturePool as o, pinNodeToLandmark as p, COCO17_KEYPOINTS as r, SpatialLandmarkTransformer as s, renderers_default as t, VisionNode as u };
3818
+ //# sourceMappingURL=renderer-XcmYTd6P.mjs.map