nx_pose_classify.nx source
↩ module page · 142 lines · 5396 B
1// nx_pose_classify.nx -- classify body pose from face + torso geometry.
2//
3// Heuristic geometric classifier — given face and torso bounding boxes
4// (already produced by nx_region), emit the most-likely pose enum that
5// drives nx_breast_drape_pose and other pose-aware detectors.
6//
7// Decision tree:
8// 1. Torso wider than tall (aspect < 1.0):
9// → LAYING_BACK if face above torso center
10// → FACE_DOWN_ASS_UP / ALL_FOURS otherwise
11// 2. Face strongly offset horizontally from torso center:
12// → SIDE_PROFILE if offset > 40% of torso width
13// → RECLINING_TWISTED if offset 20..40%
14// 3. Face above torso top with normal alignment:
15// → STANDING_FACING (or LEANING_FORWARD if torso aspect very tall)
16// 4. Face overlaps with torso vertically:
17// → ARCHED_PRESENTING (bent / kneeling) if torso aspect 1..1.4
18//
19// Pose enum values mirror nx_breast_drape_pose for direct routing.
20//
21// Output flat-array (result[]):
22// result[0] = pose_enum
23// result[1] = confidence_q10 (0=guess, 1024=clear signal)
24// result[2] = torso_aspect_q10
25// result[3] = face_x_offset_from_torso_center_q10 (% of torso width)
26// result[4] = face_above_torso (0=overlap, 1=fully above)
27
28// nx_safety_envelope:
29// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
30// sil_target: SIL1
31// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
32// verdict: NOT_YET_EVALUATED
33
34import "nx_syscalls.nx"
35const NX_MAGIC_1024: i64 = 1024
36
37// Pose enum values — match nx_breast_drape_pose constants for routing.
38const NX_POSE_UNKNOWN: i64 = 0
39const NX_POSE_STANDING_FACING: i64 = 1
40const NX_POSE_LEANING_FORWARD: i64 = 2
41const NX_POSE_LAYING_BACK: i64 = 3
42const NX_POSE_ARCHED_PRESENTING: i64 = 4
43const NX_POSE_RECLINING_TWISTED: i64 = 5
44const NX_POSE_SIDE_PROFILE: i64 = 6
45const NX_POSE_ALL_FOURS: i64 = 7
46const NX_POSE_FACE_DOWN_ASS_UP: i64 = 8
47const NX_POSE_KNEELING: i64 = 9
48
49const NX_POSE_RES_KIND: i64 = 0
50const NX_POSE_RES_CONFIDENCE: i64 = 1
51const NX_POSE_RES_TORSO_ASPECT: i64 = 2
52const NX_POSE_RES_X_OFFSET_PCT: i64 = 3
53const NX_POSE_RES_FACE_ABOVE: i64 = 4
54const NX_POSE_RES_FIELDS: i64 = 5
55
56// Decision thresholds (Q10 ratios or pct).
57const NX_POSE_LAY_ASPECT_MAX_Q10: i64 = 1024 // < 1.0 = wider than tall = laying
58const NX_POSE_LEAN_ASPECT_MIN_Q10: i64 = 1638 // > 1.6 = very tall torso = leaning
59const NX_POSE_TWIST_OFFSET_MIN_PCT: i64 = 20 // 20%+ horizontal face offset
60const NX_POSE_PROFILE_OFFSET_MIN_PCT: i64 = 40 // 40%+ = side profile
61const NX_POSE_ABOVE_GAP_MIN_PCT: i64 = 5 // face_y1 ≤ torso_y0 - 5%
62
63// bbox layout (i64 array, 8 entries):
64// [0..3] face x0, y0, x1, y1
65// [4..7] torso x0, y0, x1, y1
66func nx_pose_classify(bbox: *i64, result: *i64) -> i64 {
67 var i: i64 = 0
68 while i < NX_POSE_RES_FIELDS { result[i] = 0; i = i + 1 }
69
70 let face_x0: i64 = bbox[0]
71 let face_y0: i64 = bbox[1]
72 let face_x1: i64 = bbox[2]
73 let face_y1: i64 = bbox[3]
74 let torso_x0: i64 = bbox[4]
75 let torso_y0: i64 = bbox[5]
76 let torso_x1: i64 = bbox[6]
77 let torso_y1: i64 = bbox[7]
78
79 let torso_w: i64 = torso_x1 - torso_x0
80 let torso_h: i64 = torso_y1 - torso_y0
81 if torso_w <= 0 { return 1 }
82 if torso_h <= 0 { return 2 }
83 let face_cx: i64 = (face_x0 + face_x1) / 2
84 let torso_cx: i64 = (torso_x0 + torso_x1) / 2
85
86 let aspect_q10: i64 = (torso_h * NX_MAGIC_1024) / torso_w
87 result[NX_POSE_RES_TORSO_ASPECT] = aspect_q10
88
89 var x_offset: i64 = face_cx - torso_cx
90 if x_offset < 0 { x_offset = -x_offset }
91 let offset_pct: i64 = (x_offset * 100) / torso_w
92 result[NX_POSE_RES_X_OFFSET_PCT] = offset_pct
93
94 var face_above: i64 = 0
95 let gap_thresh: i64 = (torso_h * NX_POSE_ABOVE_GAP_MIN_PCT) / 100
96 if face_y1 + gap_thresh <= torso_y0 { face_above = 1 }
97 result[NX_POSE_RES_FACE_ABOVE] = face_above
98
99 // Decision tree.
100 var kind: i64 = NX_POSE_UNKNOWN
101 var conf: i64 = 0
102
103 if aspect_q10 < NX_POSE_LAY_ASPECT_MAX_Q10 {
104 // Torso wider than tall: laying or face-down.
105 let face_cy: i64 = (face_y0 + face_y1) / 2
106 let torso_cy: i64 = (torso_y0 + torso_y1) / 2
107 if face_cy < torso_cy {
108 kind = NX_POSE_LAYING_BACK
109 conf = 819
110 } else {
111 kind = NX_POSE_FACE_DOWN_ASS_UP
112 conf = 717
113 }
114 } else {
115 if offset_pct >= NX_POSE_PROFILE_OFFSET_MIN_PCT {
116 kind = NX_POSE_SIDE_PROFILE
117 conf = 870
118 } else {
119 if offset_pct >= NX_POSE_TWIST_OFFSET_MIN_PCT {
120 kind = NX_POSE_RECLINING_TWISTED
121 conf = 717
122 } else {
123 if aspect_q10 >= NX_POSE_LEAN_ASPECT_MIN_Q10 {
124 kind = NX_POSE_LEANING_FORWARD
125 conf = 768
126 } else {
127 if face_above == 0 {
128 kind = NX_POSE_ARCHED_PRESENTING
129 conf = 614
130 } else {
131 kind = NX_POSE_STANDING_FACING
132 conf = 870
133 }
134 }
135 }
136 }
137 }
138
139 result[NX_POSE_RES_KIND] = kind
140 result[NX_POSE_RES_CONFIDENCE] = conf
141 return 0
142}