code wiki / (root) / nx_pose_classify.nx

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}