code wiki / (root) / nx_body_measurement_extract.nx

nx_body_measurement_extract.nx source

↩ module page · 270 lines · 10639 B

1// nx_body_measurement_extract.nx -- silhouette + scale -> BodyDimensions. 2// 3// Closes the customer-facing side of the retail-fit pipeline. Customer 4// takes a body photo (or stands in front of any camera); substrate 5// extracts measurable dimensions in millimeters. Pairs directly with 6// nx_garment_fit_match for the substrate-grounded "Small" replacement. 7// 8// PRIVACY NOTE: the substrate runs CLIENT-SIDE. Pixel data + measurements 9// stay on the customer's device. Only the verdict (PERFECT_TAILORED / 10// GOOD_FIT / etc. + recommended SKU rank) ships to the retailer's 11// server. No body-photo upload; no body-measurement leak. This is 12// the privacy-preserving inversion of the typical "send us your 13// measurements" e-commerce flow. 14// 15// CONTRACT (V1): 16// silhouette binary mask (foreground=1, background=0); single 17// channel; same w x h as the original photo 18// reference_px pixels measured for the reference object's 19// long axis (e.g., card_long_axis_pixels) 20// reference_mm the reference's real-world long axis in mm 21// (credit card 85.6mm; A4 paper 297mm; etc.) 22// landmark_y[] caller-supplied landmark row indices: 23// [0] top_of_head (auto-detectable as 24// topmost foreground row) 25// [1] shoulder_y (~ head + 0.13 * height) 26// [2] bust_y (~ head + 0.25 * height) 27// [3] waist_y (~ head + 0.37 * height, 28// OR narrowest-cross- 29// section search range) 30// [4] hip_y (~ head + 0.50 * height) 31// [5] inseam_top_y (groin / crotch) 32// [6] ankle_y (auto-detectable as 33// bottommost foreground row) 34// out *BodyDimensions struct from nx_garment_fit_match 35// 36// Future brick (queued): nx_body_landmark_detect.nx -- given silhouette, 37// emit landmark_y array automatically via anthropometric proportion 38// fitting + edge curvature. Until then, the caller provides them. 39// 40// genealogy_id: iso_8559_1989_body_measure + heine_2012_anthropometry + 41// winks_1997_static_dimensions + pheasant_2003_bodyspace 42// lineage_id: body_measurement_extract_q10_mm 43 44// nx_safety_envelope: 45// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 46// sil_target: SIL1 47// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 48// verdict: NOT_YET_EVALUATED 49 50import "nx_syscalls.nx" 51import "nx_tier.nx" 52import "nx_image.nx" 53import "nx_garment_fit_match.nx" 54 55const NX_BME_Q: nx_int = 1024 56 57// Landmark Y-index sealed constants (input array indices) 58const NX_BME_LM_TOP_OF_HEAD: nx_int = 0 59const NX_BME_LM_SHOULDER: nx_int = 1 60const NX_BME_LM_BUST: nx_int = 2 61const NX_BME_LM_WAIST: nx_int = 3 62const NX_BME_LM_HIP: nx_int = 4 63const NX_BME_LM_INSEAM_TOP: nx_int = 5 64const NX_BME_LM_ANKLE: nx_int = 6 65const NX_BME_LM_COUNT: nx_int = 7 66 67// Extraction fidelity sealed-enum bands (output) 68const NX_BME_FIDELITY_HIGH: nx_int = 0 // tight reference, clean silhouette 69const NX_BME_FIDELITY_MODERATE: nx_int = 1 70const NX_BME_FIDELITY_LOW: nx_int = 2 // silhouette holes / no reference 71const NX_BME_FIDELITY_UNUSABLE: nx_int = 3 // refuse to claim measurements 72const NX_BME_N_FIDELITY_BANDS: nx_int = 4 73 74struct BodyExtractReport { 75 body: BodyDimensions, 76 scale_px_per_mm_q10: nx_int, // pixels per mm in Q10 77 silhouette_area: nx_int, // pixel count of foreground 78 fidelity_band: nx_int, // NX_BME_FIDELITY_* 79 fidelity_q10: nx_int, 80} 81 82// ===== Helpers ======================================================== 83 84// Measure horizontal width of the silhouette at row y. 85// Walks left-to-right finding first/last foreground pixel. 86// Returns pixel count (0 if no foreground at that row). 87func _bme_width_at_row(sil: *Image, y: nx_int) -> nx_int { 88 let w: nx_int = sil.width 89 let h: nx_int = sil.height 90 if y < 0 { return 0 } 91 if y >= h { return 0 } 92 var left: nx_int = -1 93 var right: nx_int = -1 94 var x: nx_int = 0 95 while x < w { 96 let v: nx_int = nx_image_get(sil, x, y, 0) 97 if v > 0 { 98 if left < 0 { left = x } 99 right = x 100 } 101 x = x + 1 102 } 103 if left < 0 { return 0 } 104 return right - left + 1 105} 106 107// Sum foreground pixels (silhouette area for fidelity gate). 108func _bme_silhouette_area(sil: *Image) -> nx_int { 109 let w: nx_int = sil.width 110 let h: nx_int = sil.height 111 var sum: nx_int = 0 112 var y: nx_int = 0 113 while y < h { 114 var x: nx_int = 0 115 while x < w { 116 let v: nx_int = nx_image_get(sil, x, y, 0) 117 if v > 0 { sum = sum + 1 } 118 x = x + 1 119 } 120 y = y + 1 121 } 122 return sum 123} 124 125// Find the narrowest horizontal width in a Y-range (waist auto-locate). 126// Returns the narrowest width. Useful when caller doesn't have an 127// exact waist_y -- pass a y-range bracketing the mid-torso and let 128// the substrate find the local minimum. 129func nx_bme_narrowest_width_in_range(sil: *Image, 130 y_lo: nx_int, y_hi: nx_int) -> nx_int { 131 if y_lo >= y_hi { return 0 } 132 var min_w: nx_int = -1 133 var y: nx_int = y_lo 134 while y <= y_hi { 135 let w: nx_int = _bme_width_at_row(sil, y) 136 if w > 0 { 137 if min_w < 0 { min_w = w } 138 if w < min_w { min_w = w } 139 } 140 y = y + 1 141 } 142 if min_w < 0 { return 0 } 143 return min_w 144} 145 146// Find the widest horizontal width in a Y-range (hip auto-locate). 147func nx_bme_widest_width_in_range(sil: *Image, 148 y_lo: nx_int, y_hi: nx_int) -> nx_int { 149 if y_lo >= y_hi { return 0 } 150 var max_w: nx_int = 0 151 var y: nx_int = y_lo 152 while y <= y_hi { 153 let w: nx_int = _bme_width_at_row(sil, y) 154 if w > max_w { max_w = w } 155 y = y + 1 156 } 157 return max_w 158} 159 160// Convert pixels to millimeters using the reference-object scale. 161// scale_q10 = px_per_mm * Q10. mm = (pixels * Q10) / scale_q10. 162func _bme_px_to_mm(pixels: nx_int, scale_q10: nx_int) -> nx_int { 163 if scale_q10 <= 0 { return 0 } 164 return (pixels * NX_BME_Q) / scale_q10 165} 166 167// ===== Public extractor =============================================== 168 169func nx_body_measurement_extract( 170 sil: *Image, 171 reference_pixels: nx_int, 172 reference_mm: nx_int, 173 landmark_y: *nx_int, 174 archetype: nx_int, 175 out: *BodyExtractReport 176) -> nx_int { 177 // ---- Compute scale ---- 178 if reference_pixels <= 0 { return 1 } 179 if reference_mm <= 0 { return 1 } 180 // scale_q10 = (reference_pixels * Q10) / reference_mm -- px-per-mm in Q10 181 let scale_q10: nx_int = (reference_pixels * NX_BME_Q) / reference_mm 182 out.scale_px_per_mm_q10 = scale_q10 183 if scale_q10 <= 0 { return 1 } 184 185 // ---- Per-landmark widths ---- 186 let shoulder_y: nx_int = landmark_y[NX_BME_LM_SHOULDER] 187 let bust_y: nx_int = landmark_y[NX_BME_LM_BUST] 188 let waist_y: nx_int = landmark_y[NX_BME_LM_WAIST] 189 let hip_y: nx_int = landmark_y[NX_BME_LM_HIP] 190 let top_y: nx_int = landmark_y[NX_BME_LM_TOP_OF_HEAD] 191 let ankle_y: nx_int = landmark_y[NX_BME_LM_ANKLE] 192 let inseam_y: nx_int = landmark_y[NX_BME_LM_INSEAM_TOP] 193 194 let shoulder_w: nx_int = _bme_width_at_row(sil, shoulder_y) 195 let bust_w: nx_int = _bme_width_at_row(sil, bust_y) 196 let waist_w: nx_int = _bme_width_at_row(sil, waist_y) 197 let hip_w: nx_int = _bme_width_at_row(sil, hip_y) 198 199 // ---- Pixel widths -> mm ---- 200 // Note: silhouette WIDTH is the chord; the human body's 201 // circumference at that level is approximated as pi * width / 2 202 // (assuming roughly elliptical cross-section). V1 uses width 203 // directly; V2 will apply the elliptical-correction factor once 204 // depth-from-2-photos primitive ships. 205 let shoulder_mm: nx_int = _bme_px_to_mm(shoulder_w, scale_q10) 206 let bust_mm: nx_int = _bme_px_to_mm(bust_w, scale_q10) 207 let waist_mm: nx_int = _bme_px_to_mm(waist_w, scale_q10) 208 let hip_mm: nx_int = _bme_px_to_mm(hip_w, scale_q10) 209 210 // Height: top_of_head to ankle (vertical distance in pixels -> mm) 211 let height_px: nx_int = ankle_y - top_y 212 let height_mm: nx_int = _bme_px_to_mm(height_px, scale_q10) 213 214 // Inseam: inseam_top to ankle 215 let inseam_px: nx_int = ankle_y - inseam_y 216 let inseam_mm: nx_int = _bme_px_to_mm(inseam_px, scale_q10) 217 218 // Arm length: shoulder to ~ wrist (estimated as 0.45 * height 219 // anthropometric average until landmark detector ships) 220 let arm_mm: nx_int = (height_mm * 45) / 100 221 222 // ---- Populate BodyDimensions ---- 223 out.body.bust_mm = bust_mm 224 out.body.waist_mm = waist_mm 225 out.body.hip_mm = hip_mm 226 out.body.height_mm = height_mm 227 out.body.shoulder_width_mm = shoulder_mm 228 out.body.arm_length_mm = arm_mm 229 out.body.inseam_mm = inseam_mm 230 out.body.archetype = archetype 231 232 // ---- Fidelity gate ---- 233 // Silhouette area must be non-trivial; landmark widths must all 234 // be > 0; height-to-width ratio must be reasonable (anthropometric 235 // average ~ 3:1 to 4:1 for full-body shots). 236 out.silhouette_area = _bme_silhouette_area(sil) 237 238 var fid: nx_int = NX_BME_Q 239 if out.silhouette_area < 100 { fid = fid / 2 } 240 if shoulder_w == 0 { fid = fid / 2 } 241 if bust_w == 0 { fid = fid / 2 } 242 if waist_w == 0 { fid = fid / 2 } 243 if hip_w == 0 { fid = fid / 2 } 244 if height_px <= 0 { fid = 0 } 245 out.fidelity_q10 = fid 246 247 if fid == 0 { 248 out.fidelity_band = NX_BME_FIDELITY_UNUSABLE 249 } else { 250 if fid >= 768 { out.fidelity_band = NX_BME_FIDELITY_HIGH } 251 if fid < 768 { out.fidelity_band = NX_BME_FIDELITY_MODERATE } 252 if fid < 384 { out.fidelity_band = NX_BME_FIDELITY_LOW } 253 } 254 255 return 0 256} 257 258// ===== Sealed-enum validity ========================================= 259 260func nx_body_measurement_fidelity_is_valid(b: nx_int) -> nx_int { 261 if b < 0 { return 0 } 262 if b >= NX_BME_N_FIDELITY_BANDS { return 0 } 263 return 1 264} 265 266func nx_body_measurement_landmark_is_valid(l: nx_int) -> nx_int { 267 if l < 0 { return 0 } 268 if l >= NX_BME_LM_COUNT { return 0 } 269 return 1 270}