nx_deadair.nx source
↩ module page · 65 lines · 2741 B
1// nx_deadair.nx -- per-interval temporal-activity metric for cam-stream
2// dead-air detection / TiVo-style trimming. R0 of the MEDIA-STUDIO arc.
3//
4// "Dead air" = the subject/scene is not changing (static hold, frozen
5// recording, empty room). We measure the FRACTION of pixels whose
6// frame-to-frame luma change exceeds a sensor-noise floor, expressed in
7// per-mille (parts per thousand, 0..1000).
8//
9// Why a noise floor is REQUIRED (not optional):
10// Real sensor noise makes nearly every pixel differ by a small amount
11// frame-to-frame. A naive sum/count of |a-b| therefore reads a static
12// but noisy frame as "active" -- a false positive that would defeat
13// trimming. Flooring counts a pixel as "changed" only when its luma
14// delta exceeds the noise floor, so only genuine subject/scene motion
15// survives. The gate proves this with floor=0 vs floor=N contrast.
16//
17// Thresholds are CALLER-SUPPLIED -- the metric carries NO magic numbers.
18// Callers source noise_floor and active_permille from svc-config.
19//
20// genealogy_id: frame_differencing + scene_change_detection
21// (commercial/dead-air advance lineage -- attribution
22// UNVERIFIED, record-hint only; do not cite as sourced)
23// lineage_id: frame_difference + noise_floor + changed_pixel_fraction
24//
25// nx_safety_envelope:
26// intended_use: "Temporal activity classification for recording
27// trim/cut. Foundation for the media-studio editor."
28// verdict: NOT_YET_EVALUATED
29// license_tier: ORIGINAL
30
31import "syscalls.nx"
32import "nx_image.nx"
33
34const NX_DEADAIR_ACTIVE: i64 = 1
35const NX_DEADAIR_DEAD: i64 = 0
36
37// Changed-pixel fraction (per-mille) over a same-size 1-channel pair.
38// A pixel counts as changed when |luma(a) - luma(b)| > noise_floor.
39// Returns 0..1000. Pass noise_floor=0 to disable flooring (naive diff).
40func nx_deadair_activity_permille(a: *Image, b: *Image, noise_floor: i64) -> i64 {
41 let w: i64 = a.width
42 let h: i64 = a.height
43 let total: i64 = w * h
44 if total <= 0 { return 0 }
45 var changed: i64 = 0
46 var y: i64 = 0
47 while y < h {
48 var x: i64 = 0
49 while x < w {
50 var d: i64 = nx_image_get(a, x, y, 0) - nx_image_get(b, x, y, 0)
51 if d < 0 { d = -d }
52 if d > noise_floor { changed = changed + 1 }
53 x = x + 1
54 }
55 y = y + 1
56 }
57 return changed * 1000 / total
58}
59
60// Classify one interval from its activity: 1 = ACTIVE, 0 = DEAD-AIR.
61// active_permille is the data-driven threshold (>= => ACTIVE).
62func nx_deadair_classify(activity_permille: i64, active_permille: i64) -> i64 {
63 if activity_permille >= active_permille { return NX_DEADAIR_ACTIVE }
64 return NX_DEADAIR_DEAD
65}