1use num_complex::Complex64;
15use crate::Grid;
16use crate::fft::{fft3d, ifft3d};
17use crate::kernels::smv::{smv_kernel, erode_mask_smv};
18
19#[cfg_attr(feature = "introspection", derive(serde::Serialize))]
21#[derive(Clone, Debug)]
22pub struct VsharpParams {
23 pub threshold: f64,
27 pub max_radius: f64,
29 pub min_radius: f64,
31}
32
33impl Default for VsharpParams {
34 fn default() -> Self {
35 Self {
36 threshold: 0.2,
41 max_radius: 12.0,
42 min_radius: 1.0,
43 }
44 }
45}
46
47pub fn vsharp(
62 field: &[f64],
63 mask: &[u8],
64 grid: &Grid,
65 params: &VsharpParams,
66 progress: impl FnMut(usize, usize),
67) -> (Vec<f64>, Vec<u8>) {
68 let radii = vsharp_radii(params);
69 vsharp_with_radii(field, mask, grid, &radii, params.threshold, progress)
70}
71
72fn vsharp_radii(params: &VsharpParams) -> Vec<f64> {
77 let mut radii = Vec::new();
78 let mut r = params.max_radius;
79 let step = params.min_radius;
80 while r >= step {
81 radii.push(r);
82 r -= step;
83 }
84 if radii.is_empty() {
85 radii.push(params.max_radius);
86 }
87 radii
88}
89
90pub(crate) fn vsharp_with_radii(
95 field: &[f64],
96 mask: &[u8],
97 grid: &Grid,
98 radii: &[f64],
99 threshold: f64,
100 mut progress: impl FnMut(usize, usize),
101) -> (Vec<f64>, Vec<u8>) {
102 let (nx, ny, nz) = grid.dims;
103
104 if radii.is_empty() {
105 return (vec![0.0; nx * ny * nz], mask.to_vec());
106 }
107
108 if radii.len() == 1 {
110 progress(1, 1);
111 return crate::bgremove::sharp::sharp_core(
112 field, mask, grid, threshold, radii[0]
113 );
114 }
115
116 let n_total = nx * ny * nz;
117 let n_radii = radii.len();
118
119 let mut sorted_radii = radii.to_vec();
121 sorted_radii.sort_by(|a, b| b.partial_cmp(a).unwrap());
122
123 let mut field_complex: Vec<Complex64> = field.iter()
125 .map(|&x| Complex64::new(x, 0.0))
126 .collect();
127 fft3d(&mut field_complex, nx, ny, nz);
128 let field_fft = field_complex.clone();
129
130 let mut processed = vec![false; n_total];
132 let mut local_field = vec![0.0; n_total];
133 let mut final_mask = vec![0u8; n_total];
134
135 let delta = 1.0 - 1e-7_f64.sqrt();
136 let mut inverse_kernel: Option<Vec<f64>> = None;
137
138 for (idx, &radius) in sorted_radii.iter().enumerate() {
139 progress(idx + 1, n_radii);
141
142 let s_kernel = smv_kernel(grid, radius);
144
145 let mut s_complex: Vec<Complex64> = s_kernel.iter()
147 .map(|&x| Complex64::new(x, 0.0))
148 .collect();
149 fft3d(&mut s_complex, nx, ny, nz);
150 let s_fft: Vec<f64> = s_complex.iter().map(|c| c.re).collect();
151
152 if inverse_kernel.is_none() {
154 inverse_kernel = Some(s_fft.iter().map(|&s| {
155 let one_minus_s = 1.0 - s;
156 if one_minus_s.abs() < threshold {
157 0.0
158 } else {
159 1.0 / one_minus_s
160 }
161 }).collect());
162 }
163
164 let eroded = erode_mask_smv(mask, &s_fft, grid, delta);
166 let current_mask: Vec<bool> = eroded.iter().map(|&m| m == 1).collect();
167
168 let mut filtered = field_fft.clone();
170 for i in 0..n_total {
171 filtered[i] *= 1.0 - s_fft[i];
172 }
173
174 ifft3d(&mut filtered, nx, ny, nz);
175
176 for i in 0..n_total {
177 if current_mask[i] && !processed[i] {
178 local_field[i] = filtered[i].re;
179 processed[i] = true;
180 final_mask[i] = 1;
181 }
182 }
183 }
184
185 if let Some(inv_kernel) = inverse_kernel {
187 let mut local_complex: Vec<Complex64> = local_field.iter()
188 .map(|&x| Complex64::new(x, 0.0))
189 .collect();
190
191 fft3d(&mut local_complex, nx, ny, nz);
192
193 for i in 0..n_total {
194 local_complex[i] *= inv_kernel[i];
195 }
196
197 ifft3d(&mut local_complex, nx, ny, nz);
198
199 for i in 0..n_total {
200 local_field[i] = if final_mask[i] == 1 { local_complex[i].re } else { 0.0 };
201 }
202 }
203
204 (local_field, final_mask)
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn test_vsharp_zero_field() {
213 let n = 8;
214 let field = vec![0.0; n * n * n];
215 let mask = vec![1u8; n * n * n];
216 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
217
218 let radii = vec![4.0, 3.0, 2.0];
219 let (local, _) = vsharp_with_radii(&field, &mask, &grid, &radii, 0.05, |_, _| {});
220
221 for &val in local.iter() {
222 assert!(val.abs() < 1e-10);
223 }
224 }
225
226 #[test]
227 fn test_vsharp_preserves_more_than_sharp() {
228 let n = 16;
229 let field = vec![0.0; n * n * n];
230 let mask = vec![1u8; n * n * n];
231 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
232
233 let radii = vec![5.0, 4.0, 3.0, 2.0];
235 let (_, vsharp_mask) = vsharp_with_radii(&field, &mask, &grid, &radii, 0.05, |_, _| {});
236
237 let (_, sharp_mask) = crate::bgremove::sharp::sharp_core(
239 &field, &mask, &grid, 0.05, 5.0
240 );
241
242 let vsharp_count: usize = vsharp_mask.iter().map(|&m| m as usize).sum();
243 let sharp_count: usize = sharp_mask.iter().map(|&m| m as usize).sum();
244
245 assert!(vsharp_count >= sharp_count,
247 "V-SHARP {} should preserve at least as many as SHARP {}",
248 vsharp_count, sharp_count);
249 }
250
251 #[test]
252 fn test_vsharp_nonuniform_voxels() {
253 let n = 8;
254 let field: Vec<f64> = (0..n*n*n).map(|i| (i as f64) * 0.001).collect();
255 let mask = vec![1u8; n * n * n];
256 let grid = Grid::new(n, n, n, 0.5, 1.0, 2.0);
257
258 let radii = vec![4.0, 3.0, 2.0];
260 let (local, final_mask) = vsharp_with_radii(
261 &field, &mask, &grid, &radii, 0.05, |_, _| {}
262 );
263
264 for (i, &val) in local.iter().enumerate() {
266 assert!(val.is_finite(), "V-SHARP nonuniform voxels: finite at index {}", i);
267 }
268
269 let mask_count: usize = final_mask.iter().map(|&m| m as usize).sum();
271 assert!(mask_count > 0, "V-SHARP nonuniform: final mask should have some voxels");
272 }
273
274 #[test]
275 fn test_vsharp_single_radius() {
276 let n = 8;
277 let field: Vec<f64> = (0..n*n*n).map(|i| (i as f64) * 0.001).collect();
278 let mask = vec![1u8; n * n * n];
279 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
280
281 let radii = vec![3.0];
283 let (local, final_mask) = vsharp_with_radii(
284 &field, &mask, &grid, &radii, 0.05, |_, _| {}
285 );
286
287 for (i, &val) in local.iter().enumerate() {
289 assert!(val.is_finite(), "V-SHARP single radius: finite at index {}", i);
290 }
291
292 let (sharp_local, sharp_mask) = crate::bgremove::sharp::sharp_core(
294 &field, &mask, &grid, 0.05, 3.0
295 );
296
297 for i in 0..n*n*n {
298 assert!(
299 (local[i] - sharp_local[i]).abs() < 1e-10,
300 "Single-radius V-SHARP should match SHARP at index {}", i
301 );
302 }
303
304 assert_eq!(final_mask, sharp_mask, "Single-radius V-SHARP mask should match SHARP mask");
305 }
306
307 #[test]
308 fn test_vsharp_empty_radii() {
309 let n = 8;
311 let field: Vec<f64> = (0..n * n * n).map(|i| (i as f64) * 0.001).collect();
312 let mask = vec![1u8; n * n * n];
313 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
314
315 let (local, returned_mask) = vsharp_with_radii(
316 &field, &mask, &grid, &[], 0.05, |_, _| {}
317 );
318
319 for &val in &local {
320 assert_eq!(val, 0.0, "Empty radii should return zero local field");
321 }
322 assert_eq!(returned_mask, mask, "Empty radii should return original mask");
323 }
324
325 #[test]
326 fn test_vsharp_larger_volume() {
327 let n = 16;
329 let mut field = vec![0.0; n * n * n];
330 for z in 0..n {
332 for y in 0..n {
333 for x in 0..n {
334 field[x + y * n + z * n * n] = (z as f64) * 0.1;
335 }
336 }
337 }
338
339 let mut mask = vec![0u8; n * n * n];
341 let center = n / 2;
342 let radius = n / 3;
343 for z in 0..n {
344 for y in 0..n {
345 for x in 0..n {
346 let dx = (x as i32) - (center as i32);
347 let dy = (y as i32) - (center as i32);
348 let dz = (z as i32) - (center as i32);
349 if dx * dx + dy * dy + dz * dz <= (radius * radius) as i32 {
350 mask[x + y * n + z * n * n] = 1;
351 }
352 }
353 }
354 }
355
356 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
357 let radii = vec![6.0, 4.0, 3.0, 2.0];
358 let (local, final_mask) = vsharp_with_radii(
359 &field, &mask, &grid, &radii, 0.05, |_, _| {}
360 );
361
362 assert_eq!(local.len(), n * n * n);
363 for &val in &local {
364 assert!(val.is_finite(), "V-SHARP larger volume values should be finite");
365 }
366
367 let mask_count: usize = final_mask.iter().map(|&m| m as usize).sum();
368 assert!(mask_count > 0, "V-SHARP larger volume should have voxels in final mask");
369
370 for i in 0..n * n * n {
372 if final_mask[i] == 0 {
373 assert_eq!(local[i], 0.0, "Outside final mask should be zero");
374 }
375 }
376 }
377
378 #[test]
379 fn test_vsharp_with_progress() {
380 let n = 8;
381 let field: Vec<f64> = (0..n * n * n).map(|i| (i as f64) * 0.001).collect();
382 let mask = vec![1u8; n * n * n];
383 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
384
385 let radii = vec![4.0, 3.0, 2.0];
386 let mut progress_calls = Vec::new();
387 let (local, _) = vsharp_with_radii(
388 &field, &mask, &grid, &radii, 0.05,
389 |idx, total| { progress_calls.push((idx, total)); }
390 );
391
392 assert_eq!(local.len(), n * n * n);
393 assert!(!progress_calls.is_empty(), "Progress should be called");
394 for &val in &local {
395 assert!(val.is_finite());
396 }
397 }
398
399 #[test]
400 fn test_vsharp_with_progress_single_radius() {
401 let n = 8;
402 let field: Vec<f64> = (0..n * n * n).map(|i| (i as f64) * 0.001).collect();
403 let mask = vec![1u8; n * n * n];
404 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
405
406 let radii = vec![3.0];
407 let mut progress_calls = Vec::new();
408 let (local, _) = vsharp_with_radii(
409 &field, &mask, &grid, &radii, 0.05,
410 |idx, total| { progress_calls.push((idx, total)); }
411 );
412
413 assert_eq!(local.len(), n * n * n);
414 assert!(!progress_calls.is_empty(), "Progress should be called for single radius");
415 for &val in &local {
416 assert!(val.is_finite());
417 }
418 }
419
420 #[test]
421 fn test_vsharp_with_progress_empty_radii() {
422 let n = 8;
423 let field = vec![0.0; n * n * n];
424 let mask = vec![1u8; n * n * n];
425 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
426
427 let mut progress_calls = Vec::new();
428 let (local, returned_mask) = vsharp_with_radii(
429 &field, &mask, &grid, &[], 0.05,
430 |idx, total| { progress_calls.push((idx, total)); }
431 );
432
433 for &val in &local {
434 assert_eq!(val, 0.0);
435 }
436 assert_eq!(returned_mask, mask);
437 }
438
439 #[test]
440 fn test_vsharp_unsorted_radii() {
441 let n = 8;
443 let field: Vec<f64> = (0..n * n * n).map(|i| (i as f64) * 0.001).collect();
444 let mask = vec![1u8; n * n * n];
445 let grid = Grid::new(n, n, n, 1.0, 1.0, 1.0);
446
447 let radii_sorted = vec![4.0, 3.0, 2.0];
448 let radii_unsorted = vec![2.0, 4.0, 3.0];
449
450 let (local_sorted, mask_sorted) = vsharp_with_radii(
451 &field, &mask, &grid, &radii_sorted, 0.05, |_, _| {}
452 );
453 let (local_unsorted, mask_unsorted) = vsharp_with_radii(
454 &field, &mask, &grid, &radii_unsorted, 0.05, |_, _| {}
455 );
456
457 assert_eq!(mask_sorted, mask_unsorted, "Sorted and unsorted radii should give same mask");
459 for i in 0..n * n * n {
460 assert!(
461 (local_sorted[i] - local_unsorted[i]).abs() < 1e-10,
462 "Results should match at index {}",
463 i
464 );
465 }
466 }
467}