nx_mise_en_scene.nx source
↩ module page · 99 lines · 3325 B
1// nx_mise_en_scene.nx -- background-clutter detector.
2// V2 calibrated thresholds (F1=0.86 at 50-150 band).
3
4// nx_safety_envelope:
5// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
6// sil_target: SIL1
7// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
8// verdict: NOT_YET_EVALUATED
9
10import "nx_syscalls.nx"
11import "nx_image.nx"
12import "nx_color_v2.nx"
13import "nx_region.nx"
14const NX_MAGIC_1024: i64 = 1024
15
16const NX_MES_CLEAN: i64 = 0
17const NX_MES_MODERATE: i64 = 1
18const NX_MES_CLUTTERED: i64 = 2
19const NX_MES_CHAOTIC: i64 = 3
20
21const NX_MES_EDGE_THRESH: i64 = 40
22const NX_MES_CLEAN_MAX_Q10: i64 = 50
23const NX_MES_MOD_MAX_Q10: i64 = 100
24const NX_MES_CLUT_MAX_Q10: i64 = 200
25
26struct MiseEnSceneVerdict {
27 kind: i64,
28 background_area: i64,
29 background_edge_pixels: i64,
30 edge_density_q10: i64,
31 severity_q10: i64,
32 fidelity_q10: i64,
33}
34
35func nx_mise_en_scene_compute(rgb: *Image, verdict: *MiseEnSceneVerdict) -> i64 {
36 verdict.kind = NX_MES_CLEAN
37 verdict.background_area = 0
38 verdict.background_edge_pixels = 0
39 verdict.edge_density_q10 = 0
40 verdict.severity_q10 = 0
41 verdict.fidelity_q10 = 0
42
43 let set: *RegionSet = nx_region_segment(rgb)
44 let bg_idx: i64 = nx_region_find_kind(set, NX_REGION_BACKGROUND)
45 if bg_idx < 0 { return 0 }
46 let bg_area: i64 = nx_region_get_field(set, bg_idx, NX_REGION_F_AREA_PX)
47 verdict.background_area = bg_area
48 if bg_area <= 0 { return 0 }
49
50 let mask: *Image = nx_color_skin_mask(rgb)
51 let gray: *Image = nx_image_to_grayscale(rgb)
52 let gx: *ImageS64 = nx_image_sobel_x(gray)
53 let gy: *ImageS64 = nx_image_sobel_y(gray)
54 let mag: *ImageS64 = nx_image_gradient_magnitude(gx, gy)
55
56 let w: i64 = gray.width
57 let h: i64 = gray.height
58 var edge_count: i64 = 0
59 var y: i64 = 0
60 while y < h {
61 var x: i64 = 0
62 while x < w {
63 if nx_image_get(mask, x, y, 0) == 0 {
64 if nx_image_s64_get(mag, x, y) > NX_MES_EDGE_THRESH {
65 edge_count = edge_count + 1
66 }
67 }
68 x = x + 1
69 }
70 y = y + 1
71 }
72 verdict.background_edge_pixels = edge_count
73 let density: i64 = (edge_count * NX_MAGIC_1024) / bg_area
74 verdict.edge_density_q10 = density
75
76 if density < NX_MES_CLEAN_MAX_Q10 { verdict.kind = NX_MES_CLEAN }
77 if density >= NX_MES_CLEAN_MAX_Q10 {
78 if density < NX_MES_MOD_MAX_Q10 { verdict.kind = NX_MES_MODERATE }
79 if density >= NX_MES_MOD_MAX_Q10 {
80 if density < NX_MES_CLUT_MAX_Q10 { verdict.kind = NX_MES_CLUTTERED }
81 if density >= NX_MES_CLUT_MAX_Q10 { verdict.kind = NX_MES_CHAOTIC }
82 }
83 }
84
85 var sev: i64 = 0
86 if verdict.kind == NX_MES_MODERATE { sev = 256 }
87 if verdict.kind == NX_MES_CLUTTERED { sev = 640 }
88 if verdict.kind == NX_MES_CHAOTIC { sev = NX_MAGIC_1024 }
89 verdict.severity_q10 = sev
90
91 let total: i64 = w * h
92 if total > 0 {
93 var fid: i64 = (bg_area * NX_MAGIC_1024) / total
94 fid = fid * 2
95 if fid > NX_MAGIC_1024 { fid = NX_MAGIC_1024 }
96 verdict.fidelity_q10 = fid
97 }
98 return 0
99}