1use crate::error::{check_len, InputError};
22
23pub fn arlo_echo_times_problem(echo_times: &[f64]) -> Option<String> {
27 if echo_times.len() < 3 {
28 Some(format!("ARLO needs at least 3 echoes, got {}", echo_times.len()))
29 } else if echo_times.iter().any(|t| !t.is_finite()) {
30 Some(format!("ARLO needs finite echo times, got {echo_times:?}"))
31 } else if echo_times.windows(2).any(|w| w[1] <= w[0]) {
32 Some(format!("ARLO needs strictly increasing echo times, got {echo_times:?} s"))
33 } else {
34 None
35 }
36}
37
38pub fn arlo(magnitude: &[f64], echo_times: &[f64], n_voxels: usize) -> Result<Vec<f64>, InputError> {
68 let mut r2 = arlo_unclipped(magnitude, echo_times, n_voxels)?;
69 for v in r2.iter_mut() {
71 *v = v.max(0.0);
72 }
73 Ok(r2)
74}
75
76pub fn arlo_unclipped(magnitude: &[f64], echo_times: &[f64], n_voxels: usize) -> Result<Vec<f64>, InputError> {
81 if let Some(problem) = arlo_echo_times_problem(echo_times) {
82 return Err(InputError(problem));
83 }
84 let nte = echo_times.len();
85 check_len("magnitude (n_echoes * n_voxels)", magnitude.len(), n_voxels * nte)?;
86 let te = echo_times;
87 let mut yy = vec![0.0f64; n_voxels];
88 let mut yx = vec![0.0f64; n_voxels];
89 let mut beta_yx = vec![0.0f64; n_voxels];
90 let mut beta_xx = vec![0.0f64; n_voxels];
91 for j in 0..nte - 2 {
92 let alpha = (te[j + 2] - te[j]) * (te[j + 2] - te[j]) / 2.0 / (te[j + 1] - te[j]);
93 let tmp = (2.0 * te[j + 2] * te[j + 2] - te[j] * te[j + 2] - te[j] * te[j]
94 + 3.0 * te[j] * te[j + 1] - 3.0 * te[j + 1] * te[j + 2]) / 6.0;
95 let beta = tmp / (te[j + 2] - te[j + 1]);
96 let gamma = tmp / (te[j + 1] - te[j]);
97 let (c0, c1, c2) = (te[j + 2] - te[j] - alpha + gamma, alpha - beta - gamma, beta);
98 let (s0, s1, s2) = (
99 &magnitude[j * n_voxels..(j + 1) * n_voxels],
100 &magnitude[(j + 1) * n_voxels..(j + 2) * n_voxels],
101 &magnitude[(j + 2) * n_voxels..(j + 3) * n_voxels],
102 );
103 for v in 0..n_voxels {
104 let y1 = s0[v] * c0 + s1[v] * c1 + s2[v] * c2;
105 let x1 = s0[v] - s2[v];
106 yy[v] += y1 * y1;
107 yx[v] += y1 * x1;
108 beta_yx[v] += beta * y1 * x1;
109 beta_xx[v] += beta * x1 * x1;
110 }
111 }
112 Ok((0..n_voxels)
113 .map(|v| {
114 let r2 = (yx[v] + beta_xx[v]) / (beta_yx[v] + yy[v]);
116 if r2.is_finite() { r2 } else { 0.0 }
117 })
118 .collect())
119}
120
121pub fn t2star_from_r2star(r2star: &[f64]) -> Vec<f64> {
127 r2star.iter().map(|&r| if r > 0.0 { 1.0 / r } else { 0.0 }).collect()
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn arlo_matches_the_closed_form() {
136 for te in [vec![0.005, 0.011, 0.017, 0.023, 0.029], vec![0.004, 0.009, 0.016, 0.030]] {
138 let r2: [f64; 3] = [5.0, 30.0, 80.0];
139 let n = r2.len();
140 let mag: Vec<f64> = te.iter().flat_map(|&t| r2.iter().map(move |&r| 100.0 * (-r * t).exp())).collect();
141 let est = arlo(&mag, &te, n).unwrap();
142 for v in 0..n {
143 assert!((est[v] - r2[v]).abs() / r2[v] < 0.02, "{te:?}: {} vs {}", est[v], r2[v]);
144 }
145 }
146 let (esp, s) = (0.006, [90.0, 70.0, 61.0, 40.0]);
148 let te: Vec<f64> = (0..4).map(|i| 0.005 + esp * i as f64).collect();
149 let (mut xy, mut xx, mut yy) = (0.0, 0.0, 0.0);
150 for j in 0..2 {
151 let y = esp / 3.0 * (s[j] + 4.0 * s[j + 1] + s[j + 2]);
152 let x = s[j] - s[j + 2];
153 xy += x * y;
154 xx += x * x;
155 yy += y * y;
156 }
157 let want = (esp / 3.0 * xx + xy) / (yy + esp / 3.0 * xy);
158 assert!((arlo(&s, &te, 1).unwrap()[0] - want).abs() < 1e-9 * want);
159 assert_eq!(arlo(&[0.0; 3], &[0.005, 0.01, 0.015], 1).unwrap(), vec![0.0]);
161 assert_eq!(arlo(&[10.0, 20.0, 40.0], &[0.005, 0.01, 0.015], 1).unwrap(), vec![0.0]);
163 }
164
165 #[test]
166 fn arlo_layout_is_echo_slowest() {
167 let te: [f64; 4] = [0.005, 0.010, 0.015, 0.020];
168 let r2: [f64; 4] = [20.0, 40.0, 60.0, 80.0];
169 let mag: Vec<f64> = te.iter().flat_map(|&t| r2.iter().map(move |&r| 500.0 * (-r * t).exp())).collect();
170 let est = arlo(&mag, &te, 4).unwrap();
171 for v in 0..4 {
172 assert!((est[v] - r2[v]).abs() / r2[v] < 0.01, "voxel {v}: {}", est[v]);
173 }
174 }
175
176 #[test]
177 fn arlo_refuses_echo_times_it_cannot_use() {
178 let run = |te: &[f64]| {
179 let mag: Vec<f64> = te.iter().map(|t| (-30.0 * t).exp()).collect();
180 arlo(&mag, te, 1)
181 };
182 assert!(run(&[0.005, 0.010, 0.015, 0.020]).is_ok());
183 assert!(run(&[0.004, 0.008, 0.012, 0.030]).is_ok(), "uneven spacing is arlo.m's general form");
184 for (te, why) in [
185 (&[0.005][..], "at least 3"),
186 (&[0.005, 0.010][..], "at least 3"),
187 (&[0.005, 0.005, 0.005][..], "strictly increasing"),
188 (&[0.015, 0.010, 0.005][..], "strictly increasing"),
189 (&[0.005, f64::NAN, 0.015][..], "finite"),
190 ] {
191 let err = run(te).unwrap_err().to_string();
192 assert!(err.contains(why), "{te:?}: {err}");
193 assert!(arlo_echo_times_problem(te).is_some());
194 }
195 assert!(arlo(&[1.0; 2], &[0.005, 0.010, 0.015], 1).is_err());
196 }
197
198 #[test]
199 fn test_t2star_from_r2star() {
200 let r2star = vec![50.0, 0.0, 100.0, 25.0, -3.0];
201 let t2star = t2star_from_r2star(&r2star);
202 assert!((t2star[0] - 0.02).abs() < 1e-10);
203 assert_eq!(t2star[1], 0.0);
204 assert!((t2star[2] - 0.01).abs() < 1e-10);
205 assert!((t2star[3] - 0.04).abs() < 1e-10);
206 assert_eq!(t2star[4], 0.0);
207 }
208}