nx_pose_coach.nx source
↩ module page · 72 lines · 3353 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"
7import "nx_vecmath.nx"
8
9func pc_isqrt(v: i64) -> i64 { return vm_isqrt(v) }
10// Euclidean distance between joint at (t1+o1) and (t2+o2), each 3 coords.
11func pc_jdist(t1: *i64, o1: i64, t2: *i64, o2: i64) -> i64 {
12 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]
13 return pc_isqrt(dx*dx + dy*dy + dz*dz)
14}
15// frame-to-frame distance: sum over joints of jdist(ref frame i, user frame j).
16func pc_framedist(ref: *i64, ri: i64, user: *i64, uj: i64, nj: i64) -> i64 {
17 var s: i64 = 0; var j: i64 = 0
18 while j < nj { s = s + pc_jdist(ref, (ri*nj+j)*3, user, (uj*nj+j)*3); j = j + 1 }
19 return s
20}
21// FRAME-ALIGNED score: total deviation (lower=better) + fills jdev[nj] with the max per-joint deviation across
22// frames (so pc_worst_joint names the culprit). Assumes same tempo.
23func pc_score(ref: *i64, user: *i64, T: i64, nj: i64, jdev: *i64) -> i64 {
24 var j0: i64 = 0
25 while j0 < nj { jdev[j0] = 0; j0 = j0 + 1 }
26 var total: i64 = 0; var f: i64 = 0
27 while f < T {
28 var j: i64 = 0
29 while j < nj {
30 let d: i64 = pc_jdist(ref, (f*nj+j)*3, user, (f*nj+j)*3)
31 total = total + d
32 if d > jdev[j] { jdev[j] = d }
33 j = j + 1
34 }
35 f = f + 1
36 }
37 return total
38}
39func pc_worst_joint(jdev: *i64, nj: i64) -> i64 {
40 var best: i64 = 0; var bv: i64 = jdev[0]; var j: i64 = 1
41 while j < nj { if jdev[j] > bv { bv = jdev[j]; best = j } j = j + 1 }
42 return best
43}
44// TEMPO-INVARIANT distance: DTW-align the two tracks (a slower/faster attempt maps onto the reference), so the
45// residual = FORM error only. cost = min-cost monotone alignment of frame distances. cap = T (<= 64 here).
46func pc_dtw(ref: *i64, user: *i64, T: i64, nj: i64) -> i64 {
47 let big: i64 = 4000000000
48 let cost: *i64 = sys_mmap(T * T * 8) as *i64
49 var i: i64 = 0
50 while i < T {
51 var j: i64 = 0
52 while j < T {
53 let fd: i64 = pc_framedist(ref, i, user, j, nj)
54 var best: i64 = big
55 if i == 0 { if j == 0 { best = 0 } }
56 if i > 0 { if cost[(i-1)*T+j] < best { best = cost[(i-1)*T+j] } }
57 if j > 0 { if cost[i*T+(j-1)] < best { best = cost[i*T+(j-1)] } }
58 if i > 0 { if j > 0 { if cost[(i-1)*T+(j-1)] < best { best = cost[(i-1)*T+(j-1)] } } }
59 cost[i*T+j] = fd + best
60 j = j + 1
61 }
62 i = i + 1
63 }
64 return cost[(T-1)*T + (T-1)]
65}
66// overall FORM SCORE in permille (1000 = perfect), from a deviation total + a tolerance scale.
67func pc_form_score(total: i64, samples: i64, tol: i64) -> i64 {
68 if samples <= 0 { return 0 }
69 let mean: i64 = total / samples // mean per-joint-frame deviation
70 if mean >= tol { return 0 }
71 return 1000 - (mean * 1000 / tol)
72}