nx_vision_pipeline_test.nx source
↩ module page · 263 lines · 10946 B
1// nx_vision_pipeline_test.nx -- full-substrate integration test.
2//
3// Builds a single synthetic 32x32 RGB image with known features
4// (skin-toned background, a bright blob, a vertical edge, a circle)
5// and runs every vision primitive across L0->V10 in sequence.
6// Verifies cross-primitive compatibility and sane output ranges.
7//
8// On any failure returns a unique error code so the smoke runner
9// can pinpoint exactly which primitive class regressed.
10//
11// Coverage:
12// L4 nx_image : alloc, get/set, grayscale, Sobel, gradient mag, Otsu
13// L8 nx_features : Harris, corners, edges, CC, Hu, BRIEF
14// V1 nx_color_v2 : HSV, YCbCr, Kovac skin, palette, dominant
15// V4 nx_geom : Hough lines, Hough circles, orientation hist
16// V5 nx_texture : GLCM (contrast/energy/hom), LBP image+hist
17// V6 nx_scale : Gauss blur, downsample, pyramid 5L, DoG, extrema
18// V2 nx_compose : thirds, H/V symmetry, centroid, balance
19// V3 nx_light : mean, stddev, range, direction hist
20// V8 nx_salience : intensity, combined, peak, flatness
21// V10 nx_aesthetics : full report (5 components + composite)
22
23import "syscalls.nx"
24import "nx_image.nx"
25import "nx_features.nx"
26import "nx_color_v2.nx"
27import "nx_geom.nx"
28import "nx_texture.nx"
29import "nx_scale.nx"
30import "nx_compose.nx"
31import "nx_light.nx"
32import "nx_salience.nx"
33import "nx_aesthetics.nx"
34
35func main() -> i64 {
36 // === Build synthetic test image: 32x32 RGB ===
37 // Background: skin-tone (200, 150, 130)
38 // Left half (x<16): skin tone
39 // Right half (x>=16): pure blue (0, 0, 255) -> creates vertical edge at x=16
40 // Bright square: (10..14, 10..14) at value (250, 250, 250) -> blob
41 let rgb: *Image = nx_image_alloc(32, 32, 3)
42 var y: i64 = 0
43 while y < 32 {
44 var x: i64 = 0
45 while x < 32 {
46 if x < 16 {
47 nx_image_set(rgb, x, y, 0, 200)
48 nx_image_set(rgb, x, y, 1, 150)
49 nx_image_set(rgb, x, y, 2, 130)
50 }
51 if x >= 16 {
52 nx_image_set(rgb, x, y, 0, 0)
53 nx_image_set(rgb, x, y, 1, 0)
54 nx_image_set(rgb, x, y, 2, 255)
55 }
56 x = x + 1
57 }
58 y = y + 1
59 }
60 // Bright square embedded in skin region
61 var yb: i64 = 10
62 while yb < 14 {
63 var xb: i64 = 10
64 while xb < 14 {
65 nx_image_set(rgb, xb, yb, 0, 250)
66 nx_image_set(rgb, xb, yb, 1, 250)
67 nx_image_set(rgb, xb, yb, 2, 250)
68 xb = xb + 1
69 }
70 yb = yb + 1
71 }
72
73 // === L4: grayscale + Sobel + gradient magnitude ===
74 let gray: *Image = nx_image_to_grayscale(rgb)
75 if gray.channels != 1 { return 100 }
76 if gray.width != 32 { return 101 }
77 let gx: *ImageS64 = nx_image_sobel_x(gray)
78 let gy: *ImageS64 = nx_image_sobel_y(gray)
79 let mag: *ImageS64 = nx_image_gradient_magnitude(gx, gy)
80 // Strong gradient should exist near vertical edge x=16
81 let mag_at_edge: i64 = nx_image_s64_get(mag, 15, 16)
82 if mag_at_edge <= 0 { return 110 }
83
84 // === L4 cont: histogram + Otsu ===
85 let bins: *i64 = (sys_mmap(256 * 8)) as *i64
86 nx_image_histogram_256(gray, 0, bins)
87 let otsu_t: i64 = nx_image_otsu_threshold(gray, 0)
88 if otsu_t < 1 { return 120 }
89 if otsu_t > 254 { return 121 }
90
91 // === L8 features: edge mask + CC + Harris + BRIEF ===
92 let edge: *Image = nx_feat_edge_mask(gray, 50)
93 let cc: *CCResult = nx_feat_connected_components(edge)
94 if cc.n_components < 1 { return 130 }
95 let harris: *ImageS64 = nx_feat_harris_response(gray)
96 if harris.width != 32 { return 140 }
97 let bri_match: i64 = nx_feat_match_brief(0xFF, 0xFF)
98 if bri_match != 0 { return 150 }
99
100 // === V1 color: HSV + YCbCr + skin mask + palette + dominant channel ===
101 let hsv: *HSVColor = (sys_mmap(24)) as *HSVColor
102 nx_color_rgb_to_hsv(200, 150, 130, hsv)
103 if hsv.v != 200 { return 200 }
104 let ycc: *YCbCrColor = (sys_mmap(24)) as *YCbCrColor
105 nx_color_rgb_to_ycbcr(200, 150, 130, ycc)
106 if ycc.y < 100 { return 210 }
107 // Skin mask should mark left half as skin
108 let skin: *Image = nx_color_skin_mask(rgb)
109 if nx_image_get(skin, 5, 16, 0) != 255 { return 220 }
110 if nx_image_get(skin, 20, 16, 0) != 0 { return 221 }
111 // Palette: 3 clusters
112 let pal: *Palette = nx_color_kmeans_palette(rgb, 3, 5)
113 if pal.k != 3 { return 230 }
114 let total_pixels: i64 = pal.counts[0] + pal.counts[1] + pal.counts[2]
115 if total_pixels != 1024 { return 231 } // 32x32
116 // Dominant channel for skin-tone region
117 if nx_color_dominant_channel(200, 150, 130) != 0 { return 240 }
118
119 // === V4 geom: Hough line on edge mask ===
120 let lines: *HoughLines = nx_geom_hough_line(edge, 10, 20)
121 if lines.n_lines == 0 { return 300 }
122 // Should find theta=0 line near rho=16 (the vertical edge)
123 var found_v_line: i64 = 0
124 var li: i64 = 0
125 while li < lines.n_lines {
126 if lines.thetas[li] == 0 {
127 if lines.rhos[li] >= 13 {
128 if lines.rhos[li] <= 19 {
129 found_v_line = 1
130 }
131 }
132 }
133 li = li + 1
134 }
135 if found_v_line != 1 { return 310 }
136 let orient_hist: *i64 = (sys_mmap(36 * 8)) as *i64
137 nx_geom_line_orientation_hist(lines, orient_hist)
138 if orient_hist[0] <= 0 { return 320 }
139
140 // === V5 texture: GLCM + LBP ===
141 let glcm: *i64 = (sys_mmap(64 * 8)) as *i64
142 nx_texture_glcm(gray, 1, 0, glcm)
143 let glcm_total: i64 = nx_texture_glcm_total(glcm)
144 if glcm_total <= 0 { return 400 }
145 let tex_contrast: i64 = nx_texture_contrast(glcm)
146 let tex_energy: i64 = nx_texture_energy(glcm)
147 let tex_hom: i64 = nx_texture_homogeneity(glcm)
148 if tex_contrast < 0 { return 410 }
149 if tex_energy < 0 { return 411 }
150 if tex_energy > 1024 { return 412 }
151 if tex_hom < 0 { return 413 }
152 if tex_hom > 1024 { return 414 }
153 let lbp_img: *Image = nx_texture_lbp_image(gray)
154 let lbp_hist: *i64 = (sys_mmap(256 * 8)) as *i64
155 nx_texture_lbp_histogram(lbp_img, 1, 1, 31, 31, lbp_hist)
156 var lbp_total: i64 = 0
157 var lbi: i64 = 0
158 while lbi < 256 {
159 lbp_total = lbp_total + lbp_hist[lbi]
160 lbi = lbi + 1
161 }
162 if lbp_total != 900 { return 420 } // 30x30 interior
163
164 // === V6 scale: pyramid + DoG + extrema ===
165 let py: *GaussPyramid = nx_scale_gauss_pyramid(gray, 5)
166 if py.n_levels != 5 { return 500 }
167 let l0_p: *Image = nx_scale_pyramid_get(py, 0)
168 let l4_p: *Image = nx_scale_pyramid_get(py, 4)
169 if l0_p.width != 32 { return 510 }
170 if l4_p.width != 2 { return 511 }
171 let dog: *ImageS64 = nx_scale_dog(gray)
172 let bx: *i64 = (sys_mmap(100 * 8)) as *i64
173 let by: *i64 = (sys_mmap(100 * 8)) as *i64
174 let bs: *i64 = (sys_mmap(100 * 8)) as *i64
175 let n_blobs: i64 = nx_scale_dog_extrema(dog, 2, 100, bx, by, bs)
176 if n_blobs <= 0 { return 520 }
177
178 // === V2 compose: thirds + symmetry + centroid + balance ===
179 let thirds: i64 = nx_compose_thirds_score(gray)
180 if thirds < 0 { return 600 }
181 if thirds > 1024 { return 601 }
182 let h_sym: i64 = nx_compose_h_symmetry(gray)
183 if h_sym < 0 { return 610 }
184 if h_sym > 1024 { return 611 }
185 let v_sym: i64 = nx_compose_v_symmetry(gray)
186 if v_sym < 0 { return 620 }
187 if v_sym > 1024 { return 621 }
188 // Left=bright skin, right=dark blue: should be strongly H-asymmetric
189 if h_sym > 700 { return 612 }
190 let cx_q: *i64 = (sys_mmap(8)) as *i64
191 let cy_q: *i64 = (sys_mmap(8)) as *i64
192 nx_compose_visual_centroid(gray, cx_q, cy_q)
193 // Centroid should lean LEFT (skin is brighter than blue)
194 if cx_q[0] >= 512 { return 630 }
195 let bal: i64 = nx_compose_balance_score(gray)
196 if bal < 0 { return 640 }
197 if bal > 1024 { return 641 }
198
199 // === V3 light: stats + direction histogram ===
200 let mean_l: i64 = nx_light_mean_luminance(gray)
201 let std_l: i64 = nx_light_stddev_luminance(gray, mean_l)
202 let range_l: i64 = nx_light_dynamic_range(gray)
203 if mean_l <= 0 { return 700 }
204 if mean_l >= 255 { return 701 }
205 if std_l <= 0 { return 710 }
206 if range_l <= 0 { return 720 }
207 let under_l: i64 = nx_light_underexposed_frac(gray, 10)
208 let over_l: i64 = nx_light_overexposed_frac(gray, 245)
209 if under_l < 0 { return 730 }
210 if under_l > 1024 { return 731 }
211 if over_l < 0 { return 732 }
212 if over_l > 1024 { return 733 }
213 let dir_hist: *i64 = (sys_mmap(8 * 8)) as *i64
214 nx_light_direction_hist(gx, gy, 100, dir_hist)
215 // Dominant gradient should be West (going dark-to-bright when read l-to-r;
216 // skin (bright) -> blue (dark) means gradient points LEFT = West = bin 4)
217 // OR East depending on sign convention. Just verify ANY direction has votes.
218 var dir_total: i64 = 0
219 var di: i64 = 0
220 while di < 8 {
221 dir_total = dir_total + dir_hist[di]
222 di = di + 1
223 }
224 if dir_total <= 0 { return 740 }
225
226 // === V8 salience: intensity + combined + peak + flatness ===
227 let sal: *Image = nx_salience_intensity(gray)
228 let sal_px: *i64 = (sys_mmap(8)) as *i64
229 let sal_py: *i64 = (sys_mmap(8)) as *i64
230 let sal_peak: i64 = nx_salience_peak(sal, sal_px, sal_py)
231 if sal_peak <= 0 { return 800 }
232 let flat: i64 = nx_salience_flatness(sal)
233 if flat < 0 { return 810 }
234 if flat > 1024 { return 811 }
235 let csal: *Image = nx_salience_combined(gray)
236 let csal_px: *i64 = (sys_mmap(8)) as *i64
237 let csal_py: *i64 = (sys_mmap(8)) as *i64
238 let csal_peak: i64 = nx_salience_peak(csal, csal_px, csal_py)
239 if csal_peak <= 0 { return 820 }
240
241 // === V10 aesthetics: full composite report ===
242 let report: *AestheticsReport = (sys_mmap(48)) as *AestheticsReport
243 nx_aesthetics_compute(rgb, gray, report)
244 if report.color_harmony < 0 { return 900 }
245 if report.color_harmony > 1024 { return 901 }
246 if report.lighting_quality < 0 { return 910 }
247 if report.lighting_quality > 1024 { return 911 }
248 if report.composition < 0 { return 920 }
249 if report.composition > 1024 { return 921 }
250 if report.subject_clarity < 0 { return 930 }
251 if report.subject_clarity > 1024 { return 931 }
252 if report.sharpness < 0 { return 940 }
253 if report.sharpness > 1024 { return 941 }
254 if report.composite < 0 { return 950 }
255 if report.composite > 1024 { return 951 }
256 let expected_composite: i64 = (
257 report.color_harmony + report.lighting_quality + report.composition +
258 report.subject_clarity + report.sharpness
259 ) / 5
260 if report.composite != expected_composite { return 960 }
261
262 return 0
263}