nx_image_feature_extract_test.nx source
↩ module page · 81 lines · 3092 B
1// nx_image_feature_extract_test.nx -- smoke for feature-vector emitter.
2
3import "nx_syscalls.nx"
4import "nx_tier.nx"
5import "nx_image.nx"
6import "nx_image_feature_extract.nx"
7
8func main() -> nx_int {
9 // === Test 1: feature vector length is the documented constant ===
10 if NX_IMAGE_FEATURE_LEN != 16 { return 1 }
11
12 // === Test 2: extract features from a uniform gray 16x16 image ===
13 // Uniform image: predictable features (high color-harmony since
14 // all-gray gets the achromatic-neutral fallback; sharpness = 0;
15 // edge density very low; symmetry trivially-maximal).
16 let rgb: *Image = nx_image_alloc(16, 16, 3)
17 let gray: *Image = nx_image_alloc(16, 16, 1)
18 var y: nx_int = 0
19 while y < 16 {
20 var x: nx_int = 0
21 while x < 16 {
22 nx_image_set(rgb, x, y, 0, 128)
23 nx_image_set(rgb, x, y, 1, 128)
24 nx_image_set(rgb, x, y, 2, 128)
25 nx_image_set(gray, x, y, 0, 128)
26 x = x + 1
27 }
28 y = y + 1
29 }
30 let vec: *nx_int = (sys_mmap(NX_IMAGE_FEATURE_LEN * NX_SIZEOF_NX_INT)) as *nx_int
31 nx_image_feature_extract(rgb, gray, vec)
32
33 // === Test 3: every feature should be in [0, Q10] (substrate invariant) ===
34 var k: nx_int = 0
35 while k < NX_IMAGE_FEATURE_LEN {
36 let v: nx_int = vec[k]
37 if v < 0 { return 10 + k }
38 // Special: SYM_TRANS_PERIOD can be larger than Q10 (it's a
39 // raw pixel count, not a Q10 fraction). Skip the upper bound
40 // for that axis only.
41 if k != NX_IMG_FEAT_SYM_TRANS_PERIOD {
42 if v > 1024 { return 30 + k }
43 }
44 k = k + 1
45 }
46
47 // === Test 4: uniform image's sharpness should be 0 ===
48 // Sobel on uniform input = 0 everywhere -> mean magnitude = 0.
49 if vec[NX_IMG_FEAT_SHARPNESS] != 0 { return 50 }
50
51 // === Test 5: uniform image's symmetry axes should be near-max ===
52 // Uniform = trivially symmetric across every axis.
53 if vec[NX_IMG_FEAT_SYM_H] < 900 { return 60 }
54 if vec[NX_IMG_FEAT_SYM_V] < 900 { return 61 }
55 if vec[NX_IMG_FEAT_SYM_DIAG] < 900 { return 62 }
56 if vec[NX_IMG_FEAT_SYM_ROT_180] < 900 { return 63 }
57
58 // === Test 6: centroid math ===
59 // 2 hand-built vectors [10, 20, 30, 40, ...] and [30, 40, 50, 60, ...]
60 // Centroid should be [20, 30, 40, 50, ...].
61 let two_vecs: *nx_int = (sys_mmap(2 * NX_IMAGE_FEATURE_LEN * NX_SIZEOF_NX_INT)) as *nx_int
62 var i: nx_int = 0
63 while i < NX_IMAGE_FEATURE_LEN {
64 two_vecs[i] = 10 + i * 10
65 two_vecs[NX_IMAGE_FEATURE_LEN + i] = 30 + i * 10
66 i = i + 1
67 }
68 let centroid: *nx_int = (sys_mmap(NX_IMAGE_FEATURE_LEN * NX_SIZEOF_NX_INT)) as *nx_int
69 nx_image_feature_centroid(two_vecs, 2, centroid)
70 var j: nx_int = 0
71 while j < NX_IMAGE_FEATURE_LEN {
72 let expected: nx_int = 20 + j * 10
73 if centroid[j] != expected { return 70 + j }
74 j = j + 1
75 }
76
77 // === Test 7: centroid with n_vectors = 0 is a no-op (return 0, no crash) ===
78 if nx_image_feature_centroid(two_vecs, 0, centroid) != 0 { return 90 }
79
80 return 0
81}