Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clarify b0 threshold #867

Merged
merged 26 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
31cc82b
Rename --force_b0_threshold. Add official --b0_threshold option.
EmmaRenauld Dec 14, 2023
af4974a
scil_dwi_detect_volume_outliers: use official arg.
EmmaRenauld Jan 22, 2024
9911552
scil_dti_metrics: use b0_threshold. Usage in dipy to be verified.
EmmaRenauld Dec 14, 2023
c3c3b63
scil_dwi_extract_b0: already ok. Use official b0_threshold.
EmmaRenauld Jan 22, 2024
98cd50f
scil_dwi_to_sh: using official arg. + [FIX] force_b0 was not used! Da…
EmmaRenauld Dec 14, 2023
4c105fd
scil_fodf_msmt: Only using tolerance. Link to dipy issue added.
EmmaRenauld Dec 14, 2023
5cb2dd9
scil_fodf_ssst: [FIX] Was always using minimal b-value as threshold. …
EmmaRenauld Dec 14, 2023
999b757
scil_sh_to_sf: No b0_threshold option existed. Added.
EmmaRenauld Dec 15, 2023
e009e2f
scil_qball_metrics: Only the minimal b-value was used. Adding b0_thre…
EmmaRenauld Jan 22, 2024
98e6517
scil_dki_metrics: could not modify the identify_shells method right n…
EmmaRenauld Jan 22, 2024
4ea5d1c
scil_frf_ssst: Add the b0_threshold option.
EmmaRenauld Dec 15, 2023
3060a84
scil_frf_msmt: Cannot modify for now. Adding explanation, using toler…
EmmaRenauld Dec 15, 2023
316febd
scil_frf_memsmt: Can't change. Added explanations.
EmmaRenauld Jan 22, 2024
0cca6f6
scil_fodf_memsmt: Idem
EmmaRenauld Jan 22, 2024
fd09544
scil_btensor_metrics: Idem. Removing unused option force_b0.
EmmaRenauld Jan 22, 2024
aceacb0
Fix pep8
EmmaRenauld Jan 23, 2024
778ddd9
Answer some comments
EmmaRenauld Jan 24, 2024
41c2533
Do no use 'args' in check_b0
EmmaRenauld Jan 29, 2024
949670c
Answering a few more comments
EmmaRenauld Jan 29, 2024
85e0d7f
Answer Phil: same explanation for tolerance everywhere
EmmaRenauld Jan 29, 2024
a1c1b23
pep8
EmmaRenauld Jan 29, 2024
d9f5e45
Added tests wrong b0
EmmaRenauld Jan 29, 2024
4981bce
sh_to_sf: Fixes + Test more exhaustively
EmmaRenauld Jan 30, 2024
56ea1d6
scil_sh_to_sf: FIX part2. Presence of b0s in the bvals actually not e…
EmmaRenauld Jan 30, 2024
b1ae3e2
Fix pep8
EmmaRenauld Feb 14, 2024
3ce0db0
Add unit test
EmmaRenauld Feb 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 42 additions & 35 deletions scilpy/gradients/bvec_bval_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,58 +55,65 @@ 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, 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.

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
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
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
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_check is
False).
"""
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)
)
'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 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.'
.format(min_bval))

if bvals_min > b0_thr:
if force_b0_threshold:
if min_bval > b0_thr:
if skip_b0_check:
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
'Your minimal bvalue ({}), is above the threshold ({})\n'
'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
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))

raise ValueError(
'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 '
'--skip_b0_check'
.format(min_bval, b0_thr))
return b0_thr


Expand Down
18 changes: 13 additions & 5 deletions scilpy/gradients/tests/test_bvec_bval_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand All @@ -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():
Expand Down
18 changes: 14 additions & 4 deletions scilpy/io/btensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
Expand Down Expand Up @@ -50,11 +51,14 @@ 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.

For the moment, this does not enable the use of a b0_threshold different
than the tolerance.

Parameters
----------
in_dwis : list of strings (file paths)
Expand All @@ -71,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
-------
Expand Down Expand Up @@ -105,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_thr=tol,
skip_b0_check=skip_b0_check)
if np.sum([bvals > tol]) != 0:
bvals = np.round(bvals)
if not is_normalized_bvecs(bvecs):
Expand Down Expand Up @@ -156,7 +166,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
karanphil marked this conversation as resolved.
Show resolved Hide resolved
# Removing the duplicate b0s from ubvals_full
duplicate_b0_ind = np.union1d(np.argwhere(ubvals_full == min(ubvals_full)),
np.argwhere(ubvals_full > tol))
Expand Down Expand Up @@ -185,4 +195,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
karanphil marked this conversation as resolved.
Show resolved Hide resolved
54 changes: 49 additions & 5 deletions scilpy/io/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,11 +215,55 @@ 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_tolerance_arg(parser):
parser.add_argument(
'--tolerance', type=int, default=20, metavar='tol',
help='The tolerated gap between the b-values to extract and the '
'current b-value.\n'
'[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'
'[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,
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 \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'
.format(b0_tol_name))
msg += 'Use with care, and only if you understand your data.'

parser.add_argument(
'--skip_b0_check', action='store_true', help=msg)


def add_verbose_arg(parser):
Expand Down
43 changes: 26 additions & 17 deletions scilpy/reconst/frf.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
import numpy as np

from scilpy.gradients.bvec_bval_tools import (check_b0_threshold,
is_normalized_bvecs, normalize_bvecs)
is_normalized_bvecs,
normalize_bvecs,
DEFAULT_B0_THRESHOLD)


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=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
Response Function from a DWI volume.
A DTI fit is made, and voxels containing a single fiber population are
Expand All @@ -29,6 +31,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
Expand All @@ -55,8 +59,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
-------
Expand All @@ -78,9 +80,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)
Expand Down Expand Up @@ -137,8 +137,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
Expand All @@ -152,6 +151,18 @@ 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: 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: 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
Expand Down Expand Up @@ -200,8 +211,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
-------
Expand All @@ -221,9 +230,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)
# 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(
Expand All @@ -244,7 +254,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 \
Expand Down
Loading