Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
7ba344f
Work in progress for gradient
frheault Feb 23, 2026
986d6b1
Working with gradients cmd_scilpy
frheault Feb 23, 2026
3d10ed4
Fix tracking
frheault Feb 23, 2026
ce3ec68
Fix validation of gradient
frheault Feb 24, 2026
fe7c175
Pep8 fixes and final tests
frheault Feb 24, 2026
509bda5
Fix in apply_transform
frheault Feb 28, 2026
db52500
Fix TRK and TCK mismatch
frheault Mar 1, 2026
01fe9ad
Fix tracking saving via LazyTractogram
frheault Mar 1, 2026
7c51aca
Added more tracking and msmt
frheault Mar 4, 2026
f86a419
Fix conflict and solve tests
frheault Apr 22, 2026
a995992
Working tracking in vox space
frheault Apr 30, 2026
e706395
Merge branch 'master' of https://github.com/scilus/scilpy into reorie…
frheault Apr 30, 2026
d3a2c41
Working world space version with tests
frheault Apr 30, 2026
c86498c
Flake8, unit tests are passing and refactor
frheault Apr 30, 2026
b83b452
Final review and manual testing (emmanuel+random)
frheault Apr 30, 2026
d867d81
Fix the parsing of voxel order
frheault Apr 30, 2026
4d3cd3d
Merge branch 'dev_3.0.x' of https://github.com/scilus/scilpy into reo…
frheault May 1, 2026
fcca92f
Fix nufo int
frheault May 5, 2026
415bd76
Extra warning and test on manu data
frheault May 6, 2026
ab6edbe
conductor(setup): Add conductor setup files
frheault May 7, 2026
87f3afb
feat(io): Implement StatefulImage direction space transformation
frheault May 7, 2026
25bb01a
conductor(plan): Mark phase 'Phase 1: Core Implementation' as complete
frheault May 7, 2026
4be1000
chore(conductor): Mark track 'Implement StatefulImage direction space…
frheault May 7, 2026
c4b239a
Almost working prototype back to voxel space
frheault May 7, 2026
386e258
Include all SH flag into the statefulImage
frheault May 11, 2026
d86c737
Fix inplace modification for TODI
frheault May 11, 2026
2656f89
Fix inplace modification for TODI
frheault May 11, 2026
c4c1429
Rel and Abs threshold fodf
frheault May 11, 2026
4258d90
tmp working version
frheault May 12, 2026
0b0ffc4
Working on SH
frheault May 12, 2026
bfd9cce
Working on SH
frheault May 12, 2026
514625a
Working SF filtering
frheault May 12, 2026
9a5364b
Improve heuristic for peaks
frheault May 12, 2026
1d37457
Improved heuristic and optimization of reorient_SH
frheault May 12, 2026
c58be43
Review and pep8
frheault May 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ description = "Scilpy: diffusion MRI tools and utilities"
authors = [{ name = "SCIL Team" }]
readme = "README.md"
requires-python = ">=3.11, <3.13"
license-files = ["LICENSE"]
license = { file = "LICENSE" }
classifiers = [
"Development Status :: 3 - Alpha",
"Environment :: Console",
Expand Down Expand Up @@ -206,6 +206,7 @@ scil_sh_fusion = "scilpy.cli.scil_sh_fusion:main"
scil_sh_to_aodf = "scilpy.cli.scil_sh_to_aodf:main"
scil_sh_to_rish = "scilpy.cli.scil_sh_to_rish:main"
scil_sh_to_sf = "scilpy.cli.scil_sh_to_sf:main"
scil_fodf_global_sf_threshold = "scilpy.cli.scil_fodf_global_sf_threshold:main"
scil_stats_group_comparison = "scilpy.cli.scil_stats_group_comparison:main"
scil_surface_apply_transform = "scilpy.cli.scil_surface_apply_transform:main"
scil_surface_convert = "scilpy.cli.scil_surface_convert:main"
Expand Down
15 changes: 11 additions & 4 deletions src/scilpy/cli/scil_NODDI_maps.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@
import tempfile

import amico
from dipy.io.gradients import read_bvals_bvecs
import numpy as np

from scilpy.io.gradients import fsl2mrtrix
from scilpy.io.stateful_image import StatefulImage
from scilpy.io.utils import (add_overwrite_arg,
add_processes_arg,
add_verbose_arg,
Expand Down Expand Up @@ -117,7 +117,11 @@ def main():
assert_headers_compatible(parser, args.in_dwi, optional=args.mask)

# Generate a scheme file from the bvals and bvecs files
bvals, _ = read_bvals_bvecs(args.in_bval, args.in_bvec)
simg = StatefulImage.load(args.in_dwi)
simg.load_gradients(args.in_bval, args.in_bvec)
bvals = simg.bvals
world_bvecs = simg.world_bvecs

_ = check_b0_threshold(bvals.min(), b0_thr=args.tolerance,
skip_b0_check=args.skip_b0_check,
overwrite_with_min=False)
Expand All @@ -135,13 +139,16 @@ def main():
logging.info('Will compute NODDI with AMICO on {} shells at found at {}.'
.format(len(shells_centroids), np.sort(shells_centroids)))

# Save the resulting bvals to a temporary file
# Save the resulting bvals and bvecs to temporary files
tmp_dir = tempfile.TemporaryDirectory()
tmp_scheme_filename = os.path.join(tmp_dir.name, 'gradients.b')
tmp_bval_filename = os.path.join(tmp_dir.name, 'bval')
tmp_bvec_filename = os.path.join(tmp_dir.name, 'bvec')
np.savetxt(tmp_bval_filename, shells_centroids[indices_shells],
newline=' ', fmt='%i')
fsl2mrtrix(tmp_bval_filename, args.in_bvec, tmp_scheme_filename)
# Use world_bvecs for the MRTrix scheme file to ensure consistency
np.savetxt(tmp_bvec_filename, world_bvecs.T, fmt='%.8f')
fsl2mrtrix(tmp_bval_filename, tmp_bvec_filename, tmp_scheme_filename)

with redirected_stdout:
# Load the data
Expand Down
21 changes: 12 additions & 9 deletions src/scilpy/cli/scil_bingham_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@
------------------------------------------------------------------------------
"""

import nibabel as nib
import time
import argparse
import logging

from scilpy.io.image import get_data_as_mask
from scilpy.io.stateful_image import StatefulImage
from scilpy.io.utils import (add_overwrite_arg, add_processes_arg,
add_verbose_arg, assert_inputs_exist,
assert_outputs_exist, validate_nbr_processes,
Expand Down Expand Up @@ -98,10 +98,13 @@ def main():
assert_outputs_exist(parser, args, [], optional=outputs)
assert_headers_compatible(parser, args.in_bingham, args.mask)

bingham_im = nib.load(args.in_bingham)
bingham = bingham_im.get_fdata()
mask = get_data_as_mask(nib.load(args.mask),
dtype=bool) if args.mask else None
simg_bingham = StatefulImage.load(args.in_bingham)
bingham = simg_bingham.get_fdata()
mask = None
if args.mask:
mask_simg = StatefulImage.load(args.mask)
mask_simg.reorient(simg_bingham.axcodes)
mask = get_data_as_mask(mask_simg, dtype=bool)

nbr_processes = validate_nbr_processes(parser, args)

Expand All @@ -112,23 +115,23 @@ def main():
t1 = time.perf_counter()
logging.info('FD computed in (s): {0}'.format(t1 - t0))
if args.out_fd:
nib.save(nib.Nifti1Image(fd, bingham_im.affine), args.out_fd)
StatefulImage.from_data(fd, simg_bingham).save(args.out_fd)

if args.out_fs:
t0 = time.perf_counter()
logging.info('Computing fiber spread.')
fs = compute_fiber_spread(bingham, fd)
t1 = time.perf_counter()
logging.info('FS computed in (s): {0}'.format(t1 - t0))
nib.save(nib.Nifti1Image(fs, bingham_im.affine), args.out_fs)
StatefulImage.from_data(fs, simg_bingham).save(args.out_fs)

if args.out_ff:
t0 = time.perf_counter()
logging.info('Computing fiber fraction.')
ff = compute_fiber_fraction(fd)
t1 = time.perf_counter()
logging.info('FS computed in (s): {0}'.format(t1 - t0))
nib.save(nib.Nifti1Image(ff, bingham_im.affine), args.out_ff)
logging.info('FF computed in (s): {0}'.format(t1 - t0))
StatefulImage.from_data(ff, simg_bingham).save(args.out_ff)


if __name__ == '__main__':
Expand Down
15 changes: 10 additions & 5 deletions src/scilpy/cli/scil_btensor_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@
import nibabel as nib
import numpy as np

from scilpy.image.utils import extract_affine
from scilpy.io.btensor import generate_btensor_input
from scilpy.io.image import get_data_as_mask
from scilpy.io.stateful_image import StatefulImage
from scilpy.io.utils import (add_overwrite_arg, assert_inputs_exist,
assert_outputs_exist, add_processes_arg,
add_verbose_arg, add_skip_b0_check_arg,
Expand Down Expand Up @@ -178,7 +178,9 @@ def main():
raise ValueError(msg)

# Loading
affine = extract_affine(args.in_dwis)
simg = StatefulImage.load(args.in_dwis[0])
simg.to_ras()
affine = simg.affine

# Note. This script does not currently allow using a separate b0_threshold
# for the b0s. Using the tolerance. To change this, we would have to
Expand All @@ -199,11 +201,14 @@ def main():
'No mask provided. The fit might not converge due to noise. '
'Please provide a mask if it is the case.')
else:
mask = get_data_as_mask(nib.load(args.mask), dtype=bool)
mask_simg = StatefulImage.load(args.mask)
mask_simg.to_ras()
mask = get_data_as_mask(mask_simg, dtype=bool)

if args.fa is not None:
vol = nib.load(args.fa)
FA = vol.get_fdata(dtype=np.float32)
fa_simg = StatefulImage.load(args.fa)
fa_simg.to_ras()
FA = fa_simg.get_fdata(dtype=np.float32)

# Processing
parameters = fit_gamma(data, gtab_infos, mask=mask,
Expand Down
36 changes: 27 additions & 9 deletions src/scilpy/cli/scil_bundle_generate_priors.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import numpy as np

from scilpy.io.image import get_data_as_mask
from scilpy.io.stateful_image import StatefulImage
from scilpy.io.streamlines import load_tractogram_with_reference
from scilpy.io.utils import (add_overwrite_arg,
add_reference_arg,
Expand Down Expand Up @@ -104,10 +105,17 @@ def main():
assert_outputs_exist(parser, args, required)

# Loading
img_sh = nib.load(args.in_fodf)
sh_shape = img_sh.shape
sh_order = find_order_from_nb_coeff(sh_shape)
sh_basis, is_legacy = parse_sh_basis_arg(args)
simg_sh = StatefulImage.load(args.in_fodf, is_orientation=True,
is_world_space=not args.is_voxel_space,
sh_basis=sh_basis, is_legacy=is_legacy)
# Bring to voxel space for multiplication with TODI (which is in vox space)
input_sh_3d = simg_sh.to_voxel_direction(sh_basis=sh_basis,
is_legacy=is_legacy).astype(np.float32)

sh_shape = input_sh_3d.shape
sh_order = find_order_from_nb_coeff(sh_shape)

img_mask = nib.load(args.in_mask)
mask_data = get_data_as_mask(img_mask)

Expand All @@ -124,17 +132,22 @@ def main():

# SF to SH
# Memory friendly saving, as soon as possible saving then delete
priors_3d = np.zeros(sh_shape)
priors_3d = np.zeros(sh_shape, dtype=np.float32)
sphere = get_sphere(name='repulsion724')
priors_3d[sub_mask_3d] = sf_to_sh(todi_sf, sphere,
sh_order_max=sh_order,
basis_type=sh_basis,
legacy=is_legacy)
nib.save(nib.Nifti1Image(priors_3d, img_mask.affine), out_priors)
legacy=is_legacy).astype(np.float32)

simg_priors = StatefulImage(priors_3d, img_mask.affine,
sh_basis=sh_basis, is_legacy=is_legacy,
is_orientation=True, is_world_space=False)
simg_priors.to_world_direction()
nib.save(simg_priors, out_priors)
del priors_3d

# Back to SF
input_sh_3d = img_sh.get_fdata(dtype=np.float32)
# input_sh_3d is already in voxel space
input_sf_1d = sh_to_sf(input_sh_3d[sub_mask_3d],
sphere, sh_order_max=sh_order,
basis_type=sh_basis, legacy=is_legacy)
Expand All @@ -155,8 +168,13 @@ def main():
input_sh_3d[sub_mask_3d] = sf_to_sh(mult_sf_1d, sphere,
sh_order_max=sh_order,
basis_type=sh_basis,
legacy=is_legacy)
nib.save(nib.Nifti1Image(input_sh_3d, img_mask.affine), out_efod)
legacy=is_legacy).astype(np.float32)

simg_efod = StatefulImage(input_sh_3d, img_mask.affine,
sh_basis=sh_basis, is_legacy=is_legacy,
is_orientation=True, is_world_space=False)
simg_efod.to_world_direction()
nib.save(simg_efod, out_efod)
del input_sh_3d

nib.save(nib.Nifti1Image(sub_mask_3d.astype(np.uint8), img_mask.affine),
Expand Down
32 changes: 18 additions & 14 deletions src/scilpy/cli/scil_dki_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,18 @@
import dipy.reconst.dki as dki
import dipy.reconst.msdki as msdki

from dipy.io.gradients import read_bvals_bvecs
from dipy.core.gradients import gradient_table

from scilpy.dwi.operations import compute_residuals
from scilpy.image.volume_operations import smooth_to_fwhm
from scilpy.io.image import get_data_as_mask
from scilpy.io.stateful_image import StatefulImage
from scilpy.io.utils import (add_overwrite_arg, add_skip_b0_check_arg,
add_verbose_arg, assert_inputs_exist,
assert_outputs_exist, add_tolerance_arg,
assert_headers_compatible)
from scilpy.gradients.bvec_bval_tools import (check_b0_threshold,
is_normalized_bvecs,
identify_shells,
normalize_bvecs)
identify_shells)
from scilpy.version import version_string


Expand Down Expand Up @@ -184,16 +182,22 @@ def main():
assert_headers_compatible(parser, args.in_dwi, args.mask)

# Loading
img = nib.load(args.in_dwi)
data = img.get_fdata(dtype=np.float32)
affine = img.affine
mask = get_data_as_mask(nib.load(args.mask),
dtype=bool) if args.mask else None

bvals, bvecs = read_bvals_bvecs(args.in_bval, args.in_bvec)
if not is_normalized_bvecs(bvecs):
logging.warning('Your b-vectors do not seem normalized...')
bvecs = normalize_bvecs(bvecs)
simg = StatefulImage.load(args.in_dwi)
simg.load_gradients(args.in_bval, args.in_bvec)

# DKI fit expects RAS (via dipy)
simg.to_ras()

data = simg.get_fdata(dtype=np.float32)
affine = simg.affine
bvals = simg.bvals
bvecs = simg.world_bvecs

mask = None
if args.mask:
mask_simg = StatefulImage.load(args.mask)
mask_simg.to_ras()
mask = get_data_as_mask(mask_simg, dtype=bool)

# Note. This script does not currently allow using a separate b0_threshold
# for the b0s. Using the tolerance. To change this, we would have to
Expand Down
Loading