1use crate::Grid;
16use crate::utils::{gaussian_smooth_3d, apply_mask_zero};
17
18#[cfg_attr(feature = "introspection", derive(serde::Serialize))]
20#[derive(Clone, Debug)]
21pub struct SwiParams {
22 pub hp_sigma: [f64; 3],
24 pub scaling: PhaseScaling,
26 pub strength: f64,
28 pub mip_window: usize,
30}
31
32impl Default for SwiParams {
33 fn default() -> Self {
34 Self {
35 hp_sigma: [4.0, 4.0, 0.0],
36 scaling: PhaseScaling::Tanh,
37 strength: 4.0,
38 mip_window: 7,
39 }
40 }
41}
42
43#[cfg_attr(feature = "introspection", derive(serde::Serialize))]
45#[derive(Debug, Clone, Copy, PartialEq)]
46pub enum PhaseScaling {
47 Tanh,
50 NegativeTanh,
52 Positive,
54 Negative,
56 Triangular,
58}
59
60pub fn highpass_filter(
71 data: &[f64],
72 mask: &[u8],
73 grid: &Grid,
74 sigma: [f64; 3],
75) -> Vec<f64> {
76 let nbox = 4; let smoothed = gaussian_smooth_3d(data, sigma, Some(mask), None, nbox, grid);
78 let n_total = grid.n_total();
79 let mut result = vec![0.0; n_total];
80 for i in 0..n_total {
81 if mask[i] == 1 {
82 result[i] = data[i] - smoothed[i];
83 }
84 }
85 result
86}
87
88pub fn create_phase_mask(
101 phase: &[f64],
102 mask: &[u8],
103 scaling: PhaseScaling,
104 strength: f64,
105) -> Vec<f64> {
106 let n = phase.len();
107 let mut result = vec![0.0; n];
108
109 for i in 0..n {
111 if mask[i] == 1 {
112 result[i] = phase[i];
113 }
114 }
115
116 let effective_scaling = if scaling == PhaseScaling::NegativeTanh {
118 for v in result.iter_mut() {
119 *v = -*v;
120 }
121 PhaseScaling::Tanh
122 } else {
123 scaling
124 };
125
126 match effective_scaling {
127 PhaseScaling::Tanh => {
128 let mut positives: Vec<f64> = (0..n)
130 .filter(|&i| mask[i] == 1 && result[i] > 0.0)
131 .map(|i| result[i])
132 .collect();
133
134 let m = if positives.is_empty() {
135 1.0
136 } else {
137 positives.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());
138 let mid = positives.len() / 2;
139 let median = if positives.len().is_multiple_of(2) {
140 (positives[mid - 1] + positives[mid]) / 2.0
141 } else {
142 positives[mid]
143 };
144 median * 10.0 / strength
145 };
146
147 for v in result.iter_mut() {
148 *v = (1.0 + (1.0 - *v / m).tanh()) / 2.0;
149 }
150 }
151 PhaseScaling::Positive => {
152 let (min_pos, max_pos) = positive_range(&result, mask);
154 for i in 0..n {
155 if result[i] > 0.0 && mask[i] == 1 {
156 result[i] = rescale(result[i], min_pos, max_pos, 1.0, 0.0).powf(strength);
157 } else {
158 result[i] = 1.0;
159 }
160 }
161 }
162 PhaseScaling::Negative => {
163 let (min_neg, max_neg) = negative_range(&result, mask);
165 for i in 0..n {
166 if result[i] <= 0.0 && mask[i] == 1 {
167 result[i] = rescale(result[i], min_neg, max_neg, 0.0, 1.0).powf(strength);
168 } else {
169 result[i] = 1.0;
170 }
171 }
172 }
173 PhaseScaling::Triangular => {
174 let (min_pos, max_pos) = positive_range(&result, mask);
176 let (min_neg, max_neg) = negative_range(&result, mask);
177 for i in 0..n {
178 if mask[i] == 0 {
179 result[i] = 0.0;
180 } else if result[i] > 0.0 {
181 result[i] = rescale(result[i], min_pos, max_pos, 1.0, 0.0).powf(strength);
182 } else {
183 result[i] = rescale(result[i], min_neg, max_neg, 0.0, 1.0).powf(strength);
184 }
185 }
186 }
187 PhaseScaling::NegativeTanh => unreachable!(),
188 }
189
190 for v in &mut result {
192 if *v < 0.0 {
193 *v = 0.0;
194 }
195 }
196
197 apply_mask_zero(&mut result, mask);
198
199 result
200}
201
202pub fn calculate_swi(
216 phase: &[f64],
217 magnitude: &[f64],
218 mask: &[u8],
219 grid: &Grid,
220 params: &SwiParams,
221) -> Vec<f64> {
222 let n_total = grid.n_total();
223
224 let filtered = highpass_filter(phase, mask, grid, params.hp_sigma);
226
227 let phase_mask = create_phase_mask(&filtered, mask, params.scaling, params.strength);
229
230 let mut swi = vec![0.0; n_total];
232 for i in 0..n_total {
233 swi[i] = magnitude[i] * phase_mask[i];
234 }
235
236 swi
237}
238
239pub fn create_mip(
253 data: &[f64],
254 grid: &Grid,
255 window: usize,
256) -> Vec<f64> {
257 let (nx, ny, nz) = grid.dims;
258
259 if window > nz || window == 0 {
260 return vec![];
261 }
262
263 let nz_out = nz - window + 1;
264 let nxy = nx * ny;
265 let mut mip = vec![0.0; nxy * nz_out];
266
267 for k_out in 0..nz_out {
268 for j in 0..ny {
269 for i in 0..nx {
270 let idx_xy = i + j * nx;
271 let mut min_val = data[idx_xy + k_out * nxy];
272 for kw in 1..window {
273 let val = data[idx_xy + (k_out + kw) * nxy];
274 if val < min_val {
275 min_val = val;
276 }
277 }
278 mip[idx_xy + k_out * nxy] = min_val;
279 }
280 }
281 }
282
283 mip
284}
285
286pub fn softplus_scaling(
300 magnitude: &[f64],
301 offset: f64,
302 factor: f64,
303) -> Vec<f64> {
304 if offset.abs() < 1e-20 {
305 return magnitude.to_vec();
306 }
307
308 let f = factor / offset;
309
310 let arg0 = f * (0.0 - offset);
312 let sp0 = ((1.0 + (-arg0.abs()).exp()).ln() + arg0.max(0.0)) / f;
313
314 magnitude.iter().map(|&val| {
315 let arg = f * (val - offset);
316 let sp = ((1.0 + (-arg.abs()).exp()).ln() + arg.max(0.0)) / f;
317 sp - sp0
318 }).collect()
319}
320
321fn positive_range(data: &[f64], mask: &[u8]) -> (f64, f64) {
325 let mut min_val = f64::MAX;
326 let mut max_val = f64::MIN;
327 for i in 0..data.len() {
328 if mask[i] == 1 && data[i] > 0.0 {
329 if data[i] < min_val { min_val = data[i]; }
330 if data[i] > max_val { max_val = data[i]; }
331 }
332 }
333 if min_val > max_val {
334 (0.0, 1.0) } else {
336 (min_val, max_val)
337 }
338}
339
340fn negative_range(data: &[f64], mask: &[u8]) -> (f64, f64) {
342 let mut min_val = f64::MAX;
343 let mut max_val = f64::MIN;
344 for i in 0..data.len() {
345 if mask[i] == 1 && data[i] <= 0.0 {
346 if data[i] < min_val { min_val = data[i]; }
347 if data[i] > max_val { max_val = data[i]; }
348 }
349 }
350 if min_val > max_val {
351 (-1.0, 0.0) } else {
353 (min_val, max_val)
354 }
355}
356
357#[inline]
359fn rescale(val: f64, old_min: f64, old_max: f64, new_min: f64, new_max: f64) -> f64 {
360 let range = old_max - old_min;
361 if range.abs() < 1e-20 {
362 return (new_min + new_max) / 2.0;
363 }
364 let t = (val - old_min) / range;
365 let t = t.clamp(0.0, 1.0);
367 new_min + t * (new_max - new_min)
368}
369
370#[cfg(test)]
371mod tests {
372 use super::*;
373
374 #[test]
375 fn test_calculate_swi_zero_phase() {
376 let n = 8;
377 let nn = n * n * n;
378 let phase = vec![0.0; nn];
379 let magnitude = vec![1.0; nn];
380 let mask = vec![1u8; nn];
381 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
382
383 let swi = calculate_swi(&phase, &magnitude, &mask, &grid, &SwiParams::default());
384
385 for &v in &swi {
387 assert!(v.is_finite(), "SWI values should be finite");
388 assert!(v >= 0.0, "SWI values should be non-negative");
389 }
390 }
391
392 #[test]
393 fn test_calculate_swi_mask() {
394 let n = 8;
395 let nn = n * n * n;
396 let phase = vec![0.1; nn];
397 let magnitude = vec![1.0; nn];
398 let mut mask = vec![1u8; nn];
399 mask[0] = 0;
400 mask[1] = 0;
401 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
402
403 let swi = calculate_swi(&phase, &magnitude, &mask, &grid, &SwiParams::default());
404
405 assert_eq!(swi[0], 0.0, "Outside mask should be 0");
406 assert_eq!(swi[1], 0.0, "Outside mask should be 0");
407 }
408
409 #[test]
410 fn test_phase_mask_range() {
411 let n = 10;
412 let nn = n * n * n;
413 let phase: Vec<f64> = (0..nn).map(|i| (i as f64 * 0.01) - 5.0).collect();
414 let mask = vec![1u8; nn];
415
416 for scaling in &[
417 PhaseScaling::Tanh,
418 PhaseScaling::NegativeTanh,
419 PhaseScaling::Positive,
420 PhaseScaling::Negative,
421 PhaseScaling::Triangular,
422 ] {
423 let pm = create_phase_mask(&phase, &mask, *scaling, 4.0);
424 for (i, &v) in pm.iter().enumerate() {
425 assert!(v >= 0.0, "{:?}: value at {} = {} < 0", scaling, i, v);
426 assert!(v <= 1.0 + 1e-10, "{:?}: value at {} = {} > 1", scaling, i, v);
427 }
428 }
429 }
430
431 #[test]
432 fn test_highpass_filter_constant() {
433 let n = 16;
435 let nn = n * n * n;
436 let data = vec![5.0; nn];
437 let mask = vec![1u8; nn];
438 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
439
440 let result = highpass_filter(&data, &mask, &grid, [2.0, 2.0, 0.0]);
441
442 for &v in &result {
443 assert!(v.abs() < 1.0, "High-pass of constant should be near zero, got {}", v);
444 }
445 }
446
447 #[test]
448 fn test_mip_basic() {
449 let (nx, ny, nz) = (3, 3, 5);
451 let grid = Grid::new(nx, ny, nz, 1.0, 1.0, 1.0);
452 let mut data = vec![10.0; nx * ny * nz];
453 let idx = 1 + 1 * nx + 2 * nx * ny; data[idx] = 1.0;
456
457 let mip = create_mip(&data, &grid, 3);
458 assert_eq!(mip.len(), nx * ny * 3);
459
460 let mip_idx_0 = 1 + 1 * nx + 0 * nx * ny;
463 assert_eq!(mip[mip_idx_0], 1.0);
464 let mip_idx_1 = 1 + 1 * nx + 1 * nx * ny;
466 assert_eq!(mip[mip_idx_1], 1.0);
467 let mip_idx_2 = 1 + 1 * nx + 2 * nx * ny;
469 assert_eq!(mip[mip_idx_2], 1.0);
470 }
471
472 #[test]
473 fn test_mip_window_too_large() {
474 let grid = Grid::new(3, 3, 3, 1.0, 1.0, 1.0);
475 let mip = create_mip(&[1.0; 27], &grid, 10);
476 assert!(mip.is_empty());
477 }
478
479 #[test]
480 fn test_softplus_scaling() {
481 let mag = vec![0.0, 0.5, 1.0, 2.0];
482 let result = softplus_scaling(&mag, 1.0, 2.0);
483
484 assert!(result[0].abs() < 1e-10, "softplus(0) should be ~0, got {}", result[0]);
486 for i in 1..result.len() {
488 assert!(result[i] >= result[i - 1], "softplus should be monotonically increasing");
489 }
490 }
491
492 #[test]
493 fn test_rescale() {
494 assert!((rescale(0.0, 0.0, 10.0, 0.0, 1.0) - 0.0).abs() < 1e-10);
495 assert!((rescale(5.0, 0.0, 10.0, 0.0, 1.0) - 0.5).abs() < 1e-10);
496 assert!((rescale(10.0, 0.0, 10.0, 0.0, 1.0) - 1.0).abs() < 1e-10);
497 assert!((rescale(0.0, 0.0, 10.0, 1.0, 0.0) - 1.0).abs() < 1e-10);
499 assert!((rescale(10.0, 0.0, 10.0, 1.0, 0.0) - 0.0).abs() < 1e-10);
500 }
501}