1use num_complex::Complex64;
219#[cfg(feature = "parallel")]
220use rayon::prelude::*;
221use crate::fft::{Box3, Fft3dWorkspace, FftOpts};
222use crate::kernels::dipole::dipole_kernel;
223use crate::{EchoTime, Grid};
224
225#[cfg_attr(feature = "introspection", derive(serde::Serialize))]
227#[derive(Clone, Debug)]
228pub struct MediParams {
229 pub lambda: f64,
231 pub merit: bool,
233 pub smv: bool,
240 pub smv_radius: f64,
242 pub smv_shrink_mask: bool,
250 pub msmv: Option<super::msmv::MsmvParams>,
258 pub data_weighting: i32,
260 pub percentage: f64,
264 pub cg_tol: f64,
266 pub cg_max_iter: usize,
268 pub max_iter: usize,
270 pub tol: f64,
272 pub b0: f64,
274 pub echo_times: Vec<f64>,
277 pub te: EchoTime,
282 pub csf: bool,
286 pub lambda_csf: f64,
288 pub csf_r2s_threshold: f64,
290 pub csf_erode: bool,
292 pub zeropad: (usize, usize, usize),
295}
296
297#[derive(Clone, Copy, Debug)]
300pub struct EchoMagnitudes<'a> {
301 pub data: &'a [f64],
303 pub n_echoes: usize,
305}
306
307impl Default for MediParams {
308 fn default() -> Self {
309 Self {
310 lambda: 1000.0,
311 merit: true,
312 smv: true,
313 smv_radius: 5.0,
314 smv_shrink_mask: true,
315 msmv: None,
316 data_weighting: 1,
317 percentage: 0.9,
318 cg_tol: 0.01,
319 cg_max_iter: 100,
320 max_iter: 10,
321 tol: 0.1,
322 b0: 3.0,
323 echo_times: vec![0.005],
324 te: EchoTime::Spacing,
325 csf: true,
326 lambda_csf: 100.0,
327 csf_r2s_threshold: 5.0,
328 csf_erode: true,
329 zeropad: (0, 0, 0),
330 }
331 }
332}
333
334pub fn medi(
349 local_field: &[f64],
350 mask: &[u8],
351 magnitude: EchoMagnitudes,
352 grid: &Grid,
353 bdir: (f64, f64, f64),
354 params: &MediParams,
355 progress: impl FnMut(usize, usize),
356) -> Vec<f64> {
357 let n = grid.n_total();
358 let EchoMagnitudes { data: m, n_echoes } = magnitude;
360 assert!(n_echoes > 0 && m.len() == n * n_echoes, "magnitude is not n_echoes volumes");
361 let imag: Vec<f64> = (0..n)
362 .map(|i| (0..n_echoes).map(|e| m[i + n * e].powi(2)).sum::<f64>().sqrt())
363 .collect();
364 let n_std = crate::utils::fit_ppm_complex_noise(m, n_echoes);
365 let te = ¶ms.echo_times;
366 assert_eq!(te.len(), n_echoes, "echo_times does not match the echo magnitudes");
367 let r2s_msmv: Option<Vec<f64>> = params.msmv.as_ref().map(|_| {
370 crate::utils::r2star::arlo_unclipped(m, te, n)
371 .unwrap_or_else(|e| panic!("MEDI-mSMV needs R2* from three or more echoes: {e}"))
372 });
373 let csf: Option<Vec<bool>> = if params.csf && n_echoes >= 3 {
375 let r2s = crate::utils::r2star::arlo(m, te, n).expect("three or more increasing echo times");
376 let c = extract_csf(&r2s, mask, grid, params.csf_r2s_threshold, params.csf_erode);
377 c.iter().any(|&v| v != 0).then(|| c.iter().map(|&v| v != 0).collect())
378 } else {
379 None
380 };
381
382 let (nx, ny, nz) = grid.dims;
383 let (zx, zy, zz) = params.zeropad;
384 let smv_on = params.smv || params.msmv.is_some();
386 let even = |n: usize, z: usize| z + usize::from(smv_on && (n + z) % 2 == 1);
387 let (px, py, pz) = (even(nx, zx), even(ny, zy), even(nz, zz));
388 if (px, py, pz) == (0, 0, 0) {
389 return medi_l1(
390 local_field, mask, &imag, &n_std, csf.as_deref(), r2s_msmv.as_deref(), grid, bdir, params, progress,
391 );
392 }
393 let (mx, my, mz) = (nx + px, ny + py, nz + pz);
395 let pad = |v: &[f64]| -> Vec<f64> {
396 let mut out = vec![0.0; mx * my * mz];
397 for k in 0..nz {
398 for j in 0..ny {
399 let (src, dst) = (nx * (j + ny * k), mx * (j + my * k));
400 out[dst..dst + nx].copy_from_slice(&v[src..src + nx]);
401 }
402 }
403 out
404 };
405 let mask_f: Vec<f64> = mask.iter().map(|&m| m as f64).collect();
406 let mask_p: Vec<u8> = pad(&mask_f).iter().map(|&v| v as u8).collect();
407 let pgrid = Grid::new(mx, my, mz, grid.vsx(), grid.vsy(), grid.vsz());
408 let csf_p: Option<Vec<bool>> = csf.map(|c| {
409 let f: Vec<f64> = c.iter().map(|&b| b as u8 as f64).collect();
410 pad(&f).iter().map(|&v| v != 0.0).collect()
411 });
412 let r2s_p = r2s_msmv.as_deref().map(pad);
413 let chi = medi_l1(
414 &pad(local_field), &mask_p, &pad(&imag), &pad(&n_std), csf_p.as_deref(), r2s_p.as_deref(),
415 &pgrid, bdir, params, progress,
416 );
417 let mut out = vec![0.0; n];
418 for k in 0..nz {
419 for j in 0..ny {
420 let (dst, src) = (nx * (j + ny * k), mx * (j + my * k));
421 out[dst..dst + nx].copy_from_slice(&chi[src..src + nx]);
422 }
423 }
424 out
425}
426
427#[allow(clippy::too_many_arguments)]
429fn medi_l1(
430 local_field: &[f64],
431 mask: &[u8],
432 magnitude: &[f64],
433 n_std: &[f64],
434 mask_csf: Option<&[bool]>,
435 r2s: Option<&[f64]>,
436 grid: &Grid,
437 bdir: (f64, f64, f64),
438 params: &MediParams,
439 mut progress: impl FnMut(usize, usize),
440) -> Vec<f64> {
441 let n = grid.n_total();
442 let vs = [grid.vsx(), grid.vsy(), grid.vsz()];
443 let mut fft = Fft3dWorkspace::new(grid.nx(), grid.ny(), grid.nz());
444 let mut buf = vec![Complex64::new(0.0, 0.0); n];
445
446 let cf = crate::constants::GAMMA_BAR_HZ_PER_T * params.b0;
449 let delta_te = params.te.seconds(¶ms.echo_times, "MEDI");
450 let ppm_to_rad = 2.0 * std::f64::consts::PI * delta_te * cf * 1e-6;
451 let mut rdf = vec![0.0f64; n];
452 par_fill(&mut rdf, |i| local_field[i] * ppm_to_rad);
453
454 let mut mask: Vec<bool> = mask.iter().map(|&m| m != 0).collect();
456 let mut n_std: Vec<f64> = (0..n)
457 .map(|i| if mask[i] { n_std[i] } else { 0.0 })
458 .collect();
459 let mut tempn = n_std.clone();
460
461 let mut d: Vec<Complex64> = dipole_kernel(grid, bdir).iter().map(|&v| Complex64::new(v, 0.0)).collect();
463
464 let sphere_k = if params.smv || params.msmv.is_some() {
465 let k = sphere_kernel(grid, params.smv_radius, &mut fft);
466 if let Some(mp) = ¶ms.msmv {
467 let r2s = r2s.expect("MEDI-mSMV needs R2*");
471 let mask_u8: Vec<u8> = mask.iter().map(|&b| u8::from(b)).collect();
472 rdf = super::msmv::msmv(&rdf, &mask_u8, r2s, grid, params.smv_radius, params.b0, mp).field;
473 } else {
474 if params.smv_shrink_mask {
476 let m: Vec<f64> = mask.iter().map(|&b| b as u8 as f64).collect();
477 smv(&m, &k, &mut fft, &mut buf);
478 par_fill(&mut mask, |i| buf[i].re > 0.999);
479 }
480 smv(&rdf, &k, &mut fft, &mut buf);
484 par_update(&mut rdf, |i, v| *v = if mask[i] { *v - buf[i].re } else { 0.0 });
485 }
486 par_update(&mut d, |i, v| *v *= Complex64::new(1.0, 0.0) - k[i]);
488 smv_noise(&mut tempn, &k, &mut fft, &mut buf);
490 Some(k)
491 } else {
492 None
493 };
494
495 let mut m = vec![0.0f64; n];
496 dataterm_mask(params.data_weighting, &tempn, &mask, &mut m);
497 let mut b0 = vec![Complex64::new(0.0, 0.0); n];
498 par_fill(&mut b0, |i| m[i] * Complex64::new(0.0, rdf[i]).exp());
499 let wg = gradient_mask(magnitude, &mask, grid, params.percentage);
500 let mbox = if params.data_weighting == 0 {
504 [0..grid.nx(), 0..grid.ny(), 0..grid.nz()]
505 } else {
506 bounding_box(&mask, grid.dims)
507 };
508
509 let lambda2 = 2.0 * params.lambda;
510 let csf_idx: Vec<usize> = mask_csf.map_or(Vec::new(), |c| (0..n).filter(|&i| c[i]).collect());
512 let reg_csf = |x: &[f64], acc: &mut [f64]| {
513 if csf_idx.is_empty() {
514 return;
515 }
516 let len = csf_idx.len() as f64;
517 let mean = csf_idx.iter().map(|&i| x[i]).sum::<f64>() / len;
518 let t1: Vec<f64> = csf_idx.iter().map(|&i| x[i] - mean).collect();
519 let mean = t1.iter().sum::<f64>() / len;
520 for (q, &i) in csf_idx.iter().enumerate() {
521 acc[i] += params.lambda_csf * (t1[q] - mean);
522 }
523 };
524 let e = 1e-6;
525 let dims = grid.dims;
526 let mut x = vec![0.0f64; n];
527 let mut wvw = [vec![0.0f64; n], vec![0.0f64; n], vec![0.0f64; n]];
529 let mut g = [vec![0.0f64; n], vec![0.0f64; n], vec![0.0f64; n]];
530 let mut w = vec![Complex64::new(0.0, 0.0); n];
531 let mut w2 = vec![0.0f64; n];
532 let mut b = vec![0.0f64; n];
533 let mut t = vec![0.0f64; n];
534 let mut rv = vec![0.0f64; n];
535 let mut cg = CgBuffers::new(n);
536 let mask_idx: Vec<usize> = (0..n).filter(|&i| mask[i]).collect();
537 let total = params.max_iter * params.cg_max_iter;
538
539 let mut res_norm_ratio = f64::INFINITY;
540 let mut iter = 0;
541 while res_norm_ratio > params.tol && iter < params.max_iter {
542 iter += 1;
543 fgrad(&x, dims, vs, &mut g);
545 for c in 0..3 {
546 let (wgc, gc) = (&wg[c], &g[c]);
547 par_fill(&mut wvw[c], |i| {
548 let u = wgc[i] * gc[i];
549 let vr = 1.0 / (u * u + e).sqrt();
550 wgc[i] * vr * wgc[i]
551 });
552 }
553 dconv(&x, &d, &mut fft, &mut buf, &mut t);
555 par_fill(&mut w, |i| m[i] * Complex64::new(0.0, t[i]).exp());
556 par_fill(&mut w2, |i| w[i].norm_sqr());
557
558 reg(&x, &wvw, dims, vs, &mut b);
560 reg_csf(&x, &mut b);
561 par_fill(&mut rv, |i| (w[i].conj() * Complex64::new(0.0, -1.0) * (w[i] - b0[i])).re);
562 dconv(&rv, &d, &mut fft, &mut buf, &mut t);
563 par_update(&mut b, |i, v| *v = -(*v + lambda2 * t[i]));
564
565 let base = (iter - 1) * params.cg_max_iter;
567 cgsolve(
568 |p: &[f64], out: &mut [f64]| {
569 reg(p, &wvw, dims, vs, out);
570 reg_csf(p, out);
571 dconv_weighted(p, &d, &w2, &mbox, &mut fft, &mut buf);
572 par_update(out, |i, v| *v += lambda2 * buf[i].re);
573 },
574 &b, params.cg_tol, params.cg_max_iter, &mut cg,
575 |k| progress(base + k, total),
576 );
577 let dx = &cg.x;
578
579 let ndx = dx.iter().map(|v| v * v).sum::<f64>().sqrt();
581 let nrm_x = x.iter().map(|v| v * v).sum::<f64>().sqrt();
582 res_norm_ratio = ndx / nrm_x;
583 par_update(&mut x, |i, v| *v += dx[i]);
584
585 if params.merit {
586 dconv(&x, &d, &mut fft, &mut buf, &mut t);
588 let wres: Vec<Complex64> = mask_idx
589 .iter()
590 .map(|&i| m[i] * Complex64::new(0.0, t[i]).exp() - b0[i])
591 .collect();
592 let mean = wres.iter().copied().sum::<Complex64>() / mask_idx.len() as f64;
593 let a: Vec<f64> = wres.iter().map(|&v| (v - mean).norm()).collect();
595 let am = a.iter().sum::<f64>() / a.len() as f64;
596 let sd = (a.iter().map(|v| (v - am).powi(2)).sum::<f64>() / (a.len() as f64 - 1.0)).sqrt();
597 let factor = sd * 6.0;
598 for (q, &i) in mask_idx.iter().enumerate() {
599 let r = (a[q] / factor).max(1.0);
600 n_std[i] *= r * r;
601 }
602 tempn.copy_from_slice(&n_std);
603 if let Some(k) = &sphere_k {
604 smv_noise(&mut tempn, k, &mut fft, &mut buf);
605 }
606 dataterm_mask(params.data_weighting, &tempn, &mask, &mut m);
607 par_fill(&mut b0, |i| m[i] * Complex64::new(0.0, rdf[i]).exp());
608 }
609 }
610 progress(total, total);
611
612 par_update(&mut x, |i, v| *v = if mask[i] { *v / ppm_to_rad } else { 0.0 });
614 if !csf_idx.is_empty() {
616 let mean = csf_idx.iter().map(|&i| x[i]).sum::<f64>() / csf_idx.len() as f64;
617 par_update(&mut x, |i, v| {
618 if mask[i] {
619 *v -= mean;
620 }
621 });
622 }
623 x
624}
625
626pub(crate) fn par_fill<T: Send>(out: &mut [T], f: impl Fn(usize) -> T + Sync + Send) {
628 #[cfg(feature = "parallel")]
629 out.par_iter_mut().with_min_len(1 << 14).enumerate().for_each(|(i, v)| *v = f(i));
630 #[cfg(not(feature = "parallel"))]
631 out.iter_mut().enumerate().for_each(|(i, v)| *v = f(i));
632}
633
634pub(crate) fn par_update<T: Send>(out: &mut [T], f: impl Fn(usize, &mut T) + Sync + Send) {
636 #[cfg(feature = "parallel")]
637 out.par_iter_mut().with_min_len(1 << 14).enumerate().for_each(|(i, v)| f(i, v));
638 #[cfg(not(feature = "parallel"))]
639 out.iter_mut().enumerate().for_each(|(i, v)| f(i, v));
640}
641
642pub(super) fn bounding_box(mask: &[bool], (nx, ny, nz): (usize, usize, usize)) -> Box3 {
644 let (mut lo, mut hi) = ([usize::MAX; 3], [0usize; 3]);
645 for (p, _) in mask.iter().enumerate().filter(|(_, &m)| m) {
646 let c = [p % nx, (p / nx) % ny, p / (nx * ny)];
647 for a in 0..3 {
648 lo[a] = lo[a].min(c[a]);
649 hi[a] = hi[a].max(c[a] + 1);
650 }
651 }
652 if lo[0] == usize::MAX {
653 return [0..nx, 0..ny, 0..nz];
654 }
655 [lo[0]..hi[0], lo[1]..hi[1], lo[2]..hi[2]]
656}
657
658fn load_real(x: &[f64]) -> impl Fn(usize, &mut [Complex64]) + Sync + '_ {
660 move |l, row| {
661 for (c, &v) in row.iter_mut().zip(&x[l..]) {
662 *c = Complex64::new(v, 0.0);
663 }
664 }
665}
666
667fn mul_kernel(k: &[Complex64]) -> impl Fn(usize, &mut [Complex64]) + Sync + '_ {
670 move |l, row| {
671 for (c, &kv) in row.iter_mut().zip(&k[l..]) {
672 *c *= kv;
673 }
674 }
675}
676
677pub(super) fn dconv(x: &[f64], d: &[Complex64], fft: &mut Fft3dWorkspace, buf: &mut [Complex64], out: &mut [f64]) {
679 let load = load_real(x);
680 fft.fft3d_with(buf, &FftOpts { load: Some(&load), ..FftOpts::default() });
681 fft.ifft3d_with(buf, &FftOpts { load: Some(&mul_kernel(d)), ..FftOpts::default() });
682 par_fill(out, |i| buf[i].re);
683}
684
685pub(super) fn dconv_weighted(
689 p: &[f64],
690 d: &[Complex64],
691 w2: &[f64],
692 mbox: &Box3,
693 fft: &mut Fft3dWorkspace,
694 buf: &mut [Complex64],
695) {
696 let load = load_real(p);
697 fft.fft3d_with(buf, &FftOpts { load: Some(&load), ..FftOpts::default() });
698 let mul_d = mul_kernel(d);
700 fft.ifft3d_with(buf, &FftOpts { needed: Some(mbox), post: Some(w2), load: Some(&mul_d), ..FftOpts::default() });
701 let drop_imag = |_: usize, row: &mut [Complex64]| {
702 for c in row.iter_mut() {
703 c.im = 0.0;
704 }
705 };
706 fft.fft3d_with(buf, &FftOpts { support: Some(mbox), load: Some(&drop_imag), ..FftOpts::default() });
707 fft.ifft3d_with(buf, &FftOpts { load: Some(&mul_d), ..FftOpts::default() });
708}
709
710pub(crate) fn fgrad(x: &[f64], dims: (usize, usize, usize), vs: [f64; 3], g: &mut [Vec<f64>; 3]) {
712 let (nx, ny, nz) = dims;
713 let nxy = nx * ny;
714 let [g0, g1, g2] = g;
715 let plane = |k: usize, ((a, b), c): ((&mut [f64], &mut [f64]), &mut [f64])| {
716 for j in 0..ny {
717 for i in 0..nx {
718 let q = i + nx * j;
719 let p = q + nxy * k;
720 a[q] = if i + 1 < nx { (x[p + 1] - x[p]) / vs[0] } else { 0.0 };
721 b[q] = if j + 1 < ny { (x[p + nx] - x[p]) / vs[1] } else { 0.0 };
722 c[q] = if k + 1 < nz { (x[p + nxy] - x[p]) / vs[2] } else { 0.0 };
723 }
724 }
725 };
726 #[cfg(feature = "parallel")]
727 g0.par_chunks_mut(nxy)
728 .zip(g1.par_chunks_mut(nxy))
729 .zip(g2.par_chunks_mut(nxy))
730 .enumerate()
731 .for_each(|(k, s)| plane(k, s));
732 #[cfg(not(feature = "parallel"))]
733 g0.chunks_mut(nxy)
734 .zip(g1.chunks_mut(nxy))
735 .zip(g2.chunks_mut(nxy))
736 .enumerate()
737 .for_each(|(k, s)| plane(k, s));
738}
739
740#[cfg(test)]
743fn bdiv(g: &[Vec<f64>; 3], dims: (usize, usize, usize), vs: [f64; 3], out: &mut [f64]) {
744 let (nx, ny, nz) = dims;
745 let nxy = nx * ny;
746 let term = |gc: &[f64], p: usize, i: usize, len: usize, stride: usize| -> f64 {
748 let cur = if i + 1 < len { gc[p] } else { 0.0 };
749 let prev = if i > 0 { gc[p - stride] } else { 0.0 };
750 cur - prev
751 };
752 for k in 0..nz {
753 for j in 0..ny {
754 for i in 0..nx {
755 let p = i + nx * j + nxy * k;
756 out[p] = -(term(&g[0], p, i, nx, 1) / vs[0]
757 + term(&g[1], p, j, ny, nx) / vs[1]
758 + term(&g[2], p, k, nz, nxy) / vs[2]);
759 }
760 }
761 }
762}
763
764pub(crate) fn reg(x: &[f64], wvw: &[Vec<f64>; 3], dims: (usize, usize, usize), vs: [f64; 3], out: &mut [f64]) {
768 let (nx, ny, nz) = dims;
769 let nxy = nx * ny;
770 let grad = |k: usize, gx: Option<(&mut [f64], &mut [f64])>, gz: &mut [f64]| {
772 let base = nxy * k;
773 for q in 0..nxy {
774 let p = base + q;
775 gz[q] = (if k + 1 < nz { (x[p + nxy] - x[p]) / vs[2] } else { 0.0 }) * wvw[2][p];
776 }
777 if let Some((gx, gy)) = gx {
778 for j in 0..ny {
779 for i in 0..nx {
780 let q = i + nx * j;
781 let p = base + q;
782 gx[q] = (if i + 1 < nx { (x[p + 1] - x[p]) / vs[0] } else { 0.0 }) * wvw[0][p];
783 gy[q] = (if j + 1 < ny { (x[p + nx] - x[p]) / vs[1] } else { 0.0 }) * wvw[1][p];
784 }
785 }
786 }
787 };
788 let planes = |k0: usize, o: &mut [f64], bufs: &mut [Vec<f64>; 4]| {
790 let [gx, gy, gz, gz_prev] = bufs;
791 if k0 > 0 {
792 grad(k0 - 1, None, gz_prev);
793 }
794 for (dk, op) in o.chunks_mut(nxy).enumerate() {
795 let k = k0 + dk;
796 grad(k, Some((gx, gy)), gz);
797 for j in 0..ny {
798 for i in 0..nx {
799 let q = i + nx * j;
800 let tx = (if i + 1 < nx { gx[q] } else { 0.0 }) - (if i > 0 { gx[q - 1] } else { 0.0 });
801 let ty = (if j + 1 < ny { gy[q] } else { 0.0 }) - (if j > 0 { gy[q - nx] } else { 0.0 });
802 let tz = (if k + 1 < nz { gz[q] } else { 0.0 }) - (if k > 0 { gz_prev[q] } else { 0.0 });
803 op[q] = -(tx / vs[0] + ty / vs[1] + tz / vs[2]);
804 }
805 }
806 std::mem::swap(gz, gz_prev);
807 }
808 };
809 let new_bufs = || [vec![0.0f64; nxy], vec![0.0f64; nxy], vec![0.0f64; nxy], vec![0.0f64; nxy]];
810 const PLANES: usize = 4;
812 #[cfg(feature = "parallel")]
813 out.par_chunks_mut(nxy * PLANES)
814 .enumerate()
815 .for_each_init(new_bufs, |bufs, (c, o)| planes(c * PLANES, o, bufs));
816 #[cfg(not(feature = "parallel"))]
817 {
818 let mut bufs = new_bufs();
819 out.chunks_mut(nxy * PLANES).enumerate().for_each(|(c, o)| planes(c * PLANES, o, &mut bufs));
820 }
821}
822
823pub(crate) struct CgBuffers {
825 pub(crate) x: Vec<f64>,
826 r: Vec<f64>,
827 d: Vec<f64>,
828 q: Vec<f64>,
829}
830
831impl CgBuffers {
832 pub(crate) fn new(n: usize) -> Self {
833 Self { x: vec![0.0; n], r: vec![0.0; n], d: vec![0.0; n], q: vec![0.0; n] }
834 }
835}
836
837pub(crate) fn cgsolve(
840 mut a: impl FnMut(&[f64], &mut [f64]),
841 b: &[f64],
842 tol: f64,
843 max_iter: usize,
844 cg: &mut CgBuffers,
845 mut progress: impl FnMut(usize),
846) {
847 let dot = |u: &[f64], v: &[f64]| u.iter().zip(v).map(|(a, b)| a * b).sum::<f64>();
848 let CgBuffers { x, r, d, q } = cg;
849 x.fill(0.0);
850 r.copy_from_slice(b);
851 d.copy_from_slice(b);
852 let mut delta = dot(r, r);
853 let delta0 = dot(b, b);
854 let mut numiter = 0;
855 while numiter < max_iter && delta > tol * tol * delta0 {
856 a(d, q);
857 let alpha = delta / dot(d, q);
858 if (numiter + 1) % 50 == 0 {
859 par_update(x, |i, v| *v += alpha * d[i]);
860 a(x, q);
862 par_fill(r, |i| b[i] - q[i]);
863 } else {
864 #[cfg(feature = "parallel")]
865 x.par_iter_mut().zip(r.par_iter_mut()).with_min_len(1 << 14).enumerate().for_each(|(i, (xv, rv))| {
866 *xv += alpha * d[i];
867 *rv -= alpha * q[i];
868 });
869 #[cfg(not(feature = "parallel"))]
870 x.iter_mut().zip(r.iter_mut()).enumerate().for_each(|(i, (xv, rv))| {
871 *xv += alpha * d[i];
872 *rv -= alpha * q[i];
873 });
874 }
875 let delta_old = delta;
876 delta = dot(r, r);
877 let beta = delta / delta_old;
878 let rr = &*r;
879 par_update(d, |i, v| *v = rr[i] + beta * *v);
880 numiter += 1;
881 progress(numiter);
882 }
883}
884
885pub(crate) fn dataterm_mask(mode: i32, n_std: &[f64], mask: &[bool], w: &mut [f64]) {
888 if mode == 0 {
889 w.fill(1.0);
890 return;
891 }
892 par_fill(w, |i| {
893 let (s, m) = (n_std[i], mask[i]);
894 let v = (m as u8 as f64) / s;
895 if m && v.is_finite() { v } else { 0.0 }
896 });
897 let count = mask.iter().filter(|&&m| m).count() as f64;
898 let mean = w.iter().zip(mask).filter(|(_, &m)| m).map(|(v, _)| v).sum::<f64>() / count;
899 par_update(w, |_, v| *v /= mean);
900}
901
902pub(crate) fn gradient_mask(mag: &[f64], mask: &[bool], grid: &Grid, percentage: f64) -> [Vec<f64>; 3] {
907 let vs = [grid.vsx(), grid.vsy(), grid.vsz()];
908 let n = mag.len();
909 let masked: Vec<f64> = mag.iter().zip(mask).map(|(&v, &m)| if m { v } else { 0.0 }).collect();
910 let mut g = [vec![0.0f64; n], vec![0.0f64; n], vec![0.0f64; n]];
911 fgrad(&masked, grid.dims, vs, &mut g);
912 for c in g.iter_mut() {
913 par_update(c, |_, v| *v = v.abs());
914 }
915 let mut sorted: Vec<f64> = g.iter().flat_map(|c| c.iter().copied()).collect();
918 let cmp = |a: &f64, b: &f64| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal);
919 #[cfg(feature = "parallel")]
920 sorted.par_sort_unstable_by(cmp);
921 #[cfg(not(feature = "parallel"))]
922 sorted.sort_unstable_by(cmp);
923 let above = |level: f64| sorted.len() - sorted.partition_point(|&v| v <= level);
924 let denominator = mask.iter().filter(|&&m| m).count() as f64;
925 let mut level = 0.01 * mag.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
926 let mut numerator = above(level) as f64;
927 if numerator / denominator > percentage {
928 while numerator / denominator > percentage {
929 level *= 1.05;
930 numerator = above(level) as f64;
931 }
932 } else {
933 let min_positive = sorted.iter().copied().find(|&v| v > 0.0).unwrap_or(f64::INFINITY);
938 while numerator / denominator < percentage && level >= min_positive && level > 0.0 {
939 level *= 0.95;
940 numerator = above(level) as f64;
941 }
942 }
943 for c in g.iter_mut() {
944 par_update(c, |_, v| *v = if *v <= level { 1.0 } else { 0.0 });
945 }
946 g
947}
948
949pub(crate) fn sphere_kernel_sparse(grid: &Grid, radius: f64) -> Vec<(usize, f64)> {
956 let (nx, ny, nz) = grid.dims;
957 let vs = [grid.vsx(), grid.vsy(), grid.vsz()];
958 let dims = [nx, ny, nz];
959 let r2 = radius * radius;
960 let coord = |i: usize, a: usize| (i as f64 - dims[a] as f64 / 2.0) * vs[a];
962 let range = |a: usize| {
964 let reach = (radius / vs[a]).ceil() + 2.0;
965 let c = dims[a] as f64 / 2.0;
966 let lo = (c - reach).floor().max(0.0) as usize;
967 let hi = ((c + reach).ceil() as usize).min(dims[a] - 1);
968 lo..=hi
969 };
970 let split = 10usize;
971 let sub: Vec<f64> = (0..2 * split).map(|s| (s as f64 - split as f64 + 0.5) / (2 * split) as f64).collect();
972 let mut out = Vec::new();
973 for k in range(2) {
974 let z = coord(k, 2);
975 for j in range(1) {
976 let y = coord(j, 1);
977 for i in range(0) {
978 let x = coord(i, 0);
979 let outside = (x.abs() - 0.5 * vs[0]).max(0.0).powi(2)
980 + (y.abs() - 0.5 * vs[1]).max(0.0).powi(2)
981 + (z.abs() - 0.5 * vs[2]).max(0.0).powi(2)
982 > r2;
983 let inside = (x.abs() + 0.5 * vs[0]).powi(2)
984 + (y.abs() + 0.5 * vs[1]).powi(2)
985 + (z.abs() + 0.5 * vs[2]).powi(2)
986 <= r2;
987 let v = if inside {
988 1.0
989 } else if outside {
990 0.0
991 } else {
992 let mut occupied = 0usize;
993 for &sz in &sub {
994 for &sy in &sub {
995 for &sx in &sub {
996 let (px, py, pz) = (x + sx * vs[0], y + sy * vs[1], z + sz * vs[2]);
997 if px * px + py * py + pz * pz <= r2 {
998 occupied += 1;
999 }
1000 }
1001 }
1002 }
1003 occupied as f64 / sub.len().pow(3) as f64
1004 };
1005 if v != 0.0 {
1006 let (si, sj, sk) = ((i + nx / 2) % nx, (j + ny / 2) % ny, (k + nz / 2) % nz);
1007 out.push((si + nx * (sj + ny * sk), v));
1008 }
1009 }
1010 }
1011 }
1012 let total: f64 = out.iter().map(|&(_, v)| v).sum();
1013 for e in out.iter_mut() {
1014 e.1 /= total;
1015 }
1016 out
1017}
1018
1019pub(crate) fn sphere_kernel_real(grid: &Grid, radius: f64) -> Vec<f64> {
1023 let mut full = vec![0.0f64; grid.n_total()];
1024 for (p, v) in sphere_kernel_sparse(grid, radius) {
1025 full[p] = v;
1026 }
1027 crate::fft::fft_real_kernel(&full, grid.nx(), grid.ny(), grid.nz())
1028}
1029
1030pub(crate) fn sphere_kernel_single(grid: &Grid, radius: f64) -> Vec<f64> {
1033 sphere_kernel_real(grid, radius).into_iter().map(|v| v as f32 as f64).collect()
1034}
1035
1036fn sphere_kernel(grid: &Grid, radius: f64, fft: &mut Fft3dWorkspace) -> Vec<Complex64> {
1040 let mut k = vec![Complex64::new(0.0, 0.0); grid.n_total()];
1041 for (p, v) in sphere_kernel_sparse(grid, radius) {
1042 k[p] = Complex64::new(v, 0.0);
1043 }
1044 fft.fft3d(&mut k);
1045 k
1046}
1047
1048fn smv(x: &[f64], k: &[Complex64], fft: &mut Fft3dWorkspace, buf: &mut [Complex64]) {
1050 let load = load_real(x);
1051 fft.fft3d_with(buf, &FftOpts { load: Some(&load), ..FftOpts::default() });
1052 fft.ifft3d_with(buf, &FftOpts { load: Some(&mul_kernel(k)), ..FftOpts::default() });
1053}
1054
1055fn smv_noise(tempn: &mut [f64], k: &[Complex64], fft: &mut Fft3dWorkspace, buf: &mut [Complex64]) {
1057 let t = &*tempn;
1058 let load = |l: usize, row: &mut [Complex64]| {
1059 for (c, &v) in row.iter_mut().zip(&t[l..]) {
1060 *c = Complex64::new(v * v, 0.0);
1061 }
1062 };
1063 fft.fft3d_with(buf, &FftOpts { load: Some(&load), ..FftOpts::default() });
1064 fft.ifft3d_with(buf, &FftOpts { load: Some(&mul_kernel(k)), ..FftOpts::default() });
1065 par_update(tempn, |i, v| *v = (buf[i].re + *v * *v).sqrt());
1066}
1067
1068pub fn extract_csf(r2s: &[f64], mask: &[u8], grid: &Grid, r2s_threshold: f64, erode: bool) -> Vec<u8> {
1086 let (nx, ny, nz) = grid.dims;
1087 let n = grid.n_total();
1088 assert!(r2s.len() == n && mask.len() == n, "r2s and mask must match the grid");
1089 let vs = [grid.vsx(), grid.vsy(), grid.vsz()];
1090 let pos = |p: usize| {
1091 let (i, j, k) = (p % nx, (p / nx) % ny, p / (nx * ny));
1092 [(i + 1) as f64 * vs[0], (j + 1) as f64 * vs[1], (k + 1) as f64 * vs[2]]
1093 };
1094 let inside: Vec<usize> = (0..n).filter(|&p| mask[p] > 0).collect();
1096 let mut cen = [0.0f64; 3];
1097 for &p in &inside {
1098 let x = pos(p);
1099 for a in 0..3 {
1100 cen[a] += x[a];
1101 }
1102 }
1103 let count = inside.len() as f64;
1104 let cen = cen.map(|c| c / count);
1105 let mask_cen: Vec<bool> = (0..n)
1106 .map(|p| {
1107 let x = pos(p);
1108 ((x[0] - cen[0]).powi(2) + (x[1] - cen[1]).powi(2) + (x[2] - cen[2]).powi(2)).sqrt() <= 30.0
1109 })
1110 .collect();
1111 let mask: Vec<bool> = if erode {
1113 let mut fft = Fft3dWorkspace::new(nx, ny, nz);
1114 let k = sphere_kernel(grid, 10.0, &mut fft);
1115 let m: Vec<f64> = mask.iter().map(|&v| v as f64).collect();
1116 let mut buf = vec![Complex64::new(0.0, 0.0); n];
1117 smv(&m, &k, &mut fft, &mut buf);
1118 buf.iter().map(|v| v.re > 0.999).collect()
1119 } else {
1120 mask.iter().map(|&v| v != 0).collect()
1121 };
1122 let low: Vec<bool> = r2s.iter().map(|&r| r < r2s_threshold).collect();
1123
1124 let raw1: Vec<bool> = (0..n).map(|p| low[p] && mask_cen[p]).collect();
1126 let (lab1, sizes1) = label6(&raw1, grid.dims);
1127 let mut order: Vec<usize> = (0..sizes1.len()).collect();
1128 order.sort_by(|&a, &b| sizes1[b].cmp(&sizes1[a])); let mut central = vec![false; sizes1.len()];
1130 for &c in order.iter().take(3) {
1131 central[c] = true;
1132 }
1133 let raw2: Vec<bool> = (0..n).map(|p| low[p] && mask[p]).collect();
1136 let (lab2, sizes2) = label6(&raw2, grid.dims);
1137 let mut keep = vec![false; sizes2.len()];
1138 for p in 0..n {
1139 if let (Some(a), Some(b)) = (lab1[p], lab2[p]) {
1140 if central[a] {
1141 keep[b] = true;
1142 }
1143 }
1144 }
1145 (0..n).map(|p| u8::from(mask[p] && lab2[p].is_some_and(|b| keep[b]))).collect()
1147}
1148
1149fn label6(fg: &[bool], dims: (usize, usize, usize)) -> (Vec<Option<usize>>, Vec<usize>) {
1153 let (nx, ny, nz) = dims;
1154 let mut label = vec![None; fg.len()];
1155 let mut sizes = Vec::new();
1156 let mut stack = Vec::new();
1157 for start in 0..fg.len() {
1158 if !fg[start] || label[start].is_some() {
1159 continue;
1160 }
1161 let c = sizes.len();
1162 let mut size = 0;
1163 label[start] = Some(c);
1164 stack.push(start);
1165 while let Some(p) = stack.pop() {
1166 size += 1;
1167 let (i, j, k) = (p % nx, (p / nx) % ny, p / (nx * ny));
1168 let mut visit = |q: usize| {
1169 if fg[q] && label[q].is_none() {
1170 label[q] = Some(c);
1171 stack.push(q);
1172 }
1173 };
1174 if i > 0 { visit(p - 1); }
1175 if i + 1 < nx { visit(p + 1); }
1176 if j > 0 { visit(p - nx); }
1177 if j + 1 < ny { visit(p + nx); }
1178 if k > 0 { visit(p - nx * ny); }
1179 if k + 1 < nz { visit(p + nx * ny); }
1180 }
1181 sizes.push(size);
1182 }
1183 (label, sizes)
1184}
1185
1186#[cfg(test)]
1187mod tests {
1188 use super::*;
1189
1190 #[test]
1193 fn gradient_mask_of_a_constant_magnitude_terminates_with_no_edges() {
1194 let grid = Grid::new(6, 6, 6, 1.0, 1.0, 1.0);
1195 let g = gradient_mask(&[1.0; 216], &[true; 216], &grid, 0.9);
1196 assert!(g.iter().all(|c| c.iter().all(|&v| v == 1.0)));
1197 }
1198
1199 fn small_params() -> MediParams {
1200 MediParams { cg_max_iter: 20, max_iter: 3, smv: false, merit: false, ..MediParams::default() }
1201 }
1202
1203 fn phantom(n: usize) -> (Grid, Vec<f64>, Vec<f64>, Vec<f64>, Vec<u8>) {
1206 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
1207 let c = n as f64 / 2.0;
1208 let r2 = |i: usize| {
1209 let (x, y, z) = ((i % n) as f64 - c, ((i / n) % n) as f64 - c, (i / (n * n)) as f64 - c);
1210 x * x + y * y + z * z
1211 };
1212 let chi: Vec<f64> = (0..n * n * n).map(|i| if r2(i) <= 9.0 { 0.1 } else { 0.0 }).collect();
1213 let d = dipole_kernel(&grid, (0.0, 0.0, 1.0));
1214 let dc: Vec<Complex64> = d.iter().map(|&v| Complex64::new(v, 0.0)).collect();
1215 let mut fft = Fft3dWorkspace::new(n, n, n);
1216 let mut buf = vec![Complex64::new(0.0, 0.0); n * n * n];
1217 let mut field = vec![0.0; n * n * n];
1218 dconv(&chi, &dc, &mut fft, &mut buf, &mut field);
1219 let mag: Vec<f64> = (0..n * n * n).map(|i| if r2(i) <= 9.0 { 0.5 } else { 1.0 }).collect();
1220 let mask: Vec<u8> = (0..n * n * n).map(|i| (r2(i) <= 49.0) as u8).collect();
1221 (grid, chi, field, mag, mask)
1222 }
1223
1224 #[test]
1225 fn defaults_are_medi_l1s() {
1226 let p = MediParams::default();
1227 assert_eq!((p.lambda, p.percentage, p.cg_tol, p.tol), (1000.0, 0.9, 0.01, 0.1));
1228 assert_eq!((p.cg_max_iter, p.max_iter, p.data_weighting), (100, 10, 1));
1229 assert!(p.smv && p.merit && p.smv_shrink_mask);
1230 assert_eq!(p.smv_radius, 5.0);
1231 assert!(p.csf && p.csf_erode);
1233 assert_eq!((p.lambda_csf, p.csf_r2s_threshold), (100.0, 5.0));
1234 }
1235
1236 #[test]
1237 fn bdiv_is_the_adjoint_of_fgrad() {
1238 let grid = Grid::new(5, 4, 3, 1.0, 0.8, 1.5);
1239 let n = grid.n_total();
1240 let vs = [grid.vsx(), grid.vsy(), grid.vsz()];
1241 let x: Vec<f64> = (0..n).map(|i| ((i * 37) % 11) as f64 - 5.0).collect();
1242 let g: [Vec<f64>; 3] = [0, 1, 2].map(|c| (0..n).map(|i| ((i * 13 + c * 7) % 9) as f64 - 4.0).collect());
1243 let mut gx = [vec![0.0; n], vec![0.0; n], vec![0.0; n]];
1244 fgrad(&x, grid.dims, vs, &mut gx);
1245 let lhs: f64 = (0..3).map(|c| gx[c].iter().zip(&g[c]).map(|(a, b)| a * b).sum::<f64>()).sum();
1246 let mut div = vec![0.0; n];
1247 bdiv(&g, grid.dims, vs, &mut div);
1248 let rhs: f64 = x.iter().zip(&div).map(|(a, b)| a * b).sum::<f64>();
1249 assert!((lhs - rhs).abs() < 1e-9, "<fgrad x, g> = {lhs}, <x, bdiv g> = {rhs}");
1250 }
1251
1252 #[test]
1253 fn dconv_weighted_equals_two_plain_dconvs() {
1254 let grid = Grid::new(12, 10, 8, 1.0, 1.0, 1.2);
1256 let n = grid.n_total();
1257 let (nx, ny) = (12, 10);
1258 let d: Vec<Complex64> = dipole_kernel(&grid, (0.1, 0.2, 0.97))
1259 .iter()
1260 .enumerate()
1261 .map(|(i, &v)| Complex64::new(v, 0.01 * ((i % 7) as f64 - 3.0)))
1262 .collect();
1263 let p: Vec<f64> = (0..n).map(|i| ((i as f64 * 12.9898).sin() * 43758.5453).fract()).collect();
1264 let mask: Vec<bool> = (0..n)
1265 .map(|i| (2..9).contains(&(i % nx)) && (3..8).contains(&((i / nx) % ny)) && (1..6).contains(&(i / (nx * ny))))
1266 .collect();
1267 let w2: Vec<f64> = (0..n).map(|i| if mask[i] { 1.0 + (i % 5) as f64 } else { 0.0 }).collect();
1268 let mut fft = Fft3dWorkspace::new(12, 10, 8);
1269 let mut buf = vec![Complex64::new(0.0, 0.0); n];
1270 let (mut t, mut f) = (vec![0.0; n], vec![0.0; n]);
1271 dconv(&p, &d, &mut fft, &mut buf, &mut t);
1272 let t: Vec<f64> = (0..n).map(|i| t[i] * w2[i]).collect();
1273 dconv(&t, &d, &mut fft, &mut buf, &mut f);
1274 let mbox = bounding_box(&mask, grid.dims);
1275 assert_eq!(mbox, [2..9, 3..8, 1..6]);
1276 dconv_weighted(&p, &d, &w2, &mbox, &mut fft, &mut buf);
1277 for i in 0..n {
1278 assert_eq!(buf[i].re, f[i], "voxel {i}");
1279 }
1280 }
1281
1282 #[test]
1283 fn reg_equals_bdiv_of_weighted_fgrad() {
1284 let grid = Grid::new(7, 6, 9, 1.0, 0.8, 1.5);
1286 let n = grid.n_total();
1287 let vs = [grid.vsx(), grid.vsy(), grid.vsz()];
1288 let x: Vec<f64> = (0..n).map(|i| ((i as f64 * 12.9898).sin() * 43758.5453).fract()).collect();
1289 let wvw: [Vec<f64>; 3] = [0, 1, 2].map(|c| (0..n).map(|i| ((i * 13 + c * 7) % 5) as f64 * 0.3).collect());
1290 let mut g = [vec![0.0; n], vec![0.0; n], vec![0.0; n]];
1291 fgrad(&x, grid.dims, vs, &mut g);
1292 for c in 0..3 {
1293 for i in 0..n {
1294 g[c][i] *= wvw[c][i];
1295 }
1296 }
1297 let (mut want, mut got) = (vec![0.0; n], vec![0.0; n]);
1298 bdiv(&g, grid.dims, vs, &mut want);
1299 reg(&x, &wvw, grid.dims, vs, &mut got);
1300 assert_eq!(want, got);
1301 }
1302
1303 #[test]
1304 fn fgrad_is_zero_at_the_last_voxel() {
1305 let grid = Grid::new(4, 4, 4, 1.0, 1.0, 1.0);
1306 let x: Vec<f64> = (0..64).map(|i| (i * i) as f64).collect();
1307 let mut g = [vec![1.0; 64], vec![1.0; 64], vec![1.0; 64]];
1308 fgrad(&x, grid.dims, [1.0; 3], &mut g);
1309 for j in 0..4 {
1310 for k in 0..4 {
1311 assert_eq!(g[0][3 + 4 * (j + 4 * k)], 0.0);
1312 }
1313 }
1314 assert_eq!(g[0][0], x[1] - x[0]);
1315 }
1316
1317 #[test]
1318 fn gradient_mask_marks_about_percentage_of_mask_voxels_as_edges() {
1319 let n = 16;
1320 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
1321 let mag: Vec<f64> = (0..n * n * n).map(|i| ((i as f64 * 12.9898).sin() * 43758.5453).fract().abs()).collect();
1322 let mask = vec![true; n * n * n];
1323 let wg = gradient_mask(&mag, &mask, &grid, 0.9);
1324 let edges: usize = wg.iter().map(|c| c.iter().filter(|&&v| v == 0.0).count()).sum();
1325 let frac = edges as f64 / (n * n * n) as f64;
1326 assert!((frac - 0.9).abs() < 0.02, "edge count / mask voxels = {frac}");
1327 }
1328
1329 #[test]
1330 fn sphere_kernel_is_normalised_and_real() {
1331 let grid = Grid::new(16, 16, 16, 1.0, 1.0, 1.0);
1332 let mut fft = Fft3dWorkspace::new(16, 16, 16);
1333 let k = sphere_kernel(&grid, 3.0, &mut fft);
1334 assert!((k[0].re - 1.0).abs() < 1e-12, "DC = sum of the sphere = {}", k[0].re);
1335 assert!(k.iter().all(|v| v.im.abs() < 1e-12));
1336 }
1337
1338 #[test]
1339 fn zero_field_gives_zero() {
1340 let (grid, _, field, mag, mask) = phantom(16);
1341 let zero = vec![0.0; field.len()];
1342 let chi = medi_l1(&zero, &mask, &mag, &vec![1.0; mag.len()], None, None, &grid, (0.0, 0.0, 1.0), &small_params(), |_, _| {});
1343 assert!(chi.iter().all(|&v| v == 0.0));
1344 }
1345
1346 #[test]
1347 fn recovers_a_sphere_in_ppm() {
1348 let (grid, chi_true, field, mag, mask) = phantom(24);
1349 let chi = medi_l1(&field, &mask, &mag, &vec![1.0; mag.len()], None, None, &grid, (0.0, 0.0, 1.0), &small_params(), |_, _| {});
1350 let c = 12 + 24 * (12 + 24 * 12);
1351 assert!((chi[c] - chi_true[c]).abs() < 0.03, "centre: {} vs {}", chi[c], chi_true[c]);
1352 for (i, &m) in mask.iter().enumerate() {
1353 if m == 0 {
1354 assert_eq!(chi[i], 0.0);
1355 }
1356 }
1357 }
1358
1359 #[test]
1360 fn uniform_noise_map_equals_none() {
1361 let (grid, _, field, mag, mask) = phantom(16);
1362 let p = small_params();
1363 let a = medi_l1(&field, &mask, &mag, &vec![1.0; mag.len()], None, None, &grid, (0.0, 0.0, 1.0), &p, |_, _| {});
1364 let b = medi_l1(&field, &mask, &mag, &vec![3.0; field.len()], None, None, &grid, (0.0, 0.0, 1.0), &p, |_, _| {});
1365 let err = a.iter().zip(&b).map(|(x, y)| (x - y).abs()).fold(0.0, f64::max);
1366 assert!(err < 1e-9, "max |diff| {err}");
1367 }
1368
1369 #[test]
1370 fn smv_and_merit_run() {
1371 let (grid, _, field, mag, mask) = phantom(16);
1372 let p = MediParams { smv: true, smv_radius: 2.0, merit: true, ..small_params() };
1373 let mut calls = 0;
1374 let chi = medi_l1(&field, &mask, &mag, &vec![1.0; mag.len()], None, None, &grid, (0.0, 0.0, 1.0), &p, |_, _| calls += 1);
1375 assert!(chi.iter().all(|v| v.is_finite()));
1376 assert!(chi.iter().any(|&v| v != 0.0));
1377 assert!(calls > 0);
1378 }
1379
1380 #[test]
1381 fn zeropad_pads_at_the_end_and_crops() {
1382 let (grid, _, field, mag, mask) = phantom(15);
1383 let p = MediParams { zeropad: (1, 0, 1), ..small_params() };
1384 let chi = medi(&field, &mask, EchoMagnitudes { data: &mag, n_echoes: 1 }, &grid, (0.0, 0.0, 1.0), &p, |_, _| {});
1385 let (n, m) = (15usize, 16usize);
1387 let pad = |v: &[f64], nyp: usize| {
1388 let mut out = vec![0.0; m * nyp * m];
1389 for k in 0..n { for j in 0..n { for i in 0..n {
1390 out[i + m * (j + nyp * k)] = v[i + n * (j + n * k)];
1391 }}}
1392 out
1393 };
1394 let mask_f: Vec<f64> = mask.iter().map(|&v| v as f64).collect();
1395 let mask_p: Vec<u8> = pad(&mask_f, n).iter().map(|&v| v as u8).collect();
1396 let pgrid = Grid::new(m, n, m, 1.0, 1.0, 1.0);
1397 let padded = medi_l1(&pad(&field, n), &mask_p, &pad(&mag, n), &pad(&crate::utils::fit_ppm_complex_noise(&mag, 1), n), None, None, &pgrid, (0.0, 0.0, 1.0), &small_params(), |_, _| {});
1398 for k in 0..n { for j in 0..n { for i in 0..n {
1399 assert_eq!(chi[i + n * (j + n * k)], padded[i + m * (j + n * k)]);
1400 }}}
1401 }
1402
1403 #[test]
1404 fn echo_magnitudes_give_rss_and_the_fit_noise() {
1405 let (grid, _, field, mag, mask) = phantom(16);
1406 let n = mag.len();
1407 let echoes: Vec<f64> = mag.iter().copied().chain(mag.iter().map(|v| 0.5 * v)).collect();
1408 let p = MediParams { echo_times: vec![0.005, 0.010], ..small_params() };
1409 let a = medi(&field, &mask, EchoMagnitudes { data: &echoes, n_echoes: 2 }, &grid, (0.0, 0.0, 1.0), &p, |_, _| {});
1410 let rss: Vec<f64> = (0..n).map(|i| (echoes[i].powi(2) + echoes[i + n].powi(2)).sqrt()).collect();
1411 let n_std = crate::utils::fit_ppm_complex_noise(&echoes, 2);
1412 let b = medi_l1(&field, &mask, &rss, &n_std, None, None, &grid, (0.0, 0.0, 1.0), &p, |_, _| {});
1413 assert_eq!(a, b);
1414 }
1415
1416 #[test]
1417 fn smv_pads_odd_axes_to_even() {
1418 let (grid, _, field, mag, mask) = phantom(15);
1419 let p = MediParams { smv: true, smv_radius: 2.0, merit: false, ..small_params() };
1420 let auto = medi(&field, &mask, EchoMagnitudes { data: &mag, n_echoes: 1 }, &grid, (0.0, 0.0, 1.0), &p, |_, _| {});
1421 let explicit = MediParams { zeropad: (1, 1, 1), ..p.clone() };
1422 let manual = medi(&field, &mask, EchoMagnitudes { data: &mag, n_echoes: 1 }, &grid, (0.0, 0.0, 1.0), &explicit, |_, _| {});
1423 assert_eq!(auto, manual);
1424 let odd = MediParams { zeropad: (2, 0, 0), ..p.clone() };
1426 let more = MediParams { zeropad: (3, 1, 1), ..p };
1427 let a = medi(&field, &mask, EchoMagnitudes { data: &mag, n_echoes: 1 }, &grid, (0.0, 0.0, 1.0), &odd, |_, _| {});
1428 let b = medi(&field, &mask, EchoMagnitudes { data: &mag, n_echoes: 1 }, &grid, (0.0, 0.0, 1.0), &more, |_, _| {});
1429 assert_eq!(a, b);
1430 }
1431
1432 fn csf_phantom() -> (Grid, Vec<f64>, Vec<u8>, impl Fn(usize) -> [f64; 3]) {
1434 let n = 100;
1435 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
1436 let at = move |p: usize| [(p % n) as f64 - 50.0, ((p / n) % n) as f64 - 50.0, (p / (n * n)) as f64 - 50.0];
1437 let mask: Vec<u8> = (0..n * n * n).map(|p| u8::from(dist(at(p), [0.0; 3]) <= 48.0)).collect();
1438 let r2s: Vec<f64> = (0..n * n * n)
1439 .map(|p| {
1440 let x = at(p);
1441 if is_ventricle(x) {
1442 2.0
1443 } else if dist(x, [0.0, 35.0, 0.0]) <= 2.0 {
1445 1.0
1446 } else if dist(x, [0.0; 3]) > 44.0 {
1448 0.0
1449 } else {
1450 20.0
1451 }
1452 })
1453 .collect();
1454 (grid, r2s, mask, at)
1455 }
1456
1457 fn dist(x: [f64; 3], c: [f64; 3]) -> f64 {
1458 ((x[0] - c[0]).powi(2) + (x[1] - c[1]).powi(2) + (x[2] - c[2]).powi(2)).sqrt()
1459 }
1460
1461 fn is_ventricle(x: [f64; 3]) -> bool {
1463 dist(x, [-5.0, 0.0, 0.0]) <= 3.0 || dist(x, [5.0, 0.0, 0.0]) <= 2.0
1464 }
1465
1466 #[test]
1467 fn extract_csf_keeps_central_low_r2s_regions_in_the_eroded_mask() {
1468 let (grid, r2s, mask, at) = csf_phantom();
1469 let csf = extract_csf(&r2s, &mask, &grid, 5.0, true);
1470 for (p, &c) in csf.iter().enumerate() {
1471 assert_eq!(c == 1, is_ventricle(at(p)), "voxel {:?}", at(p));
1472 }
1473 assert_eq!(extract_csf(&r2s, &mask, &grid, 5.0, false), csf);
1476 assert!(extract_csf(&r2s, &mask, &grid, 1.5, true).iter().all(|&v| v == 0));
1478 }
1479
1480 #[test]
1481 fn extract_csf_takes_the_three_largest_central_regions() {
1482 let n = 24;
1483 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
1484 let mask = vec![1u8; n * n * n];
1485 let mut r2s = vec![20.0; n * n * n];
1487 for (len, j) in [(4usize, 6usize), (3, 10), (2, 14), (1, 18)] {
1488 for i in 0..len {
1489 r2s[(10 + i) + n * (j + n * 12)] = 0.0;
1490 }
1491 }
1492 let csf = extract_csf(&r2s, &mask, &grid, 5.0, false);
1493 assert_eq!(csf.iter().filter(|&&v| v == 1).count(), 4 + 3 + 2);
1494 assert_eq!(csf[10 + n * (18 + n * 12)], 0);
1495 }
1496
1497 #[test]
1498 fn csf_term_zero_references_the_result() {
1499 let (grid, _, field, mag, mask) = phantom(16);
1500 let n = mag.len();
1501 let csf: Vec<bool> = (0..n).map(|i| mask[i] == 1 && i % 7 == 0).collect();
1502 let p = small_params();
1503 let off = medi_l1(&field, &mask, &mag, &vec![1.0; n], None, None, &grid, (0.0, 0.0, 1.0), &p, |_, _| {});
1504 let on = medi_l1(&field, &mask, &mag, &vec![1.0; n], Some(&csf), None, &grid, (0.0, 0.0, 1.0), &p, |_, _| {});
1505 let mean = |x: &[f64]| (0..n).filter(|&i| csf[i]).map(|i| x[i]).sum::<f64>() / csf.iter().filter(|&&c| c).count() as f64;
1506 assert!(mean(&on).abs() < 1e-12, "CSF mean {}", mean(&on));
1507 assert!(mean(&off).abs() > 1e-6);
1508 for i in 0..n {
1509 if mask[i] == 0 {
1510 assert_eq!(on[i], 0.0);
1511 }
1512 }
1513 let p0 = MediParams { lambda_csf: 0.0, ..p };
1515 let shifted = medi_l1(&field, &mask, &mag, &vec![1.0; n], Some(&csf), None, &grid, (0.0, 0.0, 1.0), &p0, |_, _| {});
1516 let m = mean(&off);
1517 for i in 0..n {
1518 let want = if mask[i] == 1 { off[i] - m } else { 0.0 };
1519 assert!((shifted[i] - want).abs() < 1e-12);
1520 }
1521 let d = (0..n).map(|i| (on[i] - shifted[i]).abs()).fold(0.0, f64::max);
1523 assert!(d > 1e-6, "lambda_CSF had no effect");
1524 }
1525
1526 #[test]
1527 fn fewer_than_three_echoes_run_without_the_csf_term() {
1528 let (grid, _, field, mag, mask) = phantom(16);
1529 let echoes: Vec<f64> = mag.iter().copied().chain(mag.iter().map(|v| 0.5 * v)).collect();
1530 let p = MediParams { echo_times: vec![0.005, 0.010], ..small_params() };
1531 let on = medi(&field, &mask, EchoMagnitudes { data: &echoes, n_echoes: 2 }, &grid, (0.0, 0.0, 1.0), &p, |_, _| {});
1532 let off = medi(&field, &mask, EchoMagnitudes { data: &echoes, n_echoes: 2 }, &grid, (0.0, 0.0, 1.0), &MediParams { csf: false, ..p }, |_, _| {});
1533 assert_eq!(on, off);
1534 }
1535}