1use std::f64::consts::PI;
11
12use super::config::*;
13
14pub fn run_dipole_inversion(
29 local_field_ppm: &[f64],
30 mask: &[u8],
31 metadata: &ScanMetadata,
32 config: &InversionConfig,
33 magnitude: Option<&[f64]>,
34 progress: &mut dyn FnMut(usize, usize),
35) -> Result<Vec<f64>, PipelineError> {
36 let grid = metadata.grid();
37 let bdir = metadata.b0_direction;
38 let n_voxels = grid.n_total();
39
40 if local_field_ppm.len() != n_voxels {
41 return Err(PipelineError::DimensionMismatch {
42 expected: n_voxels,
43 got: local_field_ppm.len(),
44 });
45 }
46
47 let chi = match config.algorithm {
48 InversionAlgorithm::Tkd => {
49 crate::inversion::tkd(
50 local_field_ppm, mask, &grid, bdir, &config.tkd,
51 )
52 }
53 InversionAlgorithm::Tsvd => {
54 crate::inversion::tsvd(
55 local_field_ppm, mask, &grid, bdir, &config.tsvd,
56 )
57 }
58 InversionAlgorithm::Tikhonov => {
59 crate::inversion::tikhonov(
60 local_field_ppm, mask, &grid, bdir, &config.tikhonov,
61 )
62 }
63 InversionAlgorithm::Tv => {
64 crate::inversion::tv_admm(
65 local_field_ppm, mask, &grid, bdir, &config.tv, progress,
66 )
67 }
68 InversionAlgorithm::Rts => {
69 crate::inversion::rts(
70 local_field_ppm, mask, &grid, bdir, &config.rts, progress,
71 )
72 }
73 InversionAlgorithm::Nltv => {
74 crate::inversion::nltv(
75 local_field_ppm, mask, &grid, bdir, &config.nltv, progress,
76 )
77 }
78 InversionAlgorithm::Medi => {
79 let gamma_hz = 42.576e6;
81 let te1 = metadata.echo_times.first().copied().unwrap_or(0.005);
82 let ppm_to_rad = 2.0 * PI * gamma_hz * metadata.field_strength * te1 * 1e-6;
83
84 let local_field_rad: Vec<f64> = local_field_ppm.iter()
85 .map(|&v| v * ppm_to_rad)
86 .collect();
87
88 let uniform_mag = vec![1.0f64; n_voxels];
89 let mag = magnitude.unwrap_or(&uniform_mag);
90 let n_std = vec![1.0f64; n_voxels];
91
92 let chi_rad = crate::inversion::medi(
93 &local_field_rad, &n_std, mag, mask,
94 &grid, bdir, &config.medi, progress,
95 );
96
97 let rad_to_ppm = 1.0 / ppm_to_rad;
98 chi_rad.iter().map(|&v| v * rad_to_ppm).collect()
99 }
100 InversionAlgorithm::Tfi => {
101 let uniform_mag = vec![1.0f64; n_voxels];
106 let mag = magnitude.unwrap_or(&uniform_mag);
107 let n_std = vec![1.0f64; n_voxels];
108
109 crate::inversion::tfi(
110 local_field_ppm, &n_std, mag, mask,
111 &grid, bdir, &config.tfi, progress,
112 )
113 }
114 InversionAlgorithm::Ilsqr => {
115 let (chi, _, _, _) = crate::inversion::ilsqr(
116 local_field_ppm, mask, &grid, bdir, &config.ilsqr, &mut *progress,
117 );
118 chi
119 }
120 InversionAlgorithm::Ndi => {
121 crate::inversion::ndi(
122 local_field_ppm, mask, &grid, bdir, &config.ndi, progress,
123 )
124 }
125 InversionAlgorithm::Fansi => {
126 let params = crate::inversion::FansiParams { is_tgv: false, ..config.fansi.clone() };
127 crate::inversion::fansi(
128 local_field_ppm, mask, &grid, bdir, ¶ms, progress,
129 )
130 }
131 InversionAlgorithm::FansiTgv => {
132 let params = crate::inversion::FansiParams { is_tgv: true, ..config.fansi.clone() };
133 crate::inversion::fansi(
134 local_field_ppm, mask, &grid, bdir, ¶ms, progress,
135 )
136 }
137 InversionAlgorithm::L1qsm => {
138 crate::inversion::l1qsm(
139 local_field_ppm, mask, &grid, bdir, &config.l1qsm, progress,
140 )
141 }
142 InversionAlgorithm::Whqsm => {
143 crate::inversion::whqsm(
144 local_field_ppm, mask, &grid, bdir, &config.whqsm, progress,
145 )
146 }
147 InversionAlgorithm::Hdqsm => {
148 crate::inversion::hdqsm(
149 local_field_ppm, mask, &grid, bdir, &config.hdqsm, progress,
150 )
151 }
152 InversionAlgorithm::AmpPe => {
153 let params = crate::inversion::AmpPeParams {
157 b0: metadata.field_strength,
158 ..config.amp_pe.clone()
159 };
160 crate::inversion::amp_pe(
161 local_field_ppm, mask, magnitude, &grid, bdir, ¶ms, |i, n| progress(i, n),
162 )
163 }
164 InversionAlgorithm::Xqsm => run_xqsm(local_field_ppm, mask, &grid, config.tile, progress)?,
165 InversionAlgorithm::Qsmnet => run_qsmnet(local_field_ppm, mask, &grid, "qsmnet", config.tile, progress)?,
166 InversionAlgorithm::QsmnetPlus => run_qsmnet(local_field_ppm, mask, &grid, "qsmnet-plus", config.tile, progress)?,
167 InversionAlgorithm::Autoqsm => run_autoqsm(local_field_ppm, mask, &grid)?,
168 InversionAlgorithm::Qsmgan => run_qsmgan(local_field_ppm, mask, &grid)?,
169 InversionAlgorithm::Ir2qsm => run_ir2qsm(local_field_ppm, mask, &grid, config.tile, progress)?,
170 InversionAlgorithm::Lpcnn => run_lpcnn(local_field_ppm, mask, &grid, bdir, config.tile, progress)?,
171 InversionAlgorithm::ModlQsm => run_modl_qsm(local_field_ppm, mask, &grid, bdir, config.tile, progress)?,
172 InversionAlgorithm::Nextqsm => run_nextqsm(local_field_ppm, mask, &grid, bdir, config.tile, progress)?,
174 InversionAlgorithm::Tgv | InversionAlgorithm::Qsmart => {
175 return Err(PipelineError::InvalidConfig(
176 format!("{:?} should use run_tgv or run_qsmart", config.algorithm),
177 ));
178 }
179 InversionAlgorithm::Iqsm | InversionAlgorithm::IqsmPlus => {
181 return Err(PipelineError::InvalidConfig(
182 format!("{:?} should use run_iqsm / run_iqsm_plus (it takes wrapped phase)", config.algorithm),
183 ));
184 }
185 };
186
187 Ok(chi)
188}
189
190#[cfg(feature = "onnx")]
194fn run_xqsm(
195 local_field_ppm: &[f64],
196 mask: &[u8],
197 grid: &crate::Grid,
198 tile: Option<(usize, usize)>,
199 progress: &mut dyn FnMut(usize, usize),
200) -> Result<Vec<f64>, PipelineError> {
201 let spec = crate::models::find_model("xqsm")
202 .ok_or_else(|| PipelineError::InvalidConfig("xqsm not in model registry".into()))?;
203 let bytes = crate::models::primary_weight_bytes(spec)
204 .map_err(PipelineError::InvalidConfig)?;
205 match tile {
206 Some((core, halo)) => crate::inversion::xqsm_tiled(
207 local_field_ppm, mask, grid, &bytes, &crate::inversion::TileConfig { core, halo }, progress,
208 ),
209 None => crate::inversion::xqsm(local_field_ppm, mask, grid, &bytes),
210 }
211 .map_err(|e| PipelineError::AlgorithmError(e.to_string()))
212}
213
214#[cfg(not(feature = "onnx"))]
215fn run_xqsm(
216 _local_field_ppm: &[f64],
217 _mask: &[u8],
218 _grid: &crate::Grid,
219 _tile: Option<(usize, usize)>,
220 _progress: &mut dyn FnMut(usize, usize),
221) -> Result<Vec<f64>, PipelineError> {
222 Err(PipelineError::InvalidConfig(
223 "xQSM requires building qsm-core with the 'onnx' feature".into(),
224 ))
225}
226
227#[cfg(feature = "onnx")]
236pub fn run_nextqsm(
237 total_field_ppm: &[f64],
238 mask: &[u8],
239 grid: &crate::Grid,
240 bdir: (f64, f64, f64),
241 tile: Option<(usize, usize)>,
242 progress: &mut dyn FnMut(usize, usize),
243) -> Result<Vec<f64>, PipelineError> {
244 let spec = crate::models::find_model("nextqsm")
245 .ok_or_else(|| PipelineError::InvalidConfig("nextqsm not in model registry".into()))?;
246 let files = crate::models::all_weight_bytes(spec).map_err(PipelineError::InvalidConfig)?;
247 let [bf, vjp] = &files[..] else {
248 return Err(PipelineError::InvalidConfig(
249 format!("nextqsm expects 2 weight files (BFR, VJP), got {}", files.len()),
250 ));
251 };
252 match tile {
253 Some((core, halo)) => crate::inversion::nextqsm_tiled(
254 total_field_ppm, mask, grid, bdir, bf, vjp, &crate::inversion::TileConfig { core, halo }, progress,
255 ),
256 None => crate::inversion::nextqsm(total_field_ppm, mask, grid, bdir, bf, vjp),
257 }
258 .map_err(|e| PipelineError::AlgorithmError(e.to_string()))
259}
260
261#[cfg(not(feature = "onnx"))]
263pub fn run_nextqsm(
264 _total_field_ppm: &[f64],
265 _mask: &[u8],
266 _grid: &crate::Grid,
267 _bdir: (f64, f64, f64),
268 _tile: Option<(usize, usize)>,
269 _progress: &mut dyn FnMut(usize, usize),
270) -> Result<Vec<f64>, PipelineError> {
271 Err(PipelineError::InvalidConfig(
272 "NeXtQSM requires building qsm-core with the 'onnx' feature".into(),
273 ))
274}
275
276#[cfg(feature = "onnx")]
280fn run_qsmnet(
281 local_field_ppm: &[f64],
282 mask: &[u8],
283 grid: &crate::Grid,
284 model_id: &str,
285 tile: Option<(usize, usize)>,
286 progress: &mut dyn FnMut(usize, usize),
287) -> Result<Vec<f64>, PipelineError> {
288 use crate::inversion::QsmnetNorm;
289 let norm = match model_id {
290 "qsmnet-plus" => QsmnetNorm::qsmnet_plus(),
291 _ => QsmnetNorm::qsmnet(),
292 };
293 let spec = crate::models::find_model(model_id).ok_or_else(|| {
294 PipelineError::InvalidConfig(format!("{model_id} not in model registry"))
295 })?;
296 let bytes = crate::models::primary_weight_bytes(spec)
297 .map_err(PipelineError::InvalidConfig)?;
298 match tile {
299 Some((core, halo)) => crate::inversion::qsmnet_tiled(
300 local_field_ppm, mask, grid, &bytes, &norm, &crate::inversion::TileConfig { core, halo }, progress,
301 ),
302 None => crate::inversion::qsmnet(local_field_ppm, mask, grid, &bytes, &norm),
303 }
304 .map_err(|e| PipelineError::AlgorithmError(e.to_string()))
305}
306
307#[cfg(not(feature = "onnx"))]
308fn run_qsmnet(
309 _local_field_ppm: &[f64],
310 _mask: &[u8],
311 _grid: &crate::Grid,
312 _model_id: &str,
313 _tile: Option<(usize, usize)>,
314 _progress: &mut dyn FnMut(usize, usize),
315) -> Result<Vec<f64>, PipelineError> {
316 Err(PipelineError::InvalidConfig(
317 "QSMnet requires building qsm-core with the 'onnx' feature".into(),
318 ))
319}
320
321#[cfg(feature = "onnx")]
324fn run_autoqsm(
325 field: &[f64],
326 mask: &[u8],
327 grid: &crate::Grid,
328) -> Result<Vec<f64>, PipelineError> {
329 let spec = crate::models::find_model("autoqsm")
330 .ok_or_else(|| PipelineError::InvalidConfig("autoqsm not in model registry".into()))?;
331 let bytes = crate::models::primary_weight_bytes(spec).map_err(PipelineError::InvalidConfig)?;
332 crate::inversion::autoqsm(field, mask, grid, &bytes)
333 .map_err(|e| PipelineError::AlgorithmError(e.to_string()))
334}
335
336#[cfg(not(feature = "onnx"))]
337fn run_autoqsm(
338 _field: &[f64],
339 _mask: &[u8],
340 _grid: &crate::Grid,
341) -> Result<Vec<f64>, PipelineError> {
342 Err(PipelineError::InvalidConfig(
343 "AutoQSM requires building qsm-core with the 'onnx' feature".into(),
344 ))
345}
346
347#[cfg(feature = "onnx")]
349fn run_qsmgan(local_field_ppm: &[f64], mask: &[u8], grid: &crate::Grid) -> Result<Vec<f64>, PipelineError> {
350 let bytes = crate::models::primary_weight("qsmgan").map_err(PipelineError::InvalidConfig)?;
351 crate::inversion::qsmgan(local_field_ppm, mask, grid, &bytes)
352 .map_err(|e| PipelineError::AlgorithmError(e.to_string()))
353}
354
355#[cfg(feature = "onnx")]
357fn run_ir2qsm(
358 local_field_ppm: &[f64],
359 mask: &[u8],
360 grid: &crate::Grid,
361 tile: Option<(usize, usize)>,
362 progress: &mut dyn FnMut(usize, usize),
363) -> Result<Vec<f64>, PipelineError> {
364 let bytes = crate::models::primary_weight("ir2qsm").map_err(PipelineError::InvalidConfig)?;
365 match tile {
366 Some((core, halo)) => crate::inversion::ir2qsm_tiled(
367 local_field_ppm, mask, grid, &bytes, &crate::inversion::TileConfig { core, halo }, progress,
368 ),
369 None => crate::inversion::ir2qsm(local_field_ppm, mask, grid, &bytes),
370 }
371 .map_err(|e| PipelineError::AlgorithmError(e.to_string()))
372}
373
374#[cfg(feature = "onnx")]
377fn run_lpcnn(
378 local_field_ppm: &[f64],
379 mask: &[u8],
380 grid: &crate::Grid,
381 bdir: (f64, f64, f64),
382 tile: Option<(usize, usize)>,
383 progress: &mut dyn FnMut(usize, usize),
384) -> Result<Vec<f64>, PipelineError> {
385 let bytes = crate::models::primary_weight("lpcnn").map_err(PipelineError::InvalidConfig)?;
386 match tile {
387 Some((core, halo)) => crate::inversion::lpcnn_tiled(
388 local_field_ppm, mask, grid, bdir, &bytes, &crate::inversion::TileConfig { core, halo }, progress,
389 ),
390 None => crate::inversion::lpcnn(local_field_ppm, mask, grid, bdir, &bytes),
391 }
392 .map_err(|e| PipelineError::AlgorithmError(e.to_string()))
393}
394
395#[cfg(feature = "onnx")]
398fn run_modl_qsm(
399 local_field_ppm: &[f64],
400 mask: &[u8],
401 grid: &crate::Grid,
402 bdir: (f64, f64, f64),
403 tile: Option<(usize, usize)>,
404 progress: &mut dyn FnMut(usize, usize),
405) -> Result<Vec<f64>, PipelineError> {
406 let bytes = crate::models::primary_weight("modl-qsm").map_err(PipelineError::InvalidConfig)?;
407 match tile {
408 Some((core, halo)) => crate::inversion::modl_qsm_tiled(
409 local_field_ppm, mask, grid, bdir, &bytes, &crate::inversion::TileConfig { core, halo }, progress,
410 ),
411 None => crate::inversion::modl_qsm(local_field_ppm, mask, grid, bdir, &bytes),
412 }
413 .map_err(|e| PipelineError::AlgorithmError(e.to_string()))
414}
415
416#[cfg(not(feature = "onnx"))]
417fn run_qsmgan(_f: &[f64], _m: &[u8], _g: &crate::Grid) -> Result<Vec<f64>, PipelineError> {
418 Err(PipelineError::InvalidConfig("QSMGAN requires building qsm-core with the 'onnx' feature".into()))
419}
420#[cfg(not(feature = "onnx"))]
421fn run_ir2qsm(
422 _f: &[f64],
423 _m: &[u8],
424 _g: &crate::Grid,
425 _tile: Option<(usize, usize)>,
426 _progress: &mut dyn FnMut(usize, usize),
427) -> Result<Vec<f64>, PipelineError> {
428 Err(PipelineError::InvalidConfig("IR2QSM requires building qsm-core with the 'onnx' feature".into()))
429}
430#[cfg(not(feature = "onnx"))]
431fn run_lpcnn(
432 _f: &[f64],
433 _m: &[u8],
434 _g: &crate::Grid,
435 _b: (f64, f64, f64),
436 _tile: Option<(usize, usize)>,
437 _progress: &mut dyn FnMut(usize, usize),
438) -> Result<Vec<f64>, PipelineError> {
439 Err(PipelineError::InvalidConfig("LPCNN requires building qsm-core with the 'onnx' feature".into()))
440}
441#[cfg(not(feature = "onnx"))]
442fn run_modl_qsm(_f: &[f64], _m: &[u8], _g: &crate::Grid, _b: (f64, f64, f64), _tile: Option<(usize, usize)>, _progress: &mut dyn FnMut(usize, usize)) -> Result<Vec<f64>, PipelineError> {
443 Err(PipelineError::InvalidConfig("MoDL-QSM requires building qsm-core with the 'onnx' feature".into()))
444}
445
446#[cfg(feature = "onnx")]
450const IQSM_PHASE_SIGN: f64 = -1.0;
451#[cfg(feature = "onnx")]
452const IQSM_ERODED_RAD: i32 = 3;
453
454#[cfg(feature = "onnx")]
462pub fn run_iqsm(
463 phases: &[&[f64]],
464 magnitudes: &[&[f64]],
465 mask: &[u8],
466 metadata: &ScanMetadata,
467 reference: QsmReference,
468) -> Result<Vec<f64>, PipelineError> {
469 let grid = metadata.grid();
470 let bytes = crate::models::primary_weight("iqsm").map_err(PipelineError::InvalidConfig)?;
471 let chi = crate::inversion::iqsm_multi_echo(
472 phases, magnitudes, mask, &grid, &metadata.echo_times,
473 metadata.field_strength, IQSM_PHASE_SIGN, IQSM_ERODED_RAD, &bytes,
474 ).map_err(|e| PipelineError::AlgorithmError(e.to_string()))?;
475 Ok(super::referencing::apply_reference(&chi, mask, reference))
476}
477
478#[cfg(not(feature = "onnx"))]
479pub fn run_iqsm(
480 _phases: &[&[f64]], _magnitudes: &[&[f64]], _mask: &[u8],
481 _metadata: &ScanMetadata, _reference: QsmReference,
482) -> Result<Vec<f64>, PipelineError> {
483 Err(PipelineError::InvalidConfig("iQSM requires building qsm-core with the 'onnx' feature".into()))
484}
485
486#[cfg(feature = "onnx")]
489pub fn run_iqsm_plus(
490 phases: &[&[f64]],
491 magnitudes: &[&[f64]],
492 mask: &[u8],
493 metadata: &ScanMetadata,
494 reference: QsmReference,
495) -> Result<Vec<f64>, PipelineError> {
496 let grid = metadata.grid();
497 let bytes = crate::models::primary_weight("iqsm-plus").map_err(PipelineError::InvalidConfig)?;
498 let chi = crate::inversion::iqsm_plus_multi_echo(
499 phases, magnitudes, mask, &grid, &metadata.echo_times,
500 metadata.field_strength, metadata.b0_direction, IQSM_PHASE_SIGN, IQSM_ERODED_RAD, &bytes,
501 ).map_err(|e| PipelineError::AlgorithmError(e.to_string()))?;
502 Ok(super::referencing::apply_reference(&chi, mask, reference))
503}
504
505#[cfg(not(feature = "onnx"))]
506pub fn run_iqsm_plus(
507 _phases: &[&[f64]], _magnitudes: &[&[f64]], _mask: &[u8],
508 _metadata: &ScanMetadata, _reference: QsmReference,
509) -> Result<Vec<f64>, PipelineError> {
510 Err(PipelineError::InvalidConfig("iQSM+ requires building qsm-core with the 'onnx' feature".into()))
511}
512
513#[cfg(feature = "onnx")]
519pub fn run_iqfm(
520 phases: &[&[f64]],
521 magnitudes: &[&[f64]],
522 mask: &[u8],
523 metadata: &ScanMetadata,
524) -> Result<Vec<f64>, PipelineError> {
525 let grid = metadata.grid();
526 let bytes = crate::models::primary_weight("iqfm").map_err(PipelineError::InvalidConfig)?;
527 crate::inversion::iqfm_multi_echo(
528 phases, magnitudes, mask, &grid, &metadata.echo_times,
529 metadata.field_strength, IQSM_PHASE_SIGN, IQSM_ERODED_RAD, &bytes,
530 ).map_err(|e| PipelineError::AlgorithmError(e.to_string()))
531}
532
533#[cfg(not(feature = "onnx"))]
534pub fn run_iqfm(
535 _phases: &[&[f64]], _magnitudes: &[&[f64]], _mask: &[u8], _metadata: &ScanMetadata,
536) -> Result<Vec<f64>, PipelineError> {
537 Err(PipelineError::InvalidConfig("iQFM requires building qsm-core with the 'onnx' feature".into()))
538}
539
540pub fn run_tgv(
549 phases: &[&[f64]],
550 magnitudes: Option<&[&[f64]]>,
551 mask: &[u8],
552 metadata: &ScanMetadata,
553 field_mapping_config: &FieldMappingConfig,
554 tgv_params: &crate::inversion::TgvParams,
555 reference: QsmReference,
556 progress: &mut dyn FnMut(usize, usize),
557) -> Result<Vec<f64>, PipelineError> {
558 let grid = metadata.grid();
559 let (bx, by, bz) = metadata.b0_direction;
560
561 let phase_data: Vec<f64> = if phases.len() > 1 {
564 let field_result = super::field_mapping::run_field_mapping(
565 phases, magnitudes, mask, metadata,
566 field_mapping_config, &mut |_, _| {},
567 )?;
568 let gamma_hz = 42.576e6;
569 let te1 = metadata.echo_times[0];
570 let ppm_to_rad = 2.0 * PI * gamma_hz * metadata.field_strength * te1 * 1e-6;
571 field_result.b0_field_ppm.iter().map(|&v| v * ppm_to_rad).collect()
572 } else {
573 phases[0].to_vec()
574 };
575
576 let chi_ppm = crate::inversion::tgv_qsm(
577 &phase_data, mask, &grid, tgv_params, (bx, by, bz), &mut *progress,
578 );
579
580 Ok(super::referencing::apply_reference(&chi_ppm, mask, reference))
581}
582
583#[cfg(test)]
584mod tests {
585 use super::*;
586
587 #[test]
588 fn test_inversion_tkd() {
589 let (nx, ny, nz) = (8, 8, 8);
590 let n = nx * ny * nz;
591 let field = vec![0.01; n];
592 let mask = vec![1u8; n];
593 let meta = ScanMetadata {
594 dims: (nx, ny, nz),
595 voxel_size: (1.0, 1.0, 1.0),
596 echo_times: vec![0.005],
597 field_strength: 3.0,
598 b0_direction: (0.0, 0.0, 1.0),
599 };
600 let config = InversionConfig {
601 algorithm: InversionAlgorithm::Tkd,
602 ..Default::default()
603 };
604
605 let result = run_dipole_inversion(
606 &field, &mask, &meta, &config, None, &mut |_, _| {},
607 );
608 assert!(result.is_ok());
609 assert_eq!(result.unwrap().len(), n);
610 }
611
612 fn make_inversion_test(alg: InversionAlgorithm) -> Vec<f64> {
613 let (nx, ny, nz) = (8, 8, 8);
614 let n = nx * ny * nz;
615 let field = vec![0.01; n];
616 let mask = vec![1u8; n];
617 let meta = ScanMetadata {
618 dims: (nx, ny, nz), voxel_size: (1.0, 1.0, 1.0),
619 echo_times: vec![0.005], field_strength: 3.0, b0_direction: (0.0, 0.0, 1.0),
620 };
621 let config = InversionConfig { algorithm: alg, ..Default::default() };
622 run_dipole_inversion(&field, &mask, &meta, &config, None, &mut |_, _| {}).unwrap()
623 }
624
625 #[test]
626 fn test_inversion_tsvd() {
627 let chi = make_inversion_test(InversionAlgorithm::Tsvd);
628 assert_eq!(chi.len(), 8 * 8 * 8);
629 }
630
631 #[test]
632 fn test_inversion_tikhonov() {
633 let chi = make_inversion_test(InversionAlgorithm::Tikhonov);
634 assert_eq!(chi.len(), 8 * 8 * 8);
635 }
636
637 #[test]
638 fn test_inversion_tv() {
639 let chi = make_inversion_test(InversionAlgorithm::Tv);
640 assert_eq!(chi.len(), 8 * 8 * 8);
641 }
642
643 #[test]
644 fn test_inversion_rts() {
645 let chi = make_inversion_test(InversionAlgorithm::Rts);
646 assert_eq!(chi.len(), 8 * 8 * 8);
647 }
648
649 #[test]
650 fn test_inversion_nltv() {
651 let chi = make_inversion_test(InversionAlgorithm::Nltv);
652 assert_eq!(chi.len(), 8 * 8 * 8);
653 }
654
655 #[test]
656 fn test_inversion_ilsqr() {
657 let chi = make_inversion_test(InversionAlgorithm::Ilsqr);
658 assert_eq!(chi.len(), 8 * 8 * 8);
659 }
660
661 #[test]
662 fn test_inversion_amp_pe() {
663 let chi = make_inversion_test(InversionAlgorithm::AmpPe);
664 assert_eq!(chi.len(), 8 * 8 * 8);
665 }
666
667 #[test]
668 fn test_inversion_medi() {
669 let (nx, ny, nz) = (8, 8, 8);
670 let n = nx * ny * nz;
671 let field = vec![0.01; n];
672 let mask = vec![1u8; n];
673 let mag = vec![1.0; n];
674 let meta = ScanMetadata {
675 dims: (nx, ny, nz), voxel_size: (1.0, 1.0, 1.0),
676 echo_times: vec![0.005], field_strength: 3.0, b0_direction: (0.0, 0.0, 1.0),
677 };
678 let config = InversionConfig { algorithm: InversionAlgorithm::Medi, ..Default::default() };
679 let chi = run_dipole_inversion(&field, &mask, &meta, &config, Some(&mag), &mut |_, _| {}).unwrap();
680 assert_eq!(chi.len(), n);
681 }
682
683 #[test]
684 fn test_inversion_rejects_tgv() {
685 let n = 64;
686 let meta = ScanMetadata {
687 dims: (4, 4, 4),
688 voxel_size: (1.0, 1.0, 1.0),
689 echo_times: vec![0.005],
690 field_strength: 3.0,
691 b0_direction: (0.0, 0.0, 1.0),
692 };
693 let config = InversionConfig {
694 algorithm: InversionAlgorithm::Tgv,
695 ..Default::default()
696 };
697
698 let result = run_dipole_inversion(
699 &vec![0.0; n], &vec![1u8; n], &meta, &config, None, &mut |_, _| {},
700 );
701 assert!(result.is_err());
702 }
703}