Skip to content

Commit

Permalink
Merge branch 'master' of github.com:scilus/scilpy into fix_bbox
Browse files Browse the repository at this point in the history
  • Loading branch information
EmmaRenauld committed Nov 8, 2022
2 parents 31d31d7 + 68005ae commit 1e27d44
Show file tree
Hide file tree
Showing 10 changed files with 588 additions and 97 deletions.
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ nilearn==0.6.*
numpy==1.21.*
Pillow==9.0.1
bids-validator==1.6.0
pybids==0.10.*
pybids==0.15.*
pyparsing==2.2.*
PySocks==1.7.*
python-dateutil==2.7.*
Expand All @@ -38,3 +38,4 @@ dmri-commit==1.4.*
openpyxl==2.6.*
cvxpy==1.1.*
dmri-amico==1.2.*
formulaic==0.2.4
11 changes: 6 additions & 5 deletions scilpy/gradientsampling/optimize_gradient_sampling.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ def swap_sampling_eddy(points, shell_idx, verbose=1):

# System energy matrix
# TODO: test other energy functions such as electron repulsion
dist = squareform(pdist(shell_pts, 'Euclidean')) + 2 * np.eye(shell_pts.shape[0])
dist = squareform(pdist(shell_pts, 'Euclidean')) \
+ 2 * np.eye(shell_pts.shape[0])

it = 0
converged = False
Expand Down Expand Up @@ -183,10 +184,9 @@ def correct_b0s_philips(points, shell_idx, verbose=1):

new_points = points.copy()

non_b0_pts = points[np.where(shell_idx != -1)]

# Assume non-collinearity of non-b0s bvecs (i.e. Caruyer sampler type)
new_points[np.where(shell_idx == -1)[0]] = non_b0_pts
new_points[np.where(shell_idx == -1)[0][1:]] \
= new_points[np.where(shell_idx == -1)[0][1:] - 1]

logging.info('Done adapting b0s for Philips scanner.')

Expand Down Expand Up @@ -260,7 +260,8 @@ def compute_min_duty_cycle_bruteforce(points, shell_idx, bvals, ker_size=10,
logging.debug('Iter {} / {} : {}'.format(it, Niter, power_best))

ordering_current = np.random.permutation(N_dir)
q_scheme_current[non_b0s_mask] = q_scheme[non_b0s_mask][ordering_current]
q_scheme_current[non_b0s_mask] \
= q_scheme[non_b0s_mask][ordering_current]

power_current = compute_peak_power(q_scheme_current, ker_size=ker_size)

Expand Down
74 changes: 74 additions & 0 deletions scilpy/reconst/dti.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-

import numpy as np

supported_tensor_formats = ['fsl', 'nifti', 'mrtrix', 'dipy']
tensor_format_description = \
"""
Dipy's order is [Dxx, Dxy, Dyy, Dxz, Dyz, Dzz]
Shape: [i, j , k, 6].
Ref: https://github.com/dipy/dipy/blob/master/dipy/reconst/dti.py#L1639
MRTRIX's order is : [Dxx, Dyy, Dzz, Dxy, Dxz, Dyz]
Shape: [i, j , k, 6].
Ref: https://mrtrix.readthedocs.io/en/dev/reference/commands/dwi2tensor.html
ANTS's order ('nifti format') is : [Dxx, Dxy, Dyy, Dxz, Dyz, Dzz].
Shape: [i, j , k, 1, 6] (Careful, file is 5D).
Ref: https://github.com/ANTsX/ANTs/wiki/Importing-diffusion-tensor-data-from-other-software
FSL's order is [Dxx, Dxy, Dxz, Dyy, Dyz, Dzz]
Shape: [i, j , k, 6].
Ref: https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FDT/UserGuide
(Also used for the Fibernavigator)
"""


def convert_tensor_to_dipy_format(tensor, initial_format):
"""
See description of formats at the top of this file.
"""
assert initial_format in supported_tensor_formats, \
"Tensor format not supported"

if initial_format == 'nifti' or initial_format == 'dipy':
correct_order = [0, 1, 2, 3, 4, 5]
tensor = np.squeeze(tensor)
elif initial_format == 'mrtrix':
correct_order = [0, 3, 1, 4, 5, 2]
else: # initial_format == 'fsl':
correct_order = [0, 1, 3, 2, 4, 5]

return tensor[..., correct_order]


def convert_tensor_from_dipy_format(tensor, final_format):
"""
See description of formats at the top of this file.
"""
assert final_format in supported_tensor_formats, \
"Tensor format not supported"

if final_format == 'nifti' or final_format == 'dipy':
correct_order = [0, 1, 2, 3, 4, 5]
elif final_format == 'mrtrix':
correct_order = [0, 2, 5, 1, 3, 4]
else: # final_format == 'fsl'.
correct_order = [0, 1, 3, 2, 4, 5]

tensor_reordered = tensor[..., correct_order]

if final_format == 'nifti':
# We need to add the fifth dimension
tensor_reordered = tensor_reordered[:, :, :, None, :]

return tensor_reordered


def convert_tensor_format(tensor, initial_format, final_format):
"""
See description of formats at the top of this file.
"""
tensor = convert_tensor_to_dipy_format(tensor, initial_format)
return convert_tensor_from_dipy_format(tensor, final_format)

20 changes: 15 additions & 5 deletions scripts/scil_compute_dti_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,12 @@
radial_diffusivity, lower_triangular)
# Aliased to avoid clashes with images called mode.
from dipy.reconst.dti import mode as dipy_mode

from scilpy.io.image import get_data_as_mask
from scilpy.io.utils import (add_overwrite_arg, assert_inputs_exist,
assert_outputs_exist, add_force_b0_arg)
from scilpy.reconst.dti import convert_tensor_from_dipy_format, \
supported_tensor_formats, tensor_format_description
from scilpy.utils.bvec_bval_tools import (normalize_bvecs, is_normalized_bvecs,
check_b0_threshold)
from scilpy.utils.filenames import add_filename_suffix, split_name_with_nii
Expand Down Expand Up @@ -75,7 +78,8 @@ def _build_arg_parser():
help='Tensor fit method.\nWLS for weighted least squares' +
'\nLS for ordinary least squares' +
'\nNLLS for non-linear least-squares' +
'\nrestore for RESTORE robust tensor fitting. (Default: %(default)s)')
'\nrestore for RESTORE robust tensor fitting. '
'(Default: %(default)s)')
p.add_argument(
'--not_all', action='store_true', dest='not_all',
help='If set, will only save the metrics explicitly specified using '
Expand Down Expand Up @@ -114,6 +118,11 @@ def _build_arg_parser():
g.add_argument(
'--tensor', dest='tensor', metavar='file', default='',
help='Output filename for the tensor coefficients.')
g.add_argument('--tensor_format', choices=supported_tensor_formats,
default='fsl',
help=("Format used for the tensors saved in --tensor file."
"(default: %(default)s)\n"
+ tensor_format_description))

g = p.add_argument_group(title='Quality control files flags')
g.add_argument(
Expand Down Expand Up @@ -203,11 +212,12 @@ def main():
FA = np.clip(FA, 0, 1)

if args.tensor:
# Get the Tensor values and format them for visualisation
# in the Fibernavigator.
# Get the Tensor values
# Format them for visualization in various software.
tensor_vals = lower_triangular(tenfit.quadratic_form)
correct_order = [0, 1, 3, 2, 4, 5]
tensor_vals_reordered = tensor_vals[..., correct_order]
tensor_vals_reordered = convert_tensor_from_dipy_format(
tensor_vals, final_format=args.tensor_format)

fiber_tensors = nib.Nifti1Image(
tensor_vals_reordered.astype(np.float32), affine)
nib.save(fiber_tensors, args.tensor)
Expand Down
60 changes: 60 additions & 0 deletions scripts/scil_convert_tensors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Conversion of tensors (the 6 values from the triangular matrix) between various
software standards. We cannot discover the input format type, user must know
how the tensors were created.
"""

import argparse

import nibabel as nib
import numpy as np

from scilpy.io.utils import (add_overwrite_arg, add_reference_arg,
assert_inputs_exist, assert_outputs_exist)
from scilpy.reconst.dti import (supported_tensor_formats,
tensor_format_description,
convert_tensor_format)


def _build_arg_parser():
p = argparse.ArgumentParser(description=__doc__ + tensor_format_description,
formatter_class=argparse.RawTextHelpFormatter)

p.add_argument('in_file',
help='Input tensors filename.')
p.add_argument('out_file',
help='Output tensors filename.')
p.add_argument('in_format', metavar='in_format',
choices=supported_tensor_formats,
help='Input format. Choices: {}'
.format(supported_tensor_formats))
p.add_argument('out_format', metavar='out_format',
choices=supported_tensor_formats,
help='Output format. Choices: {}'
.format(supported_tensor_formats))
add_overwrite_arg(p)

return p


def main():
parser = _build_arg_parser()
args = parser.parse_args()

assert_inputs_exist(parser, args.in_file)
assert_outputs_exist(parser, args, args.out_file)

in_tensors_img = nib.load(args.in_file)
in_tensors = in_tensors_img.get_fdata(dtype=np.float32)

out_tensors = convert_tensor_format(in_tensors, args.in_format,
args.out_format)
out_tensors_img = nib.Nifti1Image(
out_tensors.astype(np.float32), in_tensors_img.affine)
nib.save(out_tensors_img, args.out_file)


if __name__ == "__main__":
main()
14 changes: 12 additions & 2 deletions scripts/scil_generate_gradient_sampling.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
Generate multi-shell gradient sampling with various processing to accelerate
acquisition and help artefact correction.
Multi-shell gradient sampling is generated as in [1], the bvecs are then flipped
to maximize spread for eddy current correction, b0s are interleaved
Multi-shell gradient sampling is generated as in [1], the bvecs are then
flipped to maximize spread for eddy current correction, b0s are interleaved
at equal spacing and the non-b0 samples are finally shuffled
to minimize the total diffusion gradient amplitude over a few TR.
"""
Expand All @@ -21,6 +21,7 @@
from scilpy.gradientsampling.gen_gradient_sampling import generate_gradient_sampling
from scilpy.gradientsampling.optimize_gradient_sampling import (add_b0s,
add_bvalue_b0,
correct_b0s_philips,
compute_bvalue_lin_b,
compute_bvalue_lin_q,
compute_min_duty_cycle_bruteforce,
Expand Down Expand Up @@ -73,6 +74,10 @@ def _build_arg_parser():
p.add_argument('--b0_value',
type=float, default=0.0,
help='b-value of the b0s. [%(default)s]')
p.add_argument('--b0_philips',
action='store_true',
help='Replace values of b0s bvecs by existing bvecs for '
'Philips handling. [%(default)s]')

bvals_group = p.add_mutually_exclusive_group(required=True)
bvals_group.add_argument('--bvals',
Expand Down Expand Up @@ -138,6 +143,7 @@ def main():
b0_every = args.b0_every
b0_end = args.b0_end
b0_value = args.b0_value
b0_philips = args.b0_philips

# Only a b0 at the beginning
if (b0_every > K) or (b0_every < 0):
Expand Down Expand Up @@ -181,6 +187,10 @@ def main():
points, shell_idx = compute_min_duty_cycle_bruteforce(
points, shell_idx, bvals)

# Correcting b0s bvecs for Philips
if b0_philips and np.sum(shell_idx == -1) > 1:
points, shell_idx = correct_b0s_philips(points, shell_idx)

if fsl:
save_gradient_sampling_fsl(points, shell_idx, bvals,
out_filename[0], out_filename[1])
Expand Down
68 changes: 68 additions & 0 deletions scripts/scil_validate_and_correct_eddy_gradients.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Validate and correct gradients from eddy outputs
With full AP-PA eddy outputs a full bvec bval (2x nb of dirs and bval)
that doesnt fit with the output dwi (1x nb of dir)
"""

import argparse

import numpy as np

from scilpy.io.utils import (add_overwrite_arg, assert_inputs_exist,
assert_outputs_exist)


def _build_arg_parser():
p = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawTextHelpFormatter)
p.add_argument('in_bvec',
help='In bvec file.')
p.add_argument('in_bval',
help='In bval file.')
p.add_argument('nb_dirs', type=int,
help='Number of directions per DWI.')
p.add_argument('out_bvec',
help='Out bvec file.')
p.add_argument('out_bval',
help='Out bval file.')
add_overwrite_arg(p)
return p


def main():
parser = _build_arg_parser()
args = parser.parse_args()

assert_inputs_exist(parser, [args.in_bvec, args.in_bval])
assert_outputs_exist(parser, args, [args.out_bval, args.out_bvec])

"""
IN BVEC
"""
in_bvec = np.genfromtxt(args.in_bvec)
split_dirs = in_bvec.shape[1] / args.nb_dirs
if int(split_dirs) != split_dirs:
parser.error('Number of directions in bvec ({}) can\'t be splited in '
'even parts using nb_dirs ({}).'.format(in_bvec.shape[1],
args.nb_dirs))
in_bvec_split = np.hsplit(in_bvec, int(split_dirs))
if len(in_bvec_split) == 2:
out_bvec = np.mean(np.array([in_bvec_split[0],
in_bvec_split[1]]), axis=0)
else:
out_bvec = in_bvec_split[0]
np.savetxt(args.out_bvec, out_bvec, '%.8f')

"""
IN BVAL
"""
in_bval = np.genfromtxt(args.in_bval)
np.savetxt(args.out_bval, in_bval[0:out_bvec.shape[1]], '%.0f')


if __name__ == '__main__':
main()
Loading

0 comments on commit 1e27d44

Please sign in to comment.