nx_region.nx source
↩ module page · 194 lines · 6841 B
1// nx_region.nx -- typed body-region segmentation (V7 substrate).
2//
3// Composes V1 Kovac skin mask + L7 CC + L7 component stats into a
4// typed RegionSet with sealed BodyPart enum. Mirrors region.py.
5//
6// genealogy_id: rosenfeld_pfaltz_1966_cc + kovac_2003_skin
7// lineage_id: typed_anatomical_regioning_from_skin_topology
8
9// nx_safety_envelope:
10// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
11// sil_target: SIL1
12// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
13// verdict: NOT_YET_EVALUATED
14
15import "nx_syscalls.nx"
16import "nx_image.nx"
17import "nx_color_v2.nx"
18import "nx_features.nx"
19
20// Sealed BodyPart enum
21const NX_REGION_UNKNOWN: i64 = 0
22const NX_REGION_TORSO: i64 = 1
23const NX_REGION_FACE: i64 = 2
24const NX_REGION_LIMB: i64 = 3
25const NX_REGION_BACKGROUND: i64 = 4
26
27// Flat-array field indices
28const NX_REGION_F_KIND: i64 = 0
29const NX_REGION_F_BBOX_MIN_X: i64 = 1
30const NX_REGION_F_BBOX_MIN_Y: i64 = 2
31const NX_REGION_F_BBOX_MAX_X: i64 = 3
32const NX_REGION_F_BBOX_MAX_Y: i64 = 4
33const NX_REGION_F_CENTROID_X: i64 = 5
34const NX_REGION_F_CENTROID_Y: i64 = 6
35const NX_REGION_F_AREA_PX: i64 = 7
36const NX_REGION_F_CONF_Q10: i64 = 8
37const NX_REGION_FIELDS: i64 = 9
38const NX_REGION_MAX_COUNT: i64 = 16
39
40// Area thresholds (Q10 of total image area)
41const NX_REGION_NOISE_AREA_Q10: i64 = 5
42const NX_REGION_LIMB_AREA_Q10: i64 = 20
43const NX_REGION_TORSO_AREA_Q10: i64 = 100
44const NX_REGION_FACE_FRAC_Q10: i64 = 307 // top 30% of torso for FACE
45
46struct RegionSet {
47 data: *i64,
48 count: i64,
49 cap: i64,
50}
51
52func nx_region_get_field(set: *RegionSet, i: i64, field: i64) -> i64 {
53 return set.data[i * NX_REGION_FIELDS + field]
54}
55
56func nx_region_add(set: *RegionSet, kind: i64,
57 bmin_x: i64, bmin_y: i64, bmax_x: i64, bmax_y: i64,
58 cx: i64, cy: i64, area: i64, conf_q10: i64) -> i64 {
59 if set.count >= set.cap { return 1 }
60 let base: i64 = set.count * NX_REGION_FIELDS
61 set.data[base + NX_REGION_F_KIND] = kind
62 set.data[base + NX_REGION_F_BBOX_MIN_X] = bmin_x
63 set.data[base + NX_REGION_F_BBOX_MIN_Y] = bmin_y
64 set.data[base + NX_REGION_F_BBOX_MAX_X] = bmax_x
65 set.data[base + NX_REGION_F_BBOX_MAX_Y] = bmax_y
66 set.data[base + NX_REGION_F_CENTROID_X] = cx
67 set.data[base + NX_REGION_F_CENTROID_Y] = cy
68 set.data[base + NX_REGION_F_AREA_PX] = area
69 set.data[base + NX_REGION_F_CONF_Q10] = conf_q10
70 set.count = set.count + 1
71 return 0
72}
73
74func nx_region_count_kind(set: *RegionSet, kind: i64) -> i64 {
75 var i: i64 = 0
76 var n: i64 = 0
77 while i < set.count {
78 if nx_region_get_field(set, i, NX_REGION_F_KIND) == kind {
79 n = n + 1
80 }
81 i = i + 1
82 }
83 return n
84}
85
86func nx_region_find_kind(set: *RegionSet, kind: i64) -> i64 {
87 var i: i64 = 0
88 while i < set.count {
89 if nx_region_get_field(set, i, NX_REGION_F_KIND) == kind {
90 return i
91 }
92 i = i + 1
93 }
94 return -1
95}
96
97func nx_region_segment(rgb: *Image) -> *RegionSet {
98 let w: i64 = rgb.width
99 let h: i64 = rgb.height
100 let total_area: i64 = w * h
101 let set: *RegionSet = (sys_mmap(32)) as *RegionSet
102 set.cap = NX_REGION_MAX_COUNT
103 set.count = 0
104 set.data = (sys_mmap(NX_REGION_MAX_COUNT * NX_REGION_FIELDS * 8 + 16)) as *i64
105 if total_area <= 0 { return set }
106
107 let noise_floor: i64 = (total_area * NX_REGION_NOISE_AREA_Q10) / 1024
108 let limb_floor: i64 = (total_area * NX_REGION_LIMB_AREA_Q10) / 1024
109 let torso_floor: i64 = (total_area * NX_REGION_TORSO_AREA_Q10) / 1024
110
111 let mask: *Image = nx_color_skin_mask(rgb)
112 let cc: *CCResult = nx_feat_connected_components(mask)
113 let n: i64 = cc.n_components
114 if n == 0 {
115 nx_region_add(set, NX_REGION_BACKGROUND, 0, 0, w-1, h-1, w/2, h/2, total_area, 1024)
116 return set
117 }
118
119 let stats: *i64 = (sys_mmap(n * 7 * 8 + 16)) as *i64
120 nx_feat_component_stats(cc, stats)
121
122 var torso_idx: i64 = -1
123 var torso_area: i64 = 0
124 var i: i64 = 0
125 while i < n {
126 let area: i64 = stats[i * 7]
127 if area >= torso_floor {
128 if area > torso_area {
129 torso_area = area
130 torso_idx = i
131 }
132 }
133 i = i + 1
134 }
135
136 if torso_idx >= 0 {
137 let t_min_x: i64 = stats[torso_idx * 7 + 1]
138 let t_min_y: i64 = stats[torso_idx * 7 + 2]
139 let t_max_x: i64 = stats[torso_idx * 7 + 3]
140 let t_max_y: i64 = stats[torso_idx * 7 + 4]
141 let t_cx: i64 = stats[torso_idx * 7 + 5] / 1000
142 let t_cy: i64 = stats[torso_idx * 7 + 6] / 1000
143 var t_conf: i64 = (torso_area * 1024) / total_area
144 t_conf = t_conf * 4
145 if t_conf > 1024 { t_conf = 1024 }
146 nx_region_add(set, NX_REGION_TORSO,
147 t_min_x, t_min_y, t_max_x, t_max_y,
148 t_cx, t_cy, torso_area, t_conf)
149
150 // FACE region: top 30% of torso bbox
151 let height: i64 = t_max_y - t_min_y
152 let face_h: i64 = (height * NX_REGION_FACE_FRAC_Q10) / 1024
153 var f_max_y: i64 = t_min_y + face_h
154 if f_max_y > t_max_y { f_max_y = t_max_y }
155 let f_cx: i64 = (t_min_x + t_max_x) / 2
156 let f_cy: i64 = (t_min_y + f_max_y) / 2
157 let face_area: i64 = (t_max_x - t_min_x + 1) * (f_max_y - t_min_y + 1)
158 nx_region_add(set, NX_REGION_FACE,
159 t_min_x, t_min_y, t_max_x, f_max_y,
160 f_cx, f_cy, face_area, 768)
161 }
162
163 i = 0
164 while i < n {
165 if i != torso_idx {
166 let area: i64 = stats[i * 7]
167 if area >= limb_floor {
168 if area < torso_floor {
169 let l_min_x: i64 = stats[i * 7 + 1]
170 let l_min_y: i64 = stats[i * 7 + 2]
171 let l_max_x: i64 = stats[i * 7 + 3]
172 let l_max_y: i64 = stats[i * 7 + 4]
173 let l_cx: i64 = stats[i * 7 + 5] / 1000
174 let l_cy: i64 = stats[i * 7 + 6] / 1000
175 var l_conf: i64 = 1024
176 if torso_area > 0 {
177 l_conf = (area * 1024) / torso_area
178 if l_conf > 1024 { l_conf = 1024 }
179 }
180 nx_region_add(set, NX_REGION_LIMB,
181 l_min_x, l_min_y, l_max_x, l_max_y,
182 l_cx, l_cy, area, l_conf)
183 }
184 }
185 }
186 i = i + 1
187 }
188
189 // BACKGROUND: inverse skin (whole-image bbox, area = non-skin count)
190 var bg_area: i64 = total_area
191 if torso_area > 0 { bg_area = total_area - torso_area }
192 nx_region_add(set, NX_REGION_BACKGROUND, 0, 0, w-1, h-1, w/2, h/2, bg_area, 1024)
193 return set
194}