Skip to content

Adding SF amplitude thresholding as a way of create masks#1337

Merged
arnaudbore merged 9 commits into
scilus:masterfrom
frheault:split_branch_b_independent
Jun 4, 2026
Merged

Adding SF amplitude thresholding as a way of create masks#1337
arnaudbore merged 9 commits into
scilus:masterfrom
frheault:split_branch_b_independent

Conversation

@frheault
Copy link
Copy Markdown
Member

@frheault frheault commented May 21, 2026

Quick description

New scripts that use FOD as input to create an improve WM mask based on either global or relative lobe amplitude to determine if the signal in a voxel is sufficient to be included in the mask.

This is also added to all 3 tracking algorithms to make it easier to use ''on-the-fly'', they are optional parameters (that I believe I will always use). It improves upon or even replace the need for an FA mask in some case.

There is still work to do (move duplicated code to a function for tracking), but I would like it if someone could test it on a random FOD file they have and see if the help/description/results are clear as it is a new behavior...

Type of change

Check the relevant options.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Provide data, screenshots, command line to test (if relevant)

...

Checklist

  • My code follows the style guidelines of this project (run autopep8)
  • I added relevant citations to scripts, modules and functions docstrings and descriptions
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I moved all functions from the script file (except the argparser and main) to scilpy modules
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces global spherical-function (SF) amplitude thresholding to generate white-matter–like masks from fODF/ODF inputs, and wires that capability into multiple tracking CLIs as optional “on-the-fly” masking.

Changes:

  • Added compute_max_sf_amplitude / compute_sf_threshold_mask utilities to derive per-voxel max SF amplitude and a global-threshold-based mask (with optional postprocessing).
  • Added new CLI scil_fodf_global_sf_threshold and registered it as a console script.
  • Added optional global SF thresholding arguments to tracking scripts/utilities and applied the resulting mask to tracking constraints.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/scilpy/tracking/utils.py Adds new tracking CLI options for global SF thresholding and adjusts get_direction_getter API to accept already-loaded data.
src/scilpy/reconst/utils.py Adds core implementation for computing max SF amplitude and deriving a global-threshold SF mask.
src/scilpy/cli/scil_tracking_pft.py Adds global SF thresholding options and applies resulting mask to include/exclude maps; adds sphere argument support.
src/scilpy/cli/scil_tracking_local.py Applies optional global SF threshold mask during local tracking (CPU/GPU).
src/scilpy/cli/scil_tracking_local_dev.py Applies optional global SF threshold mask when an ODF is provided; enforces argument validity.
src/scilpy/cli/scil_fodf_global_sf_threshold.py New standalone CLI to compute and save a global SF threshold mask from SH or peaks input.
pyproject.toml Registers the new CLI entrypoint.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/scilpy/tracking/utils.py Outdated
Comment thread src/scilpy/tracking/utils.py Outdated
Comment thread src/scilpy/tracking/utils.py
Comment thread src/scilpy/cli/scil_tracking_pft.py Outdated
Comment thread src/scilpy/cli/scil_tracking_pft.py Outdated
Comment thread src/scilpy/reconst/utils.py Outdated
Comment thread src/scilpy/reconst/utils.py Outdated
Comment thread src/scilpy/reconst/utils.py Outdated
Comment thread src/scilpy/reconst/utils.py Outdated
Comment thread src/scilpy/cli/scil_fodf_threshold_by_amplitude.py
@codecov
Copy link
Copy Markdown

codecov Bot commented May 21, 2026

Codecov Report

❌ Patch coverage is 87.15084% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.59%. Comparing base (6952a18) to head (31323ad).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1337      +/-   ##
==========================================
+ Coverage   72.50%   72.59%   +0.09%     
==========================================
  Files         300      301       +1     
  Lines       26139    26276     +137     
  Branches     3671     3699      +28     
==========================================
+ Hits        18952    19076     +124     
- Misses       5640     5648       +8     
- Partials     1547     1552       +5     
Flag Coverage Δ
smoketests 69.75% <76.53%> (+0.03%) ⬆️
unittests 14.21% <35.19%> (+0.12%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
Scripts 75.33% <85.26%> (+0.05%) ⬆️
Library 69.31% <89.28%> (+0.14%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@frheault frheault changed the title (WIP) Adding SF amplitude thresholding as a way of create masks Adding SF amplitude thresholding as a way of create masks May 25, 2026
arnaudbore
arnaudbore previously approved these changes Jun 1, 2026
Copy link
Copy Markdown
Contributor

@arnaudbore arnaudbore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM: Add a test for one of the tractogram to use each param to get a better cover.

Comment thread src/scilpy/cli/scil_fodf_threshold_by_amplitude.py
@arnaudbore arnaudbore requested a review from CHrlS98 June 1, 2026 20:18
Copy link
Copy Markdown
Contributor

@CHrlS98 CHrlS98 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imports are in the middle of the code, is there a reason for this?

Comment on lines +42 to +46
thr_g = p.add_mutually_exclusive_group(required=True)
thr_g.add_argument('--relative', type=float,
help='Global SF threshold relative factor (0-1).')
thr_g.add_argument('--absolute', type=float,
help='Global SF absolute threshold.')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it need to be mutually exclusive? Could be both, no?

Comment thread src/scilpy/cli/scil_tracking_local.py Outdated
sf_mask = None
if args.global_sf_rel_thr is not None or \
args.global_sf_abs_thr is not None:
from scilpy.tracking.utils import get_global_sf_threshold_mask
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason why this is a conditional import?


if args.global_sf_rel_thr is not None or \
args.global_sf_abs_thr is not None:
from scilpy.tracking.utils import get_global_sf_threshold_mask
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, why is this import inside the main?

Comment thread src/scilpy/reconst/utils.py Outdated
else:
raise ValueError("Peaks input must be 4D or 5D.")

norms = np.linalg.norm(peaks, axis=-1)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would consider adding a warning if all peaks have the same norm, meaning they'd be normalized.

Comment thread src/scilpy/reconst/utils.py Outdated
mask = max_amp >= threshold

if postprocess_mask and np.any(mask):
import scipy.ndimage as ndi
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move import to top of file

Comment thread src/scilpy/reconst/utils.py Outdated
for label, count in enumerate(label_counts_inverted):
if label == 0:
continue # Skip background
if count < 100: # Threshold for filling holes
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels a bit ad hoc no?

Comment thread src/scilpy/tracking/utils.py Outdated
Comment on lines +56 to +67
' "1": {"propagator": "ODF", '
'"filename": str,\n'
' "sh_basis": str, '
'"algo": str,\n'
' "theta": float, '
'"step_size": float},\n'
' "2": {"propagator": "ODF", '
'"filename": str,\n'
' "sh_basis": str, '
'"algo": str,\n'
' "theta": float, '
'"step_size": float}\n'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this formatting very ugly?

Comment thread src/scilpy/reconst/utils.py Outdated
return max_sf


def compute_sf_threshold_mask(data, sphere_name='repulsion100',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given the postprocessing step this feels very tracking-oriented, not sure it's reconst.

CHrlS98
CHrlS98 previously approved these changes Jun 3, 2026
Copy link
Copy Markdown
Contributor

@CHrlS98 CHrlS98 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright thanks, I have one last request, but I'll approve right away bcause it's not breaking.

Comment thread src/scilpy/tracking/utils.py Outdated
label_counts_inverted = np.bincount(labels_inverted.ravel())

# Fill holes smaller than 5% of the largest component size
hole_threshold = 0.05 * largest_component_size
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My last request would be that this 0.05 is a parameter to the method, even if it is not exposed in the script.

Copy link
Copy Markdown
Contributor

@arnaudbore arnaudbore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GTG

@arnaudbore arnaudbore added this pull request to the merge queue Jun 4, 2026
Merged via the queue into scilus:master with commit b2bf4ac Jun 4, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants