nx_texture.nx source
↩ module page · 215 lines · 6904 B
1// nx_texture.nx -- texture primitives (V5 of vision rollout).
2//
3// Two complementary descriptors:
4//
5// 1. GLCM Haralick features (Haralick 1973)
6// Gray-Level Co-occurrence Matrix counts pixel-pair frequencies
7// at offset (dx, dy). Derived statistics: contrast, energy,
8// homogeneity.
9//
10// 2. Local Binary Patterns (Ojala 1996, 2002)
11// 8-bit signature comparing each pixel to its 8 neighbors.
12// Robust to monotonic illumination change. Histogram of LBP
13// codes characterizes texture.
14//
15// What this unlocks:
16// + material discrimination (skin / fabric / fur / feather / concrete)
17// + grass vs concrete vs sand for outdoor scene classification
18// + texture-based salience (busy region vs flat)
19// + texture homogeneity check for image-quality (over-blurred?)
20//
21// All i64. GLCM features in Q10 (scaled by 1024) for cross-image
22// comparison.
23//
24// genealogy_id: haralick_1973_glcm + ojala_1996_lbp + ojala_2002_uniform
25// lineage_id: co_occurrence_matrix + binary_pattern_histogram
26
27// nx_safety_envelope:
28// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
29// sil_target: SIL1
30// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
31// verdict: NOT_YET_EVALUATED
32
33import "syscalls.nx"
34import "nx_image.nx"
35
36// GLCM quantization: 8 gray levels (0..7) so the matrix is 8x8.
37const NX_TEX_GLEVELS: i64 = 8
38const NX_TEX_Q: i64 = 1024
39
40// ===== GLCM construction ================================================
41//
42// Builds an 8x8 matrix where glcm[i*8 + j] counts pairs where pixel
43// at (x, y) has quantized value i AND pixel at (x+dx, y+dy) has
44// quantized value j.
45//
46// Quantization: q = pixel >> 5 (0..255 -> 0..7).
47
48func nx_texture_glcm(img: *Image, dx: i64, dy: i64, out_glcm: *i64) -> i64 {
49 var i: i64 = 0
50 while i < 64 {
51 out_glcm[i] = 0
52 i = i + 1
53 }
54 let w: i64 = img.width
55 let h: i64 = img.height
56 var y: i64 = 0
57 while y < h {
58 var x: i64 = 0
59 while x < w {
60 let nx_pos: i64 = x + dx
61 let ny_pos: i64 = y + dy
62 if nx_pos >= 0 {
63 if nx_pos < w {
64 if ny_pos >= 0 {
65 if ny_pos < h {
66 let qa: i64 = nx_image_get(img, x, y, 0) >> 5
67 let qb: i64 = nx_image_get(img, nx_pos, ny_pos, 0) >> 5
68 let idx: i64 = qa * 8 + qb
69 out_glcm[idx] = out_glcm[idx] + 1
70 }
71 }
72 }
73 }
74 x = x + 1
75 }
76 y = y + 1
77 }
78 return 0
79}
80
81func nx_texture_glcm_total(glcm: *i64) -> i64 {
82 var t: i64 = 0
83 var i: i64 = 0
84 while i < 64 {
85 t = t + glcm[i]
86 i = i + 1
87 }
88 return t
89}
90
91// ===== Haralick features (Q10) ==========================================
92
93// Contrast = sum (i-j)^2 * P(i,j). Q10 scaled.
94// uniform texture -> low contrast (concentrated near diagonal)
95// busy texture -> high contrast
96func nx_texture_contrast(glcm: *i64) -> i64 {
97 let total: i64 = nx_texture_glcm_total(glcm)
98 if total == 0 { return 0 }
99 var acc: i64 = 0
100 var i: i64 = 0
101 while i < 8 {
102 var j: i64 = 0
103 while j < 8 {
104 let d: i64 = i - j
105 acc = acc + d * d * glcm[i * 8 + j]
106 j = j + 1
107 }
108 i = i + 1
109 }
110 return (acc * NX_TEX_Q) / total
111}
112
113// Energy / Angular Second Moment = sum P(i,j)^2. Q10 scaled.
114// uniform texture -> high energy (single bin dominates)
115// noisy texture -> low energy
116func nx_texture_energy(glcm: *i64) -> i64 {
117 let total: i64 = nx_texture_glcm_total(glcm)
118 if total == 0 { return 0 }
119 var acc: i64 = 0
120 var i: i64 = 0
121 while i < 64 {
122 let c: i64 = glcm[i]
123 acc = acc + c * c
124 i = i + 1
125 }
126 return (acc * NX_TEX_Q) / (total * total)
127}
128
129// Homogeneity = sum P(i,j) / (1 + (i-j)^2). Q10 scaled.
130// uniform texture -> high homogeneity
131// busy texture -> low homogeneity
132func nx_texture_homogeneity(glcm: *i64) -> i64 {
133 let total: i64 = nx_texture_glcm_total(glcm)
134 if total == 0 { return 0 }
135 var acc: i64 = 0
136 var i: i64 = 0
137 while i < 8 {
138 var j: i64 = 0
139 while j < 8 {
140 let d: i64 = i - j
141 let denom: i64 = 1 + d * d
142 acc = acc + (glcm[i * 8 + j] * NX_TEX_Q) / denom
143 j = j + 1
144 }
145 i = i + 1
146 }
147 return acc / total
148}
149
150// ===== Local Binary Patterns ============================================
151//
152// 8-bit code per pixel: each bit is 1 iff the corresponding neighbor
153// >= center. Neighbor order (clockwise from top-left):
154// bit 0: (-1,-1) bit 1: ( 0,-1) bit 2: (+1,-1)
155// bit 3: (+1, 0) bit 4: (+1,+1) bit 5: ( 0,+1)
156// bit 6: (-1,+1) bit 7: (-1, 0)
157//
158// Code values 0..255. Used to build a 256-bin histogram per region.
159
160func nx_texture_lbp_pixel(img: *Image, x: i64, y: i64) -> i64 {
161 let center: i64 = nx_image_get(img, x, y, 0)
162 var code: i64 = 0
163 if nx_image_get(img, x - 1, y - 1, 0) >= center { code = code + 1 }
164 if nx_image_get(img, x, y - 1, 0) >= center { code = code + 2 }
165 if nx_image_get(img, x + 1, y - 1, 0) >= center { code = code + 4 }
166 if nx_image_get(img, x + 1, y, 0) >= center { code = code + 8 }
167 if nx_image_get(img, x + 1, y + 1, 0) >= center { code = code + 16 }
168 if nx_image_get(img, x, y + 1, 0) >= center { code = code + 32 }
169 if nx_image_get(img, x - 1, y + 1, 0) >= center { code = code + 64 }
170 if nx_image_get(img, x - 1, y, 0) >= center { code = code + 128 }
171 return code
172}
173
174// Build LBP code image. Returns single-channel image with values 0..255.
175// Boundary pixels (x=0, x=w-1, y=0, y=h-1) get partial codes (out-of-
176// bounds neighbors treated as 0 by nx_image_get).
177func nx_texture_lbp_image(img: *Image) -> *Image {
178 let out: *Image = nx_image_alloc(img.width, img.height, 1)
179 var y: i64 = 0
180 while y < img.height {
181 var x: i64 = 0
182 while x < img.width {
183 nx_image_set(out, x, y, 0, nx_texture_lbp_pixel(img, x, y))
184 x = x + 1
185 }
186 y = y + 1
187 }
188 return out
189}
190
191// Histogram of LBP codes over a rectangular region.
192// hist must point to 256 i64 slots.
193func nx_texture_lbp_histogram(lbp_img: *Image, x0: i64, y0: i64,
194 x1: i64, y1: i64, hist: *i64) -> i64 {
195 var i: i64 = 0
196 while i < 256 {
197 hist[i] = 0
198 i = i + 1
199 }
200 var y: i64 = y0
201 while y < y1 {
202 var x: i64 = x0
203 while x < x1 {
204 let c: i64 = nx_image_get(lbp_img, x, y, 0)
205 if c >= 0 {
206 if c < 256 {
207 hist[c] = hist[c] + 1
208 }
209 }
210 x = x + 1
211 }
212 y = y + 1
213 }
214 return 0
215}