1use num_complex::Complex64;
61use rustfft::{Fft, FftPlanner};
62use std::sync::Arc;
63
64#[cfg(feature = "parallel")]
65use rayon::prelude::*;
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
69pub struct MrtrixUnringParams {
70 pub nshifts: usize,
72 pub min_w: usize,
74 pub max_w: usize,
76}
77
78impl Default for MrtrixUnringParams {
79 fn default() -> Self {
80 Self { nshifts: 20, min_w: 1, max_w: 3 }
81 }
82}
83
84fn indexshift(n: usize, size: usize) -> f64 {
86 if n > size / 2 { n as f64 - size as f64 } else { n as f64 }
87}
88
89fn fft3(data: &mut [Complex64], dims: [usize; 3], inverse: bool) {
92 let mut planner = FftPlanner::<f64>::new();
93 for axis in 0..3 {
94 let n = dims[axis];
95 if n < 2 {
96 continue;
97 }
98 let plan = if inverse { planner.plan_fft_inverse(n) } else { planner.plan_fft_forward(n) };
99 let (stride, starts) = line_starts(dims, axis);
100 let mut mat = gather(data, n, stride, &starts);
101 #[cfg(feature = "parallel")]
102 mat.par_chunks_mut(n).for_each(|row| plan.process(row));
103 #[cfg(not(feature = "parallel"))]
104 mat.chunks_mut(n).for_each(|row| plan.process(row));
105 scatter(&mat, data, n, stride, &starts, false);
106 }
107}
108
109fn line_starts(dims: [usize; 3], axis: usize) -> (usize, Vec<usize>) {
111 let [nx, ny, nz] = dims;
112 match axis {
113 0 => (1, (0..ny * nz).map(|l| l * nx).collect()),
114 1 => (nx, (0..nz).flat_map(|z| (0..nx).map(move |x| z * nx * ny + x)).collect()),
115 _ => (nx * ny, (0..ny).flat_map(|y| (0..nx).map(move |x| y * nx + x)).collect()),
116 }
117}
118
119fn gather(data: &[Complex64], n: usize, stride: usize, starts: &[usize]) -> Vec<Complex64> {
120 let mut mat = vec![Complex64::new(0.0, 0.0); starts.len() * n];
121 for (l, &s) in starts.iter().enumerate() {
122 for i in 0..n {
123 mat[l * n + i] = data[s + i * stride];
124 }
125 }
126 mat
127}
128
129fn scatter(mat: &[Complex64], data: &mut [Complex64], n: usize, stride: usize, starts: &[usize], add: bool) {
130 for (l, &s) in starts.iter().enumerate() {
131 for i in 0..n {
132 if add {
133 data[s + i * stride] += mat[l * n + i];
134 } else {
135 data[s + i * stride] = mat[l * n + i];
136 }
137 }
138 }
139}
140
141struct LineCtx {
143 n: usize,
144 shifts: Vec<f64>,
145 phases: Vec<Complex64>,
147 fft: Arc<dyn Fft<f64>>,
148 ifft: Arc<dyn Fft<f64>>,
149 params: MrtrixUnringParams,
150}
151
152impl LineCtx {
153 fn new(n: usize, params: MrtrixUnringParams) -> Self {
154 let nsh = params.nshifts;
155 let mut shifts = vec![0.0; 2 * nsh + 1];
157 for j in 0..nsh {
158 shifts[j + 1] = (j + 1) as f64 / (2.0 * nsh as f64 + 1.0);
159 shifts[1 + nsh + j] = -shifts[j + 1];
160 }
161 let mut phases = vec![Complex64::new(0.0, 0.0); shifts.len() * n];
162 for (f, &s) in shifts.iter().enumerate() {
163 for m in 0..n {
164 let arg = 2.0 * indexshift(m, n) * std::f64::consts::PI * s / n as f64;
165 phases[f * n + m] = Complex64::new(0.0, arg).exp();
166 }
167 if n.is_multiple_of(2) {
168 phases[f * n + n / 2] = Complex64::new(0.0, 0.0);
169 }
170 }
171 let mut planner = FftPlanner::<f64>::new();
172 LineCtx { n, shifts, phases, fft: planner.plan_fft_forward(n), ifft: planner.plan_fft_inverse(n), params }
173 }
174
175 fn unring(&self, line: &[Complex64], scale: f64, shifted: &mut [Complex64], out: &mut [Complex64]) {
177 let n = self.n;
178 let nf = self.shifts.len();
179 let mut spec = line.to_vec();
180 self.fft.process(&mut spec);
181 for f in 0..nf {
182 let row = &mut shifted[f * n..f * n + n];
183 for m in 0..n {
184 row[m] = spec[m] * self.phases[f * n + m];
185 }
186 self.ifft.process(row);
187 }
188 let wrap = |i: isize| i.rem_euclid(n as isize) as usize;
189 let (min_w, max_w) = (self.params.min_w as isize, self.params.max_w as isize);
190 let d = |a: Complex64, b: Complex64| (a.re - b.re).abs() + (a.im - b.im).abs();
191 for (p, o) in out.iter_mut().enumerate() {
192 let pi = p as isize;
193 let mut ind = 0;
194 let mut opt = f64::MAX;
195 for f in 0..nf {
196 let row = &shifted[f * n..f * n + n];
197 let (mut left, mut right) = (0.0, 0.0);
198 for k in min_w..=max_w {
199 left += d(row[wrap(pi - k)], row[wrap(pi - k - 1)]);
200 right += d(row[wrap(pi + k)], row[wrap(pi + k + 1)]);
201 }
202 let tv = left.min(right);
203 if tv < opt {
204 opt = tv;
205 ind = f;
206 }
207 }
208 let s = self.shifts[ind];
209 let row = &shifted[ind * n..ind * n + n];
210 let (a0, a1, a2) = (row[wrap(pi - 1)], row[p], row[wrap(pi + 1)]);
211 *o = if s > 0.0 { (a1 - (a1 - a0) * s) / scale } else { (a1 + (a1 - a2) * s) / scale };
212 }
213 }
214}
215
216pub fn gibbs_unring_3d_complex(vol: &[Complex64], dims: (usize, usize, usize), params: MrtrixUnringParams) -> Vec<Complex64> {
222 let dims = [dims.0, dims.1, dims.2];
223 let n_vox = dims[0] * dims[1] * dims[2];
224 assert_eq!(vol.len(), n_vox, "vol length must be nx*ny*nz");
225 assert!(params.min_w < params.max_w, "min_w must be smaller than max_w");
226 let mut ft = vol.to_vec();
227 fft3(&mut ft, dims, false);
228 let x: [Vec<f64>; 3] = std::array::from_fn(|d| {
229 (0..dims[d]).map(|m| 1.0 + (2.0 * std::f64::consts::PI * indexshift(m, dims[d]) / dims[d] as f64).cos()).collect()
230 });
231 let scale_vol = n_vox as f64;
232 let mut out = vec![Complex64::new(0.0, 0.0); n_vox];
233 for axis in 0..3 {
234 let mut filt = ft.clone();
236 for k in 0..dims[2] {
237 for j in 0..dims[1] {
238 for i in 0..dims[0] {
239 let w = [x[1][j] * x[2][k], x[0][i] * x[2][k], x[0][i] * x[1][j]];
240 let den = w[0] + w[1] + w[2];
241 let idx = i + j * dims[0] + k * dims[0] * dims[1];
242 filt[idx] *= if den == 0.0 { 0.0 } else { w[axis] / den };
243 }
244 }
245 }
246 fft3(&mut filt, dims, true);
247 let n = dims[axis];
249 let ctx = LineCtx::new(n, params);
250 let scale = scale_vol * n as f64;
251 let (stride, starts) = line_starts(dims, axis);
252 let src = gather(&filt, n, stride, &starts);
253 let mut res = vec![Complex64::new(0.0, 0.0); src.len()];
254 let nf = ctx.shifts.len();
255 #[cfg(feature = "parallel")]
256 res.par_chunks_mut(n).zip(src.par_chunks(n)).for_each_init(
257 || vec![Complex64::new(0.0, 0.0); nf * n],
258 |sh, (o, l)| ctx.unring(l, scale, sh, o),
259 );
260 #[cfg(not(feature = "parallel"))]
261 {
262 let mut sh = vec![Complex64::new(0.0, 0.0); nf * n];
263 for (o, l) in res.chunks_mut(n).zip(src.chunks(n)) {
264 ctx.unring(l, scale, &mut sh, o);
265 }
266 }
267 scatter(&res, &mut out, n, stride, &starts, true);
268 }
269 out
270}
271
272pub fn gibbs_unring_3d(vol: &[f64], dims: (usize, usize, usize), params: MrtrixUnringParams) -> Vec<f64> {
281 let c: Vec<Complex64> = vol.iter().map(|&v| Complex64::new(v, 0.0)).collect();
282 gibbs_unring_3d_complex(&c, dims, params).iter().map(|v| v.re.max(0.0)).collect()
283}
284
285fn unring1d_line(data: &mut [Complex64], ifft: &dyn Fft<f64>, p: MrtrixUnringParams, sh: &mut [Complex64], buf: &mut [Complex64]) {
294 let n = data.len();
295 let nsh = p.nshifts;
296 let nf = 2 * nsh + 1;
297 let (min_w, max_w) = (p.min_w as isize, p.max_w as isize);
298 let shift = |j: usize| -> f64 {
299 if j == 0 {
300 0.0
301 } else if j <= nsh {
302 j as f64
303 } else {
304 -((j - nsh) as f64)
305 }
306 };
307 let maxn = if n.is_multiple_of(2) { n / 2 - 1 } else { (n - 1) / 2 };
308 buf.copy_from_slice(data);
310 ifft.process(buf);
311 sh[..n].copy_from_slice(buf);
312 for j in 1..nf {
313 let phi = std::f64::consts::PI * shift(j) / (n * nsh) as f64;
314 let u = Complex64::new(phi.cos(), phi.sin());
315 let mut e = Complex64::new(1.0, 0.0);
316 buf[0] = data[0];
317 if n.is_multiple_of(2) {
318 buf[n / 2] = Complex64::new(0.0, 0.0);
319 }
320 for l in 0..maxn {
321 e = u * e;
322 buf[l + 1] = e * data[l + 1];
323 buf[n - 1 - l] = e.conj() * data[n - 1 - l];
324 }
325 ifft.process(buf);
326 sh[j * n..j * n + n].copy_from_slice(buf);
327 }
328 let w = |i: isize| i.rem_euclid(n as isize) as usize;
329 let at = |i: isize, j: usize| sh[j * n + w(i)];
330 let mut tv1 = vec![0.0_f64; nf];
331 let mut tv2 = vec![0.0_f64; nf];
332 let ni = n as isize;
333 for j in 0..nf {
334 for t in min_w..=max_w {
335 tv1[j] += (at(ni - t, j).re - at(ni - t - 1, j).re).abs();
336 tv1[j] += (at(ni - t, j).im - at(ni - t - 1, j).im).abs();
337 tv2[j] += (at(ni + t, j).re - at(ni + t + 1, j).re).abs();
338 tv2[j] += (at(ni + t, j).im - at(ni + t + 1, j).im).abs();
339 }
340 }
341 for l in 0..ni {
342 let mut min_tv = f64::MAX;
343 let mut min_idx = 0;
344 for j in 0..nf {
345 if tv1[j] < min_tv {
346 min_tv = tv1[j];
347 min_idx = j;
348 }
349 if tv2[j] < min_tv {
350 min_tv = tv2[j];
351 min_idx = j;
352 }
353 tv1[j] += (at(l - min_w + 1, j).re - at(l - min_w, j).re).abs();
354 tv1[j] -= (at(l - max_w, j).re - at(l - (max_w + 1), j).re).abs();
355 tv2[j] += (at(l + max_w + 1, j).re - at(l + (max_w + 2), j).re).abs();
356 tv2[j] -= (at(l + min_w, j).re - at(l + (min_w + 1), j).re).abs();
357
358 tv1[j] += (at(l - min_w + 1, j).im - at(l - min_w, j).im).abs();
359 tv1[j] -= (at(l - max_w, j).im - at(l - (max_w + 1), j).im).abs();
360 tv2[j] += (at(l + max_w + 1, j).im - at(l + (max_w + 2), j).im).abs();
361 tv2[j] -= (at(l + min_w, j).im - at(l + (min_w + 1), j).im).abs();
362 }
363 let (a0, a1, a2) = (at(l - 1, min_idx), at(l, min_idx), at(l + 1, min_idx));
364 let s = shift(min_idx) / (2.0 * nsh as f64);
365 data[l as usize] = if s > 0.0 {
366 Complex64::new(a1.re * (1.0 - s) + a0.re * s, a1.im * (1.0 - s) + a0.im * s)
367 } else {
368 Complex64::new(a1.re * (1.0 + s) - a2.re * s, a1.im * (1.0 + s) - a2.im * s)
369 };
370 }
371}
372
373struct SliceCtx {
376 nr: usize,
377 nc: usize,
378 fwd_r: Arc<dyn Fft<f64>>,
379 fwd_c: Arc<dyn Fft<f64>>,
380 inv_r: Arc<dyn Fft<f64>>,
381 inv_c: Arc<dyn Fft<f64>>,
382 p: MrtrixUnringParams,
383}
384
385impl SliceCtx {
386 fn new(nr: usize, nc: usize, p: MrtrixUnringParams) -> Self {
387 let mut planner = FftPlanner::<f64>::new();
388 SliceCtx {
389 nr,
390 nc,
391 fwd_r: planner.plan_fft_forward(nr),
392 fwd_c: planner.plan_fft_forward(nc),
393 inv_r: planner.plan_fft_inverse(nr),
394 inv_c: planner.plan_fft_inverse(nc),
395 p,
396 }
397 }
398
399 fn unring(&self, s: &mut [Complex64]) {
401 let (nr, nc) = (self.nr, self.nc);
402 let mut row = vec![Complex64::new(0.0, 0.0); nc];
403 let along_row = |s: &mut [Complex64], f: &dyn Fft<f64>, row: &mut [Complex64]| {
405 for r in 0..nr {
406 for c in 0..nc {
407 row[c] = s[r + c * nr];
408 }
409 f.process(row);
410 for c in 0..nc {
411 s[r + c * nr] = row[c];
412 }
413 }
414 };
415 let along_col = |s: &mut [Complex64], f: &dyn Fft<f64>| {
416 for c in 0..nc {
417 f.process(&mut s[c * nr..c * nr + nr]);
418 }
419 };
420 along_row(s, &*self.fwd_c, &mut row);
421 along_col(s, &*self.fwd_r);
422 let mut s2 = vec![Complex64::new(0.0, 0.0); nr * nc];
424 for k in 0..nc {
425 let ck = (1.0 + (2.0 * std::f64::consts::PI * (k as f64 / nc as f64)).cos()) * 0.5;
426 for j in 0..nr {
427 let cj = (1.0 + (2.0 * std::f64::consts::PI * (j as f64 / nr as f64)).cos()) * 0.5;
428 let i = j + k * nr;
429 if ck + cj != 0.0 {
430 s2[i] = s[i] * cj / (ck + cj);
431 s[i] *= ck / (ck + cj);
432 } else {
433 s[i] = Complex64::new(0.0, 0.0);
434 s2[i] = Complex64::new(0.0, 0.0);
435 }
436 }
437 }
438 along_row(s, &*self.inv_c, &mut row);
439 along_col(&mut s2, &*self.inv_r);
440 let nf = 2 * self.p.nshifts + 1;
441 let mut sh = vec![Complex64::new(0.0, 0.0); nf * nr.max(nc)];
442 let mut buf = vec![Complex64::new(0.0, 0.0); nr.max(nc)];
443 for c in 0..nc {
445 unring1d_line(&mut s[c * nr..c * nr + nr], &*self.inv_r, self.p, &mut sh[..nf * nr], &mut buf[..nr]);
446 }
447 for r in 0..nr {
449 for c in 0..nc {
450 row[c] = s2[r + c * nr];
451 }
452 unring1d_line(&mut row, &*self.inv_c, self.p, &mut sh[..nf * nc], &mut buf[..nc]);
453 for c in 0..nc {
454 s2[r + c * nr] = row[c];
455 }
456 }
457 let scale = (nr * nc) as f64;
458 for (a, b) in s.iter_mut().zip(&s2) {
459 *a = (*a + b) / scale;
460 }
461 }
462}
463
464pub fn gibbs_unring_2d_complex(vol: &[Complex64], dims: (usize, usize, usize), slice_axis: usize, params: MrtrixUnringParams) -> Vec<Complex64> {
472 let d = [dims.0, dims.1, dims.2];
473 assert_eq!(vol.len(), d[0] * d[1] * d[2], "vol length must be nx*ny*nz");
474 assert!(slice_axis < 3, "slice_axis must be 0, 1 or 2");
475 assert!(params.min_w < params.max_w, "min_w must be smaller than max_w");
476 let (a0, a1) = match slice_axis {
477 0 => (1, 2),
478 1 => (0, 2),
479 _ => (0, 1),
480 };
481 let st = [1, d[0], d[0] * d[1]];
482 let (nr, nc, ns) = (d[a0], d[a1], d[slice_axis]);
483 let ctx = SliceCtx::new(nr, nc, params);
484 let idx = |r: usize, c: usize, z: usize| r * st[a0] + c * st[a1] + z * st[slice_axis];
485 let mut slices = vec![Complex64::new(0.0, 0.0); nr * nc * ns];
486 for z in 0..ns {
487 for c in 0..nc {
488 for r in 0..nr {
489 slices[z * nr * nc + r + c * nr] = vol[idx(r, c, z)];
490 }
491 }
492 }
493 #[cfg(feature = "parallel")]
494 slices.par_chunks_mut(nr * nc).for_each(|s| ctx.unring(s));
495 #[cfg(not(feature = "parallel"))]
496 slices.chunks_mut(nr * nc).for_each(|s| ctx.unring(s));
497 let mut out = vec![Complex64::new(0.0, 0.0); vol.len()];
498 for z in 0..ns {
499 for c in 0..nc {
500 for r in 0..nr {
501 out[idx(r, c, z)] = slices[z * nr * nc + r + c * nr];
502 }
503 }
504 }
505 out
506}
507
508pub fn gibbs_unring_2d(vol: &[f64], dims: (usize, usize, usize), slice_axis: usize, params: MrtrixUnringParams) -> Vec<f64> {
515 let c: Vec<Complex64> = vol.iter().map(|&v| Complex64::new(v, 0.0)).collect();
516 gibbs_unring_2d_complex(&c, dims, slice_axis, params).iter().map(|v| v.re.max(0.0)).collect()
517}
518
519#[cfg(test)]
520mod tests {
521 use super::*;
522
523 #[test]
526 fn test_unring_3d_reduces_overshoot() {
527 let (nx, ny, nz) = (24, 20, 16);
528 let n = nx * ny * nz;
529 let mut img = vec![0.0; n];
530 for k in 4..12 {
531 for j in 5..15 {
532 for i in 6..18 {
533 img[i + j * nx + k * nx * ny] = 100.0;
534 }
535 }
536 }
537 let mut c: Vec<Complex64> = img.iter().map(|&v| Complex64::new(v, 0.0)).collect();
538 fft3(&mut c, [nx, ny, nz], false);
539 let keep = |m: usize, len: usize| indexshift(m, len).abs() < len as f64 / 4.0;
540 for k in 0..nz {
541 for j in 0..ny {
542 for i in 0..nx {
543 if !(keep(i, nx) && keep(j, ny) && keep(k, nz)) {
544 c[i + j * nx + k * nx * ny] = Complex64::new(0.0, 0.0);
545 }
546 }
547 }
548 }
549 fft3(&mut c, [nx, ny, nz], true);
550 let rung: Vec<f64> = c.iter().map(|v| v.re / n as f64).collect();
551 let err = |a: &[f64]| a.iter().zip(&img).map(|(x, y)| (x - y).powi(2)).sum::<f64>().sqrt();
552 let un = gibbs_unring_3d(&rung, (nx, ny, nz), MrtrixUnringParams::default());
553 assert!(un.iter().all(|&v| v >= 0.0));
554 let far = |a: &[f64]| {
556 let mut s = 0.0;
557 for k in 0..nz { for j in 0..ny { for i in 0..nx {
558 let out = |x: usize, lo: usize, hi: usize| x + 2 <= lo || x >= hi + 2;
559 if out(i, 6, 18) || out(j, 5, 15) || out(k, 4, 12) { s += a[i + j * nx + k * nx * ny].powi(2); }
560 }}}
561 s.sqrt()
562 };
563 assert!(far(&un) < 0.5 * far(&rung), "background ringing {} -> {}", far(&rung), far(&un));
564 assert!(err(&un) < err(&rung), "error {} -> {}", err(&rung), err(&un));
565 }
566
567 #[test]
569 fn test_unring_3d_keeps_a_constant() {
570 let dims = (7, 9, 5);
571 let v = vec![3.0; 7 * 9 * 5];
572 let out = gibbs_unring_3d(&v, dims, MrtrixUnringParams::default());
573 assert!(out.iter().all(|&x| (x - 3.0).abs() < 1e-12), "{:?}", &out[..5]);
574 }
575
576 #[test]
578 fn test_unring_2d_keeps_a_constant() {
579 let dims = (7, 8, 5);
580 let v = vec![3.0; 7 * 8 * 5];
581 for axis in 0..3 {
582 let out = gibbs_unring_2d(&v, dims, axis, MrtrixUnringParams::default());
583 assert!(out.iter().all(|&x| (x - 3.0).abs() < 1e-12), "axis {axis}: {:?}", &out[..5]);
584 }
585 }
586}