use std::f32::consts::TAU;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use epics_pva_rs::nt::nd_array::{
NdAlarm, NdArrayBuffer, NdCodec, NdDimension, NdTimeStamp, NtNdArray, nt_nd_array_desc,
nt_nd_array_value,
};
use epics_pva_rs::server_native::SharedPV;
pub const WIDTH: usize = 1024;
pub const HEIGHT: usize = 1024;
const FRINGE_PERIOD: usize = 128;
const NOISE_LEN: usize = 1 << 16;
const SPOT_RADIUS: usize = 120;
const POOL: usize = 3;
struct Spot {
sigma: f32,
amp: f32,
fx: f32,
fy: f32,
px: f32,
py: f32,
}
const SPOTS: [Spot; 3] = [
Spot {
sigma: 40.0,
amp: 170.0,
fx: 0.031,
fy: 0.047,
px: 0.0,
py: 1.0,
},
Spot {
sigma: 22.0,
amp: 150.0,
fx: 0.053,
fy: 0.029,
px: 2.0,
py: 0.5,
},
Spot {
sigma: 36.0,
amp: 130.0,
fx: 0.019,
fy: 0.037,
px: 4.0,
py: 3.0,
},
];
pub struct ImageSim {
fringe: Vec<u8>,
noise: Vec<u8>,
profiles: Vec<(Vec<u16>, Vec<u16>)>,
pool: Vec<Arc<[i8]>>,
rng: u32,
}
impl Default for ImageSim {
fn default() -> Self {
Self::new()
}
}
impl ImageSim {
pub fn new() -> Self {
let fringe = (0..WIDTH + FRINGE_PERIOD)
.map(|i| {
let s = (TAU * i as f32 / FRINGE_PERIOD as f32).sin();
(50.0 + 35.0 * s) as u8
})
.collect();
let mut rng = 0x2545_f491;
let noise = (0..NOISE_LEN + WIDTH)
.map(|_| (xorshift(&mut rng) % 25) as u8)
.collect();
let profiles = SPOTS
.iter()
.map(|s| {
let g = |d: usize| {
let d = d as f32 - SPOT_RADIUS as f32;
(-d * d / (2.0 * s.sigma * s.sigma)).exp()
};
let unit = (0..=2 * SPOT_RADIUS)
.map(|d| (255.0 * g(d)) as u16)
.collect();
let scaled = (0..=2 * SPOT_RADIUS)
.map(|d| (s.amp * g(d)) as u16)
.collect();
(unit, scaled)
})
.collect();
ImageSim {
fringe,
noise,
profiles,
pool: Vec::new(),
rng,
}
}
pub fn frame(&mut self, n: u32) -> Arc<[i8]> {
let mut buf = match self.pool.iter().position(|b| Arc::strong_count(b) == 1) {
Some(i) => self.pool.swap_remove(i),
None => Arc::from(vec![0i8; WIDTH * HEIGHT]),
};
let px = Arc::get_mut(&mut buf).expect("unshared pool buffer");
let t = n as f32;
let shift = (n as usize * 3) % FRINGE_PERIOD;
for (y, row) in px.as_chunks_mut::<WIDTH>().0.iter_mut().enumerate() {
let f0 = (y + shift) % FRINGE_PERIOD;
let n0 = xorshift(&mut self.rng) as usize % NOISE_LEN;
let fringe = &self.fringe[f0..f0 + WIDTH];
let noise = &self.noise[n0..n0 + WIDTH];
for ((p, &f), &z) in row.iter_mut().zip(fringe).zip(noise) {
*p = (f + z) as i8;
}
}
for (s, (unit, scaled)) in SPOTS.iter().zip(&self.profiles) {
let margin = SPOT_RADIUS as f32 + 1.0;
let cx = WIDTH as f32 / 2.0 + (WIDTH as f32 / 2.0 - margin) * (s.fx * t + s.px).sin();
let cy = HEIGHT as f32 / 2.0 + (HEIGHT as f32 / 2.0 - margin) * (s.fy * t + s.py).sin();
let (x0, y0) = (cx as usize - SPOT_RADIUS, cy as usize - SPOT_RADIUS);
let xs = &unit[..];
for (dy, &gy) in scaled.iter().enumerate() {
let start = (y0 + dy) * WIDTH + x0;
let row = &mut px[start..start + xs.len()];
for (p, &gx) in row.iter_mut().zip(xs) {
let add = ((gx * gy) >> 8) as u8;
*p = (*p as u8).saturating_add(add) as i8;
}
}
}
self.pool.push(buf.clone());
if self.pool.len() > POOL {
self.pool.remove(0);
}
buf
}
}
fn xorshift(s: &mut u32) -> u32 {
*s ^= *s << 13;
*s ^= *s >> 17;
*s ^= *s << 5;
*s
}
fn now() -> NdTimeStamp {
let d = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
NdTimeStamp {
seconds_past_epoch: d.as_secs() as i64,
nanoseconds: d.subsec_nanos() as i32,
user_tag: 0,
}
}
fn nt(pixels: Arc<[i8]>, dims: &[usize], unique_id: i32) -> NtNdArray {
let ts = now();
let size = pixels.len() as i64;
NtNdArray {
value: NdArrayBuffer::Byte(pixels.into()),
codec: NdCodec::default(),
compressed_size: size,
uncompressed_size: size,
unique_id,
data_time_stamp: ts.clone(),
alarm: NdAlarm::default(),
time_stamp: ts,
dimension: dims
.iter()
.map(|&d| NdDimension {
size: d as i32,
full_size: d as i32,
binning: 1,
..NdDimension::default()
})
.collect(),
attribute: Vec::new(),
}
}
pub fn open_pv() -> SharedPV {
let pv = SharedPV::build_readonly();
let empty = nt(Arc::from(Vec::new()), &[], 0);
pv.open(nt_nd_array_desc(), nt_nd_array_value(&empty))
.expect("NTNDArray value fits its descriptor");
pv
}
pub fn post(pv: &SharedPV, pixels: Arc<[i8]>, unique_id: i32) {
pv.try_post(nt_nd_array_value(&nt(pixels, &[WIDTH, HEIGHT], unique_id)));
}