nx_body_measurement_extract_test.nx source
↩ module page · 150 lines · 6723 B
1// nx_body_measurement_extract_test.nx -- smoke for body-from-silhouette.
2
3import "nx_syscalls.nx"
4import "nx_tier.nx"
5import "nx_image.nx"
6import "nx_garment_fit_match.nx"
7import "nx_body_measurement_extract.nx"
8
9// Helper: paint a rectangle of foreground 1s into a silhouette image
10// covering rows [y0, y1) and columns centered on cx with half-width hw.
11func _paint_band(sil: *Image, y0: nx_int, y1: nx_int, cx: nx_int, hw: nx_int) -> nx_int {
12 let w: nx_int = sil.width
13 var y: nx_int = y0
14 while y < y1 {
15 var x: nx_int = cx - hw
16 let xe: nx_int = cx + hw
17 if x < 0 { x = 0 }
18 while x < xe {
19 if x < w {
20 nx_image_set(sil, x, y, 0, 1)
21 }
22 x = x + 1
23 }
24 y = y + 1
25 }
26 return 0
27}
28
29func main() -> nx_int {
30 // === Test 1: synthetic silhouette, known dimensions, verify mm output ===
31 //
32 // Build a 100-px-tall silhouette in a 128x150 frame:
33 // shoulders at row 20, half-width 16 (full width 32 px)
34 // bust at row 30, half-width 18 (full width 36 px)
35 // waist at row 50, half-width 14 (full width 28 px)
36 // hip at row 70, half-width 17 (full width 34 px)
37 // inseam at row 80
38 // ankle at row 100
39 // top-of-head at row 5
40 //
41 // Reference object: 50 pixels = 100 mm (so 1 px = 2 mm).
42 //
43 // Expected mm:
44 // shoulder_mm = 32 * 2 = 64
45 // bust_mm = 36 * 2 = 72
46 // waist_mm = 28 * 2 = 56
47 // hip_mm = 34 * 2 = 68
48 // height_mm = (100 - 5) * 2 = 190
49 // inseam_mm = (100 - 80) * 2 = 40
50 let sil: *Image = nx_image_alloc(128, 150, 1)
51
52 // Paint the silhouette in five bands (shoulders/bust/waist/hip/legs).
53 _paint_band(sil, 5, 20, 64, 8) // head/neck region
54 _paint_band(sil, 20, 30, 64, 16) // shoulders
55 _paint_band(sil, 30, 50, 64, 18) // torso narrowing to waist
56 _paint_band(sil, 50, 70, 64, 14) // waist (narrowest -- using 14 hw)
57 _paint_band(sil, 70, 80, 64, 17) // hip
58 _paint_band(sil, 80, 101, 64, 12) // legs
59
60 let landmarks: *nx_int = (sys_mmap(NX_BME_LM_COUNT * NX_SIZEOF_NX_INT)) as *nx_int
61 landmarks[NX_BME_LM_TOP_OF_HEAD] = 5
62 landmarks[NX_BME_LM_SHOULDER] = 25 // mid-shoulder band
63 landmarks[NX_BME_LM_BUST] = 40 // mid-torso band
64 landmarks[NX_BME_LM_WAIST] = 60 // mid-waist band
65 landmarks[NX_BME_LM_HIP] = 75 // mid-hip band
66 landmarks[NX_BME_LM_INSEAM_TOP] = 80
67 landmarks[NX_BME_LM_ANKLE] = 100
68
69 let rep: *BodyExtractReport = (sys_mmap(16 * NX_SIZEOF_NX_INT)) as *BodyExtractReport
70 // Reference: 50 px = 100 mm. Scale = 50/100 * Q10 = 512.
71 let rc: nx_int = nx_body_measurement_extract(sil, 50, 100, landmarks,
72 NX_FIT_ARCHETYPE_AVERAGE, rep)
73 if rc != 0 { return 1 }
74 // Scale check
75 if rep.scale_px_per_mm_q10 != 512 { return 2 }
76 // Shoulder width: 32 px * 2 mm/px = 64 mm
77 if rep.body.shoulder_width_mm != 64 { return 3 }
78 // Bust: paint at row 30-50 half-width 18 -> width 36 px -> 72 mm
79 if rep.body.bust_mm != 72 { return 4 }
80 // Waist: row 60 falls in the 50-70 band with half-width 14 -> 28 px -> 56 mm
81 if rep.body.waist_mm != 56 { return 5 }
82 // Hip: row 75 in 70-80 band, half-width 17 -> 34 px -> 68 mm
83 if rep.body.hip_mm != 68 { return 6 }
84 // Height: (100 - 5) * 2 = 190 mm
85 if rep.body.height_mm != 190 { return 7 }
86 // Inseam: (100 - 80) * 2 = 40 mm
87 if rep.body.inseam_mm != 40 { return 8 }
88 // Archetype propagated
89 if rep.body.archetype != NX_FIT_ARCHETYPE_AVERAGE { return 9 }
90
91 // === Test 2: fidelity-HIGH band for clean silhouette ===
92 if rep.fidelity_band != NX_BME_FIDELITY_HIGH { return 10 }
93 if rep.fidelity_q10 < 768 { return 11 }
94
95 // === Test 3: reference_pixels = 0 -> error (invalid scale) ===
96 let rep2: *BodyExtractReport = (sys_mmap(16 * NX_SIZEOF_NX_INT)) as *BodyExtractReport
97 if nx_body_measurement_extract(sil, 0, 100, landmarks,
98 NX_FIT_ARCHETYPE_AVERAGE, rep2) == 0 { return 20 }
99
100 // === Test 4: reference_mm = 0 -> error ===
101 if nx_body_measurement_extract(sil, 50, 0, landmarks,
102 NX_FIT_ARCHETYPE_AVERAGE, rep2) == 0 { return 21 }
103
104 // === Test 5: empty silhouette (zero area) -> fidelity LOW or UNUSABLE ===
105 let sil_empty: *Image = nx_image_alloc(64, 100, 1)
106 nx_body_measurement_extract(sil_empty, 50, 100, landmarks,
107 NX_FIT_ARCHETYPE_AVERAGE, rep2)
108 // Empty silhouette: zero widths everywhere -> fidelity dropped
109 // through several /2 reductions. Should fall in LOW or UNUSABLE.
110 if rep2.fidelity_band == NX_BME_FIDELITY_HIGH { return 30 }
111
112 // === Test 6: narrowest-width search helper ===
113 // In the silhouette: 50-70 band has half-width 14 (width 28); the
114 // narrowest in that range.
115 let narrow: nx_int = nx_bme_narrowest_width_in_range(sil, 50, 70)
116 if narrow != 28 { return 40 }
117
118 // === Test 7: widest-width search helper ===
119 // Bust band 30-50 has half-width 18 (width 36); widest in that
120 // range.
121 let widest: nx_int = nx_bme_widest_width_in_range(sil, 30, 50)
122 if widest != 36 { return 50 }
123
124 // === Test 8: extracted dims feed into nx_garment_fit_match ===
125 // Body: 72/56/68/190. Garment: 75/58/70/180/65/sheath.
126 // Bust delta +3, waist delta +2, hip delta +2; all within +-5 -> PERFECT_TAILORED.
127 let g: *GarmentDimensions = (sys_mmap(10 * NX_SIZEOF_NX_INT)) as *GarmentDimensions
128 g.bust_mm = 75
129 g.waist_mm = 58
130 g.hip_mm = 70
131 g.length_mm = 180
132 g.shoulder_width_mm = 65
133 g.sleeve_length_mm = 0
134 g.inseam_mm = 0
135 g.cut = NX_FIT_CUT_WRAP
136 g.extracted_fidelity_q10 = 900
137 let fit_rep: *FitMatchReport = (sys_mmap(16 * NX_SIZEOF_NX_INT)) as *FitMatchReport
138 nx_garment_fit_match(g, rep.body, fit_rep)
139 // bust 75-72=3, waist 58-56=2, hip 70-68=2 all <=5 -> PERFECT
140 if fit_rep.verdict != NX_FIT_PERFECT_TAILORED { return 60 }
141
142 // === Test 9: sealed-enum validity ===
143 if nx_body_measurement_fidelity_is_valid(NX_BME_FIDELITY_HIGH) != 1 { return 70 }
144 if nx_body_measurement_fidelity_is_valid(NX_BME_FIDELITY_UNUSABLE) != 1 { return 71 }
145 if nx_body_measurement_fidelity_is_valid(99) != 0 { return 72 }
146 if nx_body_measurement_landmark_is_valid(NX_BME_LM_WAIST) != 1 { return 73 }
147 if nx_body_measurement_landmark_is_valid(99) != 0 { return 74 }
148
149 return 0
150}