1use crate::fft::{fft3d_real, ifft3d_real};
27use num_complex::Complex64;
28use rustfft::{Fft, FftPlanner};
29use std::sync::Arc;
30
31#[cfg(feature = "parallel")]
32use rayon::prelude::*;
33
34const N_SHIFTS: usize = 45;
35const PI: f64 = std::f64::consts::PI;
36
37fn tv_min_line(row: &[f64], n: usize, n_points: usize, out: &mut [f64], diff: &mut [f64]) {
45 for j in 0..n - 1 {
46 diff[j] = (row[j] - row[j + 1]).abs();
47 }
48 diff[n - 1] = (row[n - 1] - row[0]).abs();
49 for i in 0..n {
50 let mut ptv = 0.0;
51 let mut ntv = 0.0;
52 if i >= n_points && i + n_points <= n {
53 for o in 0..n_points {
55 ptv += diff[i + o];
56 ntv += diff[i - 1 - o];
57 }
58 } else {
59 for o in 0..n_points {
60 ptv += diff[(i + o) % n];
61 ntv += diff[(i + n - 1 - o) % n];
62 }
63 }
64 out[i] = ptv.min(ntv);
65 }
66}
67
68struct LineBufs {
72 c: Vec<Complex64>,
73 buf: Vec<Complex64>,
74 tvp: Vec<f64>,
75 tvn: Vec<f64>,
76 isp: Vec<f64>,
77 isn: Vec<f64>,
78 sp: Vec<f64>,
79 sn: Vec<f64>,
80 img: Vec<f64>,
81 imgn: Vec<f64>,
82 tvs: Vec<f64>,
83 diff: Vec<f64>,
84 scratch_f: Vec<Complex64>,
85 scratch_i: Vec<Complex64>,
86}
87
88impl LineBufs {
89 fn new(n: usize, sf: usize, si: usize) -> Self {
90 let z = Complex64::new(0.0, 0.0);
91 LineBufs {
92 c: vec![z; n],
93 buf: vec![z; n],
94 tvp: vec![0.0; n],
95 tvn: vec![0.0; n],
96 isp: vec![0.0; n],
97 isn: vec![0.0; n],
98 sp: vec![0.0; n],
99 sn: vec![0.0; n],
100 img: vec![0.0; n],
101 imgn: vec![0.0; n],
102 tvs: vec![0.0; n],
103 diff: vec![0.0; n],
104 scratch_f: vec![z; sf],
105 scratch_i: vec![z; si],
106 }
107 }
108}
109
110#[allow(clippy::too_many_arguments)]
119fn unring_row_inplace(
120 row: &mut [f64],
121 b: &mut LineBufs,
122 n: usize,
123 n_points: usize,
124 ssamp: &[f64],
125 phases: &[Complex64],
126 fft: &Arc<dyn Fft<f64>>,
127 ifft: &Arc<dyn Fft<f64>>,
128) {
129 let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
131 for &v in row.iter() {
132 lo = lo.min(v);
133 hi = hi.max(v);
134 }
135 if hi - lo < 1e-12 {
136 return;
137 }
138
139 let LineBufs {
140 c,
141 buf,
142 tvp,
143 tvn,
144 isp,
145 isn,
146 sp,
147 sn,
148 img,
149 imgn,
150 tvs,
151 diff,
152 scratch_f,
153 scratch_i,
154 } = b;
155
156 for m in 0..n {
157 c[m] = Complex64::new(row[m], 0.0);
158 }
159 fft.process_with_scratch(c, scratch_f);
160
161 tv_min_line(row, n, n_points, tvp, diff);
162 tvn.copy_from_slice(tvp);
163 isp.copy_from_slice(row);
164 isn.copy_from_slice(row);
165 sp.iter_mut().for_each(|x| *x = 0.0);
166 sn.iter_mut().for_each(|x| *x = 0.0);
167 let inv_n = 1.0 / n as f64;
168
169 for (s_i, &s) in ssamp.iter().enumerate() {
170 let ph = &phases[s_i * n..s_i * n + n];
171
172 for m in 0..n {
175 buf[m] = c[m] * ph[m];
176 }
177 ifft.process_with_scratch(buf, scratch_i);
178 for m in 0..n {
179 img[m] = (buf[m].re * inv_n).abs();
180 imgn[m] = (buf[m].im * inv_n).abs();
181 }
182
183 tv_min_line(img, n, n_points, tvs, diff);
184 for i in 0..n {
185 if tvp[i] > tvs[i] {
186 isp[i] = img[i];
187 sp[i] = s;
188 tvp[i] = tvs[i];
189 }
190 }
191
192 tv_min_line(imgn, n, n_points, tvs, diff);
193 for i in 0..n {
194 if tvn[i] > tvs[i] {
195 isn[i] = imgn[i];
196 sn[i] = s;
197 tvn[i] = tvs[i];
198 }
199 }
200 }
201
202 for i in 0..n {
203 let d = sp[i] + sn[i];
204 if d != 0.0 {
205 row[i] = (isp[i] - isn[i]) / d * sn[i] + isn[i];
206 }
207 }
208}
209
210fn fftfreq(n: usize) -> Vec<f64> {
211 (0..n)
212 .map(|m| {
213 if m < n.div_ceil(2) {
214 m as f64 / n as f64
215 } else {
216 (m as f64 - n as f64) / n as f64
217 }
218 })
219 .collect()
220}
221
222fn unring_axis(vol: &[f64], (nx, ny, nz): (usize, usize, usize), axis: usize) -> Vec<f64> {
229 let n = [nx, ny, nz][axis];
230 if n < 4 {
231 return vol.to_vec(); }
233 let freq = fftfreq(n);
234 let ssamp: Vec<f64> = (0..N_SHIFTS)
235 .map(|i| 0.02 + (0.9 - 0.02) * i as f64 / (N_SHIFTS - 1) as f64)
236 .collect();
237 let mut phases = vec![Complex64::new(0.0, 0.0); ssamp.len() * n];
245 for (s_i, &s) in ssamp.iter().enumerate() {
246 for m in 0..n {
247 let ph = if 2 * m == n {
248 Complex64::new((PI * s).cos(), 0.0)
249 } else {
250 Complex64::from_polar(1.0, 2.0 * PI * freq[m] * s)
251 };
252 phases[s_i * n + m] = ph + Complex64::i() * ph.conj();
253 }
254 }
255 let mut planner = FftPlanner::<f64>::new();
256 let fft = planner.plan_fft_forward(n);
257 let ifft = planner.plan_fft_inverse(n);
258 let sf = fft.get_inplace_scratch_len();
259 let si = ifft.get_inplace_scratch_len();
260
261 let (stride, lines): (usize, Vec<usize>) = match axis {
263 0 => (1, (0..ny * nz).map(|l| l * nx).collect()),
264 1 => {
265 let mut v = Vec::with_capacity(nx * nz);
266 for z in 0..nz {
267 for x in 0..nx {
268 v.push(z * nx * ny + x);
269 }
270 }
271 (nx, v)
272 }
273 _ => {
274 let mut v = Vec::with_capacity(nx * ny);
275 for y in 0..ny {
276 for x in 0..nx {
277 v.push(y * nx + x);
278 }
279 }
280 (nx * ny, v)
281 }
282 };
283
284 let num_lines = lines.len();
286 let mut mat = vec![0.0_f64; num_lines * n];
287 for (l, &start) in lines.iter().enumerate() {
288 let dst = &mut mat[l * n..l * n + n];
289 for (i, d) in dst.iter_mut().enumerate() {
290 *d = vol[start + i * stride];
291 }
292 }
293
294 #[cfg(feature = "parallel")]
296 mat.par_chunks_mut(n).for_each_init(
297 || LineBufs::new(n, sf, si),
298 |b, row| unring_row_inplace(row, b, n, 3, &ssamp, &phases, &fft, &ifft),
299 );
300 #[cfg(not(feature = "parallel"))]
301 {
302 let mut b = LineBufs::new(n, sf, si);
303 for row in mat.chunks_mut(n) {
304 unring_row_inplace(row, &mut b, n, 3, &ssamp, &phases, &fft, &ifft);
305 }
306 }
307
308 let mut out = vol.to_vec();
310 for (l, &start) in lines.iter().enumerate() {
311 let src = &mat[l * n..l * n + n];
312 for (i, &s) in src.iter().enumerate() {
313 out[start + i * stride] = s;
314 }
315 }
316 out
317}
318
319pub fn gibbs_unring_volume(vol: &[f64], dims: (usize, usize, usize)) -> Vec<f64> {
321 let (nx, ny, nz) = dims;
322 let n_vox = nx * ny * nz;
323 assert_eq!(vol.len(), n_vox, "vol length must be nx*ny*nz");
324
325 let cx = unring_axis(vol, dims, 0);
326 let cy = unring_axis(vol, dims, 1);
327 let cz = unring_axis(vol, dims, 2);
328
329 let fx = fftfreq(nx);
331 let fy = fftfreq(ny);
332 let fz = fftfreq(nz);
333 let cosk = |f: f64| 1.0 + (2.0 * PI * f).cos();
334 let ckx: Vec<f64> = fx.iter().map(|&f| cosk(f)).collect();
335 let cky: Vec<f64> = fy.iter().map(|&f| cosk(f)).collect();
336 let ckz: Vec<f64> = fz.iter().map(|&f| cosk(f)).collect();
337
338 let sx = fft3d_real(&cx, nx, ny, nz);
339 let sy = fft3d_real(&cy, nx, ny, nz);
340 let sz = fft3d_real(&cz, nx, ny, nz);
341 let mut spec = vec![Complex64::new(0.0, 0.0); n_vox];
342 for z in 0..nz {
343 for y in 0..ny {
344 for x in 0..nx {
345 let idx = x + y * nx + z * nx * ny;
346 let wx = cky[y] * ckz[z]; let wy = ckx[x] * ckz[z];
348 let wz = ckx[x] * cky[y];
349 let den = wx + wy + wz;
350 spec[idx] = if den > 1e-12 {
351 (sx[idx] * wx + sy[idx] * wy + sz[idx] * wz) / den
352 } else {
353 (sx[idx] + sy[idx] + sz[idx]) / 3.0
354 };
355 }
356 }
357 }
358 let mut out = ifft3d_real(&spec, nx, ny, nz);
359 for v in out.iter_mut() {
360 if *v < 0.0 {
361 *v = 0.0;
362 }
363 }
364 out
365}
366
367pub fn gibbs_unring(data: &[f64], dims: (usize, usize, usize), n_vols: usize) -> Vec<f64> {
378 let (nx, ny, nz) = dims;
379 let n_vox = nx * ny * nz;
380 assert_eq!(data.len(), n_vox * n_vols, "data length must be n_voxels * n_vols");
381 let mut out = data.to_vec();
382 let mut vol = vec![0.0_f64; n_vox];
383 for e in 0..n_vols {
384 for v in 0..n_vox {
385 vol[v] = data[v * n_vols + e];
386 }
387 let un = gibbs_unring_volume(&vol, dims);
388 for v in 0..n_vox {
389 out[v * n_vols + e] = un[v];
390 }
391 }
392 out
393}
394
395pub fn gibbs_unring_masked(
415 data: &[f64],
416 dims: (usize, usize, usize),
417 n_vols: usize,
418 mask: &[u8],
419 margin: usize,
420) -> Vec<f64> {
421 let (nx, ny, nz) = dims;
422 let n_vox = nx * ny * nz;
423 assert_eq!(data.len(), n_vox * n_vols, "data length must be n_voxels * n_vols");
424 assert_eq!(mask.len(), n_vox, "mask length must be n_voxels");
425
426 let (mut x0, mut y0, mut z0) = (nx, ny, nz);
428 let (mut x1, mut y1, mut z1) = (0usize, 0usize, 0usize);
429 let mut any = false;
430 for z in 0..nz {
431 for y in 0..ny {
432 for x in 0..nx {
433 if mask[x + y * nx + z * nx * ny] != 0 {
434 any = true;
435 x0 = x0.min(x);
436 y0 = y0.min(y);
437 z0 = z0.min(z);
438 x1 = x1.max(x);
439 y1 = y1.max(y);
440 z1 = z1.max(z);
441 }
442 }
443 }
444 }
445 if !any {
446 return data.to_vec();
447 }
448 x0 = x0.saturating_sub(margin);
450 y0 = y0.saturating_sub(margin);
451 z0 = z0.saturating_sub(margin);
452 x1 = (x1 + margin + 1).min(nx);
453 y1 = (y1 + margin + 1).min(ny);
454 z1 = (z1 + margin + 1).min(nz);
455 let (cx, cy, cz) = (x1 - x0, y1 - y0, z1 - z0);
456
457 if cx * cy * cz >= n_vox * 9 / 10 {
459 return gibbs_unring(data, dims, n_vols);
460 }
461
462 let n_crop = cx * cy * cz;
463 let mut out = data.to_vec();
464 let mut sub = vec![0.0_f64; n_crop];
465 for e in 0..n_vols {
466 for zz in 0..cz {
468 for yy in 0..cy {
469 for xx in 0..cx {
470 let full = (x0 + xx) + (y0 + yy) * nx + (z0 + zz) * nx * ny;
471 sub[xx + yy * cx + zz * cx * cy] = data[full * n_vols + e];
472 }
473 }
474 }
475 let un = gibbs_unring_volume(&sub, (cx, cy, cz));
476 for zz in 0..cz {
478 for yy in 0..cy {
479 for xx in 0..cx {
480 let full = (x0 + xx) + (y0 + yy) * nx + (z0 + zz) * nx * ny;
481 out[full * n_vols + e] = un[xx + yy * cx + zz * cx * cy];
482 }
483 }
484 }
485 }
486 out
487}
488
489#[cfg(test)]
490mod tests {
491 use super::*;
492
493 #[test]
494 fn test_unring_reduces_edge_overshoot() {
495 let (nx, ny, nz) = (64usize, 3usize, 3usize);
498 let mut clean = vec![0.0_f64; nx];
499 for x in 0..nx {
500 clean[x] = if x >= nx / 2 { 1.0 } else { 0.0 };
501 }
502 let mut planner = FftPlanner::<f64>::new();
504 let fft = planner.plan_fft_forward(nx);
505 let ifft = planner.plan_fft_inverse(nx);
506 let mut c: Vec<Complex64> = clean.iter().map(|&v| Complex64::new(v, 0.0)).collect();
507 fft.process(&mut c);
508 let keep = nx / 4;
509 for m in 0..nx {
510 let fm = if m < nx / 2 { m } else { nx - m };
511 if fm > keep {
512 c[m] = Complex64::new(0.0, 0.0);
513 }
514 }
515 ifft.process(&mut c);
516 let rung: Vec<f64> = c.iter().map(|z| z.re / nx as f64).collect();
517
518 let mut vol = vec![0.0_f64; nx * ny * nz];
520 for z in 0..nz {
521 for y in 0..ny {
522 for x in 0..nx {
523 vol[x + y * nx + z * nx * ny] = rung[x];
524 }
525 }
526 }
527 let un = unring_axis(&vol, (nx, ny, nz), 0);
531 let line: Vec<f64> = (0..nx).map(|x| un[x + nx + nx * ny]).collect();
533
534 let dipy_ref = [
537 0.06875, 0.25484, 0.67489, 1.06882, 1.02528, 0.96117, 0.98865, 1.02746, 1.00689,
538 0.97820, 0.97820,
539 ];
540 for (k, &r) in dipy_ref.iter().enumerate() {
541 let got = line[30 + k];
542 assert!(
543 (got - r).abs() < 5e-3,
544 "index {}: Rust {:.5} vs DIPY {:.5}",
545 30 + k,
546 got,
547 r
548 );
549 }
550 let tv = |a: &[f64]| a.windows(2).map(|w| (w[1] - w[0]).abs()).sum::<f64>();
552 let tv_rung: f64 = rung.windows(2).map(|w| (w[1] - w[0]).abs()).sum();
553 assert!(tv(&line) < 0.85 * tv_rung, "unring should cut TV: {:.3} -> {:.3}", tv_rung, tv(&line));
554 }
555
556 #[test]
560 #[ignore]
561 fn bench_unring_volume() {
562 let (nx, ny, nz) = (128usize, 128usize, 128usize);
563 let mut vol = vec![0.0_f64; nx * ny * nz];
564 let mut lcg: u64 = 42;
565 for z in 0..nz {
566 for y in 0..ny {
567 for x in 0..nx {
568 let dx = x as f64 - nx as f64 / 2.0;
569 let dy = y as f64 - ny as f64 / 2.0;
570 let dz = z as f64 - nz as f64 / 2.0;
571 let r = (dx * dx + dy * dy + dz * dz).sqrt();
572 let base = if r < nx as f64 / 3.0 { 1.0 } else { 0.1 };
573 lcg = lcg.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
574 let noise = (lcg >> 33) as f64 / (1u64 << 31) as f64 * 0.02;
575 vol[x + y * nx + z * nx * ny] = base + noise;
576 }
577 }
578 }
579 let t = std::time::Instant::now();
580 let out = gibbs_unring_volume(&vol, (nx, ny, nz));
581 let dt = t.elapsed();
582 let sum: f64 = out.iter().sum();
583 let sq: f64 = out.iter().map(|v| v * v).sum();
584 let mx = out.iter().cloned().fold(f64::MIN, f64::max);
585 println!(
586 "gibbs_unring_volume 128^3: {:?} sum {:.6} sumsq {:.6} max {:.6}",
587 dt, sum, sq, mx
588 );
589 }
590
591 #[test]
592 fn test_fftfreq_matches_numpy() {
593 let f = fftfreq(4);
595 assert!((f[0] - 0.0).abs() < 1e-12);
596 assert!((f[1] - 0.25).abs() < 1e-12);
597 assert!((f[2] + 0.5).abs() < 1e-12);
598 assert!((f[3] + 0.25).abs() < 1e-12);
599 }
600}