nx_image_grade_v2.nx source
↩ module page · 543 lines · 20419 B
1// nx_image_grade_v2.nx -- landmark-aware image grading composer.
2//
3// CAPABILITY_COMPLETENESS: PARTIAL
4// MISSING_CAPABILITIES:
5// - face keypoint detection (eye/nose/mouth within head bbox):
6// blocks nx_micro_expression's full invocation; v2 returns
7// head BBOX but doesn't yet sub-detect facial features
8// - hand keypoint detection / finger segmentation: blocks
9// nx_finger_count's full invocation
10// - pose joint-angle extraction: blocks nx_pose_naturalness
11// from running end-to-end
12// - VLM grader source (triangulation second-source)
13//
14// COVERED (PROMOTED FROM v1):
15// - Segmenter + landmark labeler composed
16// - nx_silhouette_integrity invoked on torso blob -> SHARP /
17// SOFT / MELTED verdict
18// - nx_limb_count invoked with landmark output -> OK /
19// EXTRA / FLOATING_LIMB / MISSING / etc.
20// - Skin-localized Lab + ITA + undertone (same as v1)
21// - Skin coverage Q10 (same as v1)
22// - 6 anatomical-region bboxes exposed in report for caller
23// to feed downstream tier-axes
24//
25// Composes:
26// nx_png_decoder + nx_jpeg_decoder (format dispatch)
27// nx_segmenter_classical (skin connected-components)
28// nx_landmark_anatomy (head/torso/arm/leg labeling)
29// nx_silhouette_integrity (melty detector on torso)
30// nx_limb_count (anatomy coherence)
31// nx_lab_from_rgb + nx_skin_tone_ita + nx_undertone_class
32// nx_verdict_phrase
33//
34// genealogy_id: substrate_image_grade_v2_landmark_aware_2026_05_16
35// lineage_id: nx_image_grade_v2
36
37import "nx_syscalls.nx"
38import "nx_runtime.nx"
39import "nx_tier.nx"
40import "nx_png_decoder.nx"
41import "nx_jpeg_decoder.nx"
42import "nx_verdict_phrase.nx"
43import "nx_lab_from_rgb.nx"
44import "nx_skin_tone_ita.nx"
45import "nx_undertone_class.nx"
46import "nx_segmenter_classical.nx"
47import "nx_landmark_anatomy.nx"
48import "nx_silhouette_integrity.nx"
49import "nx_limb_count.nx"
50const NX_MAGIC_1024: i64 = 1024
51
52// ===== sealed: format + error codes ==============================
53
54const NX_IGV2_FORMAT_UNKNOWN: nx_int = 0
55const NX_IGV2_FORMAT_PNG: nx_int = 1
56const NX_IGV2_FORMAT_JPEG: nx_int = 2
57
58const NX_IGV2_OK: nx_int = 0
59const NX_IGV2_ERR_TOO_SHORT: nx_int = 1
60const NX_IGV2_ERR_UNKNOWN_FORMAT: nx_int = 2
61const NX_IGV2_ERR_DECODER: nx_int = 3
62const NX_IGV2_ERR_NULL_PIXELS: nx_int = 4
63
64// ===== axis status (extended from v1) ============================
65
66const NX_IGV2_AXIS_OK: nx_int = 0
67const NX_IGV2_AXIS_PENDING_KEYPOINT: nx_int = 1 // need face/hand keypoint detection
68const NX_IGV2_AXIS_PENDING_POSE_ESTIMATE: nx_int = 2 // need joint-angle extractor
69const NX_IGV2_AXIS_PENDING_VLM: nx_int = 3
70const NX_IGV2_AXIS_NOT_APPLICABLE: nx_int = 4
71const NX_IGV2_AXIS_NO_SKIN_DETECTED: nx_int = 5
72const NX_IGV2_AXIS_INSUFFICIENT_LANDMARKS: nx_int = 6
73
74// ===== per-axis verdict ==========================================
75
76struct NxIgv2AxisVerdict {
77 axis_id: nx_int,
78 status: nx_int,
79 verdict_value: nx_int,
80 score_q10: nx_int,
81 phrase_buf: *u8,
82 phrase_len: nx_int,
83}
84
85const NX_IGV2_AXIS_VERDICT_BYTES: nx_size = 48
86
87// ===== top-level report ==========================================
88
89struct NxIgv2Report {
90 source_format: nx_int,
91 width: nx_int,
92 height: nx_int,
93 error_code: nx_int,
94 n_axes: nx_int,
95 axes: *NxIgv2AxisVerdict,
96 mean_r: nx_int,
97 mean_g: nx_int,
98 mean_b: nx_int,
99 skin_pixel_count: nx_int,
100 skin_mean_r: nx_int,
101 skin_mean_g: nx_int,
102 skin_mean_b: nx_int,
103 skin_coverage_q10: nx_int,
104 n_skin_blobs: nx_int,
105 landmarks: *NxAnatomyLandmarks,
106}
107
108const NX_IGV2_REPORT_BYTES: nx_size = 112
109
110// Axis IDs (canonical; same as v1).
111const NX_IGV2_AX_DIMENSIONS: nx_int = 0
112const NX_IGV2_AX_LUMINANCE: nx_int = 1
113const NX_IGV2_AX_SKIN_TONE_ITA: nx_int = 4
114const NX_IGV2_AX_UNDERTONE: nx_int = 12
115const NX_IGV2_AX_SILHOUETTE: nx_int = 28
116const NX_IGV2_AX_VEIN_SIGNAL: nx_int = 16
117const NX_IGV2_AX_SUBSURFACE: nx_int = 17
118const NX_IGV2_AX_MICRO_EXPRESSION: nx_int = 22
119const NX_IGV2_AX_LIMB_COUNT: nx_int = 25
120const NX_IGV2_AX_POSE_NATURALNESS: nx_int = 26
121const NX_IGV2_AX_FINGER_COUNT: nx_int = 24
122const NX_IGV2_AX_SHADOW_DIRECTION: nx_int = 23
123const NX_IGV2_AX_SURFACE_PLANE: nx_int = 27
124const NX_IGV2_AX_SKIN_COVERAGE: nx_int = 100
125const NX_IGV2_AX_LANDMARK_CONF: nx_int = 101
126
127const NX_IGV2_N_AXES: nx_int = 15
128
129// ===== format detect =============================================
130
131func _igv2_detect_format(buf: *u8, n: nx_int) -> nx_int {
132 if n < 4 { return NX_IGV2_FORMAT_UNKNOWN }
133 if buf[0] == (137 as u8) {
134 if buf[1] == (80 as u8) {
135 if buf[2] == (78 as u8) {
136 if buf[3] == (71 as u8) {
137 return NX_IGV2_FORMAT_PNG
138 }
139 }
140 }
141 }
142 if buf[0] == (255 as u8) {
143 if buf[1] == (216 as u8) {
144 return NX_IGV2_FORMAT_JPEG
145 }
146 }
147 return NX_IGV2_FORMAT_UNKNOWN
148}
149
150// ===== Kovac skin-pixel test (same as v1) ========================
151
152func _igv2_is_skin_pixel(r: nx_int, g: nx_int, b: nx_int) -> nx_int {
153 if r <= 95 { return 0 }
154 if g <= 40 { return 0 }
155 if b <= 20 { return 0 }
156 var max_v: nx_int = r
157 if g > max_v { max_v = g }
158 if b > max_v { max_v = b }
159 var min_v: nx_int = r
160 if g < min_v { min_v = g }
161 if b < min_v { min_v = b }
162 if (max_v - min_v) <= 15 { return 0 }
163 var rg_diff: nx_int = r - g
164 if rg_diff < 0 { rg_diff = 0 - rg_diff }
165 if rg_diff <= 15 { return 0 }
166 if r <= g { return 0 }
167 if r <= b { return 0 }
168 return 1
169}
170
171// ===== axis filler ===============================================
172
173func _igv2_set_axis(axes: *NxIgv2AxisVerdict, idx: nx_int,
174 axis_id: nx_int, status: nx_int,
175 verdict_value: nx_int, score_q10: nx_int) -> nx_int {
176 let slot: *NxIgv2AxisVerdict =
177 (axes as *u8 + (idx as nx_size) * NX_IGV2_AXIS_VERDICT_BYTES) as *NxIgv2AxisVerdict
178 slot.axis_id = axis_id
179 slot.status = status
180 slot.verdict_value = verdict_value
181 slot.score_q10 = score_q10
182 let phrase_buf: *u8 = (sys_mmap(128)) as *u8
183 if status == NX_IGV2_AXIS_OK {
184 let p: *u8 = nx_verdict_phrase_for(axis_id, verdict_value, NX_VP_MODE_TERSE)
185 var c: nx_int = 0
186 var safety: nx_int = 0
187 while safety < 127 {
188 let ch: nx_int = (p[c] as nx_int) & 255
189 if ch == 0 { break }
190 phrase_buf[c] = ch as u8
191 c = c + 1
192 safety = safety + 1
193 }
194 phrase_buf[c] = 0 as u8
195 slot.phrase_buf = phrase_buf
196 slot.phrase_len = c
197 } else {
198 var pmsg: *u8 = "pending" as *u8
199 if status == NX_IGV2_AXIS_PENDING_KEYPOINT { pmsg = "pending keypoint detection" as *u8 }
200 if status == NX_IGV2_AXIS_PENDING_POSE_ESTIMATE { pmsg = "pending pose estimator" as *u8 }
201 if status == NX_IGV2_AXIS_PENDING_VLM { pmsg = "pending vlm grader" as *u8 }
202 if status == NX_IGV2_AXIS_NOT_APPLICABLE { pmsg = "not applicable" as *u8 }
203 if status == NX_IGV2_AXIS_NO_SKIN_DETECTED { pmsg = "no skin pixels detected" as *u8 }
204 if status == NX_IGV2_AXIS_INSUFFICIENT_LANDMARKS { pmsg = "insufficient landmarks" as *u8 }
205 var c2: nx_int = 0
206 var s2: nx_int = 0
207 while s2 < 127 {
208 let ch2: nx_int = (pmsg[c2] as nx_int) & 255
209 if ch2 == 0 { break }
210 phrase_buf[c2] = ch2 as u8
211 c2 = c2 + 1
212 s2 = s2 + 1
213 }
214 phrase_buf[c2] = 0 as u8
215 slot.phrase_buf = phrase_buf
216 slot.phrase_len = c2
217 }
218 return 0
219}
220
221// ===== top-level trigger =========================================
222
223func nx_image_grade_v2(input: *u8, input_size: nx_int) -> *NxIgv2Report {
224 let r_ptr: *u8 = sys_mmap(NX_IGV2_REPORT_BYTES)
225 let r: *NxIgv2Report = r_ptr as *NxIgv2Report
226 r.error_code = NX_IGV2_OK
227 r.source_format = NX_IGV2_FORMAT_UNKNOWN
228 r.skin_pixel_count = 0
229 r.landmarks = 0 as *NxAnatomyLandmarks
230
231 if input_size < 8 {
232 r.error_code = NX_IGV2_ERR_TOO_SHORT
233 return r
234 }
235
236 let fmt: nx_int = _igv2_detect_format(input, input_size)
237 r.source_format = fmt
238 if fmt == NX_IGV2_FORMAT_UNKNOWN {
239 r.error_code = NX_IGV2_ERR_UNKNOWN_FORMAT
240 return r
241 }
242
243 let axes_bytes: nx_size = (NX_IGV2_N_AXES as nx_size) * NX_IGV2_AXIS_VERDICT_BYTES
244 let axes: *NxIgv2AxisVerdict = (sys_mmap(axes_bytes)) as *NxIgv2AxisVerdict
245 r.axes = axes
246 r.n_axes = NX_IGV2_N_AXES
247
248 var pixels: *u8 = 0 as *u8
249 var n_ch: nx_int = 0
250 var bpp: nx_int = 0
251 var w: nx_int = 0
252 var h: nx_int = 0
253
254 if fmt == NX_IGV2_FORMAT_PNG {
255 let png_r: *NxPngResult = nx_png_decode(input, input_size)
256 if png_r == (0 as *NxPngResult) { r.error_code = NX_IGV2_ERR_DECODER; return r }
257 if png_r.error_code != NX_PNG_OK { r.error_code = NX_IGV2_ERR_DECODER; return r }
258 pixels = png_r.pixels
259 n_ch = png_r.n_channels
260 bpp = png_r.bytes_per_pix
261 if png_r.header != (0 as *NxPngHeader) {
262 w = png_r.header.width
263 h = png_r.header.height
264 }
265 }
266 if fmt == NX_IGV2_FORMAT_JPEG {
267 let jpg_r: *NxJpgResult = nx_jpeg_decode(input, input_size)
268 if jpg_r == (0 as *NxJpgResult) { r.error_code = NX_IGV2_ERR_DECODER; return r }
269 if jpg_r.error_code != NX_JPG_OK { r.error_code = NX_IGV2_ERR_DECODER; return r }
270 pixels = jpg_r.rgb
271 n_ch = 3
272 bpp = 3
273 w = jpg_r.width
274 h = jpg_r.height
275 }
276 if pixels == (0 as *u8) { r.error_code = NX_IGV2_ERR_NULL_PIXELS; return r }
277 r.width = w
278 r.height = h
279
280 // Single-pass: mean R/G/B + skin-pixel sums + skin mask.
281 let n_pixels: nx_int = w * h
282 let mask_bytes: nx_size = (n_pixels as nx_size) * 8
283 let skin_mask: *nx_int = (sys_mmap(mask_bytes)) as *nx_int
284 var sum_r: nx_int = 0
285 var sum_g: nx_int = 0
286 var sum_b: nx_int = 0
287 var sum_sr: nx_int = 0
288 var sum_sg: nx_int = 0
289 var sum_sb: nx_int = 0
290 var n_skin: nx_int = 0
291 var y: nx_int = 0
292 while y < h {
293 var x: nx_int = 0
294 while x < w {
295 let pix_off: nx_int = (y * w + x) * bpp
296 var r_val: nx_int = (pixels[pix_off] as nx_int) & 255
297 var g_val: nx_int = r_val
298 var b_val: nx_int = r_val
299 if n_ch >= 3 {
300 g_val = (pixels[pix_off + 1] as nx_int) & 255
301 b_val = (pixels[pix_off + 2] as nx_int) & 255
302 }
303 sum_r = sum_r + r_val
304 sum_g = sum_g + g_val
305 sum_b = sum_b + b_val
306 let pix_idx: nx_int = y * w + x
307 let is_skin: nx_int = _igv2_is_skin_pixel(r_val, g_val, b_val)
308 skin_mask[pix_idx] = is_skin
309 if is_skin == 1 {
310 sum_sr = sum_sr + r_val
311 sum_sg = sum_sg + g_val
312 sum_sb = sum_sb + b_val
313 n_skin = n_skin + 1
314 }
315 x = x + 1
316 }
317 y = y + 1
318 }
319 r.mean_r = sum_r / n_pixels
320 r.mean_g = sum_g / n_pixels
321 r.mean_b = sum_b / n_pixels
322 r.skin_pixel_count = n_skin
323 r.skin_coverage_q10 = (n_skin * NX_MAGIC_1024) / n_pixels
324
325 // Segmenter.
326 let seg_r: *NxSegResult = nx_segmenter_connected(skin_mask, w, h)
327 if seg_r != (0 as *NxSegResult) {
328 r.n_skin_blobs = seg_r.n_blobs
329 }
330
331 // Landmark labeler.
332 let lm: *NxAnatomyLandmarks = nx_landmark_anatomy_label(seg_r, w, h)
333 r.landmarks = lm
334
335 // ---- per-axis verdict population ----
336
337 // T1 luma.
338 let mean_luma: nx_int = (306 * r.mean_r + 601 * r.mean_g + 117 * r.mean_b) / NX_MAGIC_1024
339 _igv2_set_axis(axes, 0, NX_IGV2_AX_LUMINANCE,
340 NX_IGV2_AXIS_OK, 0, mean_luma * 4)
341
342 // T0 dimensions.
343 _igv2_set_axis(axes, 1, NX_IGV2_AX_DIMENSIONS,
344 NX_IGV2_AXIS_OK, 0, w * h)
345
346 // T1 skin coverage.
347 _igv2_set_axis(axes, 2, NX_IGV2_AX_SKIN_COVERAGE,
348 NX_IGV2_AXIS_OK, 0, r.skin_coverage_q10)
349
350 // T1 ITA + T4 undertone (skin-localized; same as v1).
351 if n_skin <= 0 {
352 r.skin_mean_r = 0
353 r.skin_mean_g = 0
354 r.skin_mean_b = 0
355 _igv2_set_axis(axes, 3, NX_IGV2_AX_SKIN_TONE_ITA,
356 NX_IGV2_AXIS_NO_SKIN_DETECTED, 0, 0)
357 _igv2_set_axis(axes, 4, NX_IGV2_AX_UNDERTONE,
358 NX_IGV2_AXIS_NO_SKIN_DETECTED, 0, 0)
359 } else {
360 r.skin_mean_r = sum_sr / n_skin
361 r.skin_mean_g = sum_sg / n_skin
362 r.skin_mean_b = sum_sb / n_skin
363 let lab: *NxLabColor = nx_lab_from_rgb(r.skin_mean_r, r.skin_mean_g, r.skin_mean_b)
364 let ita_r: *NxSkinToneIta = nx_skin_tone_ita_compute(
365 lab.l_q10, lab.a_q10, lab.b_q10)
366 _igv2_set_axis(axes, 3, NX_IGV2_AX_SKIN_TONE_ITA,
367 NX_IGV2_AXIS_OK, ita_r.band, ita_r.ita_deg_q10)
368 let und_r: *NxUndertoneResult = nx_undertone_classify(
369 lab.a_q10, lab.b_q10)
370 _igv2_set_axis(axes, 4, NX_IGV2_AX_UNDERTONE,
371 NX_IGV2_AXIS_OK, und_r.verdict, und_r.conf_q10)
372 }
373
374 // T101 landmark confidence (new in v2).
375 _igv2_set_axis(axes, 5, NX_IGV2_AX_LANDMARK_CONF,
376 NX_IGV2_AXIS_OK, 0, lm.confidence_q10)
377
378 // T6 silhouette integrity on torso blob (v2 promotion).
379 if lm.has_torso == 1 {
380 if seg_r != (0 as *NxSegResult) {
381 if seg_r.n_blobs >= 1 {
382 let largest: *NxSegBlob = nx_segmenter_largest_blob(seg_r)
383 if largest != (0 as *NxSegBlob) {
384 let silh: *NxSilhouetteResult = nx_silhouette_integrity(
385 pixels, w, h, bpp, n_ch, seg_r.label_map, largest)
386 if silh != (0 as *NxSilhouetteResult) {
387 _igv2_set_axis(axes, 6, NX_IGV2_AX_SILHOUETTE,
388 NX_IGV2_AXIS_OK, silh.verdict, silh.fraction_strong)
389 } else {
390 _igv2_set_axis(axes, 6, NX_IGV2_AX_SILHOUETTE,
391 NX_IGV2_AXIS_INSUFFICIENT_LANDMARKS, 0, 0)
392 }
393 } else {
394 _igv2_set_axis(axes, 6, NX_IGV2_AX_SILHOUETTE,
395 NX_IGV2_AXIS_INSUFFICIENT_LANDMARKS, 0, 0)
396 }
397 } else {
398 _igv2_set_axis(axes, 6, NX_IGV2_AX_SILHOUETTE,
399 NX_IGV2_AXIS_NO_SKIN_DETECTED, 0, 0)
400 }
401 } else {
402 _igv2_set_axis(axes, 6, NX_IGV2_AX_SILHOUETTE,
403 NX_IGV2_AXIS_INSUFFICIENT_LANDMARKS, 0, 0)
404 }
405 } else {
406 _igv2_set_axis(axes, 6, NX_IGV2_AX_SILHOUETTE,
407 NX_IGV2_AXIS_INSUFFICIENT_LANDMARKS, 0, 0)
408 }
409
410 // T6 limb count (v2 promotion using landmark output).
411 if lm.has_torso == 1 {
412 // Build candidate list from landmark bboxes.
413 let cands: *NxLimbCandidate =
414 (sys_mmap(NX_LIMB_CANDIDATE_BYTES * 4)) as *NxLimbCandidate
415 var n_cands: nx_int = 0
416 if lm.arm_l != (0 as *NxBBox) {
417 if lm.arm_l.valid == 1 {
418 let cl: *NxLimbCandidate =
419 (cands as *u8 + (n_cands as nx_size) * NX_LIMB_CANDIDATE_BYTES) as *NxLimbCandidate
420 cl.end_x = (lm.arm_l.x0 + lm.arm_l.x1) / 2
421 cl.end_y = lm.arm_l.y0
422 cl.role = NX_LIMB_ROLE_ARM
423 cl.bbox_x0 = lm.arm_l.x0
424 cl.bbox_y0 = lm.arm_l.y0
425 cl.bbox_x1 = lm.arm_l.x1
426 cl.bbox_y1 = lm.arm_l.y1
427 n_cands = n_cands + 1
428 }
429 }
430 if lm.arm_r != (0 as *NxBBox) {
431 if lm.arm_r.valid == 1 {
432 let cr: *NxLimbCandidate =
433 (cands as *u8 + (n_cands as nx_size) * NX_LIMB_CANDIDATE_BYTES) as *NxLimbCandidate
434 cr.end_x = (lm.arm_r.x0 + lm.arm_r.x1) / 2
435 cr.end_y = lm.arm_r.y0
436 cr.role = NX_LIMB_ROLE_ARM
437 cr.bbox_x0 = lm.arm_r.x0
438 cr.bbox_y0 = lm.arm_r.y0
439 cr.bbox_x1 = lm.arm_r.x1
440 cr.bbox_y1 = lm.arm_r.y1
441 n_cands = n_cands + 1
442 }
443 }
444 if lm.leg_l != (0 as *NxBBox) {
445 if lm.leg_l.valid == 1 {
446 let ll: *NxLimbCandidate =
447 (cands as *u8 + (n_cands as nx_size) * NX_LIMB_CANDIDATE_BYTES) as *NxLimbCandidate
448 ll.end_x = (lm.leg_l.x0 + lm.leg_l.x1) / 2
449 ll.end_y = lm.leg_l.y0
450 ll.role = NX_LIMB_ROLE_LEG
451 ll.bbox_x0 = lm.leg_l.x0
452 ll.bbox_y0 = lm.leg_l.y0
453 ll.bbox_x1 = lm.leg_l.x1
454 ll.bbox_y1 = lm.leg_l.y1
455 n_cands = n_cands + 1
456 }
457 }
458 if lm.leg_r != (0 as *NxBBox) {
459 if lm.leg_r.valid == 1 {
460 let lr: *NxLimbCandidate =
461 (cands as *u8 + (n_cands as nx_size) * NX_LIMB_CANDIDATE_BYTES) as *NxLimbCandidate
462 lr.end_x = (lm.leg_r.x0 + lm.leg_r.x1) / 2
463 lr.end_y = lm.leg_r.y0
464 lr.role = NX_LIMB_ROLE_LEG
465 lr.bbox_x0 = lm.leg_r.x0
466 lr.bbox_y0 = lm.leg_r.y0
467 lr.bbox_x1 = lm.leg_r.x1
468 lr.bbox_y1 = lm.leg_r.y1
469 n_cands = n_cands + 1
470 }
471 }
472 let lc: *NxLimbCountResult = nx_limb_count_check(
473 lm.torso.x0, lm.torso.y0, lm.torso.x1, lm.torso.y1,
474 cands, n_cands, 10)
475 if lc != (0 as *NxLimbCountResult) {
476 _igv2_set_axis(axes, 7, NX_IGV2_AX_LIMB_COUNT,
477 NX_IGV2_AXIS_OK, lc.verdict, lc.delta_from_norm)
478 } else {
479 _igv2_set_axis(axes, 7, NX_IGV2_AX_LIMB_COUNT,
480 NX_IGV2_AXIS_INSUFFICIENT_LANDMARKS, 0, 0)
481 }
482 } else {
483 _igv2_set_axis(axes, 7, NX_IGV2_AX_LIMB_COUNT,
484 NX_IGV2_AXIS_INSUFFICIENT_LANDMARKS, 0, 0)
485 }
486
487 // Still-pending axes (need keypoint / pose estimators upstream).
488 _igv2_set_axis(axes, 8, NX_IGV2_AX_VEIN_SIGNAL,
489 NX_IGV2_AXIS_PENDING_KEYPOINT, 0, 0)
490 _igv2_set_axis(axes, 9, NX_IGV2_AX_SUBSURFACE,
491 NX_IGV2_AXIS_PENDING_KEYPOINT, 0, 0)
492 _igv2_set_axis(axes, 10, NX_IGV2_AX_MICRO_EXPRESSION,
493 NX_IGV2_AXIS_PENDING_KEYPOINT, 0, 0)
494 _igv2_set_axis(axes, 11, NX_IGV2_AX_FINGER_COUNT,
495 NX_IGV2_AXIS_PENDING_KEYPOINT, 0, 0)
496 _igv2_set_axis(axes, 12, NX_IGV2_AX_POSE_NATURALNESS,
497 NX_IGV2_AXIS_PENDING_POSE_ESTIMATE, 0, 0)
498 _igv2_set_axis(axes, 13, NX_IGV2_AX_SHADOW_DIRECTION,
499 NX_IGV2_AXIS_PENDING_KEYPOINT, 0, 0)
500 _igv2_set_axis(axes, 14, NX_IGV2_AX_SURFACE_PLANE,
501 NX_IGV2_AXIS_PENDING_KEYPOINT, 0, 0)
502
503 return r
504}
505
506// ===== self-test =================================================
507
508func main() -> nx_int {
509 // ---- TOO_SHORT path ----
510 let s_short: *u8 = (sys_mmap(4)) as *u8
511 let r_short: *NxIgv2Report = nx_image_grade_v2(s_short, 4)
512 if r_short.error_code != NX_IGV2_ERR_TOO_SHORT { return 1 }
513
514 // ---- UNKNOWN_FORMAT path ----
515 let s_unk: *u8 = (sys_mmap(8)) as *u8
516 var i: nx_int = 0
517 while i < 8 {
518 s_unk[i] = 0 as u8
519 i = i + 1
520 }
521 let r_unk: *NxIgv2Report = nx_image_grade_v2(s_unk, 8)
522 if r_unk.error_code != NX_IGV2_ERR_UNKNOWN_FORMAT { return 2 }
523
524 // ---- PNG magic detect ----
525 let pm: *u8 = (sys_mmap(4)) as *u8
526 pm[0] = 137 as u8
527 pm[1] = 80 as u8
528 pm[2] = 78 as u8
529 pm[3] = 71 as u8
530 if _igv2_detect_format(pm, 4) != NX_IGV2_FORMAT_PNG { return 10 }
531
532 // ---- JPEG magic detect ----
533 let jm: *u8 = (sys_mmap(4)) as *u8
534 jm[0] = 255 as u8
535 jm[1] = 216 as u8
536 if _igv2_detect_format(jm, 4) != NX_IGV2_FORMAT_JPEG { return 11 }
537
538 // ---- Kovac skin rule (sanity) ----
539 if _igv2_is_skin_pixel(200, 170, 150) != 1 { return 20 }
540 if _igv2_is_skin_pixel(50, 50, 50) != 0 { return 21 }
541
542 return 0
543}