nx_arm_count.nx source
↩ module page · 159 lines · 6067 B
1// nx_arm_count.nx -- limb-count detector for SDXL hallucination class.
2// v0: bbox-touches. v1 ADDS: skin-bridge ratio along centroid line.
3
4// nx_safety_envelope:
5// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
6// sil_target: SIL1
7// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
8// verdict: NOT_YET_EVALUATED
9
10import "nx_syscalls.nx"
11import "nx_image.nx"
12import "nx_color_v2.nx"
13import "nx_features.nx"
14const NX_MAGIC_1024: i64 = 1024
15
16const NX_ARM_NOISE_AREA_Q10: i64 = 5
17const NX_ARM_LIMB_AREA_Q10: i64 = 20
18const NX_ARM_TORSO_AREA_Q10: i64 = 100
19const NX_ARM_BBOX_MARGIN: i64 = 4
20const NX_ARM_BRIDGE_RATIO_Q10: i64 = 256
21const NX_ARM_LINE_SAMPLES: i64 = 20
22
23struct ArmCountVerdict {
24 total_skin_components: i64,
25 torso_present: i64,
26 torso_area: i64,
27 limb_candidate_count: i64,
28 plausible_count: i64,
29 implausible_count: i64,
30 severity_q10: i64,
31 fidelity_q10: i64,
32 min_bridge_ratio_q10: i64,
33}
34
35func nx_arm_bbox_touches(a_min_x: i64, a_min_y: i64, a_max_x: i64, a_max_y: i64,
36 b_min_x: i64, b_min_y: i64, b_max_x: i64, b_max_y: i64,
37 margin: i64) -> i64 {
38 if a_max_x + margin < b_min_x { return 0 }
39 if b_max_x + margin < a_min_x { return 0 }
40 if a_max_y + margin < b_min_y { return 0 }
41 if b_max_y + margin < a_min_y { return 0 }
42 return 1
43}
44
45func nx_arm_line_skin_ratio(mask: *Image, x0: i64, y0: i64, x1: i64, y1: i64) -> i64 {
46 let n: i64 = NX_ARM_LINE_SAMPLES
47 let denom: i64 = n - 1
48 var hits: i64 = 0
49 var s: i64 = 0
50 while s < n {
51 let x: i64 = x0 + ((x1 - x0) * s) / denom
52 let y: i64 = y0 + ((y1 - y0) * s) / denom
53 if x >= 0 {
54 if x < mask.width {
55 if y >= 0 {
56 if y < mask.height {
57 if nx_image_get(mask, x, y, 0) > 0 { hits = hits + 1 }
58 }
59 }
60 }
61 }
62 s = s + 1
63 }
64 return (hits * NX_MAGIC_1024) / n
65}
66
67func nx_arm_count_compute(rgb: *Image, verdict: *ArmCountVerdict) -> i64 {
68 let w: i64 = rgb.width
69 let h: i64 = rgb.height
70 let total_area: i64 = w * h
71 if total_area <= 0 { return 1 }
72
73 let noise_floor: i64 = (total_area * NX_ARM_NOISE_AREA_Q10) / NX_MAGIC_1024
74 let limb_floor: i64 = (total_area * NX_ARM_LIMB_AREA_Q10) / NX_MAGIC_1024
75 let torso_floor: i64 = (total_area * NX_ARM_TORSO_AREA_Q10) / NX_MAGIC_1024
76
77 let mask: *Image = nx_color_skin_mask(rgb)
78 let cc: *CCResult = nx_feat_connected_components(mask)
79 let n: i64 = cc.n_components
80
81 verdict.total_skin_components = 0
82 verdict.torso_present = 0
83 verdict.torso_area = 0
84 verdict.limb_candidate_count = 0
85 verdict.plausible_count = 0
86 verdict.implausible_count = 0
87 verdict.severity_q10 = 0
88 verdict.fidelity_q10 = 0
89 verdict.min_bridge_ratio_q10 = NX_MAGIC_1024
90 if n == 0 { return 0 }
91
92 let stats: *i64 = (sys_mmap(n * 7 * 8 + 16)) as *i64
93 nx_feat_component_stats(cc, stats)
94
95 var torso_idx: i64 = -1
96 var torso_area_val: i64 = 0
97 var i: i64 = 0
98 while i < n {
99 let area: i64 = stats[i * 7]
100 if area >= noise_floor { verdict.total_skin_components = verdict.total_skin_components + 1 }
101 if area >= torso_floor {
102 if area > torso_area_val { torso_area_val = area; torso_idx = i }
103 }
104 i = i + 1
105 }
106 if torso_idx < 0 { verdict.fidelity_q10 = 256; return 0 }
107
108 verdict.torso_present = 1
109 verdict.torso_area = torso_area_val
110 let t_min_x: i64 = stats[torso_idx * 7 + 1]
111 let t_min_y: i64 = stats[torso_idx * 7 + 2]
112 let t_max_x: i64 = stats[torso_idx * 7 + 3]
113 let t_max_y: i64 = stats[torso_idx * 7 + 4]
114 let t_cx: i64 = stats[torso_idx * 7 + 5] / 1000
115 let t_cy: i64 = stats[torso_idx * 7 + 6] / 1000
116
117 i = 0
118 while i < n {
119 if i != torso_idx {
120 let area: i64 = stats[i * 7]
121 if area >= limb_floor {
122 if area < torso_floor {
123 verdict.limb_candidate_count = verdict.limb_candidate_count + 1
124 let c_min_x: i64 = stats[i * 7 + 1]
125 let c_min_y: i64 = stats[i * 7 + 2]
126 let c_max_x: i64 = stats[i * 7 + 3]
127 let c_max_y: i64 = stats[i * 7 + 4]
128 let c_cx: i64 = stats[i * 7 + 5] / 1000
129 let c_cy: i64 = stats[i * 7 + 6] / 1000
130 let touches: i64 = nx_arm_bbox_touches(
131 c_min_x, c_min_y, c_max_x, c_max_y,
132 t_min_x, t_min_y, t_max_x, t_max_y, NX_ARM_BBOX_MARGIN)
133 let bridge_q10: i64 = nx_arm_line_skin_ratio(mask, c_cx, c_cy, t_cx, t_cy)
134 if bridge_q10 < verdict.min_bridge_ratio_q10 { verdict.min_bridge_ratio_q10 = bridge_q10 }
135 var bbox_ok: i64 = 0
136 var bridge_ok: i64 = 0
137 if touches == 1 { bbox_ok = 1 }
138 if bridge_q10 >= NX_ARM_BRIDGE_RATIO_Q10 { bridge_ok = 1 }
139 if bbox_ok == 1 {
140 if bridge_ok == 1 { verdict.plausible_count = verdict.plausible_count + 1 }
141 }
142 if bbox_ok == 0 { verdict.implausible_count = verdict.implausible_count + 1 }
143 if bbox_ok == 1 {
144 if bridge_ok == 0 { verdict.implausible_count = verdict.implausible_count + 1 }
145 }
146 }
147 }
148 }
149 i = i + 1
150 }
151 var sev: i64 = verdict.implausible_count * 256
152 if sev > NX_MAGIC_1024 { sev = NX_MAGIC_1024 }
153 verdict.severity_q10 = sev
154 var fid: i64 = (torso_area_val * NX_MAGIC_1024) / total_area
155 fid = fid * 4
156 if fid > NX_MAGIC_1024 { fid = NX_MAGIC_1024 }
157 verdict.fidelity_q10 = fid
158 return 0
159}