nx_thigh_proportion.nx source
↩ module page · 132 lines · 5054 B
1// nx_thigh_proportion.nx -- thigh thickness + thigh gap detector.
2//
3// Two axes from the same measurement scan:
4// thickness: mid-thigh width / hip width (canon 0.5-0.7)
5// gap: inner-thigh gap / hip width (0 = touching, > 0 = visible gap)
6//
7// Both inputs are pixel widths from row scans of the skin mask in the
8// upper-thigh / mid-thigh band.
9//
10// Output flat-array:
11// result[0] = mid_thigh_width_px (avg of L + R single thigh)
12// result[1] = thigh_gap_px (gap at top, 0 if legs touching)
13// result[2] = thickness_ratio_q10
14// result[3] = gap_ratio_q10
15// result[4] = thickness_verdict (0=TOO_THIN, 1=SLIM, 2=ATHLETIC, 3=CURVY, 4=THICK)
16// result[5] = gap_verdict (0=NO_GAP, 1=SMALL_GAP, 2=MODEST_GAP, 3=WIDE_GAP)
17
18// nx_safety_envelope:
19// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
20// sil_target: SIL1
21// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
22// verdict: NOT_YET_EVALUATED
23
24import "nx_syscalls.nx"
25import "nx_image.nx"
26const NX_MAGIC_1024: i64 = 1024
27
28const NX_THIGH_RES_THICK_PX: i64 = 0
29const NX_THIGH_RES_GAP_PX: i64 = 1
30const NX_THIGH_RES_THICK_Q10: i64 = 2
31const NX_THIGH_RES_GAP_Q10: i64 = 3
32const NX_THIGH_RES_THICK_VRDT: i64 = 4
33const NX_THIGH_RES_GAP_VRDT: i64 = 5
34const NX_THIGH_RES_FIELDS: i64 = 6
35
36const NX_THIGH_V_THIN: i64 = 0 // < 0.45 hip — concerning
37const NX_THIGH_V_SLIM: i64 = 1 // 0.45-0.55
38const NX_THIGH_V_ATHLETIC: i64 = 2 // 0.55-0.65
39const NX_THIGH_V_CURVY: i64 = 3 // 0.65-0.75
40const NX_THIGH_V_THICK: i64 = 4 // > 0.75
41
42const NX_THIGH_GAP_NONE: i64 = 0 // touching
43const NX_THIGH_GAP_SMALL: i64 = 1 // 1-5% hip
44const NX_THIGH_GAP_MODEST: i64 = 2 // 5-12% hip
45const NX_THIGH_GAP_WIDE: i64 = 3 // > 12% hip
46
47// Measure mask widths in upper-thigh band.
48// bbox layout (8 entries):
49// [0..3] hip_band x0,y0,x1,y1
50// [4..7] thigh_band x0,y0,x1,y1 (covers BOTH thighs from upper to mid)
51func nx_thigh_proportion(skin_mask: *Image, bbox: *i64,
52 hip_width_px: i64, result: *i64) -> i64 {
53 var i: i64 = 0
54 while i < NX_THIGH_RES_FIELDS { result[i] = 0; i = i + 1 }
55 if hip_width_px <= 0 { return 1 }
56
57 let t_x0: i64 = bbox[4]
58 let t_y0: i64 = bbox[5]
59 let t_x1: i64 = bbox[6]
60 let t_y1: i64 = bbox[7]
61 let band_h: i64 = t_y1 - t_y0
62 if band_h <= 0 { return 2 }
63
64 // Sample mid-thigh row (middle of band).
65 let mid_y: i64 = t_y0 + band_h / 2
66 let top_y: i64 = t_y0 + band_h / 8
67
68 // Sum L thigh width + R thigh width at mid_y by scanning for runs.
69 // We also count GAP pixels at top_y between L and R thigh runs.
70 var l_w: i64 = 0
71 var r_w: i64 = 0
72 var midline_x: i64 = (t_x0 + t_x1) / 2
73
74 // Mid-row: count consecutive skin pixels left and right of midline.
75 var x: i64 = midline_x - 1
76 while x >= t_x0 {
77 if nx_image_get(skin_mask, x, mid_y, 0) > 0 { l_w = l_w + 1 } else { x = t_x0 - 1 }
78 x = x - 1
79 }
80 x = midline_x
81 while x <= t_x1 {
82 if nx_image_get(skin_mask, x, mid_y, 0) > 0 { r_w = r_w + 1 } else { x = t_x1 + 1 }
83 x = x + 1
84 }
85
86 // Top-row: walk inward from each thigh edge to find gap.
87 // Find leftmost skin at top_y starting from midline going left, then
88 // first non-skin going right of that — that's the gap.
89 var gap_px: i64 = 0
90 var gx: i64 = midline_x
91 var have_l: i64 = 0
92 while gx >= t_x0 {
93 if nx_image_get(skin_mask, gx, top_y, 0) > 0 {
94 have_l = 1
95 gx = gx + 1
96 // Walk right from here counting non-skin until we hit skin again.
97 while gx <= t_x1 {
98 if nx_image_get(skin_mask, gx, top_y, 0) > 0 { gx = t_x1 + 2 }
99 else { gap_px = gap_px + 1 }
100 gx = gx + 1
101 }
102 gx = t_x0 - 1
103 } else { gx = gx - 1 }
104 }
105
106 let avg_thigh_w: i64 = (l_w + r_w) / 2
107 result[NX_THIGH_RES_THICK_PX] = avg_thigh_w
108 result[NX_THIGH_RES_GAP_PX] = gap_px
109
110 let thick_q10: i64 = (avg_thigh_w * NX_MAGIC_1024) / hip_width_px
111 let gap_q10: i64 = (gap_px * NX_MAGIC_1024) / hip_width_px
112 result[NX_THIGH_RES_THICK_Q10] = thick_q10
113 result[NX_THIGH_RES_GAP_Q10] = gap_q10
114
115 // Thickness verdict.
116 var tv: i64 = NX_THIGH_V_ATHLETIC
117 if thick_q10 < 461 { tv = NX_THIGH_V_THIN }
118 else { if thick_q10 < 563 { tv = NX_THIGH_V_SLIM }
119 else { if thick_q10 < 666 { tv = NX_THIGH_V_ATHLETIC }
120 else { if thick_q10 < 768 { tv = NX_THIGH_V_CURVY }
121 else { tv = NX_THIGH_V_THICK } } } }
122 result[NX_THIGH_RES_THICK_VRDT] = tv
123
124 // Gap verdict.
125 var gv: i64 = NX_THIGH_GAP_NONE
126 if gap_q10 < 10 { gv = NX_THIGH_GAP_NONE }
127 else { if gap_q10 < 51 { gv = NX_THIGH_GAP_SMALL }
128 else { if gap_q10 < 123 { gv = NX_THIGH_GAP_MODEST }
129 else { gv = NX_THIGH_GAP_WIDE } } }
130 result[NX_THIGH_RES_GAP_VRDT] = gv
131 return 0
132}