From 31cc82b111caba2483bc40bd015653ed385f7f28 Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Thu, 14 Dec 2023 13:40:33 -0500 Subject: [PATCH 01/26] Rename --force_b0_threshold. Add official --b0_threshold option. --- scilpy/gradients/bvec_bval_tools.py | 78 +++++++++++++++-------------- scilpy/io/utils.py | 20 ++++++-- 2 files changed, 56 insertions(+), 42 deletions(-) diff --git a/scilpy/gradients/bvec_bval_tools.py b/scilpy/gradients/bvec_bval_tools.py index 1f99d95a1..7c627f779 100644 --- a/scilpy/gradients/bvec_bval_tools.py +++ b/scilpy/gradients/bvec_bval_tools.py @@ -55,58 +55,62 @@ def normalize_bvecs(bvecs): return bvecs -def check_b0_threshold( - force_b0_threshold, bvals_min, b0_thr=DEFAULT_B0_THRESHOLD -): - """Check if the minimal bvalue is under zero or over the threshold. - If `force_b0_threshold` is true, don't raise an error even if the minimum - bvalue is over the threshold. +def check_b0_threshold(min_bval, args, b0_arg='b0_threshold'): + """ + Check if the minimal bvalue is under the threshold. If not, raise an + error to ask user to update the b0_thr. + + Also verifies if the b0_thr is suspicious (should be included in range + [0, DEFAULT_B0_THRESHOLD]). Parameters ---------- - force_b0_threshold : bool - If True, don't raise an error. - bvals_min : float + min_bval : float Minimum bvalue. - b0_thr : float - Maximum bvalue considered as a b0. + args: Namespace + The argparse arguments. Should contain at least: + args.skip_b0_validation: bool + If True, and no b0 is found, only print a warning, do not raise + an error. + args.b0_threshold: float (or args[b0_arg]) + Maximum bvalue considered as a b0. Raises ------ ValueError - If the minimal bvalue is over the threshold, and - `force_b0_threshold` is False. + If the minimal bvalue is over the threshold (and skip_b0_validation is + False). """ + b0_thr = args[b0_arg] + if b0_thr > DEFAULT_B0_THRESHOLD: logging.warning( - 'Warning: Your defined threshold is {}. This is suspicious. We ' - 'recommend using volumes with bvalues no higher ' - 'than {} as b0s.'.format(b0_thr, DEFAULT_B0_THRESHOLD) - ) + 'Warning: Your defined b0 threshold (option --{}) is {}. This is ' + 'suspicious. We recommend using volumes with bvalues no higher ' + 'than {} as b0s.'.format(b0_arg, b0_thr, DEFAULT_B0_THRESHOLD)) - if bvals_min < 0: + if min_bval < 0: logging.warning( - 'Warning: Your dataset contains negative b-values (minimal ' - 'bvalue of {}). This is suspicious. We recommend you check ' - 'your data.') + 'Warning: Your dataset contains negative b-values (minimal bvalue ' + 'of {}). This is suspicious. We recommend you check your data.') - if bvals_min > b0_thr: - if force_b0_threshold: + if min_bval > b0_thr: + if args.skip_b0_validation: logging.warning( - 'Warning: Your minimal bvalue is {}, but the threshold ' - 'is set to {}. Since --force_b0_threshold was specified, ' - 'the script will proceed with a threshold of {}' - '.'.format(bvals_min, b0_thr, bvals_min)) - return bvals_min - else: - raise ValueError('The minimal bvalue ({}) is greater than the ' - 'threshold ({}). No b0 volumes can be found.\n' - 'Please check your data to ensure everything ' - 'is correct.\n' - 'Use --force_b0_threshold to execute ' - 'regardless.' - .format(bvals_min, b0_thr)) - + 'Warning: Your minimal bvalue ({}), is above the threshold ' + 'defined with --{}: {}.\n' + 'Since --skip_b0_validation was specified, the script will' + 'proceed with a b0 threshold of {}.' + .format(min_bval, b0_arg, b0_thr, min_bval)) + return min_bval + else: + raise ValueError( + 'The minimal bvalue ({}) is is above the threshold ' + 'defined with --{}: {}.\n. ' + 'No b0 volumes can be found.\n' + 'Please check your data to ensure everything is correct.\n' + 'You may also increase the threshold or use --skip_b0_validation' + .format(min_bval, b0_arg, b0_thr, min_bval)) return b0_thr diff --git a/scilpy/io/utils.py b/scilpy/io/utils.py index 14e504b47..b76aeabfc 100644 --- a/scilpy/io/utils.py +++ b/scilpy/io/utils.py @@ -215,11 +215,21 @@ def add_overwrite_arg(parser): help='Force overwriting of the output files.') -def add_force_b0_arg(parser): - parser.add_argument('--force_b0_threshold', action='store_true', - help='If set, the script will continue even if the ' - 'minimum bvalue is suspiciously high ( > {})' - .format(DEFAULT_B0_THRESHOLD)) +def add_skip_b0_validation_arg(parser, b0_tol_name='--b0_threshold'): + parser.add_argument( + '--skip_b0_validation', action='store_true', + help='By default, we supervise that at least one b0 exists in your ' + 'data \n(i.e. b-values below the default {}). Use this option to ' + 'allow continuing even if the minimum \nbvalue is suspiciously ' + 'high.\nUse with care, and only if you understand your data.' + .format(b0_tol_name)) + + +def add_b0_thresh_arg(parser): + parser.add_argument( + '--b0_threshold', type=float, default=DEFAULT_B0_THRESHOLD, + help='Threshold under which b-values are considered to be b0s.\n' + 'Default if not set is {}.'.format(DEFAULT_B0_THRESHOLD)) def add_verbose_arg(parser): From af4974a322f733d7e6626612b33b2ba8e31545b3 Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Mon, 22 Jan 2024 15:36:12 -0500 Subject: [PATCH 02/26] scil_dwi_detect_volume_outliers: use official arg. --- scilpy/gradients/bvec_bval_tools.py | 19 ++++++++++--------- scripts/scil_dwi_detect_volume_outliers.py | 20 ++++++++------------ 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/scilpy/gradients/bvec_bval_tools.py b/scilpy/gradients/bvec_bval_tools.py index 7c627f779..678e289be 100644 --- a/scilpy/gradients/bvec_bval_tools.py +++ b/scilpy/gradients/bvec_bval_tools.py @@ -81,7 +81,7 @@ def check_b0_threshold(min_bval, args, b0_arg='b0_threshold'): If the minimal bvalue is over the threshold (and skip_b0_validation is False). """ - b0_thr = args[b0_arg] + b0_thr = vars(args)[b0_arg] # Converting Namespace to dict to allow access if b0_thr > DEFAULT_B0_THRESHOLD: logging.warning( @@ -96,6 +96,7 @@ def check_b0_threshold(min_bval, args, b0_arg='b0_threshold'): if min_bval > b0_thr: if args.skip_b0_validation: + logging.warning("GOT {} > {}".format(min_bval, b0_thr)) logging.warning( 'Warning: Your minimal bvalue ({}), is above the threshold ' 'defined with --{}: {}.\n' @@ -103,14 +104,14 @@ def check_b0_threshold(min_bval, args, b0_arg='b0_threshold'): 'proceed with a b0 threshold of {}.' .format(min_bval, b0_arg, b0_thr, min_bval)) return min_bval - else: - raise ValueError( - 'The minimal bvalue ({}) is is above the threshold ' - 'defined with --{}: {}.\n. ' - 'No b0 volumes can be found.\n' - 'Please check your data to ensure everything is correct.\n' - 'You may also increase the threshold or use --skip_b0_validation' - .format(min_bval, b0_arg, b0_thr, min_bval)) + else: + raise ValueError( + 'The minimal bvalue ({}) is is above the threshold ' + 'defined with --{}: {}.\n.No b0 volumes can be found.\n' + 'Please check your data to ensure everything is correct.\n' + 'You may also increase the threshold or use ' + '--skip_b0_validation' + .format(min_bval, b0_arg, b0_thr, min_bval)) return b0_thr diff --git a/scripts/scil_dwi_detect_volume_outliers.py b/scripts/scil_dwi_detect_volume_outliers.py index b16ec7707..c59d33f49 100755 --- a/scripts/scil_dwi_detect_volume_outliers.py +++ b/scripts/scil_dwi_detect_volume_outliers.py @@ -8,7 +8,7 @@ args.std_scale x STD) it will flag the volume as a potential outlier. This script supports multi-shells, but each shell is independant and detected -using the args.b0_thr parameter. +using the args.b0_threshold parameter. This script can be run before any processing to identify potential problem before launching pre-processing. @@ -21,9 +21,8 @@ import nibabel as nib from scilpy.dwi.operations import detect_volume_outliers -from scilpy.io.utils import (assert_inputs_exist, - add_force_b0_arg, - add_verbose_arg) +from scilpy.io.utils import (add_b0_thresh_arg, add_skip_b0_validation_arg, + add_verbose_arg, assert_inputs_exist,) from scilpy.gradients.bvec_bval_tools import (check_b0_threshold, normalize_bvecs) @@ -40,15 +39,12 @@ def _build_arg_parser(): p.add_argument('in_bvec', help='The b-vectors files in FSL format (.bvec).') - p.add_argument('--b0_thr', type=float, default=20.0, - help='All b-values with values less than or equal ' - 'to b0_thr are considered as b0s i.e. without ' - 'diffusion weighting. [%(default)s]') p.add_argument('--std_scale', type=float, default=2.0, help='How many deviation from the mean are required to be ' 'considered an outlier. [%(default)s]') - add_force_b0_arg(p) + add_b0_thresh_arg(p) + add_skip_b0_validation_arg(p) add_verbose_arg(p) return p @@ -67,13 +63,13 @@ def main(): bvals, bvecs = read_bvals_bvecs(args.in_bval, args.in_bvec) data = nib.load(args.in_dwi).get_fdata() - b0_thr = check_b0_threshold(args.force_b0_threshold, - bvals.min(), args.b0_thr) + args.b0_threshold = check_b0_threshold(bvals.min(), args) bvecs = normalize_bvecs(bvecs) # Not using the result. Only printing on screen. This is why the logging # level can never be set higher than INFO. - detect_volume_outliers(data, bvals, bvecs, args.std_scale, b0_thr) + detect_volume_outliers(data, bvals, bvecs, args.std_scale, + args.b0_threshold) if __name__ == "__main__": From 991155215bdd155750eeb1070c75ebc21bfc85ba Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Thu, 14 Dec 2023 14:00:42 -0500 Subject: [PATCH 03/26] scil_dti_metrics: use b0_threshold. Usage in dipy to be verified. --- scripts/scil_dti_metrics.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/scripts/scil_dti_metrics.py b/scripts/scil_dti_metrics.py index 18be116d0..a67ffd807 100755 --- a/scripts/scil_dti_metrics.py +++ b/scripts/scil_dti_metrics.py @@ -44,9 +44,9 @@ from scilpy.dwi.operations import compute_residuals, \ compute_residuals_statistics from scilpy.io.image import get_data_as_mask -from scilpy.io.utils import (add_overwrite_arg, assert_inputs_exist, - assert_outputs_exist, add_verbose_arg, - add_force_b0_arg) +from scilpy.io.utils import (add_b0_thresh_arg, add_overwrite_arg, + add_skip_b0_validation_arg, add_verbose_arg, + assert_inputs_exist,assert_outputs_exist) from scilpy.io.tensor import convert_tensor_from_dipy_format, \ supported_tensor_formats, tensor_format_description from scilpy.gradients.bvec_bval_tools import (normalize_bvecs, @@ -141,8 +141,9 @@ def _build_arg_parser(): '--residual', dest='residual', metavar='file', default='', help='Output filename for the map of the residual of the tensor fit.') + add_b0_thresh_arg(p) + add_skip_b0_validation_arg(p) add_verbose_arg(p) - add_force_b0_arg(p) return p @@ -266,9 +267,14 @@ def main(): logging.warning('Your b-vectors do not seem normalized...') bvecs = normalize_bvecs(bvecs) - b0_thr = check_b0_threshold( - args.force_b0_threshold, bvals.min(), bvals.min()) - gtab = gradient_table(bvals, bvecs, b0_threshold=b0_thr) + # How the b0_threshold is used: gtab.b0s_mask is used + # 1) In TensorModel in Dipy: + # - The S0 images used as any other image in the design matrix and in + # method .fit(). + # 2) But we do use this information below, with options p_i_signal, + # pulsation and residual. + check_b0_threshold(bvals.min(), args) + gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_threshold) # Processing From c3c3b6304c3bb686479494a007701f1ab321c85c Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Mon, 22 Jan 2024 15:27:02 -0500 Subject: [PATCH 04/26] scil_dwi_extract_b0: already ok. Use official b0_threshold. --- scripts/scil_dti_metrics.py | 2 +- scripts/scil_dwi_extract_b0.py | 18 +++++++----------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/scripts/scil_dti_metrics.py b/scripts/scil_dti_metrics.py index a67ffd807..1a810832d 100755 --- a/scripts/scil_dti_metrics.py +++ b/scripts/scil_dti_metrics.py @@ -273,7 +273,7 @@ def main(): # method .fit(). # 2) But we do use this information below, with options p_i_signal, # pulsation and residual. - check_b0_threshold(bvals.min(), args) + args.b0_threshold = check_b0_threshold(bvals.min(), args) gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_threshold) # Processing diff --git a/scripts/scil_dwi_extract_b0.py b/scripts/scil_dwi_extract_b0.py index 9e6cf2f70..aca206ae7 100755 --- a/scripts/scil_dwi_extract_b0.py +++ b/scripts/scil_dwi_extract_b0.py @@ -20,8 +20,9 @@ import numpy as np from scilpy.dwi.utils import extract_b0 -from scilpy.io.utils import (assert_inputs_exist, add_force_b0_arg, - add_verbose_arg, add_overwrite_arg) +from scilpy.io.utils import (add_b0_thresh_arg, add_overwrite_arg, + add_skip_b0_validation_arg, add_verbose_arg, + assert_inputs_exist) from scilpy.gradients.bvec_bval_tools import (check_b0_threshold, B0ExtractionStrategy) from scilpy.utils.filenames import split_name_with_nii @@ -40,10 +41,6 @@ def _build_arg_parser(): help='b-values filename, in FSL format (.bvec).') p.add_argument('out_b0', help='Output b0 file(s).') - p.add_argument('--b0_thr', type=float, default=0.0, - help='All b-values with values less than or equal ' - 'to b0_thr are considered as b0s i.e. without ' - 'diffusion weighting. [%(default)s]') group_ = p.add_argument_group("Options in the case of multiple b0s.") group = group_.add_mutually_exclusive_group() @@ -67,8 +64,9 @@ def _build_arg_parser(): 'outputs a single image instead of a numbered series ' 'of images.') + add_b0_thresh_arg(p) + add_skip_b0_validation_arg(p) add_verbose_arg(p) - add_force_b0_arg(p) add_overwrite_arg(p) return p @@ -97,10 +95,8 @@ def main(): bvals, bvecs = read_bvals_bvecs(args.in_bval, args.in_bvec) - b0_threshold = check_b0_threshold( - args.force_b0_threshold, bvals.min(), args.b0_thr) - - gtab = gradient_table(bvals, bvecs, b0_threshold=b0_threshold) + check_b0_threshold(bvals.min(), args) + gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_threshold) b0_idx = np.where(gtab.b0s_mask)[0] logger.info('Number of b0 images in the data: {}'.format(len(b0_idx))) From 98cd50fbe5b2fb3074be3db1e47a5d420d794f89 Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Thu, 14 Dec 2023 14:25:10 -0500 Subject: [PATCH 05/26] scil_dwi_to_sh: using official arg. + [FIX] force_b0 was not used! Data was loaded twice. Fixed --- scilpy/reconst/sh.py | 16 +++++++--------- scripts/scil_dwi_to_sh.py | 22 ++++++++++++++-------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/scilpy/reconst/sh.py b/scilpy/reconst/sh.py index 3558eca4d..ed466cb87 100644 --- a/scilpy/reconst/sh.py +++ b/scilpy/reconst/sh.py @@ -10,18 +10,17 @@ from dipy.reconst.shm import (sh_to_sf_matrix, order_from_ncoef, sf_to_sh, sph_harm_ind_list) -from scilpy.gradients.bvec_bval_tools import (check_b0_threshold, - identify_shells, +from scilpy.gradients.bvec_bval_tools import (identify_shells, is_normalized_bvecs, normalize_bvecs) from scilpy.dwi.operations import compute_dwi_attenuation -def compute_sh_coefficients(dwi, gradient_table, sh_order=4, +def compute_sh_coefficients(dwi, gradient_table, b0_threshold, sh_order=4, basis_type='descoteaux07', smooth=0.006, - use_attenuation=False, force_b0_threshold=False, - mask=None, sphere=None): + use_attenuation=False, mask=None, sphere=None): """Fit a diffusion signal with spherical harmonics coefficients. + Data must come from a single shell acquisition. Parameters ---------- @@ -29,6 +28,9 @@ def compute_sh_coefficients(dwi, gradient_table, sh_order=4, Diffusion signal as weighted images (4D). gradient_table : GradientTable Dipy object that contains all bvals and bvecs. + b0_threshold: float + Threshold for the b0 values. Used to validate that the data contains + single shell signal. sh_order : int, optional SH order to fit, by default 4. smooth : float, optional @@ -37,8 +39,6 @@ def compute_sh_coefficients(dwi, gradient_table, sh_order=4, Either 'tournier07' or 'descoteaux07' use_attenuation: bool, optional If true, we will use DWI attenuation. [False] - force_b0_threshold : bool, optional - If set, will continue even if the minimum bvalue is suspiciously high. mask: nib.Nifti1Image object, optional Binary mask. Only data inside the mask will be used for computations and reconstruction. @@ -62,8 +62,6 @@ def compute_sh_coefficients(dwi, gradient_table, sh_order=4, logging.warning("Your b-vectors do not seem normalized...") bvecs = normalize_bvecs(bvecs) - b0_threshold = check_b0_threshold(force_b0_threshold, bvals.min()) - # Ensure that this is on a single shell. shell_values, _ = identify_shells(bvals) shell_values.sort() diff --git a/scripts/scil_dwi_to_sh.py b/scripts/scil_dwi_to_sh.py index c2ec20564..502b522bf 100755 --- a/scripts/scil_dwi_to_sh.py +++ b/scripts/scil_dwi_to_sh.py @@ -15,10 +15,12 @@ import nibabel as nib import numpy as np +from scilpy.gradients.bvec_bval_tools import check_b0_threshold from scilpy.io.image import get_data_as_mask -from scilpy.io.utils import (add_force_b0_arg, add_overwrite_arg, - add_sh_basis_args, assert_inputs_exist, - add_verbose_arg, assert_outputs_exist) +from scilpy.io.utils import (add_b0_thresh_arg, add_overwrite_arg, + add_sh_basis_args, add_skip_b0_validation_arg, + add_verbose_arg, assert_inputs_exist, + assert_outputs_exist) from scilpy.reconst.sh import compute_sh_coefficients @@ -43,11 +45,12 @@ def _build_arg_parser(): p.add_argument('--use_attenuation', action='store_true', help='If set, will use signal attenuation before fitting ' 'the SH (i.e. divide by the b0).') - add_force_b0_arg(p) p.add_argument('--mask', help='Path to a binary mask.\nOnly data inside the mask ' 'will be used for computations and reconstruction ') - + + add_b0_thresh_arg(p) + add_skip_b0_validation_arg(p) add_verbose_arg(p) add_overwrite_arg(p) @@ -66,14 +69,17 @@ def main(): dwi = vol.get_fdata(dtype=np.float32) bvals, bvecs = read_bvals_bvecs(args.in_bval, args.in_bvec) - gtab = gradient_table(args.in_bval, args.in_bvec, b0_threshold=bvals.min()) + + # gtab.b0s_mask in used in compute_sh_coefficients to get the b0s. + args.b0_threshold = check_b0_threshold(bvals.min(), args) + gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_threshold) mask = None if args.mask: mask = get_data_as_mask(nib.load(args.mask), dtype=bool) - sh = compute_sh_coefficients(dwi, gtab, args.sh_order, args.sh_basis, - args.smooth, + sh = compute_sh_coefficients(dwi, gtab, args.b0_threshold, + args.sh_order, args.sh_basis, args.smooth, use_attenuation=args.use_attenuation, mask=mask) From 4c105fd596169856523538604a731087920c6eb1 Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Thu, 14 Dec 2023 14:27:34 -0500 Subject: [PATCH 06/26] scil_fodf_msmt: Only using tolerance. Link to dipy issue added. --- scilpy/gradients/bvec_bval_tools.py | 15 ++++++----- scripts/scil_fodf_msmt.py | 42 +++++++++++++++++------------ 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/scilpy/gradients/bvec_bval_tools.py b/scilpy/gradients/bvec_bval_tools.py index 678e289be..57f185bd5 100644 --- a/scilpy/gradients/bvec_bval_tools.py +++ b/scilpy/gradients/bvec_bval_tools.py @@ -55,7 +55,7 @@ def normalize_bvecs(bvecs): return bvecs -def check_b0_threshold(min_bval, args, b0_arg='b0_threshold'): +def check_b0_threshold(min_bval, args, b0_thr=None): """ Check if the minimal bvalue is under the threshold. If not, raise an error to ask user to update the b0_thr. @@ -72,8 +72,11 @@ def check_b0_threshold(min_bval, args, b0_arg='b0_threshold'): args.skip_b0_validation: bool If True, and no b0 is found, only print a warning, do not raise an error. - args.b0_threshold: float (or args[b0_arg]) + args.b0_threshold: float Maximum bvalue considered as a b0. + b0_thr: float + If set, we will use this value instead of args.b0_threshold as a + threshold. Raises ------ @@ -81,13 +84,13 @@ def check_b0_threshold(min_bval, args, b0_arg='b0_threshold'): If the minimal bvalue is over the threshold (and skip_b0_validation is False). """ - b0_thr = vars(args)[b0_arg] # Converting Namespace to dict to allow access + b0_thr = b0_thr or args.b0_threshold if b0_thr > DEFAULT_B0_THRESHOLD: logging.warning( 'Warning: Your defined b0 threshold (option --{}) is {}. This is ' 'suspicious. We recommend using volumes with bvalues no higher ' - 'than {} as b0s.'.format(b0_arg, b0_thr, DEFAULT_B0_THRESHOLD)) + 'than {} as b0s.'.format(b0_thr, b0_thr, DEFAULT_B0_THRESHOLD)) if min_bval < 0: logging.warning( @@ -102,7 +105,7 @@ def check_b0_threshold(min_bval, args, b0_arg='b0_threshold'): 'defined with --{}: {}.\n' 'Since --skip_b0_validation was specified, the script will' 'proceed with a b0 threshold of {}.' - .format(min_bval, b0_arg, b0_thr, min_bval)) + .format(min_bval, b0_thr, b0_thr, min_bval)) return min_bval else: raise ValueError( @@ -111,7 +114,7 @@ def check_b0_threshold(min_bval, args, b0_arg='b0_threshold'): 'Please check your data to ensure everything is correct.\n' 'You may also increase the threshold or use ' '--skip_b0_validation' - .format(min_bval, b0_arg, b0_thr, min_bval)) + .format(min_bval, b0_thr, b0_thr, min_bval)) return b0_thr diff --git a/scripts/scil_fodf_msmt.py b/scripts/scil_fodf_msmt.py index 8143fd0d3..ce731adb3 100755 --- a/scripts/scil_fodf_msmt.py +++ b/scripts/scil_fodf_msmt.py @@ -33,9 +33,10 @@ normalize_bvecs, is_normalized_bvecs) 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, - add_sh_basis_args, add_processes_arg, +from scilpy.io.utils import (add_overwrite_arg, add_processes_arg, + add_sh_basis_args, add_skip_b0_validation_arg, + assert_inputs_exist, assert_outputs_exist, + add_verbose_arg) from scilpy.reconst.fodf import fit_from_model from scilpy.reconst.sh import convert_sh_basis @@ -69,13 +70,14 @@ def _build_arg_parser(): p.add_argument( '--tolerance', type=int, default=20, help='The tolerated gap between the b-values to ' - 'extract and the current b-value. [%(default)s]') - - add_force_b0_arg(p) + 'extract and the current b-value.\n' + 'We would expect to find at least one b-value in the range ' + '[0, tolerance], acting as a b0.\n' + 'To skip this check, use --skip_b0_validation.\n' + 'Default: [%(default)s]') + add_skip_b0_validation_arg(p, b0_tol_name='--tolerance') add_sh_basis_args(p) add_processes_arg(p) - add_verbose_arg(p) - add_overwrite_arg(p) p.add_argument( '--not_all', action='store_true', @@ -100,6 +102,9 @@ def _build_arg_parser(): '--vf_rgb', metavar='file', default='', help='Output filename for the volume fractions map in rgb.') + add_verbose_arg(p) + add_overwrite_arg(p) + return p @@ -142,13 +147,8 @@ def main(): if mask.shape != data.shape[:-1]: raise ValueError("Mask is not the same shape as data.") - tol = args.tolerance - sh_order = args.sh_order - # Checking data and sh_order - b0_thr = check_b0_threshold( - args.force_b0_threshold, bvals.min(), bvals.min()) - + sh_order = args.sh_order if data.shape[-1] < (sh_order + 1) * (sh_order + 2) / 2: logging.warning( 'We recommend having at least {} unique DWIs volumes, but you ' @@ -160,7 +160,15 @@ def main(): if not is_normalized_bvecs(bvecs): logging.warning('Your b-vectors do not seem normalized...') bvecs = normalize_bvecs(bvecs) - gtab = gradient_table(bvals, bvecs, b0_threshold=b0_thr) + + # 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 + # change many things in dipy. An issue has been added in dipy to + # ask them to clarify the usage of gtab.b0s_mask. See here: + # https://github.com/dipy/dipy/issues/3015 + # b0_threshold option in gradient_table probably unused. + check_b0_threshold(bvals.min(), args, b0_thr=args.tolerance) + gtab = gradient_table(bvals, bvecs, b0_threshold=args.tolerance) # Checking response functions and computing msmt response function if not wm_frf.shape[1] == 4: @@ -172,10 +180,10 @@ def main(): if not csf_frf.shape[1] == 4: raise ValueError('CSF frf file did not contain 4 elements. ' 'Invalid or deprecated FRF format') - ubvals = unique_bvals_tolerance(bvals, tol=tol) + ubvals = unique_bvals_tolerance(bvals, tol=args.tolerance) msmt_response = multi_shell_fiber_response(sh_order, ubvals, wm_frf, gm_frf, csf_frf, - tol=tol) + tol=args.tolerance) # Loading spheres reg_sphere = get_sphere('symmetric362') From 5cb2dd9ae624be1e2978d20cd1aaaa614cb2a7c6 Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Thu, 14 Dec 2023 14:29:50 -0500 Subject: [PATCH 07/26] scil_fodf_ssst: [FIX] Was always using minimal b-value as threshold. Adding b0_treshold arg. --- scripts/scil_fodf_ssst.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/scripts/scil_fodf_ssst.py b/scripts/scil_fodf_ssst.py index 9c276b990..3fdd61921 100755 --- a/scripts/scil_fodf_ssst.py +++ b/scripts/scil_fodf_ssst.py @@ -23,10 +23,10 @@ normalize_bvecs, is_normalized_bvecs) from scilpy.io.image import get_data_as_mask -from scilpy.io.utils import (add_overwrite_arg, - assert_inputs_exist, add_verbose_arg, - assert_outputs_exist, add_force_b0_arg, - add_sh_basis_args, add_processes_arg) +from scilpy.io.utils import (add_b0_thresh_arg, add_overwrite_arg, + add_processes_arg, add_sh_basis_args, + add_skip_b0_validation_arg, add_verbose_arg, + assert_inputs_exist, assert_outputs_exist) from scilpy.reconst.fodf import fit_from_model from scilpy.reconst.sh import convert_sh_basis @@ -54,7 +54,8 @@ def _build_arg_parser(): help='Path to a binary mask. Only the data inside the mask will be ' 'used for computations and reconstruction.') - add_force_b0_arg(p) + add_b0_thresh_arg(p) + add_skip_b0_validation_arg(p) add_sh_basis_args(p) add_processes_arg(p) add_verbose_arg(p) @@ -89,8 +90,6 @@ def main(): sh_order = args.sh_order # Checking data and sh_order - b0_thr = check_b0_threshold( - args.force_b0_threshold, bvals.min(), bvals.min()) if data.shape[-1] < (sh_order + 1) * (sh_order + 2) / 2: logging.warning( 'We recommend having at least {} unique DWI volumes, but you ' @@ -102,7 +101,10 @@ def main(): if not is_normalized_bvecs(bvecs): logging.warning('Your b-vectors do not seem normalized...') bvecs = normalize_bvecs(bvecs) - gtab = gradient_table(bvals, bvecs, b0_threshold=b0_thr) + + # gtab.b0s_mask is used in dipy's csdeconv class. + args.b0_threshold = check_b0_threshold(bvals.min(), args) + gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_threshold) # Checking full_frf and separating it if not full_frf.shape[0] == 4: @@ -115,10 +117,9 @@ def main(): reg_sphere = get_sphere('symmetric362') # Computing CSD - csd_model = ConstrainedSphericalDeconvModel( - gtab, (frf, mean_b0_val), - reg_sphere=reg_sphere, - sh_order=sh_order) + csd_model = ConstrainedSphericalDeconvModel(gtab, (frf, mean_b0_val), + reg_sphere=reg_sphere, + sh_order=sh_order) # Computing CSD fit csd_fit = fit_from_model(csd_model, data, From 999b7576a565d653ed93c5aa31b3f01308eaf9ee Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Fri, 15 Dec 2023 09:35:49 -0500 Subject: [PATCH 08/26] scil_sh_to_sf: No b0_threshold option existed. Added. --- scripts/scil_sh_to_sf.py | 58 ++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/scripts/scil_sh_to_sf.py b/scripts/scil_sh_to_sf.py index 8a55dcd1e..5a82dd742 100755 --- a/scripts/scil_sh_to_sf.py +++ b/scripts/scil_sh_to_sf.py @@ -23,12 +23,13 @@ from dipy.data import SPHERE_FILES, get_sphere from dipy.io import read_bvals_bvecs -from scilpy.io.utils import (add_force_b0_arg, add_overwrite_arg, - add_processes_arg, add_sh_basis_args, +from scilpy.gradients.bvec_bval_tools import (check_b0_threshold, + DEFAULT_B0_THRESHOLD) +from scilpy.io.utils import (add_overwrite_arg, add_processes_arg, + add_skip_b0_validation_arg, add_sh_basis_args, assert_inputs_exist, add_verbose_arg, assert_outputs_exist, validate_nbr_processes) from scilpy.reconst.sh import convert_sh_to_sf -from scilpy.gradients.bvec_bval_tools import (check_b0_threshold) def _build_arg_parser(): @@ -56,12 +57,11 @@ def _build_arg_parser(): # Optional args for a DWI-like volume p.add_argument('--in_bval', - help='b-value file, in FSL format, ' - 'used to assign a b-value to the ' - 'output SF and generate a `.bval` file.') + help='b-value file, in FSL format, used to assign a ' + 'b-value to the \noutput SF and generate a `.bval` ' + 'file.') p.add_argument('--in_b0', - help='b0 volume to concatenate to the ' - 'final SF volume.') + help='b0 volume to concatenate to the final SF volume.') p.add_argument('--out_bval', help="Optional output bval file.") p.add_argument('--out_bvec', @@ -75,9 +75,18 @@ def _build_arg_parser(): help="If true, use a full basis for the input SH " "coefficients.") + # Could use add_b0_thresh_arg(p), but adding manually to add more + # explanation in the text. + p.add_argument( + '--b0_threshold', type=float, default=DEFAULT_B0_THRESHOLD, + help='Threshold under which b-values are considered to be b0s.\n' + 'Default if not set is {}.\n' + 'This value is used with options --in_bvec or --in_bval only.' + .format(DEFAULT_B0_THRESHOLD)) + add_skip_b0_validation_arg(p) + add_processes_arg(p) add_verbose_arg(p) - add_force_b0_arg(p) add_overwrite_arg(p) return p @@ -98,16 +107,24 @@ def main(): parser.error("--out_bval is required if --in_bval is provided, " "and vice-versa.") - if args.in_bvec and not args.in_bval: - parser.error( - "--in_bval is required when using --in_bvec, in order to remove " - "bvecs corresponding to b0 images.") - if args.b0_scaling and not args.in_b0: parser.error("--in_b0 is required when using --b0_scaling.") nbr_processes = validate_nbr_processes(parser, args) + # Load bvecs / bvals, verify options. + bvals = None + bvecs = None + if args.in_bvec: + if not args.in_bval: + parser.error( + "--in_bval is required when using --in_bvec, in order to " + "remove bvecs corresponding to b0 images.") + bvals, bvecs = read_bvals_bvecs(args.in_bval, args.in_bvec) + elif args.in_bval: + bvals, _ = read_bvals_bvecs(args.in_bval, None) + args.b0_treshold = check_b0_threshold(bvals.min(), args) + # Load SH vol_sh = nib.load(args.in_sh) data_sh = vol_sh.get_fdata(dtype=np.float32) @@ -116,8 +133,7 @@ def main(): if args.sphere: sphere = get_sphere(args.sphere) elif args.in_bvec: - bvals, bvecs = read_bvals_bvecs(args.in_bval, args.in_bvec) - gtab = gradient_table(bvals, bvecs, b0_threshold=bvals.min()) + gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_treshold) # Remove bvecs corresponding to b0 images bvecs = bvecs[np.logical_not(gtab.b0s_mask)] sphere = Sphere(xyz=bvecs) @@ -132,13 +148,9 @@ def main(): # Assign bval to SF if --in_bval was provided new_bvals = [] if args.in_bval: - # Load bvals - bvals, _ = read_bvals_bvecs(args.in_bval, None) - - # Compute average bval - b0_thr = check_b0_threshold( - args.force_b0_threshold, bvals.min(), bvals.min()) - b0s_mask = bvals <= b0_thr + # Compute average bval in case in_bvec is None. + # (If --in_bvec, this is already loaded in gtab.b0_masks) + b0s_mask = bvals <= args.b0_threshold avg_bval = np.mean(bvals[np.logical_not(b0s_mask)]) new_bvals = ([avg_bval] * len(sphere.theta)) From e009e2f5c5ff6c4ad87637a6b668eb4948a41cdd Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Mon, 22 Jan 2024 15:27:57 -0500 Subject: [PATCH 09/26] scil_qball_metrics: Only the minimal b-value was used. Adding b0_threshold. --- scripts/scil_qball_metrics.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/scripts/scil_qball_metrics.py b/scripts/scil_qball_metrics.py index 817ffd565..d33c33838 100755 --- a/scripts/scil_qball_metrics.py +++ b/scripts/scil_qball_metrics.py @@ -28,14 +28,16 @@ from dipy.direction.peaks import (peaks_from_model, reshape_peaks_for_visualization) from dipy.reconst.shm import QballModel, CsaOdfModel, anisotropic_power -from scilpy.io.utils import (add_overwrite_arg, add_processes_arg, - add_sh_basis_args, assert_inputs_exist, - assert_outputs_exist, add_force_b0_arg, - validate_nbr_processes, add_verbose_arg) -from scilpy.io.image import get_data_as_mask -from scilpy.gradients.bvec_bval_tools import (normalize_bvecs, + +from scilpy.gradients.bvec_bval_tools import (check_b0_threshold, is_normalized_bvecs, - check_b0_threshold) + normalize_bvecs) +from scilpy.io.utils import (add_b0_thresh_arg, add_overwrite_arg, + add_processes_arg, add_sh_basis_args, + add_skip_b0_validation_arg, add_verbose_arg, + assert_inputs_exist, assert_outputs_exist, + validate_nbr_processes) +from scilpy.io.image import get_data_as_mask DEFAULT_SMOOTH = 0.006 @@ -85,10 +87,11 @@ def _build_arg_parser(): help='Output filename for the anisotropic power map' '[anisotropic_power.nii.gz].') + add_b0_thresh_arg(p) + add_skip_b0_validation_arg(p) add_sh_basis_args(p) add_processes_arg(p) add_verbose_arg(p) - add_force_b0_arg(p) return p @@ -129,8 +132,10 @@ def main(): logging.warning('Your b-vectors do not seem normalized...') bvecs = normalize_bvecs(bvecs) - check_b0_threshold(args, bvals.min()) - gtab = gradient_table(bvals, bvecs, b0_threshold=bvals.min()) + # Usage of gtab.b0s_mask in dipy's models is not very well documented, but + # we can see that it is indeed used. + args.b0_threshold = check_b0_threshold(bvals.min(), args) + gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_threshold) sphere = get_sphere('symmetric724') From 98e6517288cb66b030cc426e915635c833d16bfa Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Mon, 22 Jan 2024 15:29:01 -0500 Subject: [PATCH 10/26] scil_dki_metrics: could not modify the identify_shells method right now. Only using tolerance. --- scilpy/gradients/bvec_bval_tools.py | 8 +--- scripts/scil_dki_metrics.py | 74 +++++++++++++++++------------ scripts/scil_fodf_msmt.py | 11 +++-- 3 files changed, 51 insertions(+), 42 deletions(-) diff --git a/scilpy/gradients/bvec_bval_tools.py b/scilpy/gradients/bvec_bval_tools.py index 57f185bd5..df5c008fe 100644 --- a/scilpy/gradients/bvec_bval_tools.py +++ b/scilpy/gradients/bvec_bval_tools.py @@ -55,7 +55,7 @@ def normalize_bvecs(bvecs): return bvecs -def check_b0_threshold(min_bval, args, b0_thr=None): +def check_b0_threshold(min_bval, args): """ Check if the minimal bvalue is under the threshold. If not, raise an error to ask user to update the b0_thr. @@ -74,9 +74,6 @@ def check_b0_threshold(min_bval, args, b0_thr=None): an error. args.b0_threshold: float Maximum bvalue considered as a b0. - b0_thr: float - If set, we will use this value instead of args.b0_threshold as a - threshold. Raises ------ @@ -84,8 +81,7 @@ def check_b0_threshold(min_bval, args, b0_thr=None): If the minimal bvalue is over the threshold (and skip_b0_validation is False). """ - b0_thr = b0_thr or args.b0_threshold - + b0_thr = args.b0_threshold if b0_thr > DEFAULT_B0_THRESHOLD: logging.warning( 'Warning: Your defined b0 threshold (option --{}) is {}. This is ' diff --git a/scripts/scil_dki_metrics.py b/scripts/scil_dki_metrics.py index 6fa7704b7..96dfe985f 100755 --- a/scripts/scil_dki_metrics.py +++ b/scripts/scil_dki_metrics.py @@ -57,13 +57,12 @@ 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.utils import (add_overwrite_arg, assert_inputs_exist, - assert_outputs_exist, add_force_b0_arg, - add_verbose_arg) -from scilpy.gradients.bvec_bval_tools import (normalize_bvecs, - is_normalized_bvecs, - check_b0_threshold, - identify_shells) +from scilpy.io.utils import (add_overwrite_arg, add_skip_b0_validation_arg, + add_verbose_arg, assert_inputs_exist, + assert_outputs_exist, ) +from scilpy.gradients.bvec_bval_tools import (is_normalized_bvecs, + identify_shells, + normalize_bvecs, check_b0_threshold) def _build_arg_parser(): @@ -78,37 +77,42 @@ def _build_arg_parser(): help='Path of the b-vector file, in FSL format.') p.add_argument('--mask', - help='Path to a binary mask.' + - '\nOnly data inside the mask will be used ' - 'for computations and reconstruction. ' + - '\n[Default: None]') + help='Path to a binary mask.\n' + 'Only data inside the mask will be used for ' + 'computations and reconstruction.\n[Default: None]') p.add_argument('--tolerance', '-t', metavar='INT', type=int, default=20, help='The tolerated distance between the b-values to ' - 'extract\nand the actual b-values [Default: %(default)s].') + 'extract\nand the actual b-values.\n' + 'We would expect to find at least one b-value in the range ' + '[0, tolerance], acting as a b0.\n' + 'To skip this check, use --skip_b0_validation.\n' + '[Default: %(default)s]') + add_skip_b0_validation_arg(p, b0_tol_name='--tolerance') + p.add_argument('--min_k', type=float, default=0.0, - help='Minimum kurtosis value in the output maps ' + - '\n(ak, mk, rk). In theory, -3/7 is the min kurtosis ' + - '\nlimit for regions that consist of water confined ' + - '\nto spherical pores (see DIPY example and ' + + help='Minimum kurtosis value in the output maps ' + '\n(ak, mk, rk). In theory, -3/7 is the min kurtosis ' + '\nlimit for regions that consist of water confined ' + '\nto spherical pores (see DIPY example and ' '\ndocumentation) [Default: %(default)s].') p.add_argument('--max_k', type=float, default=3.0, - help='Maximum kurtosis value in the output maps ' + - '\n(ak, mk, rk). In theory, 10 is the max kurtosis' + - '\nlimit for regions that consist of water confined' + - '\nto spherical pores (see DIPY example and ' + + help='Maximum kurtosis value in the output maps ' + '\n(ak, mk, rk). In theory, 10 is the max kurtosis' + '\nlimit for regions that consist of water confined' + '\nto spherical pores (see DIPY example and ' '\ndocumentation) [Default: %(default)s].') p.add_argument('--smooth', type=float, default=2.5, - help='Smooth input DWI with a 3D Gaussian filter with ' + - '\nfull-width-half-max (fwhm). Kurtosis fitting is ' + - '\nsensitive and outliers occur easily. According to' + - '\ntests on HCP, CB_Brain, Penthera3T, this smoothing' + - '\nis thus turned ON by default with fwhm=2.5. ' + + help='Smooth input DWI with a 3D Gaussian filter with ' + '\nfull-width-half-max (fwhm). Kurtosis fitting is ' + '\nsensitive and outliers occur easily. According to' + '\ntests on HCP, CB_Brain, Penthera3T, this smoothing' + '\nis thus turned ON by default with fwhm=2.5. ' '\n[Default: %(default)s].') p.add_argument('--not_all', action='store_true', - help='If set, will only save the metrics explicitly ' + - '\nspecified using the other metrics flags. ' + + help='If set, will only save the metrics explicitly ' + '\nspecified using the other metrics flags. ' '\n[Default: not set].') g = p.add_argument_group(title='Metrics files flags') @@ -141,7 +145,6 @@ def _build_arg_parser(): '(powder-average).') add_verbose_arg(p) - add_force_b0_arg(p) add_overwrite_arg(p) return p @@ -192,6 +195,18 @@ def main(): logging.warning('Your b-vectors do not seem normalized...') bvecs = normalize_bvecs(bvecs) + # 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 + # change our identify_shells method. + # Also, usage of gtab.b0s_mask unclear in dipy. An issue has been added in + # dipy to ask them to clarify the usage of gtab.b0s_mask. See here: + # https://github.com/dipy/dipy/issues/3015 + # b0_threshold option in gradient_table probably unused, except below with + # option dki_residual. + args.b0_threshold = args.tolerance + args.b0_threshold = check_b0_threshold(bvals.min(), args) + gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_threshold) + # Processing # Find the volume indices that correspond to the shells to extract. @@ -204,9 +219,6 @@ def main(): logging.warning('You seem to be using b > 2500 s/mm2 DWI data. ' 'In theory, this is beyond the optimal range for DKI') - b0_thr = check_b0_threshold(args, bvals.min(), bvals.min()) - gtab = gradient_table(bvals, bvecs, b0_threshold=b0_thr) - # Smooth to FWHM data = smooth_to_fwhm(data, fwhm=args.smooth) diff --git a/scripts/scil_fodf_msmt.py b/scripts/scil_fodf_msmt.py index ce731adb3..a418b1e2f 100755 --- a/scripts/scil_fodf_msmt.py +++ b/scripts/scil_fodf_msmt.py @@ -69,12 +69,12 @@ def _build_arg_parser(): 'mask will be used for computations and reconstruction.') p.add_argument( '--tolerance', type=int, default=20, - help='The tolerated gap between the b-values to ' - 'extract and the current b-value.\n' + help='The tolerated gap between the b-values to extract and the ' + 'current b-value.\n' 'We would expect to find at least one b-value in the range ' '[0, tolerance], acting as a b0.\n' 'To skip this check, use --skip_b0_validation.\n' - 'Default: [%(default)s]') + '[Default: %(default)s]') add_skip_b0_validation_arg(p, b0_tol_name='--tolerance') add_sh_basis_args(p) add_processes_arg(p) @@ -167,8 +167,9 @@ def main(): # ask them to clarify the usage of gtab.b0s_mask. See here: # https://github.com/dipy/dipy/issues/3015 # b0_threshold option in gradient_table probably unused. - check_b0_threshold(bvals.min(), args, b0_thr=args.tolerance) - gtab = gradient_table(bvals, bvecs, b0_threshold=args.tolerance) + args.b0_threshold = args.tolerance + args.b0_threshold = check_b0_threshold(bvals.min(), args) + gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_threshold) # Checking response functions and computing msmt response function if not wm_frf.shape[1] == 4: From 4ea5d1cb2880624ed722f9f7a848bd0f6abda546 Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Fri, 15 Dec 2023 10:11:14 -0500 Subject: [PATCH 11/26] scil_frf_ssst: Add the b0_threshold option. --- scilpy/reconst/frf.py | 17 ++++++++--------- scripts/scil_frf_ssst.py | 19 +++++++++---------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/scilpy/reconst/frf.py b/scilpy/reconst/frf.py index ebc395c6e..ae77efe45 100644 --- a/scilpy/reconst/frf.py +++ b/scilpy/reconst/frf.py @@ -10,12 +10,13 @@ import numpy as np from scilpy.gradients.bvec_bval_tools import (check_b0_threshold, - is_normalized_bvecs, normalize_bvecs) + is_normalized_bvecs, + normalize_bvecs) -def compute_ssst_frf(data, bvals, bvecs, mask=None, mask_wm=None, - fa_thresh=0.7, min_fa_thresh=0.5, min_nvox=300, - roi_radii=10, roi_center=None, force_b0_threshold=False): +def compute_ssst_frf(data, bvals, bvecs, b0_threshold, + mask=None, mask_wm=None, fa_thresh=0.7, min_fa_thresh=0.5, + min_nvox=300, roi_radii=10, roi_center=None): """Compute a single-shell (under b=1500), single-tissue single Fiber Response Function from a DWI volume. A DTI fit is made, and voxels containing a single fiber population are @@ -29,6 +30,8 @@ def compute_ssst_frf(data, bvals, bvecs, mask=None, mask_wm=None, 1D bvals array with shape (N,) bvecs : ndarray 2D bvecs array with shape (N, 3) + b0_threshold: float + Value under which bvals are considered b0s. mask : ndarray, optional 3D mask with shape (X,Y,Z) Binary mask. Only the data inside the mask will be used for @@ -55,8 +58,6 @@ def compute_ssst_frf(data, bvals, bvecs, mask=None, mask_wm=None, roi_center : tuple(3), optional Use this center to span the roi of size roi_radius (center of the 3D volume). - force_b0_threshold : bool, optional - If set, will continue even if the minimum bvalue is suspiciously high. Returns ------- @@ -78,9 +79,7 @@ def compute_ssst_frf(data, bvals, bvecs, mask=None, mask_wm=None, logging.warning("Your b-vectors do not seem normalized... Normalizing") bvecs = normalize_bvecs(bvecs) - b0_thr = check_b0_threshold(force_b0_threshold, bvals.min(), bvals.min()) - - gtab = gradient_table(bvals, bvecs, b0_threshold=b0_thr) + gtab = gradient_table(bvals, bvecs, b0_threshold=b0_threshold) if mask is not None: data = applymask(data, mask) diff --git a/scripts/scil_frf_ssst.py b/scripts/scil_frf_ssst.py index 2ede8bf3d..e6d948c1a 100755 --- a/scripts/scil_frf_ssst.py +++ b/scripts/scil_frf_ssst.py @@ -17,9 +17,10 @@ import nibabel as nib import numpy as np +from scilpy.gradients.bvec_bval_tools import check_b0_threshold from scilpy.io.image import get_data_as_mask -from scilpy.io.utils import (add_force_b0_arg, - add_overwrite_arg, add_verbose_arg, +from scilpy.io.utils import (add_b0_thresh_arg, add_overwrite_arg, + add_skip_b0_validation_arg, add_verbose_arg, assert_inputs_exist, assert_outputs_exist, assert_roi_radii_format) from scilpy.reconst.frf import compute_ssst_frf @@ -41,8 +42,6 @@ def _build_arg_parser(): help='Path to the output FRF file, in .txt format, ' 'saved by Numpy.') - add_force_b0_arg(p) - p.add_argument('--mask', help='Path to a binary mask. Only the data inside the ' 'mask will be used for computations and ' @@ -82,6 +81,8 @@ def _build_arg_parser(): help='If supplied, use this center to span the roi of size ' 'roi_radius. [center of the 3D volume]') + add_b0_thresh_arg(p) + add_skip_b0_validation_arg(p) add_verbose_arg(p) add_overwrite_arg(p) @@ -102,6 +103,7 @@ def main(): data = vol.get_fdata(dtype=np.float32) bvals, bvecs = read_bvals_bvecs(args.in_bval, args.in_bvec) + args.b0_threshold = check_b0_threshold(bvals.min(), args) mask = None if args.mask: @@ -112,13 +114,10 @@ def main(): mask_wm = get_data_as_mask(nib.load(args.mask_wm), dtype=bool) full_response = compute_ssst_frf( - data, bvals, bvecs, mask=mask, + data, bvals, bvecs, args.b0_threshold, mask=mask, mask_wm=mask_wm, fa_thresh=args.fa_thresh, - min_fa_thresh=args.min_fa_thresh, - min_nvox=args.min_nvox, - roi_radii=roi_radii, - roi_center=args.roi_center, - force_b0_threshold=args.force_b0_threshold) + min_fa_thresh=args.min_fa_thresh, min_nvox=args.min_nvox, + roi_radii=roi_radii, roi_center=args.roi_center) np.savetxt(args.frf_file, full_response) From 3060a843a004035da643f5f68d93acf5d6e18380 Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Fri, 15 Dec 2023 10:25:21 -0500 Subject: [PATCH 12/26] scil_frf_msmt: Cannot modify for now. Adding explanation, using tolerance. --- scilpy/reconst/frf.py | 18 ++++++++++-------- scripts/scil_frf_msmt.py | 25 ++++++++++++++----------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/scilpy/reconst/frf.py b/scilpy/reconst/frf.py index ae77efe45..cce6785ae 100644 --- a/scilpy/reconst/frf.py +++ b/scilpy/reconst/frf.py @@ -136,8 +136,7 @@ def compute_msmt_frf(data, bvals, bvecs, btens=None, data_dti=None, mask=None, mask_wm=None, mask_gm=None, mask_csf=None, fa_thr_wm=0.7, fa_thr_gm=0.2, fa_thr_csf=0.1, md_thr_gm=0.0007, md_thr_csf=0.003, min_nvox=300, - roi_radii=10, roi_center=None, tol=20, - force_b0_threshold=False): + roi_radii=10, roi_center=None, tol=20): """Compute a multi-shell, multi-tissue single Fiber Response Function from a DWI volume. A DTI fit is made, and voxels containing a single fiber population are @@ -151,6 +150,11 @@ def compute_msmt_frf(data, bvals, bvecs, btens=None, data_dti=None, 1D bvals array with shape (N,) bvecs : ndarray 2D bvecs array with shape (N, 3) + btens: ? + data_dti: ? + bvals_dti: ? + bvecs_dti: ? + btens_dti: ? mask : ndarray, optional 3D mask with shape (X,Y,Z) Binary mask. Only the data inside the mask will be used for @@ -199,8 +203,6 @@ def compute_msmt_frf(data, bvals, bvecs, btens=None, data_dti=None, 3D volume). tol : int tolerance gap for b-values clustering. Defaults to 20 - force_b0_threshold : bool, optional - If set, will continue even if the minimum bvalue is suspiciously high. Returns ------- @@ -220,9 +222,10 @@ def compute_msmt_frf(data, bvals, bvecs, btens=None, data_dti=None, logging.warning('Your b-vectors do not seem normalized...') bvecs = normalize_bvecs(bvecs) - b0_thr = check_b0_threshold(force_b0_threshold, bvals.min(), bvals.min()) - - gtab = gradient_table(bvals, bvecs, btens=btens, b0_threshold=b0_thr) + # toDo. Using a fake b0_threshold here because currently, the + # gtab.b0s_mask is not used. Below, we use the tolerance only in dipy. + # An issue has been added in dipy too. + gtab = gradient_table(bvals, bvecs, btens=btens, b0_threshold=bvals.min()) if data_dti is None and bvals_dti is None and bvecs_dti is None: logging.warning( @@ -243,7 +246,6 @@ def compute_msmt_frf(data, bvals, bvecs, btens=None, data_dti=None, logging.warning('Your b-vectors do not seem normalized...') bvecs_dti = normalize_bvecs(bvecs_dti) - check_b0_threshold(force_b0_threshold, bvals_dti.min()) gtab_dti = gradient_table(bvals_dti, bvecs_dti, btens=btens_dti) wm_frf_mask, gm_frf_mask, csf_frf_mask \ diff --git a/scripts/scil_frf_msmt.py b/scripts/scil_frf_msmt.py index c18915b54..fc4a94d99 100755 --- a/scripts/scil_frf_msmt.py +++ b/scripts/scil_frf_msmt.py @@ -37,11 +37,11 @@ import numpy as np from scilpy.dwi.utils import extract_dwi_shell +from scilpy.gradients.bvec_bval_tools import check_b0_threshold from scilpy.io.image import get_data_as_mask -from scilpy.io.utils import (add_force_b0_arg, - add_overwrite_arg, add_verbose_arg, - assert_inputs_exist, assert_outputs_exist, - assert_roi_radii_format) +from scilpy.io.utils import (add_overwrite_arg, add_skip_b0_validation_arg, + add_verbose_arg, assert_inputs_exist, + assert_outputs_exist, assert_roi_radii_format) from scilpy.reconst.frf import compute_msmt_frf @@ -118,6 +118,7 @@ def _build_arg_parser(): type=int, default=20, help='The tolerated gap between the b-values to ' 'extract and the current b-value. [%(default)s]') + add_skip_b0_validation_arg(p, b0_tol_name='--tolerance') p.add_argument('--dti_bval_limit', type=int, default=1200, help='The highest b-value taken for the DTI model. ' @@ -150,7 +151,6 @@ def _build_arg_parser(): 'used to compute the CSF frf.') add_verbose_arg(p) - add_force_b0_arg(p) add_overwrite_arg(p) return p @@ -171,14 +171,19 @@ def main(): data = vol.get_fdata(dtype=np.float32) bvals, bvecs = read_bvals_bvecs(args.in_bval, args.in_bvec) - tol = args.tolerance dti_lim = args.dti_bval_limit - list_bvals = unique_bvals_tolerance(bvals, tol=tol) + # Note. This script does not currently allow using a separate b0_threshold + # for the b0s. Using the tolerance. To fix this, we would need to change + # the unique_bvals_tolerance and extract_dwi_shell methods. + args.b0_threshold = args.tolerance + _ = check_b0_threshold(bvals.min(), args) + + list_bvals = unique_bvals_tolerance(bvals, tol=args.tolerance) if not np.all(list_bvals <= dti_lim): outputs = extract_dwi_shell(vol, bvals, bvecs, list_bvals[list_bvals <= dti_lim], - tol=tol) + tol=args.tolerance) _, data_dti, bvals_dti, bvecs_dti = outputs bvals_dti = np.squeeze(bvals_dti) else: @@ -201,7 +206,6 @@ def main(): if args.mask_csf: mask_csf = get_data_as_mask(nib.load(args.mask_csf), dtype=bool) - force_b0_thr = args.force_b0_threshold responses, frf_masks = compute_msmt_frf(data, bvals, bvecs, data_dti=data_dti, bvals_dti=bvals_dti, @@ -216,8 +220,7 @@ def main(): min_nvox=args.min_nvox, roi_radii=roi_radii, roi_center=args.roi_center, - tol=tol, - force_b0_threshold=force_b0_thr) + tol=args.tolerance) masks_files = [args.wm_frf_mask, args.gm_frf_mask, args.csf_frf_mask] for mask, mask_file in zip(frf_masks, masks_files): From 316febd53e545c7943654fa24984ef853582c389 Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Mon, 22 Jan 2024 15:36:59 -0500 Subject: [PATCH 13/26] scil_frf_memsmt: Can't change. Added explanations. --- scilpy/io/btensor.py | 5 +++-- scripts/scil_frf_memsmt.py | 30 +++++++++++++----------------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/scilpy/io/btensor.py b/scilpy/io/btensor.py index 0d4c8cab2..16605c3de 100644 --- a/scilpy/io/btensor.py +++ b/scilpy/io/btensor.py @@ -54,6 +54,7 @@ def generate_btensor_input(in_dwis, in_bvals, in_bvecs, in_bdeltas, """Generate b-tensor input from an ensemble of data, bvals and bvecs files. This generated input is mandatory for all scripts using b-tensor encoding data. Also generate the powder-averaged (PA) data if set. + toDo. Add a way to include a different b0_threshold than the tolerance. Parameters ---------- @@ -156,7 +157,7 @@ def generate_btensor_input(in_dwis, in_bvals, in_bvecs, in_bdeltas, gtab_infos[3] = acq_index_full if np.sum([ubvals_full < tol]) < acq_index - 1: gtab_infos[3] *= 0 - return(pa_signals, gtab_infos) + return pa_signals, gtab_infos # Removing the duplicate b0s from ubvals_full duplicate_b0_ind = np.union1d(np.argwhere(ubvals_full == min(ubvals_full)), np.argwhere(ubvals_full > tol)) @@ -185,4 +186,4 @@ def generate_btensor_input(in_dwis, in_bvals, in_bvecs, in_bdeltas, b0_threshold=bvals_full.min(), btens=b_shapes) - return(gtab_full, data_full, ubvals_full, ub_deltas_full) + return gtab_full, data_full, ubvals_full, ub_deltas_full diff --git a/scripts/scil_frf_memsmt.py b/scripts/scil_frf_memsmt.py index 6fe6f3c2d..f217f5ce6 100755 --- a/scripts/scil_frf_memsmt.py +++ b/scripts/scil_frf_memsmt.py @@ -50,8 +50,7 @@ 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.utils import (add_force_b0_arg, - add_overwrite_arg, add_verbose_arg, +from scilpy.io.utils import (add_overwrite_arg, add_verbose_arg, assert_inputs_exist, assert_outputs_exist, assert_roi_radii_format) from scilpy.reconst.frf import compute_msmt_frf @@ -170,7 +169,6 @@ def _build_arg_parser(): 'used to compute the CSF frf.') add_verbose_arg(p) - add_force_b0_arg(p) add_overwrite_arg(p) return p @@ -198,17 +196,17 @@ def main(): roi_radii = assert_roi_radii_format(parser) - tol = args.tolerance - dti_lim = args.dti_bval_limit - force_b0_thr = args.force_b0_threshold - + # 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 + # change generate_btensor_input. Not doing any verification on the + # bvals. Typically, we would use check_b0_threshold(bvals.min(), args) gtab, data, ubvals, ubdeltas = generate_btensor_input(args.in_dwis, args.in_bvals, args.in_bvecs, args.in_bdeltas, - tol=tol) + tol=args.tolerance) - if not np.all(ubvals <= dti_lim): + if not np.all(ubvals <= args.dti_bval_limit): if np.sum(ubdeltas == 1) > 0: dti_ubvals = ubvals[ubdeltas == 1] elif np.sum(ubdeltas == -0.5) > 0: @@ -218,12 +216,11 @@ def main(): else: raise ValueError("No encoding available for DTI.") vol = nib.Nifti1Image(data, affine) - outputs = extract_dwi_shell(vol, gtab.bvals, gtab.bvecs, - dti_ubvals[dti_ubvals <= dti_lim], - tol=1) - indices_dti, data_dti, bvals_dti, bvecs_dti = outputs - # gtab_dti = gradient_table(np.squeeze(bvals_dti), bvecs_dti, - # btens=gtab.btens[indices_dti]) + bvals_to_extract = dti_ubvals[dti_ubvals <= args.dti_bval_limit] + indices_dti, data_dti, bvals_dti, bvecs_dti = \ + extract_dwi_shell(vol, gtab.bvals, gtab.bvecs, + bvals_to_extract, tol=1) + bvals_dti = np.squeeze(bvals_dti) btens_dti = gtab.btens[indices_dti] else: @@ -263,8 +260,7 @@ def main(): min_nvox=args.min_nvox, roi_radii=roi_radii, roi_center=args.roi_center, - tol=0, - force_b0_threshold=force_b0_thr) + tol=0) masks_files = [args.wm_frf_mask, args.gm_frf_mask, args.csf_frf_mask] for mask, mask_file in zip(frf_masks, masks_files): From 0cca6f62ed3a29574a87c76454618acd0d1efb35 Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Mon, 22 Jan 2024 15:38:06 -0500 Subject: [PATCH 14/26] scil_fodf_memsmt: Idem --- scripts/scil_fodf_memsmt.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/scripts/scil_fodf_memsmt.py b/scripts/scil_fodf_memsmt.py index 543a925f5..d1017d8e7 100755 --- a/scripts/scil_fodf_memsmt.py +++ b/scripts/scil_fodf_memsmt.py @@ -49,9 +49,9 @@ from scilpy.io.btensor import (generate_btensor_input, convert_bdelta_to_bshape) from scilpy.io.image import get_data_as_mask -from scilpy.io.utils import (add_overwrite_arg, assert_inputs_exist, - assert_outputs_exist, add_sh_basis_args, - add_processes_arg, add_verbose_arg) +from scilpy.io.utils import (add_overwrite_arg, add_processes_arg, + add_sh_basis_args, add_verbose_arg, + assert_inputs_exist, assert_outputs_exist) from scilpy.reconst.fodf import fit_from_model from scilpy.reconst.sh import convert_sh_basis @@ -156,17 +156,19 @@ def main(): affine = extract_affine(args.in_dwis) - tol = args.tolerance - wm_frf = np.loadtxt(args.in_wm_frf) gm_frf = np.loadtxt(args.in_gm_frf) csf_frf = np.loadtxt(args.in_csf_frf) + # 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 + # change generate_btensor_input. Not doing any verification on the + # bvals. Typically, we would use check_b0_threshold(bvals.min(), args) gtab, data, ubvals, ubdeltas = generate_btensor_input(args.in_dwis, args.in_bvals, args.in_bvecs, args.in_bdeltas, - tol=tol) + tol=args.tolerance) # Checking mask if args.mask is None: @@ -208,7 +210,7 @@ def main(): memsmt_response = multi_shell_fiber_response(sh_order, ubvals, wm_frf, gm_frf, csf_frf, - tol=tol, + tol=args.tolerance, btens=ubshapes) reg_sphere = get_sphere('symmetric362') From fd09544439d9a48f6b4e5ced0ed59ca712aa8faf Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Mon, 22 Jan 2024 15:38:32 -0500 Subject: [PATCH 15/26] scil_btensor_metrics: Idem. Removing unused option force_b0. --- scripts/scil_btensor_metrics.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/scil_btensor_metrics.py b/scripts/scil_btensor_metrics.py index d33bfcf73..252f9b837 100755 --- a/scripts/scil_btensor_metrics.py +++ b/scripts/scil_btensor_metrics.py @@ -50,8 +50,8 @@ from scilpy.io.btensor import generate_btensor_input 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, - add_processes_arg, add_verbose_arg) + assert_outputs_exist, add_processes_arg, + add_verbose_arg) from scilpy.reconst.divide import fit_gamma, gamma_fit2metrics @@ -110,7 +110,6 @@ def _build_arg_parser(): '--fa', help='Path to a FA map. Needed for calculating the OP.') - add_force_b0_arg(p) add_processes_arg(p) add_verbose_arg(p) add_overwrite_arg(p) @@ -181,6 +180,10 @@ def main(): # Loading affine = extract_affine(args.in_dwis) + # 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 + # change generate_btensor_input. Not doing any verification on the + # bvals. Typically, we would use check_b0_threshold(bvals.min(), args) data, gtab_infos = generate_btensor_input(args.in_dwis, args.in_bvals, args.in_bvecs, From aceacb09bd024a6e4708daf61d92576d0ad5385a Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Tue, 23 Jan 2024 11:13:15 -0500 Subject: [PATCH 16/26] Fix pep8 --- scripts/scil_dki_metrics.py | 5 +++-- scripts/scil_dti_metrics.py | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/scil_dki_metrics.py b/scripts/scil_dki_metrics.py index 96dfe985f..20a8f6b1b 100755 --- a/scripts/scil_dki_metrics.py +++ b/scripts/scil_dki_metrics.py @@ -60,9 +60,10 @@ from scilpy.io.utils import (add_overwrite_arg, add_skip_b0_validation_arg, add_verbose_arg, assert_inputs_exist, assert_outputs_exist, ) -from scilpy.gradients.bvec_bval_tools import (is_normalized_bvecs, +from scilpy.gradients.bvec_bval_tools import (check_b0_threshold, + is_normalized_bvecs, identify_shells, - normalize_bvecs, check_b0_threshold) + normalize_bvecs) def _build_arg_parser(): diff --git a/scripts/scil_dti_metrics.py b/scripts/scil_dti_metrics.py index 1a810832d..01bc4810a 100755 --- a/scripts/scil_dti_metrics.py +++ b/scripts/scil_dti_metrics.py @@ -46,12 +46,12 @@ from scilpy.io.image import get_data_as_mask from scilpy.io.utils import (add_b0_thresh_arg, add_overwrite_arg, add_skip_b0_validation_arg, add_verbose_arg, - assert_inputs_exist,assert_outputs_exist) + assert_inputs_exist, assert_outputs_exist) from scilpy.io.tensor import convert_tensor_from_dipy_format, \ supported_tensor_formats, tensor_format_description -from scilpy.gradients.bvec_bval_tools import (normalize_bvecs, +from scilpy.gradients.bvec_bval_tools import (check_b0_threshold, is_normalized_bvecs, - check_b0_threshold) + normalize_bvecs) from scilpy.utils.filenames import add_filename_suffix, split_name_with_nii logger = logging.getLogger("DTI_Metrics") From 778ddd9881487c8b5487d8f3eb6525059f03f638 Mon Sep 17 00:00:00 2001 From: Emmanuelle Renauld Date: Wed, 24 Jan 2024 16:01:54 -0500 Subject: [PATCH 17/26] Answer some comments --- scilpy/gradients/bvec_bval_tools.py | 22 +++++++++++----------- scilpy/io/btensor.py | 4 +++- scilpy/reconst/frf.py | 11 ++++++++--- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/scilpy/gradients/bvec_bval_tools.py b/scilpy/gradients/bvec_bval_tools.py index df5c008fe..937bef09c 100644 --- a/scilpy/gradients/bvec_bval_tools.py +++ b/scilpy/gradients/bvec_bval_tools.py @@ -84,33 +84,33 @@ def check_b0_threshold(min_bval, args): b0_thr = args.b0_threshold if b0_thr > DEFAULT_B0_THRESHOLD: logging.warning( - 'Warning: Your defined b0 threshold (option --{}) is {}. This is ' - 'suspicious. We recommend using volumes with bvalues no higher ' - 'than {} as b0s.'.format(b0_thr, b0_thr, DEFAULT_B0_THRESHOLD)) + 'Your defined b0 threshold is {}. This is suspicious. We ' + 'recommend using volumes with bvalues no higher than {} as b0s' + .format(b0_thr, DEFAULT_B0_THRESHOLD)) if min_bval < 0: logging.warning( 'Warning: Your dataset contains negative b-values (minimal bvalue ' - 'of {}). This is suspicious. We recommend you check your data.') + 'of {}). This is suspicious. We recommend you check your data.' + .format(min_bval)) if min_bval > b0_thr: if args.skip_b0_validation: logging.warning("GOT {} > {}".format(min_bval, b0_thr)) logging.warning( - 'Warning: Your minimal bvalue ({}), is above the threshold ' - 'defined with --{}: {}.\n' - 'Since --skip_b0_validation was specified, the script will' + 'Your minimal bvalue ({}), is above the threshold ({})\n' + 'Since --skip_b0_validation was specified, the script will ' 'proceed with a b0 threshold of {}.' - .format(min_bval, b0_thr, b0_thr, min_bval)) + .format(min_bval, b0_thr, min_bval)) return min_bval else: raise ValueError( - 'The minimal bvalue ({}) is is above the threshold ' - 'defined with --{}: {}.\n.No b0 volumes can be found.\n' + 'The minimal bvalue ({}) is is above the threshold ({})\n' + 'No b0 volumes can be found.\n' 'Please check your data to ensure everything is correct.\n' 'You may also increase the threshold or use ' '--skip_b0_validation' - .format(min_bval, b0_thr, b0_thr, min_bval)) + .format(min_bval, b0_thr)) return b0_thr diff --git a/scilpy/io/btensor.py b/scilpy/io/btensor.py index 16605c3de..ead25fe9e 100644 --- a/scilpy/io/btensor.py +++ b/scilpy/io/btensor.py @@ -54,7 +54,9 @@ def generate_btensor_input(in_dwis, in_bvals, in_bvecs, in_bdeltas, """Generate b-tensor input from an ensemble of data, bvals and bvecs files. This generated input is mandatory for all scripts using b-tensor encoding data. Also generate the powder-averaged (PA) data if set. - toDo. Add a way to include a different b0_threshold than the tolerance. + + For the moment, this does not enable the use of a b0_threshold different + than the tolerance. Parameters ---------- diff --git a/scilpy/reconst/frf.py b/scilpy/reconst/frf.py index cce6785ae..e3ed4b877 100644 --- a/scilpy/reconst/frf.py +++ b/scilpy/reconst/frf.py @@ -150,9 +150,14 @@ def compute_msmt_frf(data, bvals, bvecs, btens=None, data_dti=None, 1D bvals array with shape (N,) bvecs : ndarray 2D bvecs array with shape (N, 3) - btens: ? - data_dti: ? - bvals_dti: ? + btens: ndarray 1D + btens array with shape (N,), describing the btensor shape of every + pair of bval/bvec. + data_dti: ndarray 4D + Input diffusion volume with shape (X, Y, Z, M), where M is the number + of DTI directions. + bvals_dti: ndarray 1D + bvals array with shape (M,) bvecs_dti: ? btens_dti: ? mask : ndarray, optional From 41c253384a4e95d0051b1fc758b98eb038b36069 Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Mon, 29 Jan 2024 10:02:07 -0500 Subject: [PATCH 18/26] Do no use 'args' in check_b0 --- scilpy/gradients/bvec_bval_tools.py | 14 ++++++-------- scilpy/io/utils.py | 22 +++++++++++++++------- scripts/scil_dki_metrics.py | 11 ++++++----- scripts/scil_dti_metrics.py | 8 +++++--- scripts/scil_dwi_detect_volume_outliers.py | 13 ++++++++----- scripts/scil_dwi_extract_b0.py | 8 +++++--- scripts/scil_dwi_to_sh.py | 8 +++++--- scripts/scil_fodf_msmt.py | 13 +++++++------ scripts/scil_fodf_ssst.py | 8 +++++--- scripts/scil_frf_msmt.py | 10 +++++----- scripts/scil_frf_ssst.py | 9 +++++---- scripts/scil_qball_metrics.py | 8 +++++--- scripts/scil_sh_to_sf.py | 9 +++++---- 13 files changed, 82 insertions(+), 59 deletions(-) diff --git a/scilpy/gradients/bvec_bval_tools.py b/scilpy/gradients/bvec_bval_tools.py index 937bef09c..34839f1ef 100644 --- a/scilpy/gradients/bvec_bval_tools.py +++ b/scilpy/gradients/bvec_bval_tools.py @@ -55,7 +55,7 @@ def normalize_bvecs(bvecs): return bvecs -def check_b0_threshold(min_bval, args): +def check_b0_threshold(min_bval, b0_threshold, skip_b0_check): """ Check if the minimal bvalue is under the threshold. If not, raise an error to ask user to update the b0_thr. @@ -67,13 +67,11 @@ def check_b0_threshold(min_bval, args): ---------- min_bval : float Minimum bvalue. - args: Namespace - The argparse arguments. Should contain at least: - args.skip_b0_validation: bool - If True, and no b0 is found, only print a warning, do not raise - an error. - args.b0_threshold: float - Maximum bvalue considered as a b0. + b0_threshold: float + Maximum bvalue considered as a b0. + skip_b0_check: bool + If True, and no b0 is found, only print a warning, do not raise + an error. Raises ------ diff --git a/scilpy/io/utils.py b/scilpy/io/utils.py index b76aeabfc..c5f3a17b4 100644 --- a/scilpy/io/utils.py +++ b/scilpy/io/utils.py @@ -215,14 +215,22 @@ def add_overwrite_arg(parser): help='Force overwriting of the output files.') -def add_skip_b0_validation_arg(parser, b0_tol_name='--b0_threshold'): +def add_skip_b0_check_arg(parser, will_overwrite_with_min: bool, + b0_tol_name='--b0_threshold'): + msg = ('By default, we supervise that at least one b0 exists in your ' + 'data\n' + '(i.e. b-values below the default {}). Use this option to ' + 'allow continuing \n' + 'even if the minimum b-value is suspiciously high.\n' + .format(b0_tol_name)) + if will_overwrite_with_min: + msg += ('If no b-value is found below the threshold, the script will ' + 'continue \nwith your minimal b-value as new {}.\n' + .format(b0_tol_name)) + msg += 'Use with care, and only if you understand your data.' + parser.add_argument( - '--skip_b0_validation', action='store_true', - help='By default, we supervise that at least one b0 exists in your ' - 'data \n(i.e. b-values below the default {}). Use this option to ' - 'allow continuing even if the minimum \nbvalue is suspiciously ' - 'high.\nUse with care, and only if you understand your data.' - .format(b0_tol_name)) + '--skip_b0_check', action='store_true', help=msg) def add_b0_thresh_arg(parser): diff --git a/scripts/scil_dki_metrics.py b/scripts/scil_dki_metrics.py index 20a8f6b1b..3e80c913e 100755 --- a/scripts/scil_dki_metrics.py +++ b/scripts/scil_dki_metrics.py @@ -57,7 +57,7 @@ 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.utils import (add_overwrite_arg, add_skip_b0_validation_arg, +from scilpy.io.utils import (add_overwrite_arg, add_skip_b0_check_arg, add_verbose_arg, assert_inputs_exist, assert_outputs_exist, ) from scilpy.gradients.bvec_bval_tools import (check_b0_threshold, @@ -90,7 +90,8 @@ def _build_arg_parser(): '[0, tolerance], acting as a b0.\n' 'To skip this check, use --skip_b0_validation.\n' '[Default: %(default)s]') - add_skip_b0_validation_arg(p, b0_tol_name='--tolerance') + add_skip_b0_check_arg(p, will_overwrite_with_min=False, + b0_tol_name='--tolerance') p.add_argument('--min_k', type=float, default=0.0, help='Minimum kurtosis value in the output maps ' @@ -204,9 +205,9 @@ def main(): # https://github.com/dipy/dipy/issues/3015 # b0_threshold option in gradient_table probably unused, except below with # option dki_residual. - args.b0_threshold = args.tolerance - args.b0_threshold = check_b0_threshold(bvals.min(), args) - gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_threshold) + _ = check_b0_threshold(bvals.min(), b0_threshold=args.tolerance, + skip_b0_check=args.skip_b0_check) + gtab = gradient_table(bvals, bvecs, b0_threshold=args.tolerance) # Processing diff --git a/scripts/scil_dti_metrics.py b/scripts/scil_dti_metrics.py index 01bc4810a..390ed41b3 100755 --- a/scripts/scil_dti_metrics.py +++ b/scripts/scil_dti_metrics.py @@ -45,7 +45,7 @@ compute_residuals_statistics from scilpy.io.image import get_data_as_mask from scilpy.io.utils import (add_b0_thresh_arg, add_overwrite_arg, - add_skip_b0_validation_arg, add_verbose_arg, + add_skip_b0_check_arg, add_verbose_arg, assert_inputs_exist, assert_outputs_exist) from scilpy.io.tensor import convert_tensor_from_dipy_format, \ supported_tensor_formats, tensor_format_description @@ -142,7 +142,7 @@ def _build_arg_parser(): help='Output filename for the map of the residual of the tensor fit.') add_b0_thresh_arg(p) - add_skip_b0_validation_arg(p) + add_skip_b0_check_arg(p, will_overwrite_with_min=True) add_verbose_arg(p) return p @@ -273,7 +273,9 @@ def main(): # method .fit(). # 2) But we do use this information below, with options p_i_signal, # pulsation and residual. - args.b0_threshold = check_b0_threshold(bvals.min(), args) + args.b0_threshold = check_b0_threshold(bvals.min(), + b0_threshold=args.b0_threshold, + skip_b0_check=args.skip_b0_check) gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_threshold) # Processing diff --git a/scripts/scil_dwi_detect_volume_outliers.py b/scripts/scil_dwi_detect_volume_outliers.py index c59d33f49..60109f53d 100755 --- a/scripts/scil_dwi_detect_volume_outliers.py +++ b/scripts/scil_dwi_detect_volume_outliers.py @@ -8,7 +8,7 @@ args.std_scale x STD) it will flag the volume as a potential outlier. This script supports multi-shells, but each shell is independant and detected -using the args.b0_threshold parameter. +using the --b0_threshold parameter. This script can be run before any processing to identify potential problem before launching pre-processing. @@ -20,9 +20,10 @@ from dipy.io.gradients import read_bvals_bvecs import nibabel as nib + from scilpy.dwi.operations import detect_volume_outliers -from scilpy.io.utils import (add_b0_thresh_arg, add_skip_b0_validation_arg, - add_verbose_arg, assert_inputs_exist,) +from scilpy.io.utils import (add_b0_thresh_arg, add_skip_b0_check_arg, + add_verbose_arg, assert_inputs_exist, ) from scilpy.gradients.bvec_bval_tools import (check_b0_threshold, normalize_bvecs) @@ -44,7 +45,7 @@ def _build_arg_parser(): 'considered an outlier. [%(default)s]') add_b0_thresh_arg(p) - add_skip_b0_validation_arg(p) + add_skip_b0_check_arg(p, will_overwrite_with_min=True) add_verbose_arg(p) return p @@ -63,7 +64,9 @@ def main(): bvals, bvecs = read_bvals_bvecs(args.in_bval, args.in_bvec) data = nib.load(args.in_dwi).get_fdata() - args.b0_threshold = check_b0_threshold(bvals.min(), args) + args.b0_threshold = check_b0_threshold(bvals.min(), + b0_threshold=args.b0_threshold, + skip_b0_check=args.skip_b0_check) bvecs = normalize_bvecs(bvecs) # Not using the result. Only printing on screen. This is why the logging diff --git a/scripts/scil_dwi_extract_b0.py b/scripts/scil_dwi_extract_b0.py index aca206ae7..1a46a777c 100755 --- a/scripts/scil_dwi_extract_b0.py +++ b/scripts/scil_dwi_extract_b0.py @@ -21,7 +21,7 @@ from scilpy.dwi.utils import extract_b0 from scilpy.io.utils import (add_b0_thresh_arg, add_overwrite_arg, - add_skip_b0_validation_arg, add_verbose_arg, + add_skip_b0_check_arg, add_verbose_arg, assert_inputs_exist) from scilpy.gradients.bvec_bval_tools import (check_b0_threshold, B0ExtractionStrategy) @@ -65,7 +65,7 @@ def _build_arg_parser(): 'of images.') add_b0_thresh_arg(p) - add_skip_b0_validation_arg(p) + add_skip_b0_check_arg(p, will_overwrite_with_min=True) add_verbose_arg(p) add_overwrite_arg(p) @@ -95,7 +95,9 @@ def main(): bvals, bvecs = read_bvals_bvecs(args.in_bval, args.in_bvec) - check_b0_threshold(bvals.min(), args) + args.b0_threshold = check_b0_threshold(bvals.min(), + b0_threshold=args.b0_threshold, + skip_b0_check=args.skip_b0_check) gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_threshold) b0_idx = np.where(gtab.b0s_mask)[0] diff --git a/scripts/scil_dwi_to_sh.py b/scripts/scil_dwi_to_sh.py index 502b522bf..51deb606b 100755 --- a/scripts/scil_dwi_to_sh.py +++ b/scripts/scil_dwi_to_sh.py @@ -18,7 +18,7 @@ from scilpy.gradients.bvec_bval_tools import check_b0_threshold from scilpy.io.image import get_data_as_mask from scilpy.io.utils import (add_b0_thresh_arg, add_overwrite_arg, - add_sh_basis_args, add_skip_b0_validation_arg, + add_sh_basis_args, add_skip_b0_check_arg, add_verbose_arg, assert_inputs_exist, assert_outputs_exist) from scilpy.reconst.sh import compute_sh_coefficients @@ -50,7 +50,7 @@ def _build_arg_parser(): 'will be used for computations and reconstruction ') add_b0_thresh_arg(p) - add_skip_b0_validation_arg(p) + add_skip_b0_check_arg(p, will_overwrite_with_min=True) add_verbose_arg(p) add_overwrite_arg(p) @@ -71,7 +71,9 @@ def main(): bvals, bvecs = read_bvals_bvecs(args.in_bval, args.in_bvec) # gtab.b0s_mask in used in compute_sh_coefficients to get the b0s. - args.b0_threshold = check_b0_threshold(bvals.min(), args) + args.b0_threshold = check_b0_threshold(bvals.min(), + b0_threshold=args.b0_threshold, + skip_b0_check=args.skip_b0_check) gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_threshold) mask = None diff --git a/scripts/scil_fodf_msmt.py b/scripts/scil_fodf_msmt.py index a418b1e2f..36db34673 100755 --- a/scripts/scil_fodf_msmt.py +++ b/scripts/scil_fodf_msmt.py @@ -34,7 +34,7 @@ is_normalized_bvecs) from scilpy.io.image import get_data_as_mask from scilpy.io.utils import (add_overwrite_arg, add_processes_arg, - add_sh_basis_args, add_skip_b0_validation_arg, + add_sh_basis_args, add_skip_b0_check_arg, assert_inputs_exist, assert_outputs_exist, add_verbose_arg) @@ -73,9 +73,10 @@ def _build_arg_parser(): 'current b-value.\n' 'We would expect to find at least one b-value in the range ' '[0, tolerance], acting as a b0.\n' - 'To skip this check, use --skip_b0_validation.\n' + 'To skip this check, use --skip_b0_check.\n' '[Default: %(default)s]') - add_skip_b0_validation_arg(p, b0_tol_name='--tolerance') + add_skip_b0_check_arg(p, will_overwrite_with_min=False, + b0_tol_name='--tolerance') add_sh_basis_args(p) add_processes_arg(p) @@ -167,9 +168,9 @@ def main(): # ask them to clarify the usage of gtab.b0s_mask. See here: # https://github.com/dipy/dipy/issues/3015 # b0_threshold option in gradient_table probably unused. - args.b0_threshold = args.tolerance - args.b0_threshold = check_b0_threshold(bvals.min(), args) - gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_threshold) + _ = check_b0_threshold(bvals.min(), b0_threshold=args.tolerance, + skip_b0_check=args.skip_b0_check) + gtab = gradient_table(bvals, bvecs, b0_threshold=args.tolerance) # Checking response functions and computing msmt response function if not wm_frf.shape[1] == 4: diff --git a/scripts/scil_fodf_ssst.py b/scripts/scil_fodf_ssst.py index 3fdd61921..96c260276 100755 --- a/scripts/scil_fodf_ssst.py +++ b/scripts/scil_fodf_ssst.py @@ -25,7 +25,7 @@ from scilpy.io.image import get_data_as_mask from scilpy.io.utils import (add_b0_thresh_arg, add_overwrite_arg, add_processes_arg, add_sh_basis_args, - add_skip_b0_validation_arg, add_verbose_arg, + add_skip_b0_check_arg, add_verbose_arg, assert_inputs_exist, assert_outputs_exist) from scilpy.reconst.fodf import fit_from_model from scilpy.reconst.sh import convert_sh_basis @@ -55,7 +55,7 @@ def _build_arg_parser(): 'used for computations and reconstruction.') add_b0_thresh_arg(p) - add_skip_b0_validation_arg(p) + add_skip_b0_check_arg(p, will_overwrite_with_min=True) add_sh_basis_args(p) add_processes_arg(p) add_verbose_arg(p) @@ -103,7 +103,9 @@ def main(): bvecs = normalize_bvecs(bvecs) # gtab.b0s_mask is used in dipy's csdeconv class. - args.b0_threshold = check_b0_threshold(bvals.min(), args) + args.b0_threshold = check_b0_threshold(bvals.min(), + b0_threshold=args.b0_threshold, + skip_b0_check=args.skip_b0_check) gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_threshold) # Checking full_frf and separating it diff --git a/scripts/scil_frf_msmt.py b/scripts/scil_frf_msmt.py index fc4a94d99..0158bb69b 100755 --- a/scripts/scil_frf_msmt.py +++ b/scripts/scil_frf_msmt.py @@ -39,7 +39,7 @@ from scilpy.dwi.utils import extract_dwi_shell from scilpy.gradients.bvec_bval_tools import check_b0_threshold from scilpy.io.image import get_data_as_mask -from scilpy.io.utils import (add_overwrite_arg, add_skip_b0_validation_arg, +from scilpy.io.utils import (add_overwrite_arg, add_skip_b0_check_arg, add_verbose_arg, assert_inputs_exist, assert_outputs_exist, assert_roi_radii_format) from scilpy.reconst.frf import compute_msmt_frf @@ -118,7 +118,8 @@ def _build_arg_parser(): type=int, default=20, help='The tolerated gap between the b-values to ' 'extract and the current b-value. [%(default)s]') - add_skip_b0_validation_arg(p, b0_tol_name='--tolerance') + add_skip_b0_check_arg(p, will_overwrite_with_min=False, + b0_tol_name='--tolerance') p.add_argument('--dti_bval_limit', type=int, default=1200, help='The highest b-value taken for the DTI model. ' @@ -176,9 +177,8 @@ def main(): # Note. This script does not currently allow using a separate b0_threshold # for the b0s. Using the tolerance. To fix this, we would need to change # the unique_bvals_tolerance and extract_dwi_shell methods. - args.b0_threshold = args.tolerance - _ = check_b0_threshold(bvals.min(), args) - + _ = check_b0_threshold(bvals.min(), b0_threshold=args.tolerance, + skip_b0_check=args.skip_b0_check) list_bvals = unique_bvals_tolerance(bvals, tol=args.tolerance) if not np.all(list_bvals <= dti_lim): outputs = extract_dwi_shell(vol, bvals, bvecs, diff --git a/scripts/scil_frf_ssst.py b/scripts/scil_frf_ssst.py index e6d948c1a..942069099 100755 --- a/scripts/scil_frf_ssst.py +++ b/scripts/scil_frf_ssst.py @@ -20,7 +20,7 @@ from scilpy.gradients.bvec_bval_tools import check_b0_threshold from scilpy.io.image import get_data_as_mask from scilpy.io.utils import (add_b0_thresh_arg, add_overwrite_arg, - add_skip_b0_validation_arg, add_verbose_arg, + add_skip_b0_check_arg, add_verbose_arg, assert_inputs_exist, assert_outputs_exist, assert_roi_radii_format) from scilpy.reconst.frf import compute_ssst_frf @@ -82,7 +82,7 @@ def _build_arg_parser(): 'roi_radius. [center of the 3D volume]') add_b0_thresh_arg(p) - add_skip_b0_validation_arg(p) + add_skip_b0_check_arg(p, will_overwrite_with_min=True) add_verbose_arg(p) add_overwrite_arg(p) @@ -103,8 +103,9 @@ def main(): data = vol.get_fdata(dtype=np.float32) bvals, bvecs = read_bvals_bvecs(args.in_bval, args.in_bvec) - args.b0_threshold = check_b0_threshold(bvals.min(), args) - + args.b0_threshold = check_b0_threshold(bvals.min(), + b0_threshold=args.b0_threshold, + skip_b0_check=args.skip_b0_check) mask = None if args.mask: mask = get_data_as_mask(nib.load(args.mask), dtype=bool) diff --git a/scripts/scil_qball_metrics.py b/scripts/scil_qball_metrics.py index d33c33838..e02394d18 100755 --- a/scripts/scil_qball_metrics.py +++ b/scripts/scil_qball_metrics.py @@ -34,7 +34,7 @@ normalize_bvecs) from scilpy.io.utils import (add_b0_thresh_arg, add_overwrite_arg, add_processes_arg, add_sh_basis_args, - add_skip_b0_validation_arg, add_verbose_arg, + add_skip_b0_check_arg, add_verbose_arg, assert_inputs_exist, assert_outputs_exist, validate_nbr_processes) from scilpy.io.image import get_data_as_mask @@ -88,7 +88,7 @@ def _build_arg_parser(): '[anisotropic_power.nii.gz].') add_b0_thresh_arg(p) - add_skip_b0_validation_arg(p) + add_skip_b0_check_arg(p, will_overwrite_with_min=True) add_sh_basis_args(p) add_processes_arg(p) add_verbose_arg(p) @@ -134,7 +134,9 @@ def main(): # Usage of gtab.b0s_mask in dipy's models is not very well documented, but # we can see that it is indeed used. - args.b0_threshold = check_b0_threshold(bvals.min(), args) + args.b0_threshold = check_b0_threshold(bvals.min(), + b0_threshold=args.b0_threshold, + skip_b0_check=args.skip_b0_check) gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_threshold) sphere = get_sphere('symmetric724') diff --git a/scripts/scil_sh_to_sf.py b/scripts/scil_sh_to_sf.py index 5a82dd742..5a9af29f3 100755 --- a/scripts/scil_sh_to_sf.py +++ b/scripts/scil_sh_to_sf.py @@ -26,7 +26,7 @@ from scilpy.gradients.bvec_bval_tools import (check_b0_threshold, DEFAULT_B0_THRESHOLD) from scilpy.io.utils import (add_overwrite_arg, add_processes_arg, - add_skip_b0_validation_arg, add_sh_basis_args, + add_skip_b0_check_arg, add_sh_basis_args, assert_inputs_exist, add_verbose_arg, assert_outputs_exist, validate_nbr_processes) from scilpy.reconst.sh import convert_sh_to_sf @@ -83,7 +83,7 @@ def _build_arg_parser(): 'Default if not set is {}.\n' 'This value is used with options --in_bvec or --in_bval only.' .format(DEFAULT_B0_THRESHOLD)) - add_skip_b0_validation_arg(p) + add_skip_b0_check_arg(p, will_overwrite_with_min=True) add_processes_arg(p) add_verbose_arg(p) @@ -123,8 +123,9 @@ def main(): bvals, bvecs = read_bvals_bvecs(args.in_bval, args.in_bvec) elif args.in_bval: bvals, _ = read_bvals_bvecs(args.in_bval, None) - args.b0_treshold = check_b0_threshold(bvals.min(), args) - + args.b0_threshold = check_b0_threshold(bvals.min(), + b0_threshold=args.b0_threshold, + skip_b0_check=args.skip_b0_check) # Load SH vol_sh = nib.load(args.in_sh) data_sh = vol_sh.get_fdata(dtype=np.float32) From 949670c457c7708b703508d28b4d416aac9b76c5 Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Mon, 29 Jan 2024 10:25:40 -0500 Subject: [PATCH 19/26] Answering a few more comments --- scilpy/io/btensor.py | 11 +++++++++-- scilpy/reconst/frf.py | 17 +++++++++-------- scilpy/reconst/sh.py | 6 ++++-- scripts/scil_btensor_metrics.py | 10 ++++++---- scripts/scil_fodf_memsmt.py | 13 +++++++------ scripts/scil_frf_memsmt.py | 12 ++++++------ 6 files changed, 41 insertions(+), 28 deletions(-) diff --git a/scilpy/io/btensor.py b/scilpy/io/btensor.py index ead25fe9e..829144d85 100644 --- a/scilpy/io/btensor.py +++ b/scilpy/io/btensor.py @@ -8,7 +8,8 @@ from scilpy.dwi.utils import extract_dwi_shell from scilpy.gradients.bvec_bval_tools import (normalize_bvecs, - is_normalized_bvecs) + is_normalized_bvecs, + check_b0_threshold) bshapes = {0: "STE", 1: "LTE", -0.5: "PTE", 0.5: "CTE"} @@ -50,7 +51,7 @@ def convert_bdelta_to_bshape(b_deltas): def generate_btensor_input(in_dwis, in_bvals, in_bvecs, in_bdeltas, - do_pa_signals=False, tol=20): + do_pa_signals=False, tol=20, skip_b0_check=False): """Generate b-tensor input from an ensemble of data, bvals and bvecs files. This generated input is mandatory for all scripts using b-tensor encoding data. Also generate the powder-averaged (PA) data if set. @@ -74,6 +75,10 @@ def generate_btensor_input(in_dwis, in_bvals, in_bvecs, in_bdeltas, each bvals. tol : int tolerance gap for b-values clustering. Defaults to 20 + skip_b0_check: bool + (See full explanation in io.utils.add_skip_b0_check_arg.) If true, + script will continue even if no b-values are found below the tolerance + (no b0s found). Returns ------- @@ -108,6 +113,8 @@ def generate_btensor_input(in_dwis, in_bvals, in_bvecs, in_bdeltas, if inputf: # verifies if the input file exists vol = nib.load(inputf) bvals, bvecs = read_bvals_bvecs(bvalsf, bvecsf) + _ = check_b0_threshold(bvals.min(), b0_threshold=tol, + skip_b0_check=skip_b0_check) if np.sum([bvals > tol]) != 0: bvals = np.round(bvals) if not is_normalized_bvecs(bvecs): diff --git a/scilpy/reconst/frf.py b/scilpy/reconst/frf.py index e3ed4b877..caff532ec 100644 --- a/scilpy/reconst/frf.py +++ b/scilpy/reconst/frf.py @@ -11,10 +11,11 @@ from scilpy.gradients.bvec_bval_tools import (check_b0_threshold, is_normalized_bvecs, - normalize_bvecs) + normalize_bvecs, + DEFAULT_B0_THRESHOLD) -def compute_ssst_frf(data, bvals, bvecs, b0_threshold, +def compute_ssst_frf(data, bvals, bvecs, b0_threshold=DEFAULT_B0_THRESHOLD, mask=None, mask_wm=None, fa_thresh=0.7, min_fa_thresh=0.5, min_nvox=300, roi_radii=10, roi_center=None): """Compute a single-shell (under b=1500), single-tissue single Fiber @@ -151,8 +152,8 @@ def compute_msmt_frf(data, bvals, bvecs, btens=None, data_dti=None, bvecs : ndarray 2D bvecs array with shape (N, 3) btens: ndarray 1D - btens array with shape (N,), describing the btensor shape of every - pair of bval/bvec. + btens array with shape (N,), describing the btensor shape of every + pair of bval/bvec. data_dti: ndarray 4D Input diffusion volume with shape (X, Y, Z, M), where M is the number of DTI directions. @@ -227,10 +228,10 @@ def compute_msmt_frf(data, bvals, bvecs, btens=None, data_dti=None, logging.warning('Your b-vectors do not seem normalized...') bvecs = normalize_bvecs(bvecs) - # toDo. Using a fake b0_threshold here because currently, the - # gtab.b0s_mask is not used. Below, we use the tolerance only in dipy. - # An issue has been added in dipy too. - gtab = gradient_table(bvals, bvecs, btens=btens, b0_threshold=bvals.min()) + # Note. Using the tolerance here because currently, the gtab.b0s_mask is + # not used. Below, we use the tolerance only (in dipy). + # An issue has been added in dipy too. + gtab = gradient_table(bvals, bvecs, btens=btens, b0_threshold=tol) if data_dti is None and bvals_dti is None and bvecs_dti is None: logging.warning( diff --git a/scilpy/reconst/sh.py b/scilpy/reconst/sh.py index ed466cb87..2f8e5b248 100644 --- a/scilpy/reconst/sh.py +++ b/scilpy/reconst/sh.py @@ -12,11 +12,13 @@ from scilpy.gradients.bvec_bval_tools import (identify_shells, is_normalized_bvecs, - normalize_bvecs) + normalize_bvecs, + DEFAULT_B0_THRESHOLD) from scilpy.dwi.operations import compute_dwi_attenuation -def compute_sh_coefficients(dwi, gradient_table, b0_threshold, sh_order=4, +def compute_sh_coefficients(dwi, gradient_table, + b0_threshold=DEFAULT_B0_THRESHOLD, sh_order=4, basis_type='descoteaux07', smooth=0.006, use_attenuation=False, mask=None, sphere=None): """Fit a diffusion signal with spherical harmonics coefficients. diff --git a/scripts/scil_btensor_metrics.py b/scripts/scil_btensor_metrics.py index 252f9b837..5935bce34 100755 --- a/scripts/scil_btensor_metrics.py +++ b/scripts/scil_btensor_metrics.py @@ -51,7 +51,7 @@ from scilpy.io.image import get_data_as_mask from scilpy.io.utils import (add_overwrite_arg, assert_inputs_exist, assert_outputs_exist, add_processes_arg, - add_verbose_arg) + add_verbose_arg, add_skip_b0_check_arg) from scilpy.reconst.divide import fit_gamma, gamma_fit2metrics @@ -81,6 +81,8 @@ def _build_arg_parser(): '--tolerance', type=int, default=20, help='The tolerated gap between the b-values to ' 'extract\nand the current b-value. [%(default)s]') + add_skip_b0_check_arg(p, will_overwrite_with_min=False, + b0_tol_name='--tolerance') p.add_argument( '--fit_iters', type=int, default=1, help='The number of time the gamma fit will be done [%(default)s]') @@ -182,14 +184,14 @@ def main(): # 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 - # change generate_btensor_input. Not doing any verification on the - # bvals. Typically, we would use check_b0_threshold(bvals.min(), args) + # change generate_btensor_input. data, gtab_infos = generate_btensor_input(args.in_dwis, args.in_bvals, args.in_bvecs, args.in_bdeltas, do_pa_signals=True, - tol=args.tolerance) + tol=args.tolerance, + skip_b0_check=args.skip_b0_check) gtab_infos[0] *= 1e6 # getting bvalues to SI units diff --git a/scripts/scil_fodf_memsmt.py b/scripts/scil_fodf_memsmt.py index d1017d8e7..405672a45 100755 --- a/scripts/scil_fodf_memsmt.py +++ b/scripts/scil_fodf_memsmt.py @@ -51,7 +51,8 @@ from scilpy.io.image import get_data_as_mask from scilpy.io.utils import (add_overwrite_arg, add_processes_arg, add_sh_basis_args, add_verbose_arg, - assert_inputs_exist, assert_outputs_exist) + assert_inputs_exist, assert_outputs_exist, + add_skip_b0_check_arg) from scilpy.reconst.fodf import fit_from_model from scilpy.reconst.sh import convert_sh_basis @@ -92,6 +93,8 @@ def _build_arg_parser(): '--tolerance', type=int, default=20, help='The tolerated gap between the b-values to ' 'extract\nand the current b-value. [%(default)s]') + add_skip_b0_check_arg(p, will_overwrite_with_min=False, + b0_tol_name='--tolerance') add_sh_basis_args(p) add_processes_arg(p) @@ -164,11 +167,9 @@ def main(): # for the b0s. Using the tolerance. To change this, we would have to # change generate_btensor_input. Not doing any verification on the # bvals. Typically, we would use check_b0_threshold(bvals.min(), args) - gtab, data, ubvals, ubdeltas = generate_btensor_input(args.in_dwis, - args.in_bvals, - args.in_bvecs, - args.in_bdeltas, - tol=args.tolerance) + gtab, data, ubvals, ubdeltas = generate_btensor_input( + args.in_dwis, args.in_bvals, args.in_bvecs, args.in_bdeltas, + tol=args.tolerance, skip_b0_check=args.skip_b0_check) # Checking mask if args.mask is None: diff --git a/scripts/scil_frf_memsmt.py b/scripts/scil_frf_memsmt.py index f217f5ce6..36d20d3b9 100755 --- a/scripts/scil_frf_memsmt.py +++ b/scripts/scil_frf_memsmt.py @@ -52,7 +52,7 @@ from scilpy.io.image import get_data_as_mask from scilpy.io.utils import (add_overwrite_arg, add_verbose_arg, assert_inputs_exist, assert_outputs_exist, - assert_roi_radii_format) + assert_roi_radii_format, add_skip_b0_check_arg) from scilpy.reconst.frf import compute_msmt_frf @@ -136,6 +136,8 @@ def _build_arg_parser(): type=int, default=20, help='The tolerated gap between the b-values to ' 'extract and the current b-value. [%(default)s]') + add_skip_b0_check_arg(p, will_overwrite_with_min=False, + b0_tol_name='--tolerance') p.add_argument('--dti_bval_limit', type=int, default=1200, help='The highest b-value taken for the DTI model. ' @@ -200,11 +202,9 @@ def main(): # for the b0s. Using the tolerance. To change this, we would have to # change generate_btensor_input. Not doing any verification on the # bvals. Typically, we would use check_b0_threshold(bvals.min(), args) - gtab, data, ubvals, ubdeltas = generate_btensor_input(args.in_dwis, - args.in_bvals, - args.in_bvecs, - args.in_bdeltas, - tol=args.tolerance) + gtab, data, ubvals, ubdeltas = generate_btensor_input( + args.in_dwis, args.in_bvals, args.in_bvecs, args.in_bdeltas, + tol=args.tolerance, skip_b0_check=args.skip_b0_check) if not np.all(ubvals <= args.dti_bval_limit): if np.sum(ubdeltas == 1) > 0: From 85e0d7f2bbee08aa5675825f04f75ead8cd4e8ed Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Mon, 29 Jan 2024 10:35:52 -0500 Subject: [PATCH 20/26] Answer Phil: same explanation for tolerance everywhere --- scilpy/io/utils.py | 46 ++++++++++++++++++++++++++------- scripts/scil_btensor_metrics.py | 8 +++--- scripts/scil_dki_metrics.py | 11 ++------ scripts/scil_fodf_memsmt.py | 7 ++--- scripts/scil_fodf_msmt.py | 11 ++------ 5 files changed, 45 insertions(+), 38 deletions(-) diff --git a/scilpy/io/utils.py b/scilpy/io/utils.py index c5f3a17b4..4d3b30cf1 100644 --- a/scilpy/io/utils.py +++ b/scilpy/io/utils.py @@ -215,14 +215,47 @@ def add_overwrite_arg(parser): help='Force overwriting of the output files.') -def add_skip_b0_check_arg(parser, will_overwrite_with_min: bool, +def add_tolerance_arg(parser): + parser.add_argument( + '--tolerance', type=int, default=20, + help='The tolerated gap between the b-values to extract and the ' + 'current b-value.\n' + 'We would expect to find at least one b-value in the range ' + '[0, tolerance], acting as a b0.\n' + 'To skip this check, use --skip_b0_check.\n' + '[Default: %(default)s]') + + +def add_b0_thresh_arg(parser): + parser.add_argument( + '--b0_threshold', type=float, default=DEFAULT_B0_THRESHOLD, + help='Threshold under which b-values are considered to be b0s.\n' + 'We would expect to find at least one b-value in the range ' + '[0, b0_threshold], acting as a b0.\n' + 'To skip this check, use --skip_b0_check.\n' + 'Default if not set is {}.'.format(DEFAULT_B0_THRESHOLD)) + + +def add_skip_b0_check_arg(parser, will_overwrite_with_min, b0_tol_name='--b0_threshold'): + """ + Parameters + ---------- + parser: argparse.ArgumentParser object + Parser. + will_overwrite_with_min: bool + If true, the help message will explain that b0_threshold could be + overwritten. + b0_tol_name: str + Name of the argparse parameter acting as b0_threshold. Should probably + be either '--b0_threshold' or '--tolerance'. + """ msg = ('By default, we supervise that at least one b0 exists in your ' 'data\n' '(i.e. b-values below the default {}). Use this option to ' - 'allow continuing \n' + 'allow continuing \n' 'even if the minimum b-value is suspiciously high.\n' - .format(b0_tol_name)) + .format(b0_tol_name)) if will_overwrite_with_min: msg += ('If no b-value is found below the threshold, the script will ' 'continue \nwith your minimal b-value as new {}.\n' @@ -233,13 +266,6 @@ def add_skip_b0_check_arg(parser, will_overwrite_with_min: bool, '--skip_b0_check', action='store_true', help=msg) -def add_b0_thresh_arg(parser): - parser.add_argument( - '--b0_threshold', type=float, default=DEFAULT_B0_THRESHOLD, - help='Threshold under which b-values are considered to be b0s.\n' - 'Default if not set is {}.'.format(DEFAULT_B0_THRESHOLD)) - - def add_verbose_arg(parser): parser.add_argument('-v', default="WARNING", const='INFO', nargs='?', choices=['DEBUG', 'INFO', 'WARNING'], dest='verbose', diff --git a/scripts/scil_btensor_metrics.py b/scripts/scil_btensor_metrics.py index 5935bce34..3441dd11c 100755 --- a/scripts/scil_btensor_metrics.py +++ b/scripts/scil_btensor_metrics.py @@ -51,7 +51,8 @@ from scilpy.io.image import get_data_as_mask 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) + add_verbose_arg, add_skip_b0_check_arg, + add_tolerance_arg) from scilpy.reconst.divide import fit_gamma, gamma_fit2metrics @@ -77,10 +78,7 @@ def _build_arg_parser(): '--mask', help='Path to a binary mask. Only the data inside the ' 'mask will be used for computations and reconstruction.') - p.add_argument( - '--tolerance', type=int, default=20, - help='The tolerated gap between the b-values to ' - 'extract\nand the current b-value. [%(default)s]') + add_tolerance_arg(p) add_skip_b0_check_arg(p, will_overwrite_with_min=False, b0_tol_name='--tolerance') p.add_argument( diff --git a/scripts/scil_dki_metrics.py b/scripts/scil_dki_metrics.py index 3e80c913e..315a74978 100755 --- a/scripts/scil_dki_metrics.py +++ b/scripts/scil_dki_metrics.py @@ -59,7 +59,7 @@ from scilpy.io.image import get_data_as_mask from scilpy.io.utils import (add_overwrite_arg, add_skip_b0_check_arg, add_verbose_arg, assert_inputs_exist, - assert_outputs_exist, ) + assert_outputs_exist, add_tolerance_arg, ) from scilpy.gradients.bvec_bval_tools import (check_b0_threshold, is_normalized_bvecs, identify_shells, @@ -82,14 +82,7 @@ def _build_arg_parser(): 'Only data inside the mask will be used for ' 'computations and reconstruction.\n[Default: None]') - p.add_argument('--tolerance', '-t', - metavar='INT', type=int, default=20, - help='The tolerated distance between the b-values to ' - 'extract\nand the actual b-values.\n' - 'We would expect to find at least one b-value in the range ' - '[0, tolerance], acting as a b0.\n' - 'To skip this check, use --skip_b0_validation.\n' - '[Default: %(default)s]') + add_tolerance_arg(p) add_skip_b0_check_arg(p, will_overwrite_with_min=False, b0_tol_name='--tolerance') diff --git a/scripts/scil_fodf_memsmt.py b/scripts/scil_fodf_memsmt.py index 405672a45..db28a2796 100755 --- a/scripts/scil_fodf_memsmt.py +++ b/scripts/scil_fodf_memsmt.py @@ -52,7 +52,7 @@ from scilpy.io.utils import (add_overwrite_arg, add_processes_arg, add_sh_basis_args, add_verbose_arg, assert_inputs_exist, assert_outputs_exist, - add_skip_b0_check_arg) + add_skip_b0_check_arg, add_tolerance_arg) from scilpy.reconst.fodf import fit_from_model from scilpy.reconst.sh import convert_sh_basis @@ -89,10 +89,7 @@ def _build_arg_parser(): '--mask', help='Path to a binary mask. Only the data inside the ' 'mask will be used for computations and reconstruction.') - p.add_argument( - '--tolerance', type=int, default=20, - help='The tolerated gap between the b-values to ' - 'extract\nand the current b-value. [%(default)s]') + add_tolerance_arg(p) add_skip_b0_check_arg(p, will_overwrite_with_min=False, b0_tol_name='--tolerance') diff --git a/scripts/scil_fodf_msmt.py b/scripts/scil_fodf_msmt.py index 36db34673..a361b5619 100755 --- a/scripts/scil_fodf_msmt.py +++ b/scripts/scil_fodf_msmt.py @@ -37,7 +37,7 @@ add_sh_basis_args, add_skip_b0_check_arg, assert_inputs_exist, assert_outputs_exist, - add_verbose_arg) + add_verbose_arg, add_tolerance_arg) from scilpy.reconst.fodf import fit_from_model from scilpy.reconst.sh import convert_sh_basis @@ -67,14 +67,7 @@ def _build_arg_parser(): '--mask', metavar='', help='Path to a binary mask. Only the data inside the ' 'mask will be used for computations and reconstruction.') - p.add_argument( - '--tolerance', type=int, default=20, - help='The tolerated gap between the b-values to extract and the ' - 'current b-value.\n' - 'We would expect to find at least one b-value in the range ' - '[0, tolerance], acting as a b0.\n' - 'To skip this check, use --skip_b0_check.\n' - '[Default: %(default)s]') + add_tolerance_arg(p) add_skip_b0_check_arg(p, will_overwrite_with_min=False, b0_tol_name='--tolerance') add_sh_basis_args(p) From a1c1b2340f826c3f4b4e867a6cdeac9eb701f434 Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Mon, 29 Jan 2024 10:38:40 -0500 Subject: [PATCH 21/26] pep8 --- scilpy/reconst/frf.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scilpy/reconst/frf.py b/scilpy/reconst/frf.py index caff532ec..c14848b7d 100644 --- a/scilpy/reconst/frf.py +++ b/scilpy/reconst/frf.py @@ -153,14 +153,16 @@ def compute_msmt_frf(data, bvals, bvecs, btens=None, data_dti=None, 2D bvecs array with shape (N, 3) btens: ndarray 1D btens array with shape (N,), describing the btensor shape of every - pair of bval/bvec. + pair of bval/bvec. data_dti: ndarray 4D Input diffusion volume with shape (X, Y, Z, M), where M is the number of DTI directions. bvals_dti: ndarray 1D bvals array with shape (M,) - bvecs_dti: ? - btens_dti: ? + bvecs_dti: ndarray 2D + bvals array with shape (M, 3) + btens_dti: ndarray 1D + bvals array with shape (M,) mask : ndarray, optional 3D mask with shape (X,Y,Z) Binary mask. Only the data inside the mask will be used for From d9f5e45abca9405b8baa7e8d0bb5a3c6af75b756 Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Mon, 29 Jan 2024 11:29:16 -0500 Subject: [PATCH 22/26] Added tests wrong b0 --- scilpy/gradients/bvec_bval_tools.py | 13 +++++----- scilpy/io/btensor.py | 2 +- scilpy/io/utils.py | 26 +++++++++---------- scripts/scil_dki_metrics.py | 2 +- scripts/scil_dti_metrics.py | 2 +- scripts/scil_dwi_detect_volume_outliers.py | 2 +- scripts/scil_dwi_extract_b0.py | 6 ++--- scripts/scil_dwi_to_sh.py | 2 +- scripts/scil_fodf_msmt.py | 2 +- scripts/scil_fodf_ssst.py | 4 +-- scripts/scil_frf_memsmt.py | 8 +++--- scripts/scil_frf_msmt.py | 2 +- scripts/scil_frf_ssst.py | 20 +++++++------- scripts/scil_qball_metrics.py | 2 +- scripts/scil_sh_to_sf.py | 3 ++- scripts/tests/test_dti_metrics.py | 5 ++++ .../tests/test_dwi_detect_volume_outliers.py | 5 ++++ scripts/tests/test_dwi_extract_b0.py | 5 ++++ scripts/tests/test_dwi_to_sh.py | 6 +++++ scripts/tests/test_fodf_ssst.py | 7 +++++ scripts/tests/test_frf_msmt.py | 17 +++++++++--- scripts/tests/test_frf_ssst.py | 9 +++++++ scripts/tests/test_qball_metrics.py | 6 +++++ scripts/tests/test_sh_to_sf.py | 9 +++++++ 24 files changed, 112 insertions(+), 53 deletions(-) diff --git a/scilpy/gradients/bvec_bval_tools.py b/scilpy/gradients/bvec_bval_tools.py index 34839f1ef..ad254362c 100644 --- a/scilpy/gradients/bvec_bval_tools.py +++ b/scilpy/gradients/bvec_bval_tools.py @@ -55,7 +55,7 @@ def normalize_bvecs(bvecs): return bvecs -def check_b0_threshold(min_bval, b0_threshold, skip_b0_check): +def check_b0_threshold(min_bval, b0_thr, skip_b0_check): """ Check if the minimal bvalue is under the threshold. If not, raise an error to ask user to update the b0_thr. @@ -67,7 +67,7 @@ def check_b0_threshold(min_bval, b0_threshold, skip_b0_check): ---------- min_bval : float Minimum bvalue. - b0_threshold: float + b0_thr: float Maximum bvalue considered as a b0. skip_b0_check: bool If True, and no b0 is found, only print a warning, do not raise @@ -76,10 +76,9 @@ def check_b0_threshold(min_bval, b0_threshold, skip_b0_check): Raises ------ ValueError - If the minimal bvalue is over the threshold (and skip_b0_validation is + If the minimal bvalue is over the threshold (and skip_b0_check is False). """ - b0_thr = args.b0_threshold if b0_thr > DEFAULT_B0_THRESHOLD: logging.warning( 'Your defined b0 threshold is {}. This is suspicious. We ' @@ -93,11 +92,11 @@ def check_b0_threshold(min_bval, b0_threshold, skip_b0_check): .format(min_bval)) if min_bval > b0_thr: - if args.skip_b0_validation: + if skip_b0_check: logging.warning("GOT {} > {}".format(min_bval, b0_thr)) logging.warning( 'Your minimal bvalue ({}), is above the threshold ({})\n' - 'Since --skip_b0_validation was specified, the script will ' + 'Since --skip_b0_check was specified, the script will ' 'proceed with a b0 threshold of {}.' .format(min_bval, b0_thr, min_bval)) return min_bval @@ -107,7 +106,7 @@ def check_b0_threshold(min_bval, b0_threshold, skip_b0_check): 'No b0 volumes can be found.\n' 'Please check your data to ensure everything is correct.\n' 'You may also increase the threshold or use ' - '--skip_b0_validation' + '--skip_b0_check' .format(min_bval, b0_thr)) return b0_thr diff --git a/scilpy/io/btensor.py b/scilpy/io/btensor.py index 829144d85..f8c120d5a 100644 --- a/scilpy/io/btensor.py +++ b/scilpy/io/btensor.py @@ -113,7 +113,7 @@ def generate_btensor_input(in_dwis, in_bvals, in_bvecs, in_bdeltas, if inputf: # verifies if the input file exists vol = nib.load(inputf) bvals, bvecs = read_bvals_bvecs(bvalsf, bvecsf) - _ = check_b0_threshold(bvals.min(), b0_threshold=tol, + _ = check_b0_threshold(bvals.min(), b0_thr=tol, skip_b0_check=skip_b0_check) if np.sum([bvals > tol]) != 0: bvals = np.round(bvals) diff --git a/scilpy/io/utils.py b/scilpy/io/utils.py index 4d3b30cf1..f46a46314 100644 --- a/scilpy/io/utils.py +++ b/scilpy/io/utils.py @@ -217,23 +217,24 @@ def add_overwrite_arg(parser): def add_tolerance_arg(parser): parser.add_argument( - '--tolerance', type=int, default=20, + '--tolerance', type=int, default=20, metavar='tol', help='The tolerated gap between the b-values to extract and the ' 'current b-value.\n' - 'We would expect to find at least one b-value in the range ' - '[0, tolerance], acting as a b0.\n' - 'To skip this check, use --skip_b0_check.\n' - '[Default: %(default)s]') + '[Default: %(default)s]\n' + '* Note. We would expect to find at least one b-value in the \n' + ' range [0, tolerance]. To skip this check, use ' + '--skip_b0_check.') def add_b0_thresh_arg(parser): parser.add_argument( '--b0_threshold', type=float, default=DEFAULT_B0_THRESHOLD, + metavar='thr', help='Threshold under which b-values are considered to be b0s.\n' - 'We would expect to find at least one b-value in the range ' - '[0, b0_threshold], acting as a b0.\n' - 'To skip this check, use --skip_b0_check.\n' - 'Default if not set is {}.'.format(DEFAULT_B0_THRESHOLD)) + '[Default: %(default)s] \n' + '* Note. We would expect to find at least one b-value in the \n' + ' range [0, b0_threshold]. To skip this check, use ' + '--skip_b0_check.') def add_skip_b0_check_arg(parser, will_overwrite_with_min, @@ -252,10 +253,9 @@ def add_skip_b0_check_arg(parser, will_overwrite_with_min, """ msg = ('By default, we supervise that at least one b0 exists in your ' 'data\n' - '(i.e. b-values below the default {}). Use this option to ' - 'allow continuing \n' - 'even if the minimum b-value is suspiciously high.\n' - .format(b0_tol_name)) + '(i.e. b-values below the default {}). Use this option to \n' + 'allow continuing even if the minimum b-value is suspiciously ' + 'high.\n'.format(b0_tol_name)) if will_overwrite_with_min: msg += ('If no b-value is found below the threshold, the script will ' 'continue \nwith your minimal b-value as new {}.\n' diff --git a/scripts/scil_dki_metrics.py b/scripts/scil_dki_metrics.py index 315a74978..1fd454a7c 100755 --- a/scripts/scil_dki_metrics.py +++ b/scripts/scil_dki_metrics.py @@ -198,7 +198,7 @@ def main(): # https://github.com/dipy/dipy/issues/3015 # b0_threshold option in gradient_table probably unused, except below with # option dki_residual. - _ = check_b0_threshold(bvals.min(), b0_threshold=args.tolerance, + _ = check_b0_threshold(bvals.min(), b0_thr=args.tolerance, skip_b0_check=args.skip_b0_check) gtab = gradient_table(bvals, bvecs, b0_threshold=args.tolerance) diff --git a/scripts/scil_dti_metrics.py b/scripts/scil_dti_metrics.py index 390ed41b3..eecafb288 100755 --- a/scripts/scil_dti_metrics.py +++ b/scripts/scil_dti_metrics.py @@ -274,7 +274,7 @@ def main(): # 2) But we do use this information below, with options p_i_signal, # pulsation and residual. args.b0_threshold = check_b0_threshold(bvals.min(), - b0_threshold=args.b0_threshold, + b0_thr=args.b0_threshold, skip_b0_check=args.skip_b0_check) gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_threshold) diff --git a/scripts/scil_dwi_detect_volume_outliers.py b/scripts/scil_dwi_detect_volume_outliers.py index 60109f53d..19b6256c9 100755 --- a/scripts/scil_dwi_detect_volume_outliers.py +++ b/scripts/scil_dwi_detect_volume_outliers.py @@ -65,7 +65,7 @@ def main(): data = nib.load(args.in_dwi).get_fdata() args.b0_threshold = check_b0_threshold(bvals.min(), - b0_threshold=args.b0_threshold, + b0_thr=args.b0_threshold, skip_b0_check=args.skip_b0_check) bvecs = normalize_bvecs(bvecs) diff --git a/scripts/scil_dwi_extract_b0.py b/scripts/scil_dwi_extract_b0.py index 1a46a777c..754dd273d 100755 --- a/scripts/scil_dwi_extract_b0.py +++ b/scripts/scil_dwi_extract_b0.py @@ -61,8 +61,8 @@ def _build_arg_parser(): p.add_argument('--single-image', action='store_true', help='If output b0 volume has multiple time points, only ' - 'outputs a single image instead of a numbered series ' - 'of images.') + 'outputs a single \nimage instead of a numbered ' + 'series of images.') add_b0_thresh_arg(p) add_skip_b0_check_arg(p, will_overwrite_with_min=True) @@ -96,7 +96,7 @@ def main(): bvals, bvecs = read_bvals_bvecs(args.in_bval, args.in_bvec) args.b0_threshold = check_b0_threshold(bvals.min(), - b0_threshold=args.b0_threshold, + b0_thr=args.b0_threshold, skip_b0_check=args.skip_b0_check) gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_threshold) b0_idx = np.where(gtab.b0s_mask)[0] diff --git a/scripts/scil_dwi_to_sh.py b/scripts/scil_dwi_to_sh.py index 51deb606b..94a9b7379 100755 --- a/scripts/scil_dwi_to_sh.py +++ b/scripts/scil_dwi_to_sh.py @@ -72,7 +72,7 @@ def main(): # gtab.b0s_mask in used in compute_sh_coefficients to get the b0s. args.b0_threshold = check_b0_threshold(bvals.min(), - b0_threshold=args.b0_threshold, + b0_thr=args.b0_threshold, skip_b0_check=args.skip_b0_check) gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_threshold) diff --git a/scripts/scil_fodf_msmt.py b/scripts/scil_fodf_msmt.py index a361b5619..12c8dfdd7 100755 --- a/scripts/scil_fodf_msmt.py +++ b/scripts/scil_fodf_msmt.py @@ -161,7 +161,7 @@ def main(): # ask them to clarify the usage of gtab.b0s_mask. See here: # https://github.com/dipy/dipy/issues/3015 # b0_threshold option in gradient_table probably unused. - _ = check_b0_threshold(bvals.min(), b0_threshold=args.tolerance, + _ = check_b0_threshold(bvals.min(), b0_thr=args.tolerance, skip_b0_check=args.skip_b0_check) gtab = gradient_table(bvals, bvecs, b0_threshold=args.tolerance) diff --git a/scripts/scil_fodf_ssst.py b/scripts/scil_fodf_ssst.py index 96c260276..c54037dd1 100755 --- a/scripts/scil_fodf_ssst.py +++ b/scripts/scil_fodf_ssst.py @@ -52,7 +52,7 @@ def _build_arg_parser(): p.add_argument( '--mask', metavar='', help='Path to a binary mask. Only the data inside the mask will be ' - 'used for computations and reconstruction.') + 'used \nfor computations and reconstruction.') add_b0_thresh_arg(p) add_skip_b0_check_arg(p, will_overwrite_with_min=True) @@ -104,7 +104,7 @@ def main(): # gtab.b0s_mask is used in dipy's csdeconv class. args.b0_threshold = check_b0_threshold(bvals.min(), - b0_threshold=args.b0_threshold, + b0_thr=args.b0_threshold, skip_b0_check=args.skip_b0_check) gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_threshold) diff --git a/scripts/scil_frf_memsmt.py b/scripts/scil_frf_memsmt.py index 36d20d3b9..2c4be3b61 100755 --- a/scripts/scil_frf_memsmt.py +++ b/scripts/scil_frf_memsmt.py @@ -52,7 +52,8 @@ from scilpy.io.image import get_data_as_mask from scilpy.io.utils import (add_overwrite_arg, add_verbose_arg, assert_inputs_exist, assert_outputs_exist, - assert_roi_radii_format, add_skip_b0_check_arg) + assert_roi_radii_format, add_skip_b0_check_arg, + add_tolerance_arg) from scilpy.reconst.frf import compute_msmt_frf @@ -132,10 +133,7 @@ def _build_arg_parser(): help='Minimal number of voxels needed for each tissue masks' ' in order to \nproceed to frf estimation. ' '[%(default)s]') - p.add_argument('--tolerance', - type=int, default=20, - help='The tolerated gap between the b-values to ' - 'extract and the current b-value. [%(default)s]') + add_tolerance_arg(p) add_skip_b0_check_arg(p, will_overwrite_with_min=False, b0_tol_name='--tolerance') p.add_argument('--dti_bval_limit', diff --git a/scripts/scil_frf_msmt.py b/scripts/scil_frf_msmt.py index 0158bb69b..2d26a566b 100755 --- a/scripts/scil_frf_msmt.py +++ b/scripts/scil_frf_msmt.py @@ -177,7 +177,7 @@ def main(): # Note. This script does not currently allow using a separate b0_threshold # for the b0s. Using the tolerance. To fix this, we would need to change # the unique_bvals_tolerance and extract_dwi_shell methods. - _ = check_b0_threshold(bvals.min(), b0_threshold=args.tolerance, + _ = check_b0_threshold(bvals.min(), b0_thr=args.tolerance, skip_b0_check=args.skip_b0_check) list_bvals = unique_bvals_tolerance(bvals, tol=args.tolerance) if not np.all(list_bvals <= dti_lim): diff --git a/scripts/scil_frf_ssst.py b/scripts/scil_frf_ssst.py index 942069099..44f1c62ad 100755 --- a/scripts/scil_frf_ssst.py +++ b/scripts/scil_frf_ssst.py @@ -29,7 +29,7 @@ def _build_arg_parser(): p = argparse.ArgumentParser( description=__doc__, - formatter_class=argparse.RawDescriptionHelpFormatter, + formatter_class=argparse.RawTextHelpFormatter, epilog="References: [1] Tournier et al. NeuroImage 2007") p.add_argument('in_dwi', @@ -44,28 +44,28 @@ def _build_arg_parser(): p.add_argument('--mask', help='Path to a binary mask. Only the data inside the ' - 'mask will be used for computations and ' - 'reconstruction. Useful if no white matter mask ' + 'mask will be used \nfor computations and ' + 'reconstruction. Useful if no white matter mask \n' 'is available.') p.add_argument('--mask_wm', help='Path to a binary white matter mask. Only the data ' - 'inside this mask and above the threshold defined ' - 'by --fa will be used to estimate the fiber response ' - 'function.') + 'inside this mask \nand above the threshold defined ' + 'by --fa will be used to estimate the \nfiber ' + 'response function.') p.add_argument('--fa', dest='fa_thresh', default=0.7, type=float, help='If supplied, use this threshold as the initial ' - 'threshold to select single fiber voxels. ' + 'threshold to select \nsingle fiber voxels. ' '[%(default)s]') p.add_argument('--min_fa', dest='min_fa_thresh', default=0.5, type=float, help='If supplied, this is the minimal value that will be ' - 'tried when looking for single fiber ' + 'tried when looking \nfor single fiber ' 'voxels. [%(default)s]') p.add_argument('--min_nvox', default=300, type=int, help='Minimal number of voxels needing to be identified ' - 'as single fiber voxels in the automatic ' + 'as single fiber voxels \nin the automatic ' 'estimation. [%(default)s]') p.add_argument('--roi_radii', @@ -104,7 +104,7 @@ def main(): bvals, bvecs = read_bvals_bvecs(args.in_bval, args.in_bvec) args.b0_threshold = check_b0_threshold(bvals.min(), - b0_threshold=args.b0_threshold, + b0_thr=args.b0_threshold, skip_b0_check=args.skip_b0_check) mask = None if args.mask: diff --git a/scripts/scil_qball_metrics.py b/scripts/scil_qball_metrics.py index e02394d18..f9d77101c 100755 --- a/scripts/scil_qball_metrics.py +++ b/scripts/scil_qball_metrics.py @@ -135,7 +135,7 @@ def main(): # Usage of gtab.b0s_mask in dipy's models is not very well documented, but # we can see that it is indeed used. args.b0_threshold = check_b0_threshold(bvals.min(), - b0_threshold=args.b0_threshold, + b0_thr=args.b0_threshold, skip_b0_check=args.skip_b0_check) gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_threshold) diff --git a/scripts/scil_sh_to_sf.py b/scripts/scil_sh_to_sf.py index 5a9af29f3..b1f33460b 100755 --- a/scripts/scil_sh_to_sf.py +++ b/scripts/scil_sh_to_sf.py @@ -79,6 +79,7 @@ def _build_arg_parser(): # explanation in the text. p.add_argument( '--b0_threshold', type=float, default=DEFAULT_B0_THRESHOLD, + metavar='thr', help='Threshold under which b-values are considered to be b0s.\n' 'Default if not set is {}.\n' 'This value is used with options --in_bvec or --in_bval only.' @@ -124,7 +125,7 @@ def main(): elif args.in_bval: bvals, _ = read_bvals_bvecs(args.in_bval, None) args.b0_threshold = check_b0_threshold(bvals.min(), - b0_threshold=args.b0_threshold, + b0_thr=args.b0_threshold, skip_b0_check=args.skip_b0_check) # Load SH vol_sh = nib.load(args.in_sh) diff --git a/scripts/tests/test_dti_metrics.py b/scripts/tests/test_dti_metrics.py index bf8c1cb1d..093f1bf9e 100644 --- a/scripts/tests/test_dti_metrics.py +++ b/scripts/tests/test_dti_metrics.py @@ -37,3 +37,8 @@ def test_execution_processing(script_runner): '--residual', 'residual.nii.gz', '--mask', mask_uint8) assert ret.success + + ret = script_runner.run('scil_dti_metrics.py', in_dwi, + in_bval, in_bvec, '--not_all', + '--fa', 'fa.nii.gz', '--b0_threshold', '1', '-f') + assert not ret.success diff --git a/scripts/tests/test_dwi_detect_volume_outliers.py b/scripts/tests/test_dwi_detect_volume_outliers.py index 4f86d7761..2eec5a142 100644 --- a/scripts/tests/test_dwi_detect_volume_outliers.py +++ b/scripts/tests/test_dwi_detect_volume_outliers.py @@ -27,3 +27,8 @@ def test_execution(script_runner): ret = script_runner.run('scil_dwi_detect_volume_outliers.py', in_dwi, in_bval, in_bvec, '-v') assert ret.success + + # Test wrong b0. Current minimal b-value is 5. + ret = script_runner.run('scil_dwi_detect_volume_outliers.py', in_dwi, + in_bval, in_bvec, '--b0_threshold', '1') + assert not ret.success diff --git a/scripts/tests/test_dwi_extract_b0.py b/scripts/tests/test_dwi_extract_b0.py index f51432112..68706fc8f 100644 --- a/scripts/tests/test_dwi_extract_b0.py +++ b/scripts/tests/test_dwi_extract_b0.py @@ -27,3 +27,8 @@ def test_execution_processing(script_runner): ret = script_runner.run('scil_dwi_extract_b0.py', in_dwi, in_bval, in_bvec, 'b0_mean.nii.gz', '--mean', '--b0', '20') assert ret.success + + # Test wrong b0. Current minimal b-value is 5. + ret = script_runner.run('scil_dwi_extract_b0.py', in_dwi, in_bval, in_bvec, + 'b0_mean.nii.gz', '--mean', '--b0', '1', '-f') + assert not ret.success diff --git a/scripts/tests/test_dwi_to_sh.py b/scripts/tests/test_dwi_to_sh.py index f8a9ece92..7870631b0 100644 --- a/scripts/tests/test_dwi_to_sh.py +++ b/scripts/tests/test_dwi_to_sh.py @@ -27,3 +27,9 @@ def test_execution_processing(script_runner): ret = script_runner.run('scil_dwi_to_sh.py', in_dwi, in_bval, in_bvec, 'sh_1000.nii.gz') assert ret.success + + # Test wrong b0. Current minimal b-value is 5. + ret = script_runner.run('scil_dwi_to_sh.py', in_dwi, in_bval, + in_bvec, 'sh_1000.nii.gz', '--b0_threshold', '1', + '-f') + assert not ret.success diff --git a/scripts/tests/test_fodf_ssst.py b/scripts/tests/test_fodf_ssst.py index 36f2ab0f3..8a4c57b5b 100644 --- a/scripts/tests/test_fodf_ssst.py +++ b/scripts/tests/test_fodf_ssst.py @@ -30,3 +30,10 @@ def test_execution_processing(script_runner): in_bvec, in_frf, 'fodf.nii.gz', '--sh_order', '4', '--sh_basis', 'tournier07', '--processes', '1') assert ret.success + + # Test wrong b0. Current minimal b-value is 5. + ret = script_runner.run('scil_fodf_ssst.py', in_dwi, in_bval, + in_bvec, in_frf, 'fodf.nii.gz', '--sh_order', '4', + '--sh_basis', 'tournier07', '--processes', '1', + '--b0_threshold', '1', '-f') + assert not ret.success diff --git a/scripts/tests/test_frf_msmt.py b/scripts/tests/test_frf_msmt.py index a4bbcb740..818266ade 100644 --- a/scripts/tests/test_frf_msmt.py +++ b/scripts/tests/test_frf_msmt.py @@ -24,13 +24,20 @@ def test_roi_radii_shape_parameter(script_runner): in_bvec = os.path.join(get_home(), 'commit_amico', 'dwi.bvec') mask = os.path.join(get_home(), 'commit_amico', - 'mask.nii.gz') + 'mask.nii.gz') ret = script_runner.run('scil_frf_msmt.py', in_dwi, in_bval, in_bvec, 'wm_frf.txt', 'gm_frf.txt', 'csf_frf.txt', '--mask', mask, '--roi_center', '15', '15', '15', '-f') assert ret.success + # Test wrong tolerance, leading to no b0. Current minimal b-val is 5. + ret = script_runner.run('scil_frf_msmt.py', in_dwi, + in_bval, in_bvec, 'wm_frf.txt', 'gm_frf.txt', + 'csf_frf.txt', '--mask', mask, '--roi_center', + '15', '15', '15', '-f', '--tol', '1') + assert not ret.success + ret = script_runner.run('scil_frf_msmt.py', in_dwi, in_bval, in_bvec, 'wm_frf.txt', 'gm_frf.txt', 'csf_frf.txt', '--mask', mask, '--roi_center', @@ -38,7 +45,8 @@ def test_roi_radii_shape_parameter(script_runner): assert (not ret.success) -def test_roi_radii_shape_parameter(script_runner): + +def test_roi_radii_shape_parameter2(script_runner): os.chdir(os.path.expanduser(tmp_dir.name)) in_dwi = os.path.join(get_home(), 'commit_amico', 'dwi.nii.gz') @@ -47,7 +55,7 @@ def test_roi_radii_shape_parameter(script_runner): in_bvec = os.path.join(get_home(), 'commit_amico', 'dwi.bvec') mask = os.path.join(get_home(), 'commit_amico', - 'mask.nii.gz') + 'mask.nii.gz') ret = script_runner.run('scil_frf_msmt.py', in_dwi, in_bval, in_bvec, 'wm_frf.txt', 'gm_frf.txt', 'csf_frf.txt', '--mask', mask, '--roi_radii', @@ -67,6 +75,7 @@ def test_roi_radii_shape_parameter(script_runner): assert (not ret.success) + def test_execution_processing(script_runner): os.chdir(os.path.expanduser(tmp_dir.name)) in_dwi = os.path.join(get_home(), 'commit_amico', @@ -76,7 +85,7 @@ def test_execution_processing(script_runner): in_bvec = os.path.join(get_home(), 'commit_amico', 'dwi.bvec') mask = os.path.join(get_home(), 'commit_amico', - 'mask.nii.gz') + 'mask.nii.gz') ret = script_runner.run('scil_frf_msmt.py', in_dwi, in_bval, in_bvec, 'wm_frf.txt', 'gm_frf.txt', 'csf_frf.txt', '--mask', mask, '--min_nvox', '20', diff --git a/scripts/tests/test_frf_ssst.py b/scripts/tests/test_frf_ssst.py index b326d9641..40cea3878 100644 --- a/scripts/tests/test_frf_ssst.py +++ b/scripts/tests/test_frf_ssst.py @@ -35,6 +35,14 @@ def test_roi_center_parameter(script_runner): assert (not ret.success) + # Test wrong b0 threshold. Current minimal b-value is 5. + ret = script_runner.run('scil_frf_ssst.py', in_dwi, + in_bval, in_bvec, 'frf.txt', '--roi_center', + '15', '15', '15', '-f', '--b0_threshold', '1') + + assert not ret.success + + def test_roi_radii_shape_parameter(script_runner): os.chdir(os.path.expanduser(tmp_dir.name)) in_dwi = os.path.join(get_home(), 'processing', @@ -59,6 +67,7 @@ def test_roi_radii_shape_parameter(script_runner): assert (not ret.success) + def test_execution_processing(script_runner): os.chdir(os.path.expanduser(tmp_dir.name)) in_dwi = os.path.join(get_home(), 'processing', diff --git a/scripts/tests/test_qball_metrics.py b/scripts/tests/test_qball_metrics.py index 5ed85f142..97ae505f7 100644 --- a/scripts/tests/test_qball_metrics.py +++ b/scripts/tests/test_qball_metrics.py @@ -39,3 +39,9 @@ def test_execution_not_all(script_runner): ret = script_runner.run('scil_qball_metrics.py', in_dwi, in_bval, in_bvec, "--not_all", "--sh", "2.nii.gz") assert ret.success + + # Test wrong b0. Current minimal b-val is 5. + ret = script_runner.run('scil_qball_metrics.py', in_dwi, + in_bval, in_bvec, "--not_all", "--sh", "2.nii.gz", + '--b0_threshold', '1', '-f') + assert not ret.success diff --git a/scripts/tests/test_sh_to_sf.py b/scripts/tests/test_sh_to_sf.py index 1bc835bd6..89307b174 100755 --- a/scripts/tests/test_sh_to_sf.py +++ b/scripts/tests/test_sh_to_sf.py @@ -28,3 +28,12 @@ def test_execution_processing(script_runner): 'sf_724.bval', '--out_bvec', 'sf_724.bvec', '--sphere', 'symmetric724', '--dtype', 'float32') assert ret.success + + # Test wrong b0. Current minimal b-val is 5 + ret = script_runner.run('scil_sh_to_sf.py', in_sh, + 'sf_724.nii.gz', '--in_bval', + in_bval, '--in_b0', in_b0, '--out_bval', + 'sf_724.bval', '--out_bvec', 'sf_724.bvec', + '--sphere', 'symmetric724', '--dtype', 'float32', + '--b0_threshold', '1', '-f') + assert not ret.success From 4981bce7f989d107220313bb328ff7a12ea16900 Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Tue, 30 Jan 2024 10:24:10 -0500 Subject: [PATCH 23/26] sh_to_sf: Fixes + Test more exhaustively --- scripts/scil_sh_to_sf.py | 14 +++++++----- scripts/tests/test_sh_to_sf.py | 39 +++++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/scripts/scil_sh_to_sf.py b/scripts/scil_sh_to_sf.py index b1f33460b..ab88dd3c3 100755 --- a/scripts/scil_sh_to_sf.py +++ b/scripts/scil_sh_to_sf.py @@ -48,7 +48,8 @@ def _build_arg_parser(): help='Sphere used for the SH to SF projection. ') directions.add_argument('--in_bvec', help="Directions used for the SH to SF " - "projection.") + "projection. \nIf given, --in_bval must also be " + "provided.") p.add_argument('--dtype', default="float32", choices=["float32", "float64"], @@ -124,9 +125,12 @@ def main(): bvals, bvecs = read_bvals_bvecs(args.in_bval, args.in_bvec) elif args.in_bval: bvals, _ = read_bvals_bvecs(args.in_bval, None) - args.b0_threshold = check_b0_threshold(bvals.min(), - b0_thr=args.b0_threshold, - skip_b0_check=args.skip_b0_check) + + if bvals is not None: + args.b0_threshold = check_b0_threshold( + bvals.min(), b0_thr=args.b0_threshold, + skip_b0_check=args.skip_b0_check) + # Load SH vol_sh = nib.load(args.in_sh) data_sh = vol_sh.get_fdata(dtype=np.float32) @@ -135,7 +139,7 @@ def main(): if args.sphere: sphere = get_sphere(args.sphere) elif args.in_bvec: - gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_treshold) + gtab = gradient_table(bvals, bvecs, b0_threshold=args.b0_threshold) # Remove bvecs corresponding to b0 images bvecs = bvecs[np.logical_not(gtab.b0s_mask)] sphere = Sphere(xyz=bvecs) diff --git a/scripts/tests/test_sh_to_sf.py b/scripts/tests/test_sh_to_sf.py index 89307b174..092bea861 100755 --- a/scripts/tests/test_sh_to_sf.py +++ b/scripts/tests/test_sh_to_sf.py @@ -16,12 +16,13 @@ def test_help_option(script_runner): assert ret.success -def test_execution_processing(script_runner): +def test_execution_in_sphere(script_runner): os.chdir(os.path.expanduser(tmp_dir.name)) in_sh = os.path.join(get_home(), 'processing', 'sh_1000.nii.gz') in_b0 = os.path.join(get_home(), 'processing', 'fa.nii.gz') in_bval = os.path.join(get_home(), 'processing', '1000.bval') + # Required: either --sphere or --in_bvec. Here, --sphere ret = script_runner.run('scil_sh_to_sf.py', in_sh, 'sf_724.nii.gz', '--in_bval', in_bval, '--in_b0', in_b0, '--out_bval', @@ -37,3 +38,39 @@ def test_execution_processing(script_runner): '--sphere', 'symmetric724', '--dtype', 'float32', '--b0_threshold', '1', '-f') assert not ret.success + + +def test_execution_in_bvec(script_runner): + os.chdir(os.path.expanduser(tmp_dir.name)) + in_sh = os.path.join(get_home(), 'processing', 'sh_1000.nii.gz') + in_bval = os.path.join(get_home(), 'processing', '1000.bval') + in_bvec = os.path.join(get_home(), 'processing', '1000.bvec') + + # --in_bvec: in_bval is required. + ret = script_runner.run('scil_sh_to_sf.py', in_sh, + 'sf_724.nii.gz', '--in_bval', in_bval, + '--out_bval', 'sf_724.bval', + '--out_bvec', 'sf_724.bvec', + '--in_bvec', in_bvec, '--dtype', 'float32', '-f') + assert ret.success + + # Test that fails if no bvals is given. + ret = script_runner.run('scil_sh_to_sf.py', in_sh, + 'sf_724.nii.gz', + '--out_bvec', 'sf_724.bvec', + '--in_bvec', in_bvec, '--dtype', 'float32', '-f') + assert not ret.success + + +def test_execution_no_bval(script_runner): + os.chdir(os.path.expanduser(tmp_dir.name)) + in_sh = os.path.join(get_home(), 'processing', 'sh_1000.nii.gz') + in_b0 = os.path.join(get_home(), 'processing', 'fa.nii.gz') + + # --sphere but no --bval + ret = script_runner.run('scil_sh_to_sf.py', in_sh, + 'sf_724.nii.gz', '--in_b0', in_b0, + '--out_bvec', 'sf_724.bvec', + '--sphere', 'symmetric724', '--dtype', 'float32', + '-f') + assert ret.success From 56ea1d639734b7ab001b298be717cdf3970b169c Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Tue, 30 Jan 2024 11:11:02 -0500 Subject: [PATCH 24/26] scil_sh_to_sf: FIX part2. Presence of b0s in the bvals actually not expected --- scilpy/gradients/bvec_bval_tools.py | 2 +- scripts/scil_sh_to_sf.py | 46 ++++++++++++++++++----------- scripts/tests/test_sh_to_sf.py | 9 ------ 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/scilpy/gradients/bvec_bval_tools.py b/scilpy/gradients/bvec_bval_tools.py index ad254362c..377b6fac4 100644 --- a/scilpy/gradients/bvec_bval_tools.py +++ b/scilpy/gradients/bvec_bval_tools.py @@ -102,7 +102,7 @@ def check_b0_threshold(min_bval, b0_thr, skip_b0_check): return min_bval else: raise ValueError( - 'The minimal bvalue ({}) is is above the threshold ({})\n' + 'The minimal bvalue ({}) is above the threshold ({})\n' 'No b0 volumes can be found.\n' 'Please check your data to ensure everything is correct.\n' 'You may also increase the threshold or use ' diff --git a/scripts/scil_sh_to_sf.py b/scripts/scil_sh_to_sf.py index ab88dd3c3..e3c480c16 100755 --- a/scripts/scil_sh_to_sf.py +++ b/scripts/scil_sh_to_sf.py @@ -23,12 +23,11 @@ from dipy.data import SPHERE_FILES, get_sphere from dipy.io import read_bvals_bvecs -from scilpy.gradients.bvec_bval_tools import (check_b0_threshold, - DEFAULT_B0_THRESHOLD) +from scilpy.gradients.bvec_bval_tools import DEFAULT_B0_THRESHOLD from scilpy.io.utils import (add_overwrite_arg, add_processes_arg, - add_skip_b0_check_arg, add_sh_basis_args, - assert_inputs_exist, add_verbose_arg, - assert_outputs_exist, validate_nbr_processes) + add_sh_basis_args, add_verbose_arg, + assert_inputs_exist, assert_outputs_exist, + validate_nbr_processes) from scilpy.reconst.sh import convert_sh_to_sf @@ -60,7 +59,17 @@ def _build_arg_parser(): p.add_argument('--in_bval', help='b-value file, in FSL format, used to assign a ' 'b-value to the \noutput SF and generate a `.bval` ' - 'file.') + 'file.\n' + '- If used, --out_bval is required.\n' + '- The output bval will contain one b-value per point ' + 'in the SF \n output (i.e. one per point on the ' + '--sphere or one per --in_bvec.)\n' + '- The values of the output bval will all be set to ' + 'the same b-value:\n the average of your in_bval. ' + '(Any b0 found in this file, i.e \n b-values under ' + '--b0_threshold, will be removed beforehand.)\n' + '- To add b0s to both the SF volume and the ' + '--out_bval file, use --in_b0.') p.add_argument('--in_b0', help='b0 volume to concatenate to the final SF volume.') p.add_argument('--out_bval', @@ -69,7 +78,8 @@ def _build_arg_parser(): help="Optional output bvec file.") p.add_argument('--b0_scaling', action="store_true", - help="Scale resulting SF by the b0 image.") + help="Scale resulting SF by the b0 image (--in_b0 must" + "be given).") add_sh_basis_args(p) p.add_argument('--full_basis', action="store_true", @@ -83,9 +93,9 @@ def _build_arg_parser(): metavar='thr', help='Threshold under which b-values are considered to be b0s.\n' 'Default if not set is {}.\n' - 'This value is used with options --in_bvec or --in_bval only.' + 'This value is used with option --in_bval only: any b0 found in ' + 'the in_bval will be removed.' .format(DEFAULT_B0_THRESHOLD)) - add_skip_b0_check_arg(p, will_overwrite_with_min=True) add_processes_arg(p) add_verbose_arg(p) @@ -104,6 +114,13 @@ def main(): assert_outputs_exist(parser, args, args.out_sf, optional=[args.out_bvec, args.out_bval]) + if args.in_bval: + if args.b0_threshold > DEFAULT_B0_THRESHOLD: + logging.warning( + 'Your defined b0 threshold is {}. This is suspicious. ' + 'Typical b0_thresholds are no higher than {}.' + .format(args.b0_threshold, DEFAULT_B0_THRESHOLD)) + if (args.in_bval and not args.out_bval) or ( args.out_bval and not args.in_bval): parser.error("--out_bval is required if --in_bval is provided, " @@ -126,11 +143,6 @@ def main(): elif args.in_bval: bvals, _ = read_bvals_bvecs(args.in_bval, None) - if bvals is not None: - args.b0_threshold = check_b0_threshold( - bvals.min(), b0_thr=args.b0_threshold, - skip_b0_check=args.skip_b0_check) - # Load SH vol_sh = nib.load(args.in_sh) data_sh = vol_sh.get_fdata(dtype=np.float32) @@ -154,8 +166,7 @@ def main(): # Assign bval to SF if --in_bval was provided new_bvals = [] if args.in_bval: - # Compute average bval in case in_bvec is None. - # (If --in_bvec, this is already loaded in gtab.b0_masks) + # Compute average bval (except b0s), and create n out_bvals. b0s_mask = bvals <= args.b0_threshold avg_bval = np.mean(bvals[np.logical_not(b0s_mask)]) @@ -169,7 +180,8 @@ def main(): if data_b0.ndim == 3: data_b0 = data_b0[..., np.newaxis] - new_bvals = ([0] * data_b0.shape[-1]) + new_bvals + if args.in_bval: + new_bvals = ([0] * data_b0.shape[-1]) + new_bvals # Append zeros to bvecs new_bvecs = np.concatenate( diff --git a/scripts/tests/test_sh_to_sf.py b/scripts/tests/test_sh_to_sf.py index 092bea861..6473bc85a 100755 --- a/scripts/tests/test_sh_to_sf.py +++ b/scripts/tests/test_sh_to_sf.py @@ -30,15 +30,6 @@ def test_execution_in_sphere(script_runner): '--sphere', 'symmetric724', '--dtype', 'float32') assert ret.success - # Test wrong b0. Current minimal b-val is 5 - ret = script_runner.run('scil_sh_to_sf.py', in_sh, - 'sf_724.nii.gz', '--in_bval', - in_bval, '--in_b0', in_b0, '--out_bval', - 'sf_724.bval', '--out_bvec', 'sf_724.bvec', - '--sphere', 'symmetric724', '--dtype', 'float32', - '--b0_threshold', '1', '-f') - assert not ret.success - def test_execution_in_bvec(script_runner): os.chdir(os.path.expanduser(tmp_dir.name)) From b1ae3e2bbc4e33bef44d0ba7a06234af38dc01f9 Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Wed, 14 Feb 2024 10:21:58 -0500 Subject: [PATCH 25/26] Fix pep8 From 3ce0db06d740c2a7cb1ca19f7208e293d612db08 Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Thu, 15 Feb 2024 08:30:26 -0500 Subject: [PATCH 26/26] Add unit test --- scilpy/gradients/bvec_bval_tools.py | 8 +++++++- scilpy/gradients/tests/test_bvec_bval_tools.py | 18 +++++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/scilpy/gradients/bvec_bval_tools.py b/scilpy/gradients/bvec_bval_tools.py index 377b6fac4..2bcf53848 100644 --- a/scilpy/gradients/bvec_bval_tools.py +++ b/scilpy/gradients/bvec_bval_tools.py @@ -73,6 +73,13 @@ def check_b0_threshold(min_bval, b0_thr, skip_b0_check): If True, and no b0 is found, only print a warning, do not raise an error. + Returns + ------- + b0_thr: float + Either the unmodified b0_thr, or, in the case where the minimal b-value + is larger than b0_thr, and skip_b0_check is set to True, then returns + min_bval. + Raises ------ ValueError @@ -93,7 +100,6 @@ def check_b0_threshold(min_bval, b0_thr, skip_b0_check): if min_bval > b0_thr: if skip_b0_check: - logging.warning("GOT {} > {}".format(min_bval, b0_thr)) logging.warning( 'Your minimal bvalue ({}), is above the threshold ({})\n' 'Since --skip_b0_check was specified, the script will ' diff --git a/scilpy/gradients/tests/test_bvec_bval_tools.py b/scilpy/gradients/tests/test_bvec_bval_tools.py index 3d82cc188..4085dd5d3 100644 --- a/scilpy/gradients/tests/test_bvec_bval_tools.py +++ b/scilpy/gradients/tests/test_bvec_bval_tools.py @@ -2,9 +2,9 @@ import numpy as np from scilpy.gradients.bvec_bval_tools import ( - identify_shells, is_normalized_bvecs, flip_gradient_sampling, - normalize_bvecs, round_bvals_to_shell, str_to_axis_index, - swap_gradient_axis) + check_b0_threshold, identify_shells, is_normalized_bvecs, + flip_gradient_sampling, normalize_bvecs, round_bvals_to_shell, + str_to_axis_index, swap_gradient_axis) bvecs = np.asarray([[1.0, 1.0, 1.0], [1.0, 0.0, 1.0], @@ -23,8 +23,16 @@ def test_normalize_bvecs(): def test_check_b0_threshold(): - # toDo To be modified (see PR#867). - pass + assert check_b0_threshold(min_bval=0, b0_thr=0, skip_b0_check=False) == 0 + assert check_b0_threshold(min_bval=0, b0_thr=20, skip_b0_check=False) == 20 + assert check_b0_threshold(min_bval=20, b0_thr=0, skip_b0_check=True) == 20 + + error_raised = False + try: + _ = check_b0_threshold(min_bval=20, b0_thr=0, skip_b0_check=False) + except ValueError: + error_raised = True + assert error_raised def test_identify_shells():