1use std::f32::consts::PI;
32use crate::utils::mask::erode_mask;
33#[cfg(feature = "parallel")]
34use rayon::prelude::*;
35
36#[cfg_attr(feature = "introspection", derive(serde::Serialize))]
38#[derive(Clone, Debug)]
39pub struct TgvParams {
40 pub alpha1: f32,
42 pub alpha0: f32,
44 pub iterations: usize,
46 pub erosions: usize,
48 pub step_size: f32,
50 pub fieldstrength: f32,
52 pub te: f32,
54 pub tol: f32,
56}
57
58impl Default for TgvParams {
59 fn default() -> Self {
60 Self {
61 alpha1: 0.0005,
67 alpha0: 0.0015,
68 iterations: 1000,
69 erosions: 3,
70 step_size: 3.0,
71 fieldstrength: 3.0,
72 te: 0.020, tol: 1e-5,
74 }
75 }
76}
77
78pub fn get_default_alpha(regularization: u8) -> (f32, f32) {
82 match regularization.clamp(1, 4) {
83 1 => (0.0005, 0.0005),
84 2 => (0.001, 0.001),
85 3 => (0.002, 0.003),
86 _ => (0.003, 0.005),
87 }
88}
89
90pub fn get_default_iterations(res: (f32, f32, f32), step_size: f32) -> usize {
94 let prod_res = res.0 * res.1 * res.2;
95 let it = (1000.0_f32).max(3200.0 / prod_res.powf(0.42)) / step_size.powf(0.6);
96 it.round() as usize
97}
98
99#[derive(Clone, Debug)]
101struct BoundingBox {
102 i_min: usize,
103 i_max: usize,
104 j_min: usize,
105 j_max: usize,
106 k_min: usize,
107 k_max: usize,
108}
109
110impl BoundingBox {
111 fn from_mask(mask: &[u8], nx: usize, ny: usize, nz: usize, padding: usize) -> Self {
113 let mut i_min = nx;
114 let mut i_max = 0;
115 let mut j_min = ny;
116 let mut j_max = 0;
117 let mut k_min = nz;
118 let mut k_max = 0;
119
120 for k in 0..nz {
121 for j in 0..ny {
122 for i in 0..nx {
123 if mask[i + j * nx + k * nx * ny] != 0 {
124 i_min = i_min.min(i);
125 i_max = i_max.max(i);
126 j_min = j_min.min(j);
127 j_max = j_max.max(j);
128 k_min = k_min.min(k);
129 k_max = k_max.max(k);
130 }
131 }
132 }
133 }
134
135 let i_min = i_min.saturating_sub(padding);
137 let j_min = j_min.saturating_sub(padding);
138 let k_min = k_min.saturating_sub(padding);
139 let i_max = (i_max + padding + 1).min(nx);
140 let j_max = (j_max + padding + 1).min(ny);
141 let k_max = (k_max + padding + 1).min(nz);
142
143 Self { i_min, i_max, j_min, j_max, k_min, k_max }
144 }
145
146 fn dims(&self) -> (usize, usize, usize) {
147 (self.i_max - self.i_min, self.j_max - self.j_min, self.k_max - self.k_min)
148 }
149
150 fn total(&self) -> usize {
151 let (bx, by, bz) = self.dims();
152 bx * by * bz
153 }
154}
155
156fn extract_subvolume<T: Copy + Default>(
158 full: &[T],
159 bbox: &BoundingBox,
160 nx: usize, ny: usize, _nz: usize,
161) -> Vec<T> {
162 let (bx, by, bz) = bbox.dims();
163 let mut sub = vec![T::default(); bx * by * bz];
164
165 for k in 0..bz {
166 for j in 0..by {
167 for i in 0..bx {
168 let full_idx = (bbox.i_min + i) + (bbox.j_min + j) * nx + (bbox.k_min + k) * nx * ny;
169 let sub_idx = i + j * bx + k * bx * by;
170 sub[sub_idx] = full[full_idx];
171 }
172 }
173 }
174 sub
175}
176
177fn insert_subvolume<T: Copy>(
179 full: &mut [T],
180 sub: &[T],
181 bbox: &BoundingBox,
182 nx: usize, ny: usize, _nz: usize,
183) {
184 let (bx, by, bz) = bbox.dims();
185
186 for k in 0..bz {
187 for j in 0..by {
188 for i in 0..bx {
189 let full_idx = (bbox.i_min + i) + (bbox.j_min + j) * nx + (bbox.k_min + k) * nx * ny;
190 let sub_idx = i + j * bx + k * bx * by;
191 full[full_idx] = sub[sub_idx];
192 }
193 }
194 }
195}
196
197pub fn compute_dipole_stencil(
199 res: (f32, f32, f32),
200 b0_dir: (f32, f32, f32),
201) -> [[[f32; 3]; 3]; 3] {
202 let (dx, dy, dz) = res;
203 let (bx, by, bz) = b0_dir;
204
205 let b_norm = (bx * bx + by * by + bz * bz).sqrt();
207 let bx = bx / b_norm;
208 let by = by / b_norm;
209 let bz = bz / b_norm;
210
211 let mut stencil = [[[0.0f32; 3]; 3]; 3];
212
213 let hx2 = 1.0 / (dx * dx);
214 let hy2 = 1.0 / (dy * dy);
215 let hz2 = 1.0 / (dz * dz);
216 let factor = 1.0 / 3.0;
217
218 stencil[1][1][1] = -2.0 * (hx2 + hy2 + hz2) * factor
220 + 2.0 * (bx * bx * hx2 + by * by * hy2 + bz * bz * hz2);
221
222 stencil[0][1][1] = hx2 * factor - bx * bx * hx2;
224 stencil[2][1][1] = hx2 * factor - bx * bx * hx2;
225
226 stencil[1][0][1] = hy2 * factor - by * by * hy2;
228 stencil[1][2][1] = hy2 * factor - by * by * hy2;
229
230 stencil[1][1][0] = hz2 * factor - bz * bz * hz2;
232 stencil[1][1][2] = hz2 * factor - bz * bz * hz2;
233
234 let hxy = 1.0 / (dx * dy);
236 let hxz = 1.0 / (dx * dz);
237 let hyz = 1.0 / (dy * dz);
238
239 let xy_factor = -bx * by * hxy;
240 stencil[0][0][1] = xy_factor;
241 stencil[2][2][1] = xy_factor;
242 stencil[0][2][1] = -xy_factor;
243 stencil[2][0][1] = -xy_factor;
244
245 let xz_factor = -bx * bz * hxz;
246 stencil[0][1][0] = xz_factor;
247 stencil[2][1][2] = xz_factor;
248 stencil[0][1][2] = -xz_factor;
249 stencil[2][1][0] = -xz_factor;
250
251 let yz_factor = -by * bz * hyz;
252 stencil[1][0][0] = yz_factor;
253 stencil[1][2][2] = yz_factor;
254 stencil[1][0][2] = -yz_factor;
255 stencil[1][2][0] = -yz_factor;
256
257 stencil
258}
259
260pub fn compute_oblique_stencil(
273 res: (f32, f32, f32),
274 b0_dir: (f32, f32, f32),
275) -> [[[f32; 3]; 3]; 3] {
276 let n: usize = 64;
277 let singularity_cutout = 4.0_f64;
278 let mid = n / 2; let n2 = n * n;
280 let n3 = n * n * n;
281
282 let (dx, dy, dz) = (res.0 as f64, res.1 as f64, res.2 as f64);
283
284 let b_norm = ((b0_dir.0 * b0_dir.0 + b0_dir.1 * b0_dir.1 + b0_dir.2 * b0_dir.2) as f64).sqrt();
286 let bdir = (b0_dir.0 as f64 / b_norm, b0_dir.1 as f64 / b_norm, b0_dir.2 as f64 / b_norm);
287
288 let mut d = vec![f64::NAN; n3];
290 let mut d_mask = vec![false; n3];
291
292 for k in 0..n {
293 for j in 0..n {
294 for i in 0..n {
295 let x = i as f64 - mid as f64;
296 let y = j as f64 - mid as f64;
297 let z = k as f64 - mid as f64;
298 let r = (x * x + y * y + z * z).sqrt();
299
300 let idx = i + j * n + k * n2;
301 if r < singularity_cutout {
302 } else {
304 let xz = (bdir.0 * x + bdir.1 * y + bdir.2 * z) / r;
305 let kappa = (3.0 * xz * xz - 1.0) / (4.0 * std::f64::consts::PI * r * r * r);
306 d[idx] = kappa;
307 d_mask[idx] = true;
308 }
309 }
310 }
311 }
312
313 let coord2_sq: Vec<f64> = (0..n)
315 .map(|k| {
316 let v = 2.0 * (std::f64::consts::PI * (k as f64 + 1.0) / (2.0 * (n as f64 + 1.0))).sin();
317 v * v
318 })
319 .collect();
320
321 let mut coord2_grid = vec![0.0_f64; n3];
323 for k in 0..n {
324 for j in 0..n {
325 for i in 0..n {
326 coord2_grid[i + j * n + k * n2] = coord2_sq[i] + coord2_sq[j] + coord2_sq[k];
327 }
328 }
329 }
330
331 let sin_table = dst_sin_table(n);
333 let idst_scale = (2.0 / (n as f64 + 1.0)).powi(3);
336
337 let mut stencil_positions: Vec<(i32, i32, i32)> = Vec::with_capacity(26);
339 for dk in -1..=1_i32 {
340 for dj in -1..=1_i32 {
341 for di in -1..=1_i32 {
342 if di == 0 && dj == 0 && dk == 0 {
343 continue;
344 }
345 stencil_positions.push((di, dj, dk));
346 }
347 }
348 }
349
350 let valid_indices: Vec<usize> = d_mask
352 .iter()
353 .enumerate()
354 .filter(|(_, &m)| m)
355 .map(|(idx, _)| idx)
356 .collect();
357
358 let num_pairs = 13;
362 let mut a_rows: Vec<Vec<f64>> = Vec::with_capacity(num_pairs);
363
364 for p in 0..num_pairs {
365 let (di, dj, dk) = stencil_positions[p];
366
367 let mut delta = vec![0.0_f64; n3];
369 let pi = (mid as i32 + di) as usize;
370 let pj = (mid as i32 + dj) as usize;
371 let pk = (mid as i32 + dk) as usize;
372 let mi = (mid as i32 - di) as usize;
373 let mj = (mid as i32 - dj) as usize;
374 let mk = (mid as i32 - dk) as usize;
375
376 delta[pi + pj * n + pk * n2] = 1.0;
377 delta[mi + mj * n + mk * n2] += 1.0; delta[mid + mid * n + mid * n2] = -2.0;
379
380 let mut fdelta = dst3d(&delta, n, &sin_table);
382
383 for idx in 0..n3 {
385 fdelta[idx] /= -coord2_grid[idx];
386 }
387
388 let vdelta = dst3d(&fdelta, n, &sin_table);
390
391 let row: Vec<f64> = valid_indices
393 .iter()
394 .map(|&idx| vdelta[idx] * idst_scale)
395 .collect();
396 a_rows.push(row);
397 }
398
399 let d_valid: Vec<f64> = valid_indices.iter().map(|&idx| d[idx]).collect();
401
402 let mut g = vec![vec![0.0_f64; num_pairs]; num_pairs];
405 for i in 0..num_pairs {
406 for j in 0..=i {
407 let dot: f64 = a_rows[i]
408 .iter()
409 .zip(a_rows[j].iter())
410 .map(|(&a, &b)| a * b)
411 .sum();
412 g[i][j] = dot;
413 g[j][i] = dot;
414 }
415 }
416
417 let mut h = vec![0.0_f64; num_pairs];
418 for i in 0..num_pairs {
419 h[i] = a_rows[i]
420 .iter()
421 .zip(d_valid.iter())
422 .map(|(&a, &b)| a * b)
423 .sum();
424 }
425
426 let y = solve_symmetric_pseudoinverse(&g, &h);
429
430 let mut result = [[[0.0f32; 3]; 3]; 3];
433 for p in 0..num_pairs {
434 let coeff = y[p] as f32;
435
436 let (di, dj, dk) = stencil_positions[p];
438 result[(di + 1) as usize][(dj + 1) as usize][(dk + 1) as usize] = coeff;
439
440 let (di2, dj2, dk2) = stencil_positions[25 - p];
442 result[(di2 + 1) as usize][(dj2 + 1) as usize][(dk2 + 1) as usize] = coeff;
443 }
444
445 for dk in -1..=1_i32 {
447 for dj in -1..=1_i32 {
448 for di in -1..=1_i32 {
449 if di == 0 && dj == 0 && dk == 0 {
450 continue;
451 }
452 let si = (di + 1) as usize;
453 let sj = (dj + 1) as usize;
454 let sk = (dk + 1) as usize;
455
456 let i2 = (di * di) as f64;
457 let j2 = (dj * dj) as f64;
458 let k2 = (dk * dk) as f64;
459 let weight = (i2 / (dx * dx) + j2 / (dy * dy) + k2 / (dz * dz)) / (i2 + j2 + k2);
460 result[si][sj][sk] *= weight as f32;
461 }
462 }
463 }
464
465 let mut total = 0.0f32;
467 for dk in 0..3 {
468 for dj in 0..3 {
469 for di in 0..3 {
470 if !(di == 1 && dj == 1 && dk == 1) {
471 total += result[di][dj][dk];
472 }
473 }
474 }
475 }
476 result[1][1][1] = -total;
477
478 result
479}
480
481fn dst_sin_table(n: usize) -> Vec<Vec<f64>> {
484 let scale = std::f64::consts::PI / (n as f64 + 1.0);
485 let mut table = vec![vec![0.0_f64; n]; n];
486 for j in 0..n {
487 for k in 0..n {
488 table[j][k] = ((j as f64 + 1.0) * (k as f64 + 1.0) * scale).sin();
489 }
490 }
491 table
492}
493
494fn dst3d(input: &[f64], n: usize, sin_table: &[Vec<f64>]) -> Vec<f64> {
496 let n2 = n * n;
497 let mut data = input.to_vec();
498 let mut buf_in = vec![0.0_f64; n];
499 let mut buf_out = vec![0.0_f64; n];
500
501 for k in 0..n {
503 for j in 0..n {
504 let base = j * n + k * n2;
505 buf_in.copy_from_slice(&data[base..base + n]);
506 dst1(&buf_in, sin_table, &mut buf_out);
507 data[base..base + n].copy_from_slice(&buf_out);
508 }
509 }
510
511 for k in 0..n {
513 for i in 0..n {
514 for j in 0..n {
515 buf_in[j] = data[i + j * n + k * n2];
516 }
517 dst1(&buf_in, sin_table, &mut buf_out);
518 for j in 0..n {
519 data[i + j * n + k * n2] = buf_out[j];
520 }
521 }
522 }
523
524 for j in 0..n {
526 for i in 0..n {
527 for k in 0..n {
528 buf_in[k] = data[i + j * n + k * n2];
529 }
530 dst1(&buf_in, sin_table, &mut buf_out);
531 for k in 0..n {
532 data[i + j * n + k * n2] = buf_out[k];
533 }
534 }
535 }
536
537 data
538}
539
540fn dst1(input: &[f64], sin_table: &[Vec<f64>], output: &mut [f64]) {
542 let n = input.len();
543 for k in 0..n {
544 let mut sum = 0.0_f64;
545 for j in 0..n {
546 sum += input[j] * sin_table[j][k];
547 }
548 output[k] = sum;
549 }
550}
551
552fn solve_symmetric_pseudoinverse(g: &[Vec<f64>], h: &[f64]) -> Vec<f64> {
558 let n = h.len();
559
560 let mut a: Vec<Vec<f64>> = g.to_vec();
562 let mut v = vec![vec![0.0_f64; n]; n];
563 for i in 0..n {
564 v[i][i] = 1.0;
565 }
566
567 let max_sweeps = 100;
569 let tol = 1e-15;
570
571 for _ in 0..max_sweeps {
572 let mut max_off = 0.0_f64;
574 for i in 0..n {
575 for j in (i + 1)..n {
576 max_off = max_off.max(a[i][j].abs());
577 }
578 }
579 if max_off < tol {
580 break;
581 }
582
583 for p in 0..n {
585 for q in (p + 1)..n {
586 if a[p][q].abs() < tol {
587 continue;
588 }
589
590 let app = a[p][p];
592 let aqq = a[q][q];
593 let apq = a[p][q];
594 let tau = (aqq - app) / (2.0 * apq);
595 let t = if tau >= 0.0 {
596 1.0 / (tau + (1.0 + tau * tau).sqrt())
597 } else {
598 -1.0 / (-tau + (1.0 + tau * tau).sqrt())
599 };
600 let c = 1.0 / (1.0 + t * t).sqrt();
601 let s = t * c;
602
603 for i in 0..n {
606 if i == p || i == q {
607 continue;
608 }
609 let aip = a[i][p];
610 let aiq = a[i][q];
611 a[i][p] = c * aip - s * aiq;
612 a[p][i] = a[i][p];
613 a[i][q] = s * aip + c * aiq;
614 a[q][i] = a[i][q];
615 }
616
617 a[p][p] = c * c * app - 2.0 * c * s * apq + s * s * aqq;
619 a[q][q] = s * s * app + 2.0 * c * s * apq + c * c * aqq;
620 a[p][q] = 0.0;
621 a[q][p] = 0.0;
622
623 for i in 0..n {
625 let vip = v[i][p];
626 let viq = v[i][q];
627 v[i][p] = c * vip - s * viq;
628 v[i][q] = s * vip + c * viq;
629 }
630 }
631 }
632 }
633
634 let eigenvalues: Vec<f64> = (0..n).map(|i| a[i][i]).collect();
636 let max_eigen = eigenvalues.iter().cloned().fold(0.0_f64, |a, b| a.max(b.abs()));
637 let threshold = 1e-10 * max_eigen;
638
639 let mut vt_h = vec![0.0_f64; n];
641 for i in 0..n {
642 for j in 0..n {
643 vt_h[i] += v[j][i] * h[j];
644 }
645 }
646
647 for i in 0..n {
648 if eigenvalues[i].abs() > threshold {
649 vt_h[i] /= eigenvalues[i];
650 } else {
651 vt_h[i] = 0.0;
652 }
653 }
654
655 let mut x = vec![0.0_f64; n];
656 for i in 0..n {
657 for j in 0..n {
658 x[i] += v[i][j] * vt_h[j];
659 }
660 }
661
662 x
663}
664
665fn apply_stencil(
668 output: &mut [f32],
669 input: &[f32],
670 stencil: &[[[f32; 3]; 3]; 3],
671 mask: &[u8],
672 nx: usize, ny: usize, nz: usize,
673) {
674 let nxy = nx * ny;
675 maybe_par_chunks_mut!(output, nxy).enumerate().for_each(|(k, out_slab)| {
677 for j in 0..ny {
678 for i in 0..nx {
679 let local = i + j * nx;
680 let idx = local + k * nxy;
681
682 if mask[idx] == 0 {
683 out_slab[local] = 0.0;
684 continue;
685 }
686
687 if i == 0 || j == 0 || k == 0 || i + 1 >= nx || j + 1 >= ny || k + 1 >= nz {
690 out_slab[local] = 0.0;
691 continue;
692 }
693
694 let mut sum = 0.0f32;
695
696 for dk in 0..3i32 {
697 for dj in 0..3i32 {
698 for di in 0..3i32 {
699 let ni = (i as i32 + di - 1) as usize;
700 let nj = (j as i32 + dj - 1) as usize;
701 let nk = (k as i32 + dk - 1) as usize;
702
703 let nidx = ni + nj * nx + nk * nxy;
704 sum += stencil[di as usize][dj as usize][dk as usize] * input[nidx];
705 }
706 }
707 }
708
709 out_slab[local] = sum;
710 }
711 }
712 });
713}
714
715pub fn compute_phase_laplacian(
717 phase: &[f32],
718 mask: &[u8],
719 nx: usize, ny: usize, nz: usize,
720 vsx: f32, vsy: f32, vsz: f32,
721) -> Vec<f32> {
722 let n_total = nx * ny * nz;
723
724 let sin_phase: Vec<f32> = phase.iter().map(|&p| p.sin()).collect();
725 let cos_phase: Vec<f32> = phase.iter().map(|&p| p.cos()).collect();
726
727 let lap_sin = compute_laplacian(&sin_phase, nx, ny, nz, vsx, vsy, vsz);
728 let lap_cos = compute_laplacian(&cos_phase, nx, ny, nz, vsx, vsy, vsz);
729
730 let mut laplacian = vec![0.0f32; n_total];
731 for i in 0..n_total {
732 if mask[i] != 0 {
733 laplacian[i] = lap_sin[i] * cos_phase[i] - lap_cos[i] * sin_phase[i];
734 }
735 }
736
737 laplacian
738}
739
740fn compute_laplacian(
742 input: &[f32],
743 nx: usize, ny: usize, nz: usize,
744 vsx: f32, vsy: f32, vsz: f32,
745) -> Vec<f32> {
746 let n_total = nx * ny * nz;
747 let mut output = vec![0.0f32; n_total];
748
749 let hx2 = 1.0 / (vsx * vsx);
750 let hy2 = 1.0 / (vsy * vsy);
751 let hz2 = 1.0 / (vsz * vsz);
752 let center = -2.0 * (hx2 + hy2 + hz2);
753
754 for k in 0..nz {
755 let km1 = if k == 0 { 0 } else { k - 1 };
756 let kp1 = if k + 1 >= nz { nz - 1 } else { k + 1 };
757
758 for j in 0..ny {
759 let jm1 = if j == 0 { 0 } else { j - 1 };
760 let jp1 = if j + 1 >= ny { ny - 1 } else { j + 1 };
761
762 for i in 0..nx {
763 let im1 = if i == 0 { 0 } else { i - 1 };
764 let ip1 = if i + 1 >= nx { nx - 1 } else { i + 1 };
765
766 let idx = i + j * nx + k * nx * ny;
767
768 output[idx] = center * input[idx]
769 + hx2 * (input[im1 + j * nx + k * nx * ny] + input[ip1 + j * nx + k * nx * ny])
770 + hy2 * (input[i + jm1 * nx + k * nx * ny] + input[i + jp1 * nx + k * nx * ny])
771 + hz2 * (input[i + j * nx + km1 * nx * ny] + input[i + j * nx + kp1 * nx * ny]);
772 }
773 }
774 }
775
776 output
777}
778
779fn apply_laplacian_inplace(
781 output: &mut [f32],
782 input: &[f32],
783 mask: &[u8],
784 nx: usize, ny: usize, nz: usize,
785 vsx: f32, vsy: f32, vsz: f32,
786) {
787 let hx2 = 1.0 / (vsx * vsx);
788 let hy2 = 1.0 / (vsy * vsy);
789 let hz2 = 1.0 / (vsz * vsz);
790 let nxy = nx * ny;
791
792 maybe_par_chunks_mut!(output, nxy).enumerate().for_each(|(k, out_slab)| {
793 let k_offset = k * nxy;
794
795 for j in 0..ny {
796 let j_offset = j * nx;
797
798 for i in 0..nx {
799 let local = i + j_offset;
800 let idx = local + k_offset;
801
802 if mask[idx] == 0 {
803 out_slab[local] = 0.0;
804 continue;
805 }
806
807 let a0 = input[idx];
808
809 let a_xm = if i > 0 { input[(i - 1) + j_offset + k_offset] } else { a0 };
811 let a_xp = if i + 1 < nx { input[(i + 1) + j_offset + k_offset] } else { a0 };
812 let a_ym = if j > 0 { input[i + (j - 1) * nx + k_offset] } else { a0 };
813 let a_yp = if j + 1 < ny { input[i + (j + 1) * nx + k_offset] } else { a0 };
814 let a_zm = if k > 0 { input[i + j_offset + (k - 1) * nxy] } else { a0 };
815 let a_zp = if k + 1 < nz { input[i + j_offset + (k + 1) * nxy] } else { a0 };
816
817 out_slab[local] = hx2 * (a_xm - 2.0 * a0 + a_xp)
819 + hy2 * (a_ym - 2.0 * a0 + a_yp)
820 + hz2 * (a_zm - 2.0 * a0 + a_zp);
821 }
822 }
823 });
824}
825
826
827#[inline]
829fn grad_norm_sq(res: (f32, f32, f32)) -> f32 {
830 let (dx, dy, dz) = res;
831 4.0 * (1.0 / (dx * dx) + 1.0 / (dy * dy) + 1.0 / (dz * dz))
832}
833
834fn compute_operator_norm_sqr(g: f32, g2: f32, w: f32) -> f32 {
843 let g4 = g2 * g2;
847 let g2w = g2 * w;
848 let g2_w2 = g2 + w * w;
849 let g2_1 = g2 + 1.0;
850
851 let mut v = [1.0f32, 1.0, 1.0];
853
854 for _ in 0..20 {
855 let y0 = g4 * v[0] + g2w * v[1];
857 let y1 = g2w * v[0] + g2_w2 * v[1] + g * v[2];
858 let y2 = g * v[1] + g2_1 * v[2];
859
860 let norm = (y0 * y0 + y1 * y1 + y2 * y2).sqrt();
862 if norm < 1e-10 {
863 break;
864 }
865
866 v[0] = y0 / norm;
868 v[1] = y1 / norm;
869 v[2] = y2 / norm;
870 }
871
872 let y0 = g4 * v[0] + g2w * v[1];
874 let y1 = g2w * v[0] + g2_w2 * v[1] + g * v[2];
875 let y2 = g * v[1] + g2_1 * v[2];
876
877 v[0] * y0 + v[1] * y1 + v[2] * y2
878}
879
880#[inline]
882fn norm3(x: f32, y: f32, z: f32) -> f32 {
883 (x * x + y * y + z * z).sqrt()
884}
885
886#[inline]
888fn frobenius_norm(sxx: f32, sxy: f32, sxz: f32, syy: f32, syz: f32, szz: f32) -> f32 {
889 (sxx * sxx + syy * syy + szz * szz + 2.0 * (sxy * sxy + sxz * sxz + syz * syz)).sqrt()
890}
891
892#[inline]
894fn project_linf3(px: &mut f32, py: &mut f32, pz: &mut f32, threshold: f32) {
895 let norm = norm3(*px, *py, *pz);
896 if norm > threshold {
897 let scale = threshold / norm;
898 *px *= scale;
899 *py *= scale;
900 *pz *= scale;
901 }
902}
903
904#[inline]
906fn project_linf6(
907 qxx: &mut f32, qxy: &mut f32, qxz: &mut f32,
908 qyy: &mut f32, qyz: &mut f32, qzz: &mut f32,
909 threshold: f32,
910) {
911 let norm = frobenius_norm(*qxx, *qxy, *qxz, *qyy, *qyz, *qzz);
912 if norm > threshold {
913 let scale = threshold / norm;
914 *qxx *= scale;
915 *qxy *= scale;
916 *qxz *= scale;
917 *qyy *= scale;
918 *qyz *= scale;
919 *qzz *= scale;
920 }
921}
922
923fn compute_relative_change(chi: &[f32], chi_prev: &[f32], mask: &[u8]) -> f32 {
925 let mut diff_sq = 0.0f32;
926 let mut norm_sq = 0.0f32;
927
928 for i in 0..chi.len() {
929 if mask[i] != 0 {
930 let d = chi[i] - chi_prev[i];
931 diff_sq += d * d;
932 norm_sq += chi[i] * chi[i];
933 }
934 }
935
936 if norm_sq > 1e-10 {
937 (diff_sq / norm_sq).sqrt()
938 } else {
939 1.0
940 }
941}
942
943struct TgvWorkspace {
945 chi: Vec<f32>,
947 chi_: Vec<f32>,
948 chi_prev: Vec<f32>, phi: Vec<f32>,
950 phi_: Vec<f32>,
951 wx: Vec<f32>,
952 wy: Vec<f32>,
953 wz: Vec<f32>,
954 wx_: Vec<f32>,
955 wy_: Vec<f32>,
956 wz_: Vec<f32>,
957
958 eta: Vec<f32>,
960 px: Vec<f32>,
961 py: Vec<f32>,
962 pz: Vec<f32>,
963 qxx: Vec<f32>,
964 qxy: Vec<f32>,
965 qxz: Vec<f32>,
966 qyy: Vec<f32>,
967 qyz: Vec<f32>,
968 qzz: Vec<f32>,
969
970 temp1: Vec<f32>,
972 temp2: Vec<f32>,
973 gx: Vec<f32>,
974 gy: Vec<f32>,
975 gz: Vec<f32>,
976
977 sxx: Vec<f32>,
979 sxy: Vec<f32>,
980 sxz: Vec<f32>,
981 syy: Vec<f32>,
982 syz: Vec<f32>,
983 szz: Vec<f32>,
984
985 divqx: Vec<f32>,
987 divqy: Vec<f32>,
988 divqz: Vec<f32>,
989}
990
991impl TgvWorkspace {
992 fn new(n: usize) -> Self {
993 Self {
994 chi: vec![0.0; n],
995 chi_: vec![0.0; n],
996 chi_prev: vec![0.0; n],
997 phi: vec![0.0; n],
998 phi_: vec![0.0; n],
999 wx: vec![0.0; n],
1000 wy: vec![0.0; n],
1001 wz: vec![0.0; n],
1002 wx_: vec![0.0; n],
1003 wy_: vec![0.0; n],
1004 wz_: vec![0.0; n],
1005 eta: vec![0.0; n],
1006 px: vec![0.0; n],
1007 py: vec![0.0; n],
1008 pz: vec![0.0; n],
1009 qxx: vec![0.0; n],
1010 qxy: vec![0.0; n],
1011 qxz: vec![0.0; n],
1012 qyy: vec![0.0; n],
1013 qyz: vec![0.0; n],
1014 qzz: vec![0.0; n],
1015 temp1: vec![0.0; n],
1016 temp2: vec![0.0; n],
1017 gx: vec![0.0; n],
1018 gy: vec![0.0; n],
1019 gz: vec![0.0; n],
1020 sxx: vec![0.0; n],
1021 sxy: vec![0.0; n],
1022 sxz: vec![0.0; n],
1023 syy: vec![0.0; n],
1024 syz: vec![0.0; n],
1025 szz: vec![0.0; n],
1026 divqx: vec![0.0; n],
1027 divqy: vec![0.0; n],
1028 divqz: vec![0.0; n],
1029 }
1030 }
1031}
1032
1033pub fn tgv_qsm(
1046 phase: &[f64],
1047 mask: &[u8],
1048 grid: &crate::Grid,
1049 params: &TgvParams,
1050 b0_dir: (f64, f64, f64),
1051 mut progress: impl FnMut(usize, usize),
1052) -> Vec<f64> {
1053 let phase_f32: Vec<f32> = phase.iter().map(|&v| v as f32).collect();
1055 let phase: &[f32] = &phase_f32;
1056 let b0_dir = (b0_dir.0 as f32, b0_dir.1 as f32, b0_dir.2 as f32);
1057
1058 let (nx, ny, nz) = grid.dims;
1059 let n_total = grid.n_total();
1060 let vsx = grid.vsx() as f32;
1061 let vsy = grid.vsy() as f32;
1062 let vsz = grid.vsz() as f32;
1063 let res = (vsx, vsy, vsz);
1064
1065 let full_grid = crate::Grid::new(nx, ny, nz, vsx as f64, vsy as f64, vsz as f64);
1067 let mut mask_eroded = mask.to_vec();
1068 for _ in 0..params.erosions {
1069 mask_eroded = erode_mask(&mask_eroded, &full_grid, 1);
1070 }
1071
1072 let mask0 = erode_mask(&mask_eroded, &full_grid, 1);
1074
1075 let bbox = BoundingBox::from_mask(&mask0, nx, ny, nz, 2);
1077 let (bx, by, bz) = bbox.dims();
1078 let b_total = bbox.total();
1079 let sub_grid = crate::Grid::new(bx, by, bz, vsx as f64, vsy as f64, vsz as f64);
1080
1081 let phase_sub = extract_subvolume(phase, &bbox, nx, ny, nz);
1083 let mask0_sub = extract_subvolume(&mask0, &bbox, nx, ny, nz);
1084 let mask_eroded_sub = extract_subvolume(&mask_eroded, &bbox, nx, ny, nz);
1085
1086 let mut laplace_phi0 = compute_phase_laplacian(&phase_sub, &mask0_sub, bx, by, bz, vsx, vsy, vsz);
1088
1089 let (sum, count): (f32, usize) = laplace_phi0.iter().zip(mask0_sub.iter())
1091 .filter(|(_, &m)| m != 0)
1092 .fold((0.0, 0), |(s, c), (&v, _)| (s + v, c + 1));
1093 if count > 0 {
1094 let mean = sum / count as f32;
1095 for (v, &m) in laplace_phi0.iter_mut().zip(mask0_sub.iter()) {
1096 if m != 0 {
1097 *v -= mean;
1098 }
1099 }
1100 }
1101
1102 let stencil = compute_oblique_stencil(res, b0_dir);
1104
1105 let grad_norm_squared = grad_norm_sq(res);
1107 let grad_norm = grad_norm_squared.sqrt();
1108 let wave_norm: f32 = stencil.iter().flatten().flatten().map(|x| x.abs()).sum();
1109 let norm_sqr = compute_operator_norm_sqr(grad_norm, grad_norm_squared, wave_norm);
1110
1111 let tau = 1.0 / norm_sqr.sqrt();
1112 let sigma = tau;
1113
1114 let sigma_step = sigma * params.step_size;
1118 let tau_step = tau * params.step_size;
1119
1120 let alpha = (params.alpha0, params.alpha1);
1123
1124 let mut ws = TgvWorkspace::new(b_total);
1126
1127 let mut _converged = false;
1128 let mut final_iter = params.iterations;
1129
1130 for iter in 0..params.iterations {
1132 progress(iter, params.iterations);
1133
1134 if iter > 0 && iter % 100 == 0 {
1136 let rel_change = compute_relative_change(&ws.chi, &ws.chi_prev, &mask0_sub);
1137 if rel_change < params.tol {
1138 _converged = true;
1139 final_iter = iter;
1140 break;
1141 }
1142 ws.chi_prev.copy_from_slice(&ws.chi);
1144 }
1145
1146 apply_laplacian_inplace(&mut ws.temp1, &ws.phi_, &mask0_sub, bx, by, bz, vsx, vsy, vsz);
1150 apply_stencil(&mut ws.temp2, &ws.chi_, &stencil, &mask0_sub, bx, by, bz);
1151
1152 for i in 0..b_total {
1153 if mask0_sub[i] != 0 {
1154 ws.eta[i] += sigma * (-ws.temp1[i] + ws.temp2[i] - laplace_phi0[i]);
1155 }
1156 }
1157
1158 crate::utils::gradient::fgrad_inplace_f32(
1162 &mut ws.gx, &mut ws.gy, &mut ws.gz, &ws.chi_, &sub_grid
1163 );
1164
1165 for i in 0..b_total {
1166 let in_mask0 = mask0_sub[i] != 0;
1167 let in_mask = mask_eroded_sub[i] != 0;
1168
1169 if in_mask0 || in_mask {
1170 let sigmaw0 = if in_mask0 { sigma_step } else { 0.0 };
1172 let sigmaw = if in_mask { sigma_step } else { 0.0 };
1173
1174 ws.px[i] += sigmaw0 * ws.gx[i] - sigmaw * ws.wx_[i];
1175 ws.py[i] += sigmaw0 * ws.gy[i] - sigmaw * ws.wy_[i];
1176 ws.pz[i] += sigmaw0 * ws.gz[i] - sigmaw * ws.wz_[i];
1177
1178 project_linf3(&mut ws.px[i], &mut ws.py[i], &mut ws.pz[i], alpha.1);
1179 }
1180 }
1181
1182 crate::utils::gradient::symgrad_inplace_f32(
1184 &mut ws.sxx, &mut ws.sxy, &mut ws.sxz, &mut ws.syy, &mut ws.syz, &mut ws.szz,
1185 &ws.wx_, &ws.wy_, &ws.wz_, &sub_grid
1186 );
1187
1188 for i in 0..b_total {
1189 if mask0_sub[i] != 0 {
1190 ws.qxx[i] += sigma_step * ws.sxx[i];
1191 ws.qxy[i] += sigma_step * ws.sxy[i];
1192 ws.qxz[i] += sigma_step * ws.sxz[i];
1193 ws.qyy[i] += sigma_step * ws.syy[i];
1194 ws.qyz[i] += sigma_step * ws.syz[i];
1195 ws.qzz[i] += sigma_step * ws.szz[i];
1196
1197 project_linf6(
1198 &mut ws.qxx[i], &mut ws.qxy[i], &mut ws.qxz[i],
1199 &mut ws.qyy[i], &mut ws.qyz[i], &mut ws.qzz[i],
1200 alpha.0
1201 );
1202 }
1203 }
1204
1205 std::mem::swap(&mut ws.phi, &mut ws.phi_);
1207 std::mem::swap(&mut ws.chi, &mut ws.chi_);
1208 std::mem::swap(&mut ws.wx, &mut ws.wx_);
1209 std::mem::swap(&mut ws.wy, &mut ws.wy_);
1210 std::mem::swap(&mut ws.wz, &mut ws.wz_);
1211
1212 for i in 0..b_total {
1216 ws.temp1[i] = if mask0_sub[i] != 0 { ws.eta[i] } else { 0.0 };
1217 }
1218 apply_laplacian_inplace(&mut ws.temp2, &ws.temp1, &mask0_sub, bx, by, bz, vsx, vsy, vsz);
1219
1220 for i in 0..b_total {
1221 let denom = 1.0 + if mask_eroded_sub[i] != 0 { tau } else { 0.0 };
1222 ws.phi[i] = (ws.phi_[i] + tau * ws.temp2[i]) / denom;
1223 }
1224
1225 crate::utils::gradient::bdiv_masked_inplace_f32(
1227 &mut ws.temp1, &ws.px, &ws.py, &ws.pz, &mask0_sub, &sub_grid
1228 );
1229
1230 for i in 0..b_total {
1231 ws.gx[i] = if mask0_sub[i] != 0 { ws.eta[i] } else { 0.0 };
1232 }
1233 apply_stencil(&mut ws.temp2, &ws.gx, &stencil, &mask0_sub, bx, by, bz);
1234
1235 for i in 0..b_total {
1236 ws.chi[i] = ws.chi_[i] + tau_step * (ws.temp1[i] - ws.temp2[i]);
1237 }
1238
1239 for i in 0..b_total {
1241 let m = if mask0_sub[i] != 0 { 1.0 } else { 0.0 };
1242 ws.sxx[i] = ws.qxx[i] * m;
1243 ws.sxy[i] = ws.qxy[i] * m;
1244 ws.sxz[i] = ws.qxz[i] * m;
1245 ws.syy[i] = ws.qyy[i] * m;
1246 ws.syz[i] = ws.qyz[i] * m;
1247 ws.szz[i] = ws.qzz[i] * m;
1248 }
1249
1250 crate::utils::gradient::symdiv_inplace_f32(
1251 &mut ws.divqx, &mut ws.divqy, &mut ws.divqz,
1252 &ws.sxx, &ws.sxy, &ws.sxz, &ws.syy, &ws.syz, &ws.szz,
1253 &sub_grid
1254 );
1255
1256 for i in 0..b_total {
1258 ws.wx[i] = ws.wx_[i];
1259 ws.wy[i] = ws.wy_[i];
1260 ws.wz[i] = ws.wz_[i];
1261 if mask_eroded_sub[i] != 0 {
1262 ws.wx[i] += tau_step * (ws.px[i] + ws.divqx[i]);
1263 ws.wy[i] += tau_step * (ws.py[i] + ws.divqy[i]);
1264 ws.wz[i] += tau_step * (ws.pz[i] + ws.divqz[i]);
1265 }
1266 }
1267
1268 for i in 0..b_total {
1270 ws.phi_[i] = 2.0 * ws.phi[i] - ws.phi_[i];
1271 ws.chi_[i] = 2.0 * ws.chi[i] - ws.chi_[i];
1272 ws.wx_[i] = 2.0 * ws.wx[i] - ws.wx_[i];
1273 ws.wy_[i] = 2.0 * ws.wy[i] - ws.wy_[i];
1274 ws.wz_[i] = 2.0 * ws.wz[i] - ws.wz_[i];
1275 }
1276 }
1277
1278 progress(final_iter, params.iterations);
1279
1280 let gamma = 42.5781f32; let scale = 1.0 / (2.0 * PI * params.te * params.fieldstrength * gamma);
1283
1284 let mut result = vec![0.0f32; n_total];
1286
1287 let mut chi_scaled = vec![0.0f32; b_total];
1289 for i in 0..b_total {
1290 if mask_eroded_sub[i] != 0 {
1291 chi_scaled[i] = ws.chi[i] * scale;
1292 }
1293 }
1294
1295 insert_subvolume(&mut result, &chi_scaled, &bbox, nx, ny, nz);
1297
1298 result.iter().map(|&v| v as f64).collect()
1299}
1300
1301#[cfg(test)]
1302mod tests {
1303 use super::*;
1304
1305 #[test]
1306 fn test_dipole_stencil() {
1307 let stencil = compute_dipole_stencil((1.0, 1.0, 1.0), (0.0, 0.0, 1.0));
1308
1309 let mut sum = 0.0f32;
1310 for k in 0..3 {
1311 for j in 0..3 {
1312 for i in 0..3 {
1313 sum += stencil[i][j][k];
1314 }
1315 }
1316 }
1317 assert!(sum.abs() < 1e-6, "Stencil sum should be ~0, got {}", sum);
1318 }
1319
1320 #[test]
1321 fn test_phase_laplacian() {
1322 let nx = 4;
1323 let ny = 4;
1324 let nz = 4;
1325 let n = nx * ny * nz;
1326
1327 let phase = vec![1.0f32; n];
1328 let mask = vec![1u8; n];
1329
1330 let lap = compute_phase_laplacian(&phase, &mask, nx, ny, nz, 1.0, 1.0, 1.0);
1331
1332 let max_val = lap.iter().map(|x| x.abs()).fold(0.0f32, f32::max);
1333 assert!(max_val < 1e-5, "Laplacian of constant should be ~0, got max {}", max_val);
1334 }
1335
1336 #[test]
1337 fn test_erode_mask() {
1338 let nx = 5;
1339 let ny = 5;
1340 let nz = 5;
1341
1342 let mask = vec![1u8; nx * ny * nz];
1343 let grid5 = crate::Grid::new(nx, ny, nz, 1.0, 1.0, 1.0);
1344 let eroded = erode_mask(&mask, &grid5, 1);
1345
1346 let center = 2 + 2 * nx + 2 * nx * ny;
1347 assert_eq!(eroded[center], 1);
1348 assert_eq!(eroded[0], 0);
1349 }
1350
1351 #[test]
1352 fn test_default_alpha() {
1353 let (a0, a1) = get_default_alpha(2);
1354 assert!((a0 - 0.001).abs() < 1e-6);
1355 assert!((a1 - 0.001).abs() < 1e-6);
1356 }
1357
1358 #[test]
1359 fn test_bounding_box() {
1360 let nx = 10;
1361 let ny = 10;
1362 let nz = 10;
1363 let mut mask = vec![0u8; nx * ny * nz];
1364
1365 for k in 3..7 {
1367 for j in 3..7 {
1368 for i in 3..7 {
1369 mask[i + j * nx + k * nx * ny] = 1;
1370 }
1371 }
1372 }
1373
1374 let bbox = BoundingBox::from_mask(&mask, nx, ny, nz, 1);
1375
1376 assert_eq!(bbox.i_min, 2);
1378 assert_eq!(bbox.i_max, 8);
1379 assert_eq!(bbox.j_min, 2);
1380 assert_eq!(bbox.j_max, 8);
1381 }
1382
1383 #[test]
1384 fn test_tgv_qsm_small() {
1385 let n = 12;
1386 let n_total = n * n * n;
1387 let center = 6.0_f32;
1388 let radius = 4.0_f32;
1389
1390 let mut mask = vec![0u8; n_total];
1392 for k in 0..n {
1393 for j in 0..n {
1394 for i in 0..n {
1395 let dx = i as f32 - center;
1396 let dy = j as f32 - center;
1397 let dz = k as f32 - center;
1398 if (dx * dx + dy * dy + dz * dz).sqrt() < radius {
1399 mask[i + j * n + k * n * n] = 1;
1400 }
1401 }
1402 }
1403 }
1404
1405 let mut phase = vec![0.0f64; n_total];
1407 for k in 0..n {
1408 for j in 0..n {
1409 for i in 0..n {
1410 let idx = i + j * n + k * n * n;
1411 if mask[idx] != 0 {
1412 phase[idx] = 0.1 * k as f64;
1413 }
1414 }
1415 }
1416 }
1417
1418 let params = TgvParams {
1419 iterations: 10,
1420 erosions: 1,
1421 ..TgvParams::default()
1422 };
1423
1424 let grid = crate::Grid::new(n, n, n, 1.0, 1.0, 1.0);
1425 let result = tgv_qsm(&phase, &mask, &grid, ¶ms, (0.0, 0.0, 1.0), |_, _| {});
1426
1427 assert_eq!(result.len(), n_total);
1428
1429 for &v in &result {
1431 assert!(v.is_finite(), "Result contains non-finite value: {}", v);
1432 }
1433
1434 let has_nonzero = result.iter().any(|&v| v.abs() > 1e-20);
1436 assert!(has_nonzero, "Result is entirely zero; expected non-zero values within mask");
1437 }
1438
1439 #[test]
1440 fn test_oblique_stencil() {
1441 let stencil = compute_oblique_stencil((1.0, 1.0, 1.0), (0.0, 0.0, 1.0));
1442
1443 let mut sum = 0.0f32;
1445 for k in 0..3 {
1446 for j in 0..3 {
1447 for i in 0..3 {
1448 sum += stencil[i][j][k];
1449 }
1450 }
1451 }
1452 assert!(
1453 sum.abs() < 1e-4,
1454 "Oblique stencil sum should be ~0, got {}",
1455 sum
1456 );
1457
1458 let mut off_sum = 0.0f32;
1460 for k in 0..3 {
1461 for j in 0..3 {
1462 for i in 0..3 {
1463 if !(i == 1 && j == 1 && k == 1) {
1464 off_sum += stencil[i][j][k];
1465 }
1466 }
1467 }
1468 }
1469 assert!(
1470 (stencil[1][1][1] + off_sum).abs() < 1e-6,
1471 "Center should be -sum(others): center={}, off_sum={}",
1472 stencil[1][1][1], off_sum
1473 );
1474 }
1475
1476 #[test]
1477 fn test_oblique_stencil_aniso() {
1478 let stencil = compute_oblique_stencil((1.0, 1.0, 2.0), (0.2, 0.3, 0.9));
1479
1480 let mut sum = 0.0f32;
1482 for k in 0..3 {
1483 for j in 0..3 {
1484 for i in 0..3 {
1485 sum += stencil[i][j][k];
1486 }
1487 }
1488 }
1489 assert!(
1490 sum.abs() < 1e-4,
1491 "Anisotropic oblique stencil sum should be ~0, got {}",
1492 sum
1493 );
1494 }
1495
1496 #[test]
1497 fn test_get_default_iterations() {
1498 let it = get_default_iterations((1.0, 1.0, 1.0), 1.0);
1500 assert!(it >= 1000, "Iterations should be >= 1000 for 1mm iso, got {}", it);
1501
1502 let it_large = get_default_iterations((2.0, 2.0, 2.0), 1.0);
1504 assert!(it_large >= 1000, "Iterations should still be >= 1000 for 2mm iso");
1505
1506 let it_small = get_default_iterations((0.5, 0.5, 0.5), 1.0);
1508 assert!(it_small > it, "Smaller voxels should need more iterations: {} vs {}", it_small, it);
1509
1510 let it_fast = get_default_iterations((1.0, 1.0, 1.0), 3.0);
1512 assert!(it_fast < it, "Higher step_size should give fewer iterations: {} vs {}", it_fast, it);
1513 }
1514
1515 #[test]
1516 fn test_compute_relative_change() {
1517 let chi = vec![1.0f32, 2.0, 3.0, 4.0];
1519 let chi_prev = vec![1.0f32, 2.0, 3.0, 4.0];
1520 let mask = vec![1u8, 1, 1, 1];
1521 let rc = compute_relative_change(&chi, &chi_prev, &mask);
1522 assert!(rc.abs() < 1e-10, "Identical arrays should give 0 change, got {}", rc);
1523
1524 let chi2 = vec![1.1f32, 2.0, 3.0, 4.0];
1526 let rc2 = compute_relative_change(&chi2, &chi_prev, &mask);
1527 assert!(rc2 > 0.0, "Different arrays should give positive change");
1528 let expected = (0.01f32 / 30.21).sqrt();
1530 assert!(
1531 (rc2 - expected).abs() < 1e-5,
1532 "Expected relative change ~{}, got {}",
1533 expected,
1534 rc2
1535 );
1536
1537 let mask_partial = vec![1u8, 0, 0, 0];
1539 let chi3 = vec![2.0f32, 999.0, 999.0, 999.0];
1540 let chi_prev3 = vec![1.0f32, 0.0, 0.0, 0.0];
1541 let rc3 = compute_relative_change(&chi3, &chi_prev3, &mask_partial);
1542 let expected3 = (1.0f32 / 4.0).sqrt();
1544 assert!(
1545 (rc3 - expected3).abs() < 1e-6,
1546 "Masked relative change expected {}, got {}",
1547 expected3,
1548 rc3
1549 );
1550
1551 let zeros = vec![0.0f32; 4];
1553 let rc4 = compute_relative_change(&zeros, &zeros, &mask);
1554 assert!(
1555 (rc4 - 1.0).abs() < 1e-6,
1556 "Zero norm should return 1.0, got {}",
1557 rc4
1558 );
1559 }
1560
1561 #[test]
1562 fn test_tgv_convergence() {
1563 let n = 12;
1565 let n_total = n * n * n;
1566 let center = 6.0_f32;
1567 let radius = 4.0_f32;
1568
1569 let mut mask = vec![0u8; n_total];
1570 for k in 0..n {
1571 for j in 0..n {
1572 for i in 0..n {
1573 let dx = i as f32 - center;
1574 let dy = j as f32 - center;
1575 let dz = k as f32 - center;
1576 if (dx * dx + dy * dy + dz * dz).sqrt() < radius {
1577 mask[i + j * n + k * n * n] = 1;
1578 }
1579 }
1580 }
1581 }
1582
1583 let phase = vec![0.0f64; n_total];
1584
1585 let params = TgvParams {
1586 iterations: 1000,
1587 erosions: 1,
1588 tol: 1.1, ..TgvParams::default()
1590 };
1591
1592 let grid = crate::Grid::new(n, n, n, 1.0, 1.0, 1.0);
1593 let progress_iters = std::cell::RefCell::new(Vec::new());
1594 let result = tgv_qsm(
1595 &phase, &mask, &grid, ¶ms, (0.0, 0.0, 1.0),
1596 |iter, _total| { progress_iters.borrow_mut().push(iter); }
1597 );
1598
1599 assert_eq!(result.len(), n_total);
1600
1601 let max_abs = result.iter().map(|v| v.abs()).fold(0.0f64, f64::max);
1603 assert!(
1604 max_abs < 1e-3,
1605 "Zero-phase TGV result should be ~0, got max abs {}",
1606 max_abs
1607 );
1608
1609 let iters = progress_iters.borrow();
1612 let &last_iter = iters.last().unwrap();
1613 assert!(
1614 last_iter <= 100,
1615 "Expected early convergence by iter 100, but last progress was at iter {}",
1616 last_iter
1617 );
1618 }
1619}