nx_pose_coach.nx source
↩ module page · 71 lines · 3428 B
1// nx_pose_coach.nx -- the COACH's read (roadmap R8): compare a USER pose track to a REFERENCE track and say
2// WHAT is off (which joint, how far) and score the form -- with TEMPO-INVARIANT alignment (DTW) so a slower/
3// faster attempt isn't penalised for form. Pure integer math on joint-position tracks (track[f*nj*3 + j*3 + c]);
4// the tracks come from R3-retarget (from video via R2) or the rig. This is "help people achieve their goals":
5// name the error, not just a number. license_tier: ORIGINAL
6import "nx_syscalls.nx"
7
8func pc_isqrt(v: i64) -> i64 { if v <= 0 { return 0 } var x: i64 = v; var y: i64 = (x + 1) / 2; while y < x { x = y; y = (x + v / x) / 2 } return x }
9// Euclidean distance between joint at (t1+o1) and (t2+o2), each 3 coords.
10func pc_jdist(t1: *i64, o1: i64, t2: *i64, o2: i64) -> i64 {
11 let dx: i64 = t1[o1] - t2[o2]; let dy: i64 = t1[o1+1] - t2[o2+1]; let dz: i64 = t1[o1+2] - t2[o2+2]
12 return pc_isqrt(dx*dx + dy*dy + dz*dz)
13}
14// frame-to-frame distance: sum over joints of jdist(ref frame i, user frame j).
15func pc_framedist(ref: *i64, ri: i64, user: *i64, uj: i64, nj: i64) -> i64 {
16 var s: i64 = 0; var j: i64 = 0
17 while j < nj { s = s + pc_jdist(ref, (ri*nj+j)*3, user, (uj*nj+j)*3); j = j + 1 }
18 return s
19}
20// FRAME-ALIGNED score: total deviation (lower=better) + fills jdev[nj] with the max per-joint deviation across
21// frames (so pc_worst_joint names the culprit). Assumes same tempo.
22func pc_score(ref: *i64, user: *i64, T: i64, nj: i64, jdev: *i64) -> i64 {
23 var j0: i64 = 0
24 while j0 < nj { jdev[j0] = 0; j0 = j0 + 1 }
25 var total: i64 = 0; var f: i64 = 0
26 while f < T {
27 var j: i64 = 0
28 while j < nj {
29 let d: i64 = pc_jdist(ref, (f*nj+j)*3, user, (f*nj+j)*3)
30 total = total + d
31 if d > jdev[j] { jdev[j] = d }
32 j = j + 1
33 }
34 f = f + 1
35 }
36 return total
37}
38func pc_worst_joint(jdev: *i64, nj: i64) -> i64 {
39 var best: i64 = 0; var bv: i64 = jdev[0]; var j: i64 = 1
40 while j < nj { if jdev[j] > bv { bv = jdev[j]; best = j } j = j + 1 }
41 return best
42}
43// TEMPO-INVARIANT distance: DTW-align the two tracks (a slower/faster attempt maps onto the reference), so the
44// residual = FORM error only. cost = min-cost monotone alignment of frame distances. cap = T (<= 64 here).
45func pc_dtw(ref: *i64, user: *i64, T: i64, nj: i64) -> i64 {
46 let big: i64 = 4000000000
47 let cost: *i64 = sys_mmap(T * T * 8) as *i64
48 var i: i64 = 0
49 while i < T {
50 var j: i64 = 0
51 while j < T {
52 let fd: i64 = pc_framedist(ref, i, user, j, nj)
53 var best: i64 = big
54 if i == 0 { if j == 0 { best = 0 } }
55 if i > 0 { if cost[(i-1)*T+j] < best { best = cost[(i-1)*T+j] } }
56 if j > 0 { if cost[i*T+(j-1)] < best { best = cost[i*T+(j-1)] } }
57 if i > 0 { if j > 0 { if cost[(i-1)*T+(j-1)] < best { best = cost[(i-1)*T+(j-1)] } } }
58 cost[i*T+j] = fd + best
59 j = j + 1
60 }
61 i = i + 1
62 }
63 return cost[(T-1)*T + (T-1)]
64}
65// overall FORM SCORE in permille (1000 = perfect), from a deviation total + a tolerance scale.
66func pc_form_score(total: i64, samples: i64, tol: i64) -> i64 {
67 if samples <= 0 { return 0 }
68 let mean: i64 = total / samples // mean per-joint-frame deviation
69 if mean >= tol { return 0 }
70 return 1000 - (mean * 1000 / tol)
71}