1use super::config::*;
28use crate::separation::{
29 ChiSepIlsqrInputs, ChiSepIlsqrParams, ChiSepMediInputs, ChiSepMediParams, DecomposeParams, HcChisepParams, R2starQsmParams,
30};
31
32#[derive(Clone, Copy)]
35pub struct SeparationInputs<'a> {
36 pub local_field_ppm: &'a [f64],
38 pub qsm: &'a [f64],
41 pub qsm_per_echo: Option<&'a [f64]>,
43 pub mask: &'a [u8],
45 pub r2prime: Option<&'a [f64]>,
47 pub r2star: Option<&'a [f64]>,
49 pub magnitude_rss: Option<&'a [f64]>,
52 pub n_std: Option<&'a [f64]>,
55 pub mask_csf: Option<&'a [u8]>,
57 pub magnitude_multi: Option<&'a [f64]>,
60 pub se_magnitude_multi: Option<&'a [f64]>,
62}
63
64#[derive(Clone, Debug)]
66pub struct SeparationResult {
67 pub chi_pos: Vec<f64>,
69 pub chi_neg: Vec<f64>,
71 pub chi_total: Vec<f64>,
73}
74
75pub fn run_separation(
83 inputs: SeparationInputs,
84 metadata: &ScanMetadata,
85 config: &SeparationConfig,
86 progress: &mut dyn FnMut(usize, usize),
87) -> Result<SeparationResult, PipelineError> {
88 metadata.require_contiguous_slices()?;
89 let grid = metadata.grid();
90 let n = grid.n_total();
91 let bdir = metadata.b0_direction;
92 let cf = crate::constants::GAMMA_BAR_HZ_PER_T * metadata.field_strength;
93 let b0 = metadata.field_strength;
94
95 if inputs.qsm.len() != n || inputs.mask.len() != n {
96 return Err(PipelineError::DimensionMismatch { expected: n, got: inputs.qsm.len() });
97 }
98 check_inputs(&inputs, metadata, config.algorithm, n)?;
99 let mask = inputs.mask;
100
101 let (chi_pos, chi_neg, chi_total) = match config.algorithm {
102 SeparationAlgorithm::ChiSepIlsqr => {
103 let ilsqr_inputs = ChiSepIlsqrInputs {
104 local_field: inputs.local_field_ppm,
105 r2prime: need(inputs.r2prime, "r2prime")?,
106 qsm: inputs.qsm,
107 mask,
108 };
109 let params = ChiSepIlsqrParams { cf, ..config.chi_sep_ilsqr.clone() };
110 crate::separation::chi_sep_ilsqr(&ilsqr_inputs, &grid, bdir, ¶ms, progress)?
111 }
112 SeparationAlgorithm::ChiSepMedi => {
113 let medi_inputs = ChiSepMediInputs {
114 magnitude: need(inputs.magnitude_rss, "magnitude_rss")?,
115 local_field: inputs.local_field_ppm,
116 r2prime: need(inputs.r2prime, "r2prime")?,
117 n_std: need(inputs.n_std, "n_std")?,
118 qsm: inputs.qsm,
119 mask,
120 mask_csf: need(inputs.mask_csf, "mask_csf")?,
121 };
122 let params = ChiSepMediParams { cf, ..config.chi_sep_medi.clone() };
123 crate::separation::chi_sep_medi(&medi_inputs, &grid, bdir, ¶ms, progress)?
124 }
125 SeparationAlgorithm::R2starQsm => {
126 let params = R2starQsmParams { b0, ..config.r2star_qsm.clone() };
127 if let Some(r2star) = inputs.r2star {
128 crate::separation::r2star_qsm(inputs.qsm, r2star, mask, ¶ms)
129 } else {
130 let magnitude = need(inputs.magnitude_multi, "r2star or magnitude_multi")?;
131 crate::separation::r2star_qsm_from_magnitude(
132 magnitude, &metadata.echo_times, inputs.qsm, mask, ¶ms,
133 )
134 }
135 }
136 SeparationAlgorithm::WaveSep => {
137 let r2prime = need(inputs.r2prime, "r2prime")?;
138 crate::separation::wavesep(
139 inputs.qsm, r2prime, mask, &grid, &config.wavesep, progress,
140 )?
141 }
142 SeparationAlgorithm::Decompose => {
143 let magnitude = need(inputs.magnitude_multi, "magnitude_multi")?;
144 let qsm_per_echo = need(inputs.qsm_per_echo, "qsm_per_echo")?;
145 let params = DecomposeParams { b0, ..config.decompose.clone() };
146 crate::separation::decompose(
147 qsm_per_echo, magnitude, &metadata.echo_times, mask, ¶ms, progress,
148 )?
149 }
150 SeparationAlgorithm::HcChisep => {
151 let r2prime = need(inputs.r2prime, "r2prime")?;
152 let magnitude = need(inputs.magnitude_multi, "magnitude_multi")?;
153 let params = HcChisepParams { b0, ..config.hc_chisep.clone() };
154 crate::separation::hc_chisep(
155 inputs.qsm, r2prime, magnitude, &metadata.echo_times,
156 inputs.se_magnitude_multi, mask, &grid, ¶ms, progress,
157 )
158 }
159 SeparationAlgorithm::SusepNet => {
160 let r2prime = need(inputs.r2prime, "r2prime")?;
161 config.susep_net.validate().map_err(PipelineError::InvalidConfig)?;
162 run_susep_net(
163 inputs.local_field_ppm, inputs.qsm, r2prime, mask, &grid, &config.susep_net,
164 progress,
165 )?
166 }
167 SeparationAlgorithm::ChiSepNet => {
168 config.chi_sepnet.validate().map_err(PipelineError::InvalidConfig)?;
169 match config.chi_sepnet.route {
170 crate::separation::ChiSepNetRoute::Native => {
171 let r2prime = need(inputs.r2prime, "r2prime")?;
172 run_chi_sepnet(
173 inputs.local_field_ppm, inputs.qsm, r2prime, mask, &grid, &config.chi_sepnet,
174 )?
175 }
176 crate::separation::ChiSepNetRoute::Toolbox => run_chi_sepnet_toolbox(
180 inputs.local_field_ppm, inputs.r2prime, inputs.r2star, mask, &grid, bdir, b0,
181 &config.chi_sepnet,
182 )?,
183 }
184 }
185 };
186
187 Ok(SeparationResult { chi_pos, chi_neg, chi_total })
188}
189
190#[cfg(feature = "onnx")]
192fn run_susep_net(
193 local_field_ppm: &[f64],
194 qsm: &[f64],
195 r2prime: &[f64],
196 mask: &[u8],
197 grid: &crate::Grid,
198 params: &crate::separation::SusepNetParams,
199 progress: &mut dyn FnMut(usize, usize),
200) -> Result<crate::separation::ChiSepMaps, PipelineError> {
201 let spec = crate::models::find_model("susep-net")
202 .ok_or_else(|| PipelineError::InvalidConfig("susep-net not in model registry".into()))?;
203 let bytes = crate::models::primary_weight_bytes(spec).map_err(PipelineError::InvalidConfig)?;
204 crate::separation::susep_net(
205 local_field_ppm, qsm, r2prime, mask, grid, &bytes,
206 &crate::separation::SusepNetNorm::default(),
207 params,
208 progress,
209 )
210 .map_err(|e| PipelineError::AlgorithmError(e.to_string()))
211}
212
213#[cfg(not(feature = "onnx"))]
214fn run_susep_net(
215 _local_field_ppm: &[f64],
216 _qsm: &[f64],
217 _r2prime: &[f64],
218 _mask: &[u8],
219 _grid: &crate::Grid,
220 _params: &crate::separation::SusepNetParams,
221 _progress: &mut dyn FnMut(usize, usize),
222) -> Result<crate::separation::ChiSepMaps, PipelineError> {
223 Err(PipelineError::InvalidConfig(
224 "SUSEP-Net requires building qsm-core with the 'onnx' feature".into(),
225 ))
226}
227
228#[cfg(feature = "onnx")]
230fn run_chi_sepnet(
231 local_field_ppm: &[f64],
232 qsm: &[f64],
233 r2prime: &[f64],
234 mask: &[u8],
235 grid: &crate::Grid,
236 params: &crate::separation::ChiSepNetParams,
237) -> Result<crate::separation::ChiSepMaps, PipelineError> {
238 let bytes = crate::models::primary_weight("chi-sepnet").map_err(PipelineError::InvalidConfig)?;
239 crate::separation::chisepnet(
240 local_field_ppm, qsm, r2prime, mask, grid, &bytes,
241 &crate::separation::ChiSepNetNorm::default(),
242 params,
243 )
244 .map_err(|e| PipelineError::AlgorithmError(e.to_string()))
245}
246
247#[cfg(not(feature = "onnx"))]
248fn run_chi_sepnet(
249 _local_field_ppm: &[f64],
250 _qsm: &[f64],
251 _r2prime: &[f64],
252 _mask: &[u8],
253 _grid: &crate::Grid,
254 _params: &crate::separation::ChiSepNetParams,
255) -> Result<crate::separation::ChiSepMaps, PipelineError> {
256 Err(PipelineError::InvalidConfig(
257 "χ-sepnet requires building qsm-core with the 'onnx' feature".into(),
258 ))
259}
260
261#[cfg(feature = "onnx")]
263#[allow(clippy::too_many_arguments)]
264fn run_chi_sepnet_toolbox(
265 local_field_ppm: &[f64],
266 r2prime: Option<&[f64]>,
267 r2star: Option<&[f64]>,
268 mask: &[u8],
269 grid: &crate::Grid,
270 bdir: (f64, f64, f64),
271 b0_tesla: f64,
272 params: &crate::separation::ChiSepNetParams,
273) -> Result<crate::separation::ChiSepMaps, PipelineError> {
274 use crate::separation::{ChiSepNetR2, ChiSepNetToolboxModels};
275 let r2 = match (r2prime, r2star) {
276 (Some(v), _) => ChiSepNetR2::R2Prime(v),
277 (None, Some(v)) => ChiSepNetR2::R2Star(v),
278 (None, None) => {
279 return Err(PipelineError::InvalidInput(
280 "χ-sepnet needs a measured R2' (χ-sepnet-R2') or an R2* map (χ-sepnet-R2*)".into(),
281 ))
282 }
283 };
284 let weight = |id: &str| crate::models::primary_weight(id).map_err(PipelineError::InvalidConfig);
285 let chisepnet = weight("chi-sepnet")?;
286 let qsmnetme = weight("qsmnetme")?;
287 let r2primenet = match r2 {
288 ChiSepNetR2::R2Star(_) => Some(weight("r2primenet")?),
289 ChiSepNetR2::R2Prime(_) => None,
290 };
291 let models = ChiSepNetToolboxModels { chisepnet: &chisepnet, qsmnetme: &qsmnetme, r2primenet: r2primenet.as_deref() };
292 crate::separation::chisepnet_toolbox(
293 local_field_ppm, r2, mask, grid, bdir, b0_tesla, &models,
294 &crate::separation::ChiSepNetNorm::default(), params,
295 )
296 .map_err(|e| PipelineError::AlgorithmError(e.to_string()))
297}
298
299#[cfg(not(feature = "onnx"))]
300#[allow(clippy::too_many_arguments)]
301fn run_chi_sepnet_toolbox(
302 _local_field_ppm: &[f64],
303 _r2prime: Option<&[f64]>,
304 _r2star: Option<&[f64]>,
305 _mask: &[u8],
306 _grid: &crate::Grid,
307 _bdir: (f64, f64, f64),
308 _b0_tesla: f64,
309 _params: &crate::separation::ChiSepNetParams,
310) -> Result<crate::separation::ChiSepMaps, PipelineError> {
311 Err(PipelineError::InvalidConfig(
312 "χ-sepnet requires building qsm-core with the 'onnx' feature".into(),
313 ))
314}
315
316fn check_inputs(
320 inputs: &SeparationInputs,
321 metadata: &ScanMetadata,
322 algorithm: SeparationAlgorithm,
323 n: usize,
324) -> Result<(), PipelineError> {
325 let maps = [
326 ("local_field_ppm", Some(inputs.local_field_ppm)),
327 ("r2prime", inputs.r2prime),
328 ("r2star", inputs.r2star),
329 ("magnitude_rss", inputs.magnitude_rss),
330 ];
331 let needs_local_field = matches!(
332 algorithm,
333 SeparationAlgorithm::ChiSepIlsqr | SeparationAlgorithm::ChiSepMedi
334 | SeparationAlgorithm::SusepNet | SeparationAlgorithm::ChiSepNet
335 );
336 for (name, map) in maps {
337 if name == "local_field_ppm" && !needs_local_field {
338 continue;
339 }
340 if let Some(m) = map {
341 crate::error::check_len(name, m.len(), n)?;
342 }
343 }
344 let n_echoes = metadata.echo_times.len();
345 if let Some(m) = inputs.magnitude_multi {
346 crate::error::check_len(
347 &format!("magnitude_multi ({n_echoes} echoes x {n} voxels)"), m.len(), n * n_echoes,
348 )?;
349 }
350 if let Some(m) = inputs.qsm_per_echo {
351 crate::error::check_len(
352 &format!("qsm_per_echo ({n_echoes} echoes x {n} voxels)"), m.len(), n * n_echoes,
353 )?;
354 }
355 if algorithm == SeparationAlgorithm::Decompose && n_echoes < 3 {
356 return Err(PipelineError::InvalidInput(format!(
357 "Decompose needs at least 3 echoes, got {n_echoes}"
358 )));
359 }
360 if !inputs.mask.iter().any(|&m| m != 0) {
361 return Err(PipelineError::InvalidInput("the mask is empty".into()));
362 }
363 Ok(())
364}
365
366fn need<'a, T>(opt: Option<&'a [T]>, what: &str) -> Result<&'a [T], PipelineError> {
368 opt.ok_or_else(|| PipelineError::InvalidInput(format!("{what} required for this algorithm")))
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374
375 fn meta(nx: usize, ny: usize, nz: usize) -> ScanMetadata {
376 ScanMetadata {
377 dims: (nx, ny, nz),
378 voxel_size: (1.0, 1.0, 1.0),
379 echo_times: vec![0.004, 0.012, 0.020, 0.028],
380 field_strength: 3.0,
381 b0_direction: (0.0, 0.0, 1.0),
382 slice_geometry: None,
383 affine: None,
384 }
385 }
386
387 fn base_inputs<'a>(qsm: &'a [f64], mask: &'a [u8]) -> SeparationInputs<'a> {
388 SeparationInputs {
389 local_field_ppm: qsm,
390 qsm,
391 qsm_per_echo: None,
392 mask,
393 r2prime: None,
394 r2star: None,
395 magnitude_rss: None,
396 n_std: None,
397 mask_csf: None,
398 magnitude_multi: None,
399 se_magnitude_multi: None,
400 }
401 }
402
403 #[test]
404 fn r2star_qsm_dispatch_closed_form() {
405 let (nx, ny, nz) = (6, 6, 6);
406 let n = nx * ny * nz;
407 let qsm = vec![0.02; n];
408 let r2star = vec![10.0; n];
409 let mask = vec![1u8; n];
410 let m = meta(nx, ny, nz);
411 let mut inputs = base_inputs(&qsm, &mask);
412 inputs.r2star = Some(&r2star);
413 let cfg = SeparationConfig { algorithm: SeparationAlgorithm::R2starQsm, ..Default::default() };
414 let r = run_separation(inputs, &m, &cfg, &mut |_, _| {}).unwrap();
415 assert_eq!(r.chi_pos.len(), n);
416 for i in 0..n {
417 assert!(r.chi_pos[i] >= 0.0 && r.chi_neg[i] <= 0.0);
418 assert!((r.chi_total[i] - (r.chi_pos[i] + r.chi_neg[i])).abs() < 1e-9);
419 }
420 }
421
422 #[test]
423 fn wavesep_dispatch_runs() {
424 let (nx, ny, nz) = (8, 8, 8);
425 let n = nx * ny * nz;
426 let qsm = vec![0.01; n];
427 let r2prime = vec![2.0; n];
428 let mask = vec![1u8; n];
429 let m = meta(nx, ny, nz);
430 let mut inputs = base_inputs(&qsm, &mask);
431 inputs.r2prime = Some(&r2prime);
432 let cfg = SeparationConfig { algorithm: SeparationAlgorithm::WaveSep, ..Default::default() };
433 let r = run_separation(inputs, &m, &cfg, &mut |_, _| {}).unwrap();
434 assert_eq!(r.chi_pos.len(), n);
435 }
436
437 #[test]
438 fn chi_sep_ilsqr_and_medi_dispatch() {
439 let (nx, ny, nz) = (6, 6, 6);
440 let n = nx * ny * nz;
441 let field = vec![0.01; n];
442 let r2prime = vec![2.0; n];
443 let mag = vec![1.0; n];
444 let mask = vec![1u8; n];
445 let n_std = vec![0.01; n];
446 let no_csf = vec![0u8; n];
447 let m = meta(nx, ny, nz);
448 for alg in [SeparationAlgorithm::ChiSepIlsqr, SeparationAlgorithm::ChiSepMedi] {
449 let mut inputs = base_inputs(&field, &mask);
450 inputs.r2prime = Some(&r2prime);
451 inputs.magnitude_rss = Some(&mag);
452 inputs.n_std = Some(&n_std);
453 inputs.mask_csf = Some(&no_csf);
454 let cfg = SeparationConfig { algorithm: alg, ..Default::default() };
455 let r = run_separation(inputs, &m, &cfg, &mut |_, _| {}).unwrap();
456 assert_eq!(r.chi_pos.len(), n);
457 }
458 let mut inputs = base_inputs(&field, &mask);
461 inputs.r2prime = Some(&r2prime);
462 let cfg = SeparationConfig { algorithm: SeparationAlgorithm::ChiSepIlsqr, ..Default::default() };
463 assert!(run_separation(inputs, &m, &cfg, &mut |_, _| {}).is_ok());
464 for missing in ["n_std", "mask_csf"] {
466 let mut inputs = base_inputs(&field, &mask);
467 inputs.r2prime = Some(&r2prime);
468 inputs.magnitude_rss = Some(&mag);
469 if missing != "n_std" {
470 inputs.n_std = Some(&n_std);
471 }
472 if missing != "mask_csf" {
473 inputs.mask_csf = Some(&no_csf);
474 }
475 let cfg = SeparationConfig { algorithm: SeparationAlgorithm::ChiSepMedi, ..Default::default() };
476 let e = run_separation(inputs, &m, &cfg, &mut |_, _| {}).unwrap_err();
477 assert!(e.to_string().contains(&format!("{missing} required")), "{e}");
478 }
479 }
480
481 #[test]
482 fn decompose_and_hc_dispatch() {
483 let (nx, ny, nz) = (6, 6, 6);
484 let n = nx * ny * nz;
485 let ne = 4;
486 let qsm = vec![0.01; n];
487 let r2prime = vec![2.0; n];
488 let mag_multi = vec![1.0; n * ne];
489 let mask = vec![1u8; n];
490 let m = meta(nx, ny, nz);
491
492 let qsm_per_echo = vec![0.01; n * ne];
493 let mut d_inputs = base_inputs(&qsm, &mask);
494 d_inputs.magnitude_multi = Some(&mag_multi);
495 let d_cfg = SeparationConfig { algorithm: SeparationAlgorithm::Decompose, ..Default::default() };
496 assert!(run_separation(d_inputs, &m, &d_cfg, &mut |_, _| {}).is_err());
498 d_inputs.qsm_per_echo = Some(&qsm_per_echo);
499 let rd = run_separation(d_inputs, &m, &d_cfg, &mut |_, _| {}).unwrap();
500 assert_eq!(rd.chi_pos.len(), n);
501 d_inputs.qsm_per_echo = Some(&qsm_per_echo[..n]);
503 assert!(run_separation(d_inputs, &m, &d_cfg, &mut |_, _| {}).is_err());
504
505 let mut h_inputs = base_inputs(&qsm, &mask);
506 h_inputs.r2prime = Some(&r2prime);
507 h_inputs.magnitude_multi = Some(&mag_multi);
508 let h_cfg = SeparationConfig { algorithm: SeparationAlgorithm::HcChisep, ..Default::default() };
509 let rh = run_separation(h_inputs, &m, &h_cfg, &mut |_, _| {}).unwrap();
510 assert_eq!(rh.chi_pos.len(), n);
511 }
512
513 #[test]
514 fn r2star_from_magnitude_path() {
515 let (nx, ny, nz) = (6, 6, 6);
516 let n = nx * ny * nz;
517 let ne = 4;
518 let qsm = vec![0.02; n];
519 let mag_multi = vec![1.0; n * ne];
520 let mask = vec![1u8; n];
521 let m = meta(nx, ny, nz);
522 let mut inputs = base_inputs(&qsm, &mask);
524 inputs.magnitude_multi = Some(&mag_multi);
525 let cfg = SeparationConfig { algorithm: SeparationAlgorithm::R2starQsm, ..Default::default() };
526 let r = run_separation(inputs, &m, &cfg, &mut |_, _| {}).unwrap();
527 assert_eq!(r.chi_pos.len(), n);
528 }
529
530 #[test]
531 fn missing_input_errors() {
532 let (nx, ny, nz) = (6, 6, 6);
533 let n = nx * ny * nz;
534 let qsm = vec![0.02; n];
535 let mask = vec![1u8; n];
536 let m = meta(nx, ny, nz);
537 let inputs = base_inputs(&qsm, &mask);
539 let cfg = SeparationConfig { algorithm: SeparationAlgorithm::R2starQsm, ..Default::default() };
540 let err = run_separation(inputs, &m, &cfg, &mut |_, _| {}).unwrap_err();
541 assert!(matches!(err, PipelineError::InvalidInput(_)), "got {err:?}");
542 }
543
544 #[test]
549 fn dl_patches_from_config() {
550 use crate::separation::{chisepnet, ChiSepNetParams, SusepNetParams};
551 let d = SeparationConfig::default();
552 assert_eq!(d.chi_sepnet, ChiSepNetParams { patch: chisepnet::AUTHORS_PATCH, ..Default::default() });
553 assert_eq!(d.chi_sepnet.route, crate::separation::ChiSepNetRoute::Toolbox);
554 assert_eq!(d.susep_net, SusepNetParams { patch: None });
555
556 let (nx, ny, nz) = (6, 6, 6);
557 let n = nx * ny * nz;
558 let v = vec![0.01; n];
559 let mask = vec![1u8; n];
560 let m = meta(nx, ny, nz);
561 let mut inputs = base_inputs(&v, &mask);
562 inputs.r2prime = Some(&v);
563 let bad = [
564 SeparationConfig {
565 algorithm: SeparationAlgorithm::ChiSepNet,
566 chi_sepnet: ChiSepNetParams { patch: (128, 120, 64), ..Default::default() },
567 ..Default::default()
568 },
569 SeparationConfig {
570 algorithm: SeparationAlgorithm::SusepNet,
571 susep_net: SusepNetParams { patch: Some((128, 128, 4)) },
572 ..Default::default()
573 },
574 ];
575 for cfg in bad {
576 let err = run_separation(inputs, &m, &cfg, &mut |_, _| {}).unwrap_err();
577 match err {
578 PipelineError::InvalidConfig(msg) => assert!(msg.contains("patch"), "{msg}"),
579 e => panic!("{:?}: expected InvalidConfig, got {e:?}", cfg.algorithm),
580 }
581 }
582 }
583
584 #[test]
588 fn bad_inputs_are_errors_not_panics() {
589 let (nx, ny, nz) = (6, 6, 6);
590 let n = nx * ny * nz;
591 let qsm = vec![0.01; n];
592 let mask = vec![1u8; n];
593 let mut m = meta(nx, ny, nz);
594 let run = |inputs: SeparationInputs, m: &ScanMetadata, alg| {
595 let cfg = SeparationConfig { algorithm: alg, ..Default::default() };
596 run_separation(inputs, m, &cfg, &mut |_, _| {}).map(|_| ())
597 };
598 let invalid = |r: Result<(), PipelineError>| matches!(r, Err(PipelineError::InvalidInput(_)));
599
600 m.echo_times = vec![0.004, 0.012];
602 let mag2 = vec![1.0; n * 2];
603 let mut inputs = base_inputs(&qsm, &mask);
604 inputs.magnitude_multi = Some(&mag2);
605 assert!(invalid(run(inputs, &m, SeparationAlgorithm::Decompose)));
606
607 let m4 = meta(nx, ny, nz);
609 let mag3 = vec![1.0; n * 3];
610 let mut inputs = base_inputs(&qsm, &mask);
611 inputs.magnitude_multi = Some(&mag3);
612 assert!(invalid(run(inputs, &m4, SeparationAlgorithm::R2starQsm)));
613
614 let short = vec![1.0; n - 1];
616 let mut inputs = base_inputs(&qsm, &mask);
617 inputs.r2prime = Some(&short);
618 assert!(invalid(run(inputs, &m4, SeparationAlgorithm::WaveSep)));
619
620 let empty = vec![0u8; n];
622 let r2prime = vec![2.0; n];
623 let mut inputs = base_inputs(&qsm, &empty);
624 inputs.r2prime = Some(&r2prime);
625 inputs.magnitude_rss = Some(&qsm);
626 assert!(invalid(run(inputs, &m4, SeparationAlgorithm::ChiSepIlsqr)));
627 }
628}