code wiki / (root) / nx_vision_pipeline_test.nx

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}