1use num_complex::Complex64;
15
16use crate::fft::{fft3d_real, ifft3d_real};
17use crate::grid::Grid;
18use crate::models::onnx::{OnnxError, OnnxModel, Tensor};
19
20pub const LPCNN_ALPHA: f64 = 3.718_820_571_899_414;
22pub const LPCNN_GT_MEAN: f64 = -0.000_247_246_032_403_825_13;
24pub const LPCNN_GT_STD: f64 = 0.028_365_555_003_933_052;
25const ITER_NUM: usize = 3;
26
27pub fn lpcnn(
30 local_field_ppm: &[f64],
31 mask: &[u8],
32 grid: &Grid,
33 bdir: (f64, f64, f64),
34 gen_onnx: &[u8],
35) -> Result<Vec<f64>, OnnxError> {
36 let (nx, ny, nz) = grid.dims;
37 let n = nx * ny * nz;
38 assert_eq!(local_field_ppm.len(), n, "field length must match grid");
39 assert_eq!(mask.len(), n, "mask length must match grid");
40
41 let model = OnnxModel::load(gen_onnx)?;
42 let maskf: Vec<f64> = mask.iter().map(|&m| m as f64).collect();
43 let dk = lpcnn_dipole_kernel(grid, bdir);
44 let dipole = |v: &[f64]| dipole_apply(v, &dk, grid);
45
46 let x_est: Vec<f64> = dipole(local_field_ppm).iter().map(|&v| LPCNN_ALPHA * v).collect();
48
49 let mut den = vec![0.0f64; n];
50 let mut x_pred = vec![0.0f64; n];
51 for i in 0..ITER_NUM {
52 let pn: Vec<f64> = if i == 0 {
54 x_est.clone()
55 } else {
56 let dd = dipole(&dipole(&den)); (0..n).map(|k| den[k] + x_est[k] - LPCNN_ALPHA * dd[k]).collect()
58 };
59 let x_input: Vec<f64> =
61 (0..n).map(|k| ((pn[k] - LPCNN_GT_MEAN) / LPCNN_GT_STD) * maskf[k]).collect();
62 x_pred = run_gen(&model, &x_input, grid)?;
63 den = (0..n)
64 .map(|k| (x_pred[k] * LPCNN_GT_STD + LPCNN_GT_MEAN) * maskf[k])
65 .collect();
66 }
67 Ok((0..n)
69 .map(|k| (x_pred[k] * LPCNN_GT_STD + LPCNN_GT_MEAN) * maskf[k])
70 .collect())
71}
72
73pub(crate) fn lpcnn_dipole_kernel(grid: &Grid, bdir: (f64, f64, f64)) -> Vec<f64> {
76 let (nx, ny, nz) = grid.dims;
77 let (vx, vy, vz) = grid.voxel_size;
78 let (bx, by, bz) = bdir;
79 let bn = (bx * bx + by * by + bz * bz).sqrt();
80 let (bx, by, bz) = (bx / bn, by / bn, bz / bn);
81 let freq = |i: usize, ntot: usize, d: f64| -> f64 {
82 let ii = if i < ntot.div_ceil(2) { i as f64 } else { i as f64 - ntot as f64 };
83 ii / (ntot as f64 * d)
84 };
85 let mut k = vec![0.0f64; nx * ny * nz];
86 for z in 0..nz {
87 let kz = freq(z, nz, vz);
88 for y in 0..ny {
89 let ky = freq(y, ny, vy);
90 for x in 0..nx {
91 let kx = freq(x, nx, vx);
92 let k2 = kx * kx + ky * ky + kz * kz;
93 let kb = kx * bx + ky * by + kz * bz;
94 k[x + nx * (y + ny * z)] = if k2 > 0.0 { 1.0 / 3.0 - kb * kb / k2 } else { 0.0 };
95 }
96 }
97 }
98 k
99}
100
101fn dipole_apply(v: &[f64], dk: &[f64], grid: &Grid) -> Vec<f64> {
103 let (nx, ny, nz) = grid.dims;
104 let mut spec = fft3d_real(v, nx, ny, nz);
105 for (s, &d) in spec.iter_mut().zip(dk) {
106 *s *= Complex64::new(d, 0.0);
107 }
108 ifft3d_real(&spec, nx, ny, nz)
109}
110
111fn run_gen(model: &OnnxModel, vol: &[f64], grid: &Grid) -> Result<Vec<f64>, OnnxError> {
113 let (nx, ny, nz) = grid.dims;
114 let mut input = vec![0.0f32; nx * ny * nz];
115 for z in 0..nz {
116 for y in 0..ny {
117 for x in 0..nx {
118 input[(x * ny + y) * nz + z] = vol[x + nx * (y + ny * z)] as f32;
119 }
120 }
121 }
122 let out = model.run_single(&Tensor::new(vec![1, 1, nx, ny, nz], input))?;
123 let mut v = vec![0.0f64; nx * ny * nz];
124 for z in 0..nz {
125 for y in 0..ny {
126 for x in 0..nx {
127 v[x + nx * (y + ny * z)] = out.data[(x * ny + y) * nz + z] as f64;
128 }
129 }
130 }
131 Ok(v)
132}
133
134pub fn lpcnn_tiled(
139 local_field_ppm: &[f64],
140 mask: &[u8],
141 grid: &Grid,
142 bdir: (f64, f64, f64),
143 gen_onnx: &[u8],
144 cfg: &super::tiled::TileConfig,
145 progress: impl FnMut(usize, usize),
146) -> Result<Vec<f64>, OnnxError> {
147 super::tiled::tiled_volume_algorithm(
148 local_field_ppm, mask, grid, 8, cfg,
149 |f, m, g| lpcnn(f, m, g, bdir, gen_onnx),
150 progress,
151 )
152}