qsm_core/utils/
denoise.rs1#[cfg(feature = "parallel")]
20use rayon::prelude::*;
21
22fn jacobi_eigh(a_in: &[f64], n: usize) -> (Vec<f64>, Vec<f64>) {
27 let mut a = a_in.to_vec();
28 let mut v = vec![0.0_f64; n * n];
30 for i in 0..n {
31 v[i * n + i] = 1.0;
32 }
33 let diag_scale: f64 = (0..n).map(|i| a[i * n + i] * a[i * n + i]).sum::<f64>().max(1e-300);
37 for _sweep in 0..20 {
38 let mut off = 0.0;
40 for p in 0..n {
41 for q in (p + 1)..n {
42 off += a[p * n + q] * a[p * n + q];
43 }
44 }
45 if off <= 1e-24 * diag_scale {
46 break;
47 }
48 for p in 0..n {
49 for q in (p + 1)..n {
50 let apq = a[p * n + q];
51 if apq.abs() < 1e-300 {
52 continue;
53 }
54 let app = a[p * n + p];
55 let aqq = a[q * n + q];
56 let theta = (aqq - app) / (2.0 * apq);
57 let t = theta.signum() / (theta.abs() + (theta * theta + 1.0).sqrt());
58 let c = 1.0 / (t * t + 1.0).sqrt();
59 let s = t * c;
60 for k in 0..n {
62 let akp = a[k * n + p];
63 let akq = a[k * n + q];
64 a[k * n + p] = c * akp - s * akq;
65 a[k * n + q] = s * akp + c * akq;
66 }
67 for k in 0..n {
68 let apk = a[p * n + k];
69 let aqk = a[q * n + k];
70 a[p * n + k] = c * apk - s * aqk;
71 a[q * n + k] = s * apk + c * aqk;
72 }
73 for k in 0..n {
75 let vkp = v[k * n + p];
76 let vkq = v[k * n + q];
77 v[k * n + p] = c * vkp - s * vkq;
78 v[k * n + q] = s * vkp + c * vkq;
79 }
80 }
81 }
82 }
83 let mut eig: Vec<(f64, usize)> = (0..n).map(|i| (a[i * n + i], i)).collect();
85 eig.sort_by(|x, y| x.0.partial_cmp(&y.0).unwrap());
86 let mut d = vec![0.0_f64; n];
87 let mut w = vec![0.0_f64; n * n];
88 for (new_col, &(val, old_col)) in eig.iter().enumerate() {
89 d[new_col] = val;
90 for row in 0..n {
91 w[row * n + new_col] = v[row * n + old_col];
92 }
93 }
94 (d, w)
95}
96
97fn mp_noise_variance(d: &[f64], n_samples: usize) -> f64 {
102 let start = if d.len() > n_samples.saturating_sub(1) {
104 d.len() - (n_samples - 1)
105 } else {
106 0
107 };
108 let l = &d[start..];
109 let m = l.len();
110 if m == 0 {
111 return 0.0;
112 }
113 let mean = |k: usize| -> f64 {
114 if k == 0 {
115 0.0
116 } else {
117 l[..k].iter().sum::<f64>() / k as f64
118 }
119 };
120 let mut var = l.iter().sum::<f64>() / m as f64;
121 let mut c = m - 1;
122 let mut r = l[c] - l[0] - 4.0 * ((c as f64 + 1.0) / n_samples as f64).sqrt() * var;
123 while r > 0.0 && c > 0 {
124 var = mean(c);
125 c -= 1;
126 r = l[c] - l[0] - 4.0 * ((c as f64 + 1.0) / n_samples as f64).sqrt() * var;
127 }
128 var
129}
130
131pub fn mppca_denoise(
145 data: &[f64],
146 dims: (usize, usize, usize),
147 n_vols: usize,
148 patch_radius: usize,
149 mask: Option<&[u8]>,
150) -> Vec<f64> {
151 let (nx, ny, nz) = dims;
152 let n_voxels = nx * ny * nz;
153 assert_eq!(data.len(), n_voxels * n_vols, "data length must be n_voxels * n_vols");
154 let n = n_vols;
155 let r = patch_radius;
156 let mut out = data.to_vec();
157
158 let denoise_center = |cx: usize, cy: usize, cz: usize, slot: &mut [f64]| {
160 let side = 2 * r + 1;
161 let m = side * side * side; let mut x = vec![0.0_f64; m * n];
164 let mut center_row = 0usize;
165 let mut rr = 0usize;
166 for dz in 0..side {
167 let z = cz + dz - r;
168 for dy in 0..side {
169 let y = cy + dy - r;
170 for dx in 0..side {
171 let xx = cx + dx - r;
172 if dx == r && dy == r && dz == r {
173 center_row = rr;
174 }
175 let vox = xx + y * nx + z * nx * ny;
176 x[rr * n..rr * n + n]
177 .copy_from_slice(&data[vox * n..vox * n + n]);
178 rr += 1;
179 }
180 }
181 }
182 let mut mean = vec![0.0_f64; n];
184 for row in 0..m {
185 for t in 0..n {
186 mean[t] += x[row * n + t];
187 }
188 }
189 for t in 0..n {
190 mean[t] /= m as f64;
191 }
192 for row in 0..m {
193 for t in 0..n {
194 x[row * n + t] -= mean[t];
195 }
196 }
197 let mut cov = vec![0.0_f64; n * n];
199 for row in 0..m {
200 for a in 0..n {
201 let xa = x[row * n + a];
202 if xa == 0.0 {
203 continue;
204 }
205 for b in a..n {
206 cov[a * n + b] += xa * x[row * n + b];
207 }
208 }
209 }
210 for a in 0..n {
211 for b in a..n {
212 let v = cov[a * n + b] / m as f64;
213 cov[a * n + b] = v;
214 cov[b * n + a] = v;
215 }
216 }
217 let (d, w) = jacobi_eigh(&cov, n);
218 let var = mp_noise_variance(&d, m);
219 let tau_factor = 1.0 + (n as f64 / m as f64).sqrt();
220 let tau = tau_factor * tau_factor * var;
221 let xc = &x[center_row * n..center_row * n + n];
224 for t in 0..n {
225 slot[t] = mean[t];
226 }
227 for k in 0..n {
228 if d[k] < tau {
229 continue; }
231 let mut proj = 0.0;
232 for a in 0..n {
233 proj += xc[a] * w[a * n + k];
234 }
235 for t in 0..n {
236 slot[t] += proj * w[t * n + k];
237 }
238 }
239 for t in 0..n {
241 if slot[t] < 0.0 {
242 slot[t] = 0.0;
243 }
244 }
245 };
246
247 let process = |c: usize, slot: &mut [f64]| {
249 let cx = c % nx;
250 let cy = (c / nx) % ny;
251 let cz = c / (nx * ny);
252 if cx < r || cx >= nx - r || cy < r || cy >= ny - r || cz < r || cz >= nz - r {
253 return; }
255 if let Some(mk) = mask {
256 if mk[c] == 0 {
257 return;
258 }
259 }
260 denoise_center(cx, cy, cz, slot);
261 };
262
263 #[cfg(feature = "parallel")]
264 {
265 out.par_chunks_mut(n)
266 .enumerate()
267 .for_each(|(c, slot)| process(c, slot));
268 }
269 #[cfg(not(feature = "parallel"))]
270 {
271 for (c, slot) in out.chunks_mut(n).enumerate() {
272 process(c, slot);
273 }
274 }
275
276 out
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 #[test]
284 fn test_jacobi_diagonal() {
285 let a = vec![3.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 2.0];
287 let (d, _) = jacobi_eigh(&a, 3);
288 assert!((d[0] - 1.0).abs() < 1e-10);
289 assert!((d[1] - 2.0).abs() < 1e-10);
290 assert!((d[2] - 3.0).abs() < 1e-10);
291 }
292
293 #[test]
294 fn test_jacobi_known_symmetric() {
295 let a = vec![2.0, 1.0, 1.0, 2.0];
297 let (d, w) = jacobi_eigh(&a, 2);
298 assert!((d[0] - 1.0).abs() < 1e-9);
299 assert!((d[1] - 3.0).abs() < 1e-9);
300 let mut recon = [0.0_f64; 4];
302 for i in 0..2 {
303 for j in 0..2 {
304 for k in 0..2 {
305 recon[i * 2 + j] += w[i * 2 + k] * d[k] * w[j * 2 + k];
306 }
307 }
308 }
309 for (r, a) in recon.iter().zip(a.iter()) {
310 assert!((r - a).abs() < 1e-9);
311 }
312 }
313
314 #[test]
315 fn test_mppca_reduces_noise_preserves_signal() {
316 let (nx, ny, nz, n) = (12usize, 12usize, 12usize, 16usize);
319 let nvox = nx * ny * nz;
320 let mut clean = vec![0.0_f64; nvox * n];
321 for z in 0..nz {
322 for y in 0..ny {
323 for x in 0..nx {
324 let v = x + y * nx + z * nx * ny;
325 let r2 = if x < nx / 2 { 20.0 } else { 40.0 };
327 let s0 = 100.0;
328 for t in 0..n {
329 let te = (t + 1) as f64 * 0.005;
330 clean[v * n + t] = s0 * (-r2 * te).exp();
331 }
332 }
333 }
334 }
335 let mut noisy = clean.clone();
337 let mut seed = 12345u64;
338 let mut rand = || {
339 seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
340 let u = |s: u64| (s >> 33) as f64 / (1u64 << 31) as f64; let s2 = seed.wrapping_mul(2862933555777941757).wrapping_add(3037000493);
343 (u(seed) - 0.5) + (u(s2) - 0.5) };
345 let sigma = 3.0;
346 for v in noisy.iter_mut() {
347 *v += sigma * rand();
348 }
349 let den = mppca_denoise(&noisy, (nx, ny, nz), n, 2, None);
350 let r = 2usize;
353 let interior_err = |a: &[f64]| -> f64 {
354 let mut s = 0.0;
355 for z in r..nz - r {
356 for y in r..ny - r {
357 for x in r..nx - r {
358 let v = x + y * nx + z * nx * ny;
359 for t in 0..n {
360 let d = a[v * n + t] - clean[v * n + t];
361 s += d * d;
362 }
363 }
364 }
365 }
366 s.sqrt()
367 };
368 let e_noisy = interior_err(&noisy);
369 let e_den = interior_err(&den);
370 assert!(
371 e_den < 0.4 * e_noisy,
372 "MP-PCA should cut interior error substantially: noisy {:.1} -> denoised {:.1}",
373 e_noisy,
374 e_den
375 );
376 }
377}