nx_image_clarity.nx source
↩ module page · 113 lines · 4030 B
1// nx_clarity.nx -- photographic vs image-level clarity.
2// Per-region Sobel + salience-peak window + focal-vs-ambient ratio.
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_salience.nx"
13
14const NX_CLARITY_NONE: i64 = 0
15const NX_CLARITY_PHOTOGRAPHIC: i64 = 1
16const NX_CLARITY_IMAGE_LEVEL: i64 = 2
17const NX_CLARITY_MIXED: i64 = 3
18
19const NX_CLARITY_FOCAL_RADIUS_Q10: i64 = 128
20const NX_CLARITY_SHARP_FLOOR_Q10: i64 = 256
21const NX_CLARITY_PHOTO_RATIO_Q10: i64 = 2048
22const NX_CLARITY_LEVEL_RATIO_Q10: i64 = 1280
23const NX_CLARITY_RATIO_CAP: i64 = 10240
24const NX_CLARITY_SOBEL_TO_Q10: i64 = 16
25
26struct ClarityVerdict {
27 kind: i64,
28 focus_x: i64,
29 focus_y: i64,
30 sharpness_focal_q10: i64,
31 sharpness_ambient_q10: i64,
32 sharpness_ratio_q10: i64,
33 fidelity_q10: i64,
34}
35
36func nx_clarity_compute(gray: *Image, verdict: *ClarityVerdict) -> i64 {
37 let w: i64 = gray.width
38 let h: i64 = gray.height
39 if w <= 0 { return 1 }
40 if h <= 0 { return 1 }
41 verdict.kind = NX_CLARITY_NONE
42 verdict.focus_x = 0
43 verdict.focus_y = 0
44 verdict.sharpness_focal_q10 = 0
45 verdict.sharpness_ambient_q10 = 0
46 verdict.sharpness_ratio_q10 = 0
47 verdict.fidelity_q10 = 0
48
49 let sal: *Image = nx_salience_intensity(gray)
50 let fx: *i64 = (sys_mmap(8)) as *i64
51 let fy: *i64 = (sys_mmap(8)) as *i64
52 nx_salience_peak(sal, fx, fy)
53 verdict.focus_x = fx[0]
54 verdict.focus_y = fy[0]
55
56 var min_dim: i64 = w
57 if h < min_dim { min_dim = h }
58 let radius: i64 = (min_dim * NX_CLARITY_FOCAL_RADIUS_Q10) / 1024
59
60 let gx: *ImageS64 = nx_image_sobel_x(gray)
61 let gy: *ImageS64 = nx_image_sobel_y(gray)
62 let mag: *ImageS64 = nx_image_gradient_magnitude(gx, gy)
63
64 var focal_sum: i64 = 0; var focal_count: i64 = 0
65 var amb_sum: i64 = 0; var amb_count: i64 = 0
66 var y: i64 = 0
67 while y < h {
68 var x: i64 = 0
69 while x < w {
70 var ax: i64 = x - verdict.focus_x
71 if ax < 0 { ax = -ax }
72 var ay: i64 = y - verdict.focus_y
73 if ay < 0 { ay = -ay }
74 var in_focal: i64 = 0
75 if ax <= radius {
76 if ay <= radius { in_focal = 1 }
77 }
78 let s: i64 = nx_image_s64_get(mag, x, y)
79 if in_focal == 1 { focal_sum = focal_sum + s; focal_count = focal_count + 1 }
80 if in_focal == 0 { amb_sum = amb_sum + s; amb_count = amb_count + 1 }
81 x = x + 1
82 }
83 y = y + 1
84 }
85 var focal_mean: i64 = 0
86 var amb_mean: i64 = 0
87 if focal_count > 0 { focal_mean = focal_sum / focal_count }
88 if amb_count > 0 { amb_mean = amb_sum / amb_count }
89
90 var focal_q10: i64 = focal_mean * NX_CLARITY_SOBEL_TO_Q10
91 if focal_q10 > 1024 { focal_q10 = 1024 }
92 var amb_q10: i64 = amb_mean * NX_CLARITY_SOBEL_TO_Q10
93 if amb_q10 > 1024 { amb_q10 = 1024 }
94 verdict.sharpness_focal_q10 = focal_q10
95 verdict.sharpness_ambient_q10 = amb_q10
96
97 var ratio_q10: i64 = NX_CLARITY_RATIO_CAP
98 if amb_q10 > 0 {
99 ratio_q10 = (focal_q10 * 1024) / amb_q10
100 if ratio_q10 > NX_CLARITY_RATIO_CAP { ratio_q10 = NX_CLARITY_RATIO_CAP }
101 }
102 verdict.sharpness_ratio_q10 = ratio_q10
103
104 if focal_q10 >= NX_CLARITY_SHARP_FLOOR_Q10 {
105 if ratio_q10 >= NX_CLARITY_PHOTO_RATIO_Q10 { verdict.kind = NX_CLARITY_PHOTOGRAPHIC }
106 if ratio_q10 < NX_CLARITY_PHOTO_RATIO_Q10 {
107 if ratio_q10 >= NX_CLARITY_LEVEL_RATIO_Q10 { verdict.kind = NX_CLARITY_MIXED }
108 if ratio_q10 < NX_CLARITY_LEVEL_RATIO_Q10 { verdict.kind = NX_CLARITY_IMAGE_LEVEL }
109 }
110 }
111 verdict.fidelity_q10 = focal_q10
112 return 0
113}