nx_vulva_innie_outie.nx source
↩ module page · 164 lines · 5985 B
1// nx_vulva_innie_outie.nx -- labia minora protrusion classifier.
2//
3// Vulva aesthetic axis: innie vs outie. Measure how far inner-labia
4// pixels protrude BEYOND the outer-labia centerline. Positive = outie;
5// negative = innie (minora tucked inside).
6//
7// Algorithm:
8// 1. Within vulva bbox, find the outer-labia midline (vertical axis).
9// 2. Identify "inner labia" pixels: darker pink than outer skin.
10// 3. For each row in vulva band, measure max horizontal extent of
11// inner-labia pixels from the midline.
12// 4. Average extent → labia_protrusion_q10 (relative to vulva half-width).
13//
14// Output flat-array:
15// result[0] = protrusion_avg_px (avg per-row inner-labia max extent)
16// result[1] = vulva_half_width_px
17// result[2] = protrusion_q10 (avg_extent * 1024 / half_width)
18// result[3] = inner_labia_pixel_count
19// result[4] = verdict
20// 0=DEEP_INNIE / 1=INNIE / 2=NEUTRAL / 3=OUTIE / 4=PROMINENT_OUTIE
21
22// nx_safety_envelope:
23// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
24// sil_target: SIL1
25// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
26// verdict: NOT_YET_EVALUATED
27
28import "nx_syscalls.nx"
29import "nx_image.nx"
30const NX_MAGIC_1024: i64 = 1024
31
32const NX_INNIE_RES_PROT_PX: i64 = 0
33const NX_INNIE_RES_HALF_W: i64 = 1
34const NX_INNIE_RES_PROT_Q10: i64 = 2
35const NX_INNIE_RES_INNER_COUNT: i64 = 3
36const NX_INNIE_RES_VERDICT: i64 = 4
37const NX_INNIE_RES_FIELDS: i64 = 5
38
39const NX_INNIE_V_DEEP_INNIE: i64 = 0
40const NX_INNIE_V_INNIE: i64 = 1
41const NX_INNIE_V_NEUTRAL: i64 = 2
42const NX_INNIE_V_OUTIE: i64 = 3
43const NX_INNIE_V_PROMINENT_OUTIE: i64 = 4
44
45// Thresholds in Q10 of vulva half-width.
46const NX_INNIE_DEEP_INNIE_MAX: i64 = 102 // <= 10% = deep innie
47const NX_INNIE_INNIE_MAX: i64 = 256 // 10-25% = innie
48const NX_INNIE_NEUTRAL_MAX: i64 = 410 // 25-40% = neutral
49const NX_INNIE_OUTIE_MAX: i64 = 614 // 40-60% = outie; > 60% = prominent
50
51// Inner labia detection threshold: R-channel > outer skin by this delta.
52// Outer labia / surrounding skin is pinkish; inner labia is more red-pink.
53const NX_INNIE_R_DELTA_MIN: i64 = 12
54
55// Mean R of the perimeter band (outer labia / vulva margin baseline).
56func nx_innie_perimeter_r(rgb: *Image, x0: i64, y0: i64,
57 x1: i64, y1: i64) -> i64 {
58 var sum: i64 = 0
59 var n: i64 = 0
60 var y: i64 = y0
61 while y <= y1 {
62 if y >= 0 { if y < rgb.height {
63 // Top + bottom row.
64 if y == y0 {
65 var x: i64 = x0
66 while x <= x1 {
67 if x >= 0 { if x < rgb.width {
68 sum = sum + nx_image_get(rgb, x, y, 0)
69 n = n + 1
70 } }
71 x = x + 1
72 }
73 }
74 if y == y1 {
75 var x: i64 = x0
76 while x <= x1 {
77 if x >= 0 { if x < rgb.width {
78 sum = sum + nx_image_get(rgb, x, y, 0)
79 n = n + 1
80 } }
81 x = x + 1
82 }
83 }
84 // Left + right columns.
85 if x0 >= 0 { if x0 < rgb.width {
86 sum = sum + nx_image_get(rgb, x0, y, 0)
87 n = n + 1
88 } }
89 if x1 >= 0 { if x1 < rgb.width {
90 sum = sum + nx_image_get(rgb, x1, y, 0)
91 n = n + 1
92 } }
93 } }
94 y = y + 1
95 }
96 if n == 0 { return 0 }
97 return sum / n
98}
99
100// Score labia protrusion.
101// bbox: [vulva_x0, vulva_y0, vulva_x1, vulva_y1]
102func nx_vulva_innie_outie(rgb: *Image, bbox: *i64, result: *i64) -> i64 {
103 var i: i64 = 0
104 while i < NX_INNIE_RES_FIELDS { result[i] = 0; i = i + 1 }
105 let x0: i64 = bbox[0]
106 let y0: i64 = bbox[1]
107 let x1: i64 = bbox[2]
108 let y1: i64 = bbox[3]
109 if x1 <= x0 { return 1 }
110 if y1 <= y0 { return 2 }
111
112 let vulva_w: i64 = x1 - x0 + 1
113 let half_w: i64 = vulva_w / 2
114 let midline_x: i64 = (x0 + x1) / 2
115 result[NX_INNIE_RES_HALF_W] = half_w
116
117 let baseline_r: i64 = nx_innie_perimeter_r(rgb, x0, y0, x1, y1)
118 let inner_thresh: i64 = baseline_r + NX_INNIE_R_DELTA_MIN
119
120 var sum_max_extent: i64 = 0
121 var row_count: i64 = 0
122 var inner_count: i64 = 0
123
124 var y: i64 = y0
125 while y <= y1 {
126 if y >= 0 { if y < rgb.height {
127 var max_extent: i64 = 0
128 var x: i64 = x0
129 while x <= x1 {
130 if x >= 0 { if x < rgb.width {
131 let r: i64 = nx_image_get(rgb, x, y, 0)
132 if r > inner_thresh {
133 inner_count = inner_count + 1
134 var dx: i64 = x - midline_x
135 if dx < 0 { dx = -dx }
136 if dx > max_extent { max_extent = dx }
137 }
138 } }
139 x = x + 1
140 }
141 sum_max_extent = sum_max_extent + max_extent
142 row_count = row_count + 1
143 } }
144 y = y + 1
145 }
146
147 result[NX_INNIE_RES_INNER_COUNT] = inner_count
148 if row_count == 0 { return 3 }
149 let avg_extent: i64 = sum_max_extent / row_count
150 result[NX_INNIE_RES_PROT_PX] = avg_extent
151
152 if half_w == 0 { return 4 }
153 let prot_q10: i64 = (avg_extent * NX_MAGIC_1024) / half_w
154 result[NX_INNIE_RES_PROT_Q10] = prot_q10
155
156 var verdict: i64 = NX_INNIE_V_NEUTRAL
157 if prot_q10 <= NX_INNIE_DEEP_INNIE_MAX { verdict = NX_INNIE_V_DEEP_INNIE }
158 else { if prot_q10 <= NX_INNIE_INNIE_MAX { verdict = NX_INNIE_V_INNIE }
159 else { if prot_q10 <= NX_INNIE_NEUTRAL_MAX { verdict = NX_INNIE_V_NEUTRAL }
160 else { if prot_q10 <= NX_INNIE_OUTIE_MAX { verdict = NX_INNIE_V_OUTIE }
161 else { verdict = NX_INNIE_V_PROMINENT_OUTIE } } } }
162 result[NX_INNIE_RES_VERDICT] = verdict
163 return 0
164}