nx_features_test.nx source
↩ module page · 194 lines · 6806 B
1// nx_features_test.nx -- smoke for image + feature primitives.
2//
3// Uses SYNTHETIC test images (generated procedurally) so the smoke
4// has no external dependency.
5
6import "syscalls.nx"
7import "nx_axioms.nx"
8import "nx_image.nx"
9import "nx_features.nx"
10import "nx_qed_freek.nx"
11
12func main() -> i64 {
13 // === Test 1: build a 16x16 RGB image (solid white)
14 // and verify pixel access.
15 let img: *Image = nx_image_alloc(16, 16, 3)
16 var y: i64 = 0
17 while y < 16 {
18 var x: i64 = 0
19 while x < 16 {
20 nx_image_set(img, x, y, 0, 200) // R
21 nx_image_set(img, x, y, 1, 150) // G
22 nx_image_set(img, x, y, 2, 100) // B
23 x = x + 1
24 }
25 y = y + 1
26 }
27 if nx_image_get(img, 5, 5, 0) != 200 { return 1 }
28 if nx_image_get(img, 5, 5, 1) != 150 { return 2 }
29 if nx_image_get(img, 5, 5, 2) != 100 { return 3 }
30 if nx_image_get(img, 100, 100, 0) != 0 { return 4 } // out of bounds returns 0
31
32 // === Test 2: grayscale conversion
33 let gray: *Image = nx_image_to_grayscale(img)
34 if gray.channels != 1 { return 10 }
35 // Expected: (218*200 + 732*150 + 74*100) / 1024
36 // = (43600 + 109800 + 7400) / 1024
37 // = 160800 / 1024 ~= 157
38 let g_expected: i64 = (218 * 200 + 732 * 150 + 74 * 100) / 1024
39 if nx_image_get(gray, 5, 5, 0) != g_expected { return 11 }
40
41 // === Test 3: build a synthetic edge image
42 // (left half black, right half white)
43 let edge: *Image = nx_image_alloc(16, 16, 1)
44 var y2: i64 = 0
45 while y2 < 16 {
46 var x2: i64 = 0
47 while x2 < 16 {
48 var v: i64 = 0
49 if x2 >= 8 { v = 255 }
50 nx_image_set(edge, x2, y2, 0, v)
51 x2 = x2 + 1
52 }
53 y2 = y2 + 1
54 }
55 // Sobel x should detect the vertical edge at x=8.
56 let gx: *ImageS64 = nx_image_sobel_x(edge)
57 // At (7, 8) we expect a strong positive response (pixels to the
58 // right are 255, pixels to the left are 0).
59 let gx_at_7: i64 = nx_image_s64_get(gx, 7, 8)
60 if gx_at_7 <= 0 { return 20 }
61 // At (3, 8) we're inside the dark region, gradient should be 0.
62 let gx_at_3: i64 = nx_image_s64_get(gx, 3, 8)
63 if gx_at_3 != 0 { return 21 }
64
65 // === Test 4: Sobel y on a horizontal edge
66 let hedge: *Image = nx_image_alloc(16, 16, 1)
67 var y3: i64 = 0
68 while y3 < 16 {
69 var x3: i64 = 0
70 while x3 < 16 {
71 var v: i64 = 0
72 if y3 >= 8 { v = 255 }
73 nx_image_set(hedge, x3, y3, 0, v)
74 x3 = x3 + 1
75 }
76 y3 = y3 + 1
77 }
78 let gy: *ImageS64 = nx_image_sobel_y(hedge)
79 let gy_at_8: i64 = nx_image_s64_get(gy, 8, 7)
80 if gy_at_8 <= 0 { return 30 }
81
82 // === Test 5: gradient magnitude
83 let edge_gx: *ImageS64 = nx_image_sobel_x(edge)
84 let edge_gy: *ImageS64 = nx_image_sobel_y(edge)
85 let mag: *ImageS64 = nx_image_gradient_magnitude(edge_gx, edge_gy)
86 let mag_at_7: i64 = nx_image_s64_get(mag, 7, 8)
87 if mag_at_7 <= 0 { return 40 }
88
89 // === Test 6: histogram of edge image
90 let bins: *i64 = (sys_mmap(256 * 8)) as *i64
91 nx_image_histogram_256(edge, 0, bins)
92 // half black (0) + half white (255). 8 columns * 16 rows = 128 each.
93 if bins[0] != 128 { return 50 }
94 if bins[255] != 128 { return 51 }
95 // Middle bins all zero.
96 if bins[128] != 0 { return 52 }
97
98 // === Test 7: Otsu threshold should land between black and white
99 let t: i64 = nx_image_otsu_threshold(edge, 0)
100 if t < 1 { return 60 }
101 if t > 254 { return 61 }
102
103 // === Test 8: threshold to binary mask
104 let mask: *Image = nx_image_threshold(edge, 0, 128)
105 if nx_image_get(mask, 3, 8, 0) != 0 { return 70 }
106 if nx_image_get(mask, 12, 8, 0) != 255 { return 71 }
107
108 // === Test 9: edge mask via Sobel + threshold
109 let emask: *Image = nx_feat_edge_mask(edge, 100)
110 // Edge should be detected near x=7-8.
111 if nx_image_get(emask, 7, 8, 0) != 255 { return 80 }
112 // Interior should not be edge.
113 if nx_image_get(emask, 2, 8, 0) != 0 { return 81 }
114
115 // === Test 10: connected components on a synthetic mask with 2 blobs
116 let blob: *Image = nx_image_alloc(16, 16, 1)
117 // Blob 1: 3x3 at (2,2)
118 var by1: i64 = 2
119 while by1 < 5 {
120 var bx1: i64 = 2
121 while bx1 < 5 {
122 nx_image_set(blob, bx1, by1, 0, 255)
123 bx1 = bx1 + 1
124 }
125 by1 = by1 + 1
126 }
127 // Blob 2: 4x4 at (10, 10)
128 var by2: i64 = 10
129 while by2 < 14 {
130 var bx2: i64 = 10
131 while bx2 < 14 {
132 nx_image_set(blob, bx2, by2, 0, 255)
133 bx2 = bx2 + 1
134 }
135 by2 = by2 + 1
136 }
137 let cc: *CCResult = nx_feat_connected_components(blob)
138 if cc.n_components != 2 { return 90 }
139
140 // === Test 11: component stats
141 let stats: *i64 = (sys_mmap(2 * 7 * 8)) as *i64
142 nx_feat_component_stats(cc, stats)
143 // Blob 1 has 9 pixels, Blob 2 has 16 pixels.
144 let b1_count: i64 = stats[0]
145 let b2_count: i64 = stats[7]
146 var ok_count: i64 = 0
147 if b1_count == 9 { if b2_count == 16 { ok_count = 1 } }
148 if b1_count == 16 { if b2_count == 9 { ok_count = 1 } }
149 if ok_count != 1 { return 100 }
150
151 // === Test 12: Hu moments on the blob
152 let hu: *i64 = (sys_mmap(7 * 8)) as *i64
153 nx_feat_hu_moments(blob, hu)
154 // Hu1 should be positive (sum of central moments).
155 if hu[0] <= 0 { return 110 }
156
157 // === Test 13: Hamming descriptor matching
158 let d_match: i64 = nx_feat_match_brief(0xFF, 0xFF)
159 if d_match != 0 { return 120 }
160 let d_diff: i64 = nx_feat_match_brief(0xFF, 0x00)
161 if d_diff != 8 { return 121 }
162
163 // === Test 14: Harris response on a corner-rich image (checkerboard)
164 let cb: *Image = nx_image_alloc(16, 16, 1)
165 var cby: i64 = 0
166 while cby < 16 {
167 var cbx: i64 = 0
168 while cbx < 16 {
169 let blockx: i64 = cbx / 4
170 let blocky: i64 = cby / 4
171 let on: i64 = (blockx + blocky) - ((blockx + blocky) / 2) * 2
172 var v: i64 = 0
173 if on == 1 { v = 255 }
174 nx_image_set(cb, cbx, cby, 0, v)
175 cbx = cbx + 1
176 }
177 cby = cby + 1
178 }
179 let resp: *ImageS64 = nx_feat_harris_response(cb)
180 // Some pixels should have positive Harris response (corners).
181 var found_corner: i64 = 0
182 var ry: i64 = 1
183 while ry < 15 {
184 var rx: i64 = 1
185 while rx < 15 {
186 if nx_image_s64_get(resp, rx, ry) > 0 { found_corner = 1 }
187 rx = rx + 1
188 }
189 ry = ry + 1
190 }
191 if found_corner != 1 { return 130 }
192
193 return 0
194}