code wiki / (root) / nx_garment_extract_measurements.nx

nx_garment_extract_measurements.nx source

↩ module page · 290 lines · 9990 B

1// nx_garment_extract_measurements.nx -- product photo -> GarmentDimensions. 2// 3// Closes the RETAILER side of the lossless-sizing pipeline. Retailer 4// photographs each SKU with a reference object in frame (or beside it); 5// substrate extracts real bust/waist/hip/length/shoulder/sleeve/inseam 6// in millimeters. Eliminates the manual "type 36 measurements per 7// SKU" tax that's the chief reason apparel sizing data is so dirty 8// in the first place. 9// 10// Pairs directly with nx_body_measurement_extract on the customer 11// side + nx_garment_fit_match for the verdict. All three primitives 12// use the same silhouette + reference-scale + caller-supplied 13// landmark-Y pattern; substrate doesn't pretend to detect landmarks 14// (that's a separate future primitive nx_garment_landmark_detect.nx). 15// 16// CONTRACT (V1): 17// silhouette binary mask of the garment (background-removed) 18// reference_pixels pixel length of reference object's long axis 19// reference_mm real-world long axis in mm 20// landmark_y[] row indices: top / bust / waist / hip / hem / 21// sleeve_end / inseam_top 22// cut garment cut sealed-enum (NX_FIT_CUT_*) -- caller 23// knows the SKU's cut; substrate routes flattery 24// math accordingly 25// out *GarmentExtractReport carrying GarmentDimensions 26// 27// genealogy_id: iso_8559_1989_body_measure + asbm_2017_apparel_grading + 28// ashdown_2007_sizing_systems 29// lineage_id: garment_measurement_extract_q10_mm 30 31// nx_safety_envelope: 32// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 33// sil_target: SIL1 34// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 35// verdict: NOT_YET_EVALUATED 36 37import "nx_syscalls.nx" 38import "nx_tier.nx" 39import "nx_image.nx" 40import "nx_garment_fit_match.nx" 41 42const NX_GME_Q: nx_int = 1024 43 44// Landmark Y-index sealed constants (input array indices) 45const NX_GME_LM_TOP: nx_int = 0 // top of shoulder seam OR waistband 46const NX_GME_LM_BUST: nx_int = 1 47const NX_GME_LM_WAIST: nx_int = 2 48const NX_GME_LM_HIP: nx_int = 3 49const NX_GME_LM_HEM: nx_int = 4 // bottom of garment 50const NX_GME_LM_SLEEVE_END: nx_int = 5 // 0 if sleeveless 51const NX_GME_LM_INSEAM_TOP: nx_int = 6 // 0 if not pants 52const NX_GME_LM_COUNT: nx_int = 7 53 54// Fidelity sealed-enum bands 55const NX_GME_FIDELITY_HIGH: nx_int = 0 56const NX_GME_FIDELITY_MODERATE: nx_int = 1 57const NX_GME_FIDELITY_LOW: nx_int = 2 58const NX_GME_FIDELITY_UNUSABLE: nx_int = 3 59const NX_GME_N_FIDELITY_BANDS: nx_int = 4 60 61struct GarmentExtractReport { 62 garment: GarmentDimensions, 63 scale_px_per_mm_q10: nx_int, 64 silhouette_area: nx_int, 65 fidelity_band: nx_int, 66 fidelity_q10: nx_int, 67} 68 69// ===== Helpers (mirrors nx_body_measurement_extract) ================= 70 71func _gme_width_at_row(sil: *Image, y: nx_int) -> nx_int { 72 let w: nx_int = sil.width 73 let h: nx_int = sil.height 74 if y < 0 { return 0 } 75 if y >= h { return 0 } 76 var left: nx_int = -1 77 var right: nx_int = -1 78 var x: nx_int = 0 79 while x < w { 80 let v: nx_int = nx_image_get(sil, x, y, 0) 81 if v > 0 { 82 if left < 0 { left = x } 83 right = x 84 } 85 x = x + 1 86 } 87 if left < 0 { return 0 } 88 return right - left + 1 89} 90 91func _gme_silhouette_area(sil: *Image) -> nx_int { 92 let w: nx_int = sil.width 93 let h: nx_int = sil.height 94 var sum: nx_int = 0 95 var y: nx_int = 0 96 while y < h { 97 var x: nx_int = 0 98 while x < w { 99 let v: nx_int = nx_image_get(sil, x, y, 0) 100 if v > 0 { sum = sum + 1 } 101 x = x + 1 102 } 103 y = y + 1 104 } 105 return sum 106} 107 108func _gme_px_to_mm(pixels: nx_int, scale_q10: nx_int) -> nx_int { 109 if scale_q10 <= 0 { return 0 } 110 return (pixels * NX_GME_Q) / scale_q10 111} 112 113// ===== Auto-locate helpers for callers without exact landmark Y ==== 114// 115// Narrowest-in-range is the canonical waist locator (mid-torso local 116// minimum); widest-in-range is the canonical hip locator (below-waist 117// local maximum). 118 119func nx_gme_narrowest_width_in_range(sil: *Image, 120 y_lo: nx_int, y_hi: nx_int) -> nx_int { 121 if y_lo >= y_hi { return 0 } 122 var min_w: nx_int = -1 123 var y: nx_int = y_lo 124 while y <= y_hi { 125 let w: nx_int = _gme_width_at_row(sil, y) 126 if w > 0 { 127 if min_w < 0 { min_w = w } 128 if w < min_w { min_w = w } 129 } 130 y = y + 1 131 } 132 if min_w < 0 { return 0 } 133 return min_w 134} 135 136func nx_gme_widest_width_in_range(sil: *Image, 137 y_lo: nx_int, y_hi: nx_int) -> nx_int { 138 if y_lo >= y_hi { return 0 } 139 var max_w: nx_int = 0 140 var y: nx_int = y_lo 141 while y <= y_hi { 142 let w: nx_int = _gme_width_at_row(sil, y) 143 if w > max_w { max_w = w } 144 y = y + 1 145 } 146 return max_w 147} 148 149// Find the leftmost-foreground pixel's column at row y. 150// Used for sleeve_end measurement (horizontal extent of arm from 151// shoulder line out to wrist). Returns -1 if no foreground in row. 152func _gme_left_edge_at_row(sil: *Image, y: nx_int) -> nx_int { 153 let w: nx_int = sil.width 154 let h: nx_int = sil.height 155 if y < 0 { return -1 } 156 if y >= h { return -1 } 157 var x: nx_int = 0 158 while x < w { 159 let v: nx_int = nx_image_get(sil, x, y, 0) 160 if v > 0 { return x } 161 x = x + 1 162 } 163 return -1 164} 165 166func _gme_right_edge_at_row(sil: *Image, y: nx_int) -> nx_int { 167 let w: nx_int = sil.width 168 let h: nx_int = sil.height 169 if y < 0 { return -1 } 170 if y >= h { return -1 } 171 var x: nx_int = w - 1 172 while x >= 0 { 173 let v: nx_int = nx_image_get(sil, x, y, 0) 174 if v > 0 { return x } 175 x = x - 1 176 } 177 return -1 178} 179 180// ===== Public extractor =============================================== 181 182func nx_garment_extract_measurements( 183 sil: *Image, 184 reference_pixels: nx_int, 185 reference_mm: nx_int, 186 landmark_y: *nx_int, 187 cut: nx_int, 188 out: *GarmentExtractReport 189) -> nx_int { 190 if reference_pixels <= 0 { return 1 } 191 if reference_mm <= 0 { return 1 } 192 193 let scale_q10: nx_int = (reference_pixels * NX_GME_Q) / reference_mm 194 out.scale_px_per_mm_q10 = scale_q10 195 if scale_q10 <= 0 { return 1 } 196 197 let top_y: nx_int = landmark_y[NX_GME_LM_TOP] 198 let bust_y: nx_int = landmark_y[NX_GME_LM_BUST] 199 let waist_y: nx_int = landmark_y[NX_GME_LM_WAIST] 200 let hip_y: nx_int = landmark_y[NX_GME_LM_HIP] 201 let hem_y: nx_int = landmark_y[NX_GME_LM_HEM] 202 let sleeve_end_y: nx_int = landmark_y[NX_GME_LM_SLEEVE_END] 203 let inseam_top_y: nx_int = landmark_y[NX_GME_LM_INSEAM_TOP] 204 205 // ---- Cross-section widths ---- 206 let top_w: nx_int = _gme_width_at_row(sil, top_y) 207 let bust_w: nx_int = _gme_width_at_row(sil, bust_y) 208 let waist_w: nx_int = _gme_width_at_row(sil, waist_y) 209 let hip_w: nx_int = _gme_width_at_row(sil, hip_y) 210 211 // ---- Pixel widths -> mm ---- 212 let shoulder_mm: nx_int = _gme_px_to_mm(top_w, scale_q10) 213 let bust_mm: nx_int = _gme_px_to_mm(bust_w, scale_q10) 214 let waist_mm: nx_int = _gme_px_to_mm(waist_w, scale_q10) 215 let hip_mm: nx_int = _gme_px_to_mm(hip_w, scale_q10) 216 217 // Length: top to hem 218 let length_px: nx_int = hem_y - top_y 219 let length_mm: nx_int = _gme_px_to_mm(length_px, scale_q10) 220 221 // Sleeve length: horizontal extent from shoulder line outward. 222 // If sleeve_end_y is 0 (sleeveless), sleeve_length stays 0. 223 var sleeve_mm: nx_int = 0 224 if sleeve_end_y > 0 { 225 // Sleeve length = vertical drop from top_y to sleeve_end_y 226 // (sleeves typically drop diagonally; this is a chord 227 // approximation -- accurate to ~5% for typical sleeve angles; 228 // future primitive nx_arc_length_along_silhouette gives true 229 // arc length). 230 let sleeve_px: nx_int = sleeve_end_y - top_y 231 if sleeve_px > 0 { 232 sleeve_mm = _gme_px_to_mm(sleeve_px, scale_q10) 233 } 234 } 235 236 // Inseam: inseam_top to hem (pants only). 237 var inseam_mm: nx_int = 0 238 if inseam_top_y > 0 { 239 let inseam_px: nx_int = hem_y - inseam_top_y 240 if inseam_px > 0 { 241 inseam_mm = _gme_px_to_mm(inseam_px, scale_q10) 242 } 243 } 244 245 // ---- Populate GarmentDimensions ---- 246 out.garment.bust_mm = bust_mm 247 out.garment.waist_mm = waist_mm 248 out.garment.hip_mm = hip_mm 249 out.garment.length_mm = length_mm 250 out.garment.shoulder_width_mm = shoulder_mm 251 out.garment.sleeve_length_mm = sleeve_mm 252 out.garment.inseam_mm = inseam_mm 253 out.garment.cut = cut 254 255 // ---- Fidelity gate ---- 256 out.silhouette_area = _gme_silhouette_area(sil) 257 258 var fid: nx_int = NX_GME_Q 259 if out.silhouette_area < 100 { fid = fid / 2 } 260 if bust_w == 0 { fid = fid / 2 } 261 if waist_w == 0 { fid = fid / 2 } 262 if hip_w == 0 { fid = fid / 2 } 263 if length_px <= 0 { fid = 0 } 264 out.fidelity_q10 = fid 265 out.garment.extracted_fidelity_q10 = fid 266 267 if fid == 0 { 268 out.fidelity_band = NX_GME_FIDELITY_UNUSABLE 269 } else { 270 if fid >= 768 { out.fidelity_band = NX_GME_FIDELITY_HIGH } 271 if fid < 768 { out.fidelity_band = NX_GME_FIDELITY_MODERATE } 272 if fid < 384 { out.fidelity_band = NX_GME_FIDELITY_LOW } 273 } 274 275 return 0 276} 277 278// ===== Sealed-enum validity ========================================= 279 280func nx_garment_extract_fidelity_is_valid(b: nx_int) -> nx_int { 281 if b < 0 { return 0 } 282 if b >= NX_GME_N_FIDELITY_BANDS { return 0 } 283 return 1 284} 285 286func nx_garment_extract_landmark_is_valid(l: nx_int) -> nx_int { 287 if l < 0 { return 0 } 288 if l >= NX_GME_LM_COUNT { return 0 } 289 return 1 290}