code wiki / (root) / nx_areola_localize.nx

nx_areola_localize.nx source

↩ module page · 172 lines · 6010 B

1// nx_areola_localize.nx -- find areola disk within breast bbox. 2// 3// Within the breast bbox produced by nx_breast_localize, the areola 4// is the small darker-pigmented region. Substrate threshold: 5// areola pixel = luminance < (breast_mean_luminance - DARKNESS_DELTA) 6// 7// Returns bbox + area + centroid of the largest contiguous-by-row dark 8// cluster. Feeds nx_areola_pigment for the pigment-vs-reproductive- 9// state verdict. 10// 11// Output flat-array (per single breast): 12// result[0..3] areola bbox: x0, y0, x1, y1 13// result[4] area_px 14// result[5] centroid_x 15// result[6] centroid_y 16// result[7] breast_mean_luminance (debug) 17 18// nx_safety_envelope: 19// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 20// sil_target: SIL1 21// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 22// verdict: NOT_YET_EVALUATED 23 24import "nx_syscalls.nx" 25import "nx_image.nx" 26 27const NX_AREOLAL_RES_X0: i64 = 0 28const NX_AREOLAL_RES_Y0: i64 = 1 29const NX_AREOLAL_RES_X1: i64 = 2 30const NX_AREOLAL_RES_Y1: i64 = 3 31const NX_AREOLAL_RES_AREA: i64 = 4 32const NX_AREOLAL_RES_CENTROID_X: i64 = 5 33const NX_AREOLAL_RES_CENTROID_Y: i64 = 6 34const NX_AREOLAL_RES_BREAST_MEAN: i64 = 7 35const NX_AREOLAL_RES_FIELDS: i64 = 8 36 37// Pixels darker than baseline by this Y delta = areola candidate. 38const NX_AREOLAL_DARKNESS_DELTA: i64 = 18 39 40// Compute mean luminance over the breast bbox. 41// breast_bbox: i64 array [x0, y0, x1, y1] 42func nx_areolal_breast_mean(rgb: *Image, breast_bbox: *i64) -> i64 { 43 let x0: i64 = breast_bbox[0] 44 let y0: i64 = breast_bbox[1] 45 let x1: i64 = breast_bbox[2] 46 let y1: i64 = breast_bbox[3] 47 var sum: i64 = 0 48 var n: i64 = 0 49 var y: i64 = y0 50 while y <= y1 { 51 if y >= 0 { if y < rgb.height { 52 var x: i64 = x0 53 while x <= x1 { 54 if x >= 0 { if x < rgb.width { 55 let r: i64 = nx_image_get(rgb, x, y, 0) 56 let g: i64 = nx_image_get(rgb, x, y, 1) 57 let b: i64 = nx_image_get(rgb, x, y, 2) 58 let yv: i64 = (77 * r + 150 * g + 29 * b) / 256 59 sum = sum + yv 60 n = n + 1 61 } } 62 x = x + 1 63 } 64 } } 65 y = y + 1 66 } 67 if n == 0 { return 0 } 68 return sum / n 69} 70 71// Locate areola via luminance threshold within breast bbox. 72func nx_areola_localize(rgb: *Image, breast_bbox: *i64, result: *i64) -> i64 { 73 var i: i64 = 0 74 while i < NX_AREOLAL_RES_FIELDS { result[i] = 0; i = i + 1 } 75 76 let x0: i64 = breast_bbox[0] 77 let y0: i64 = breast_bbox[1] 78 let x1: i64 = breast_bbox[2] 79 let y1: i64 = breast_bbox[3] 80 if x1 <= x0 { return 1 } 81 if y1 <= y0 { return 2 } 82 83 let baseline: i64 = nx_areolal_breast_mean(rgb, breast_bbox) 84 result[NX_AREOLAL_RES_BREAST_MEAN] = baseline 85 let threshold: i64 = baseline - NX_AREOLAL_DARKNESS_DELTA 86 if threshold < 0 { return 3 } 87 88 // Accumulate dark-pixel bbox + sum-coords for centroid. 89 var areola_x0: i64 = x1 90 var areola_y0: i64 = y1 91 var areola_x1: i64 = x0 92 var areola_y1: i64 = y0 93 var area: i64 = 0 94 var sum_x: i64 = 0 95 var sum_y: i64 = 0 96 var y: i64 = y0 97 while y <= y1 { 98 if y >= 0 { if y < rgb.height { 99 var x: i64 = x0 100 while x <= x1 { 101 if x >= 0 { if x < rgb.width { 102 let r: i64 = nx_image_get(rgb, x, y, 0) 103 let g: i64 = nx_image_get(rgb, x, y, 1) 104 let b: i64 = nx_image_get(rgb, x, y, 2) 105 let yv: i64 = (77 * r + 150 * g + 29 * b) / 256 106 if yv < threshold { 107 if x < areola_x0 { areola_x0 = x } 108 if x > areola_x1 { areola_x1 = x } 109 if y < areola_y0 { areola_y0 = y } 110 if y > areola_y1 { areola_y1 = y } 111 area = area + 1 112 sum_x = sum_x + x 113 sum_y = sum_y + y 114 } 115 } } 116 x = x + 1 117 } 118 } } 119 y = y + 1 120 } 121 122 if area > 0 { 123 result[NX_AREOLAL_RES_X0] = areola_x0 124 result[NX_AREOLAL_RES_Y0] = areola_y0 125 result[NX_AREOLAL_RES_X1] = areola_x1 126 result[NX_AREOLAL_RES_Y1] = areola_y1 127 result[NX_AREOLAL_RES_AREA] = area 128 result[NX_AREOLAL_RES_CENTROID_X] = sum_x / area 129 result[NX_AREOLAL_RES_CENTROID_Y] = sum_y / area 130 } 131 return 0 132} 133 134// Build an areola binary mask (255 in dark pixels within breast bbox, 135// 0 elsewhere). Used downstream by nx_areola_pigment which expects a 136// mask input. 137func nx_areola_build_mask(rgb: *Image, breast_bbox: *i64, mask_out: *Image) -> i64 { 138 let w: i64 = mask_out.width 139 let h: i64 = mask_out.height 140 var yy: i64 = 0 141 while yy < h { 142 var xx: i64 = 0 143 while xx < w { nx_image_set(mask_out, xx, yy, 0, 0); xx = xx + 1 } 144 yy = yy + 1 145 } 146 let x0: i64 = breast_bbox[0] 147 let y0: i64 = breast_bbox[1] 148 let x1: i64 = breast_bbox[2] 149 let y1: i64 = breast_bbox[3] 150 let baseline: i64 = nx_areolal_breast_mean(rgb, breast_bbox) 151 let threshold: i64 = baseline - NX_AREOLAL_DARKNESS_DELTA 152 if threshold < 0 { return 1 } 153 154 var y: i64 = y0 155 while y <= y1 { 156 if y >= 0 { if y < h { 157 var x: i64 = x0 158 while x <= x1 { 159 if x >= 0 { if x < w { 160 let r: i64 = nx_image_get(rgb, x, y, 0) 161 let g: i64 = nx_image_get(rgb, x, y, 1) 162 let b: i64 = nx_image_get(rgb, x, y, 2) 163 let yv: i64 = (77 * r + 150 * g + 29 * b) / 256 164 if yv < threshold { nx_image_set(mask_out, x, y, 0, 255) } 165 } } 166 x = x + 1 167 } 168 } } 169 y = y + 1 170 } 171 return 0 172}