1use crate::Grid;
19use crate::utils::curvature::calculate_curvature_proximity;
20
21#[derive(Clone, Debug)]
23pub struct SdfParams {
24 pub sigma1: f64,
26 pub sigma2: f64,
28 pub spatial_radius: i32,
30 pub lower_lim: f64,
32 pub curv_constant: f64,
34 pub use_curvature: bool,
36}
37
38impl Default for SdfParams {
39 fn default() -> Self {
40 Self {
41 sigma1: 10.0,
42 sigma2: 10.0,
46 spatial_radius: 8,
47 lower_lim: 0.6,
48 curv_constant: 500.0,
49 use_curvature: true,
50 }
51 }
52}
53
54impl SdfParams {
55 pub fn stage1() -> Self {
58 Self {
59 sigma1: 10.0,
60 sigma2: 10.0,
61 spatial_radius: 8,
62 lower_lim: 0.6,
63 curv_constant: 500.0,
64 use_curvature: true,
65 }
66 }
67
68 pub fn stage2() -> Self {
70 Self {
71 sigma1: 8.0,
72 sigma2: 2.0,
73 spatial_radius: 8,
74 lower_lim: 0.6,
75 curv_constant: 500.0,
76 use_curvature: true,
77 }
78 }
79}
80
81pub fn sdf(
96 tfs: &[f64],
97 mask: &[f64],
98 vasc_only: &[f64],
99 grid: &Grid,
100 params: &SdfParams,
101 progress: impl Fn(usize, usize),
102) -> Vec<f64> {
103 let (nx, ny, nz) = grid.dims;
104 let n_total = nx * ny * nz;
105
106 let mask_binary: Vec<u8> = mask.iter().map(|&v| if v > 0.0 { 1 } else { 0 }).collect();
108
109 let sigma = (params.sigma1 * params.sigma1 + params.sigma2 * params.sigma2).sqrt();
111 let n = if sigma > 0.0 { -sigma.ln() / 0.5_f64.ln() } else { 0.0 };
112
113 let prox1 = if params.sigma1 > 0.0 {
116 gaussian_smooth_3d_masked_f64(mask, mask, nx, ny, nz, &[params.sigma1, 2.0 * params.sigma1, 2.0 * params.sigma1])
117 } else {
118 mask.to_vec()
119 };
120
121 let prox = if params.use_curvature {
123 let (prox_curv, _curv_i) = calculate_curvature_proximity(
124 &mask_binary,
125 &prox1,
126 params.lower_lim,
127 params.curv_constant,
128 params.sigma1,
129 grid,
130 );
131 prox_curv
132 } else {
133 prox1.iter()
137 .zip(mask.iter())
138 .map(|(&p, &m)| {
139 if m > 0.0 && p > 0.0 && p < params.lower_lim {
140 params.lower_lim
141 } else {
142 p
143 }
144 })
145 .collect()
146 };
147
148 let prox_final = if params.sigma2 > 0.0 {
150 let prox2 = gaussian_smooth_3d_masked_f64(vasc_only, mask, nx, ny, nz, &[params.sigma2, params.sigma2, params.sigma2]);
151 prox.iter().zip(prox2.iter()).map(|(&p, &p2)| p * p2).collect()
153 } else {
154 prox
155 };
156
157 let mut alpha: Vec<f64> = prox_final.iter()
160 .zip(mask.iter())
161 .map(|(&p, &m)| {
162 if m > 0.0 {
163 sigma * (p.powf(n) * 100.0).round() / 100.0
164 } else {
165 0.0
166 }
167 })
168 .collect();
169
170 for i in 0..n_total {
173 if mask[i] > 0.0 && vasc_only[i] == 0.0 {
174 alpha[i] = 1.0;
175 }
176 }
177
178 let mut unique_alphas: Vec<f64> = alpha.iter()
180 .filter(|&&a| a > 0.0)
181 .copied()
182 .collect();
183 unique_alphas.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
184 unique_alphas.dedup();
185
186 let total_alphas = unique_alphas.len();
187
188 let mut alpha_index: Vec<usize> = vec![0; n_total];
190 for i in 0..n_total {
191 if alpha[i] > 0.0 {
192 let idx = unique_alphas.iter().position(|&a| (a - alpha[i]).abs() < 1e-10).unwrap_or(0);
194 alpha_index[i] = idx + 1; }
196 }
197
198 let mut background = vec![0.0f64; n_total];
201
202 let filter_size = 2 * (2.0 * sigma).ceil() as usize + 1;
204
205 for (alpha_idx, ¤t_alpha) in unique_alphas.iter().enumerate() {
206 progress(alpha_idx, total_alphas);
207
208 let smoothed: Vec<f64> = if current_alpha > 0.0 {
210 let weighted_tfs: Vec<f64> = tfs.iter()
212 .zip(mask.iter())
213 .map(|(&t, &m)| t * m)
214 .collect();
215
216 let num = gaussian_smooth_3d_with_filter_size(&weighted_tfs, nx, ny, nz, current_alpha, filter_size);
217 let denom = gaussian_smooth_3d_with_filter_size(&mask.to_vec(), nx, ny, nz, current_alpha, filter_size);
218
219 num.iter()
221 .zip(denom.iter())
222 .map(|(&n, &d)| if d.abs() > 1e-10 { n / d } else { 0.0 })
223 .collect()
224 } else {
225 tfs.iter().zip(mask.iter()).map(|(&t, &m)| t * m).collect()
227 };
228
229 for i in 0..n_total {
231 if alpha_index[i] == alpha_idx + 1 {
232 background[i] = smoothed[i];
233 }
234 }
235 }
236
237 progress(total_alphas, total_alphas);
238
239 let local_field: Vec<f64> = tfs.iter()
241 .zip(background.iter())
242 .zip(mask.iter())
243 .map(|((&t, &b), &m)| (t - b) * m)
244 .collect();
245
246 local_field
247}
248
249pub fn sdf_curvature(
253 tfs: &[f64],
254 mask: &[f64],
255 vasc_only: &[f64],
256 grid: &Grid,
257 params: &SdfParams,
258) -> Vec<f64> {
259 let params_with_curv = SdfParams {
261 use_curvature: true,
262 ..params.clone()
263 };
264
265 sdf(tfs, mask, vasc_only, grid, ¶ms_with_curv, |_, _| {})
266}
267
268fn gaussian_smooth_3d_with_filter_size(
270 data: &[f64],
271 nx: usize, ny: usize, nz: usize,
272 sigma: f64,
273 filter_size: usize,
274) -> Vec<f64> {
275 if sigma <= 0.0 {
276 return data.to_vec();
277 }
278
279 let kernel_radius = (filter_size - 1) / 2;
281 let mut kernel = vec![0.0f64; filter_size];
282
283 let mut sum = 0.0;
284 for i in 0..filter_size {
285 let x = i as f64 - kernel_radius as f64;
286 kernel[i] = (-x * x / (2.0 * sigma * sigma)).exp();
287 sum += kernel[i];
288 }
289
290 for k in kernel.iter_mut() {
292 *k /= sum;
293 }
294
295 let smoothed_x = convolve_1d_direction(data, nx, ny, nz, &kernel, 'x');
297 let smoothed_xy = convolve_1d_direction(&smoothed_x, nx, ny, nz, &kernel, 'y');
298 convolve_1d_direction(&smoothed_xy, nx, ny, nz, &kernel, 'z')
299}
300
301fn gaussian_smooth_3d_masked_f64(
303 data: &[f64],
304 mask: &[f64],
305 nx: usize, ny: usize, nz: usize,
306 sigmas: &[f64; 3],
307) -> Vec<f64> {
308 let smoothed_x = convolve_1d_direction_sigma(data, nx, ny, nz, sigmas[0], 'x');
310 let smoothed_xy = convolve_1d_direction_sigma(&smoothed_x, nx, ny, nz, sigmas[1], 'y');
311 let smoothed_xyz = convolve_1d_direction_sigma(&smoothed_xy, nx, ny, nz, sigmas[2], 'z');
312
313 smoothed_xyz.iter()
315 .zip(mask.iter())
316 .map(|(&v, &m)| if m > 0.0 { v } else { 0.0 })
317 .collect()
318}
319
320fn convolve_1d_direction(
323 data: &[f64],
324 nx: usize, ny: usize, nz: usize,
325 kernel: &[f64],
326 direction: char,
327) -> Vec<f64> {
328 let n_total = nx * ny * nz;
329 let mut result = vec![0.0f64; n_total];
330 let kernel_radius = (kernel.len() - 1) / 2;
331
332 let idx = |i: usize, j: usize, k: usize| i + j * nx + k * nx * ny;
333
334 let clamp_x = |x: isize| -> usize { x.max(0).min(nx as isize - 1) as usize };
336 let clamp_y = |y: isize| -> usize { y.max(0).min(ny as isize - 1) as usize };
337 let clamp_z = |z: isize| -> usize { z.max(0).min(nz as isize - 1) as usize };
338
339 match direction {
340 'x' => {
341 for k in 0..nz {
342 for j in 0..ny {
343 for i in 0..nx {
344 let mut sum = 0.0;
345
346 for ki in 0..kernel.len() {
347 let offset = ki as isize - kernel_radius as isize;
348 let ni = clamp_x(i as isize + offset);
349 sum += data[idx(ni, j, k)] * kernel[ki];
350 }
351
352 result[idx(i, j, k)] = sum;
353 }
354 }
355 }
356 }
357 'y' => {
358 for k in 0..nz {
359 for j in 0..ny {
360 for i in 0..nx {
361 let mut sum = 0.0;
362
363 for ki in 0..kernel.len() {
364 let offset = ki as isize - kernel_radius as isize;
365 let nj = clamp_y(j as isize + offset);
366 sum += data[idx(i, nj, k)] * kernel[ki];
367 }
368
369 result[idx(i, j, k)] = sum;
370 }
371 }
372 }
373 }
374 'z' => {
375 for k in 0..nz {
376 for j in 0..ny {
377 for i in 0..nx {
378 let mut sum = 0.0;
379
380 for ki in 0..kernel.len() {
381 let offset = ki as isize - kernel_radius as isize;
382 let nk = clamp_z(k as isize + offset);
383 sum += data[idx(i, j, nk)] * kernel[ki];
384 }
385
386 result[idx(i, j, k)] = sum;
387 }
388 }
389 }
390 }
391 _ => panic!("Invalid convolution direction"),
392 }
393
394 result
395}
396
397fn convolve_1d_direction_sigma(
399 data: &[f64],
400 nx: usize, ny: usize, nz: usize,
401 sigma: f64,
402 direction: char,
403) -> Vec<f64> {
404 if sigma <= 0.0 {
405 return data.to_vec();
406 }
407
408 let kernel_radius = (2.0 * sigma).ceil() as usize;
411 let kernel_size = 2 * kernel_radius + 1;
412 let mut kernel = vec![0.0f64; kernel_size];
413
414 let mut sum = 0.0;
415 for i in 0..kernel_size {
416 let x = i as f64 - kernel_radius as f64;
417 kernel[i] = (-x * x / (2.0 * sigma * sigma)).exp();
418 sum += kernel[i];
419 }
420
421 for k in kernel.iter_mut() {
423 *k /= sum;
424 }
425
426 convolve_1d_direction(data, nx, ny, nz, &kernel, direction)
427}
428
429pub fn sdf_simple(
431 tfs: &[f64],
432 mask: &[f64],
433 grid: &Grid,
434 sigma1: f64,
435) -> Vec<f64> {
436 let vasc_only = vec![1.0f64; mask.len()];
437 let params = SdfParams {
438 sigma1,
439 sigma2: 0.0,
440 use_curvature: false,
441 ..Default::default()
442 };
443
444 sdf(tfs, mask, &vasc_only, grid, ¶ms, |_, _| {})
445}
446
447#[cfg(test)]
448mod tests {
449 use super::*;
450
451 #[test]
452 fn test_sdf_simple() {
453 let n = 10;
455 let n_total = n * n * n;
456
457 let tfs = vec![1.0f64; n_total];
458 let mask = vec![1.0f64; n_total];
459 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
460
461 let lfs = sdf_simple(&tfs, &mask, &grid, 2.0);
462
463 let max_lfs = lfs.iter().fold(0.0f64, |a, &b| a.max(b.abs()));
465 assert!(max_lfs < 0.1, "Max LFS was {}", max_lfs);
466 }
467
468 #[test]
469 fn test_gaussian_smooth_constant() {
470 let data = vec![5.0f64; 27];
472 let smoothed = gaussian_smooth_3d_with_filter_size(&data, 3, 3, 3, 1.0, 5);
473
474 for &v in &smoothed {
475 assert!((v - 5.0).abs() < 0.1);
476 }
477 }
478}