code wiki / (root) / nx_texture.nx

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}