-
Notifications
You must be signed in to change notification settings - Fork 75
Adding script to restride data and bvecs #1254
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
Merged
+418
−20
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
448578b
Adding script
karanphil 9ac122d
Adding bvec support
karanphil b8cbd4b
WIP
karanphil 0ed2f78
WIP
karanphil 722fb39
WIP
karanphil 6331b9a
WIP
karanphil 6c6e124
Adding tests
karanphil 634045b
Adding check for 4D data if bvec given
karanphil c37b6e1
Adding new tests
karanphil 9146d3a
Update processing.zip hash
karanphil e6c444f
Update test data names
karanphil 350a246
Merge branch 'master' of github.com:karanphil/scilpy into restride_data
karanphil ffc2fb9
Merge branch 'restride_data' of github.com:karanphil/scilpy into rest…
karanphil fd35965
Adding comments and cleaning
karanphil 4d12890
Address some comments part 1
karanphil 56194b9
Adding function for flip/swap format
karanphil d29c07e
Adding other functions
karanphil 90968e3
Merge branch 'master' of github.com:karanphil/scilpy into restride_data
karanphil File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| #! /usr/bin/env python3 | ||
| # -*- coding: utf-8 -*- | ||
| """ | ||
| Detect when data strides are different from [1, 2, 3] and correct them. | ||
| The script takes as input a nifti file and outputs a nifti file with the | ||
| corrected strides if needed. | ||
|
|
||
| Input file can be 3D or 4D. Only the first 3 dimensions are considered | ||
| for the stride correction. In the case of DWI data, we recommand to also input | ||
| the b-values and b-vectors files to correct the b-vectors accordingly. If the | ||
| --validate_bvecs is set, the script first detects sign flips and/or axes swaps | ||
| in the b-vectors from a fiber coherence index [1] and corrects the b-vectors. | ||
| Then, the b-vectors are permuted and sign flipped to match the new strides. | ||
|
|
||
| A typical pipeline could be: | ||
| >>> scil_volume_validate_correct_strides t1.nii.gz t1_restride.nii.gz | ||
| >>> scil_volume_validate_correct_strides dwi.nii.gz dwi_restride.nii.gz | ||
| --in_bvec dwi.bvec --out_bvec dwi_restride.bvec --validate_bvec | ||
| --in_bval dwi.bval | ||
|
|
||
| ------------------------------------------------------------------------------ | ||
| Reference: | ||
| [1] Schilling KG, Yeh FC, Nath V, Hansen C, Williams O, Resnick S, Anderson AW, | ||
| Landman BA. A fiber coherence index for quality control of B-table | ||
| orientation in diffusion MRI scans. Magn Reson Imaging. 2019 May;58:82-89. | ||
| doi: 10.1016/j.mri.2019.01.018. | ||
| ------------------------------------------------------------------------------ | ||
| """ | ||
|
|
||
| import argparse | ||
| import logging | ||
|
|
||
| from dipy.core.gradients import gradient_table | ||
| from dipy.io.gradients import read_bvals_bvecs | ||
| from dipy.reconst.dti import TensorModel, fractional_anisotropy | ||
| import numpy as np | ||
| import nibabel as nib | ||
|
|
||
| from scilpy.gradients.bvec_bval_tools import (check_b0_threshold, | ||
| find_flip_swap_from_order, | ||
| flip_gradient_axis, | ||
| is_normalized_bvecs, | ||
| normalize_bvecs, | ||
| swap_gradient_axis) | ||
| from scilpy.image.utils import verify_strides, find_strides_transform | ||
| from scilpy.io.utils import (add_b0_thresh_arg, add_overwrite_arg, | ||
| add_skip_b0_check_arg, assert_inputs_exist, | ||
| assert_outputs_exist, add_verbose_arg) | ||
| from scilpy.reconst.fiber_coherence import compute_coherence_table_for_transforms | ||
| from scilpy.version import version_string | ||
|
|
||
|
|
||
| def _build_arg_parser(): | ||
| p = argparse.ArgumentParser(description=__doc__, | ||
| formatter_class=argparse.RawTextHelpFormatter, | ||
| epilog=version_string) | ||
|
|
||
| p.add_argument('in_data', | ||
| help='Path to input nifti file.') | ||
| p.add_argument('out_data', | ||
| help='Path to output nifti file with corrected strides.') | ||
|
|
||
| p.add_argument('--in_bvec', | ||
| help='Path to bvec file (FSL format). If provided, the ' | ||
| 'bvecs will \nbe permuted and sign flipped to match ' | ||
| 'the new strides.') | ||
| p.add_argument('--out_bvec', | ||
| help='Path to output bvec file (FSL format). Must be ' | ||
| 'provided if --in_bvec is used.') | ||
| p.add_argument('--validate_bvec', action='store_true', | ||
| help='If set, the script first detects sign flips and/or ' | ||
| 'axes swaps \nin the b-vectors from a fiber coherence ' | ||
| 'index [1] and corrects \nthe b-vectors before ' | ||
| 'permuting/sign flipping them to match the new ' | ||
| 'strides. \nIf not set, the b-vectors are only ' | ||
| 'permuted and sign flipped to match the new strides.') | ||
| p.add_argument('--in_bval', | ||
| help='Path to bval file. Must be provided if ' | ||
| '--validate_bvecs is used.') | ||
|
|
||
| add_b0_thresh_arg(p) | ||
| add_skip_b0_check_arg(p, will_overwrite_with_min=True) | ||
| add_verbose_arg(p) | ||
| add_overwrite_arg(p) | ||
| return p | ||
|
|
||
|
|
||
| def main(): | ||
| parser = _build_arg_parser() | ||
| args = parser.parse_args() | ||
| logging.getLogger().setLevel(logging.getLevelName(args.verbose)) | ||
|
|
||
| assert_inputs_exist(parser, [args.in_data], | ||
| optional=[args.in_bvec, args.in_bval]) | ||
| assert_outputs_exist(parser, args, args.out_data, optional=args.out_bvec) | ||
|
|
||
| if args.in_bvec and not args.out_bvec: | ||
| parser.error('--out_bvec must be provided if --in_bvec is used.') | ||
| if args.validate_bvec and (not args.in_bvec or not args.in_bval): | ||
| parser.error('--in_bvec and --in_bval must be provided if ' | ||
| '--validate_bvecs is set.') | ||
|
|
||
| # Get the current strides | ||
| img = nib.load(args.in_data) | ||
| strides, is_stride_correct = verify_strides(img) | ||
| if not is_stride_correct: | ||
| # Compute the required transform to get to [1, 2, 3] | ||
| transform = find_strides_transform(strides) | ||
|
|
||
| # Write the transform in a format compatible with the | ||
| # flip_gradient_axis and swap_gradient_axis functions (for bvecs) | ||
| axes_to_flip, swapped_order = find_flip_swap_from_order(transform) | ||
|
|
||
| # Write the transform in a format compatible with the nibabel | ||
| # as_reoriented function (for image) | ||
| ornt = np.column_stack((np.array(swapped_order, dtype=np.int8), | ||
| np.where(np.isin(range(len(strides)), | ||
| axes_to_flip), | ||
| -1, 1))) | ||
| # Apply the transform to the image and save it | ||
| new_img = img.as_reoriented(ornt) | ||
| nib.save(new_img, args.out_data) | ||
|
|
||
| if args.validate_bvec: | ||
| logging.info('Validating b-vectors from fiber coherence index...') | ||
| # Load and validate the data and bvals/bvecs | ||
| data = img.get_fdata().astype(np.float32) | ||
| if len(data.shape) != 4: | ||
| parser.error('Input data must be DWI (4D) when --validate_bvec ' | ||
| 'is set.') | ||
| bvals, bvecs = read_bvals_bvecs(args.in_bval, args.in_bvec) | ||
| if not is_normalized_bvecs(bvecs): | ||
| logging.warning('Your b-vectors do not seem normalized...') | ||
| bvecs = normalize_bvecs(bvecs) | ||
| args.b0_threshold = check_b0_threshold(bvals.min(), | ||
| b0_thr=args.b0_threshold, | ||
| skip_b0_check=args.skip_b0_check) | ||
| gtab = gradient_table(bvals, bvecs=bvecs, | ||
| b0_threshold=args.b0_threshold) | ||
| tenmodel = TensorModel(gtab, fit_method='WLS', | ||
| min_signal=np.min(data[data > 0])) | ||
| # Generate a mask to avoid fitting tensor on the whole image | ||
| mask = np.zeros(data.shape[:3], dtype=bool) | ||
| # Use a small cubic ROI at the center of the volume | ||
| interval_i = slice(data.shape[0]//2 - data.shape[0]//4, | ||
| data.shape[0]//2 + data.shape[0]//4) | ||
| interval_j = slice(data.shape[1]//2 - data.shape[1]//4, | ||
| data.shape[1]//2 + data.shape[1]//4) | ||
| interval_k = slice(data.shape[2]//2 - data.shape[2]//4, | ||
| data.shape[2]//2 + data.shape[2]//4) | ||
| mask[interval_i, interval_j, interval_k] = 1 | ||
| # Compute the necessary DTI metrics to compute the coherence of bvecs | ||
| tenfit = tenmodel.fit(data, mask=mask) | ||
| fa = fractional_anisotropy(tenfit.evals) | ||
| evecs = tenfit.evecs.astype(np.float32)[..., 0] | ||
| evecs[fa < 0.2] = 0 | ||
| coherence, transform = compute_coherence_table_for_transforms(evecs, | ||
| fa) | ||
| # Find the best transform and apply it to the bvecs if needed | ||
| best_t = transform[np.argmax(coherence)] | ||
| if (best_t == np.eye(3)).all(): | ||
| logging.info('The b-vectors are aligned with the original data.') | ||
| valid_bvecs = bvecs | ||
| else: | ||
| logging.warning('Applying correction to b-vectors.') | ||
| logging.info('Transform is: \n{0}.'.format(best_t)) | ||
| valid_bvecs = np.dot(bvecs, best_t) | ||
| # If the data strides were correct, save the bvecs now | ||
| if is_stride_correct: | ||
| np.savetxt(args.out_bvec, valid_bvecs.T, "%.8f") | ||
|
|
||
| # Apply the permutation and sign flip to the bvecs and save them | ||
| if args.in_bvec and not is_stride_correct: | ||
| if not args.validate_bvec: | ||
| _, bvecs = read_bvals_bvecs(None, args.in_bvec) | ||
karanphil marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| else: | ||
| bvecs = valid_bvecs | ||
| flipped_bvecs = flip_gradient_axis(bvecs.T, axes_to_flip, 'fsl') | ||
| swapped_flipped_bvecs = swap_gradient_axis(flipped_bvecs, | ||
| swapped_order, 'fsl') | ||
| np.savetxt(args.out_bvec, swapped_flipped_bvecs, "%.8f") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
128 changes: 128 additions & 0 deletions
128
src/scilpy/cli/tests/test_volume_validate_correct_strides.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| #!/usr/bin/env python3 | ||
| # -*- coding: utf-8 -*- | ||
|
|
||
| import os | ||
| import tempfile | ||
|
|
||
| from scilpy import SCILPY_HOME | ||
| from scilpy.io.fetcher import fetch_data, get_testing_files_dict | ||
|
|
||
| # If they already exist, this only takes 5 seconds (check md5sum) | ||
| fetch_data(get_testing_files_dict(), keys=['processing.zip']) | ||
| tmp_dir = tempfile.TemporaryDirectory() | ||
|
|
||
|
|
||
| def test_help_option(script_runner): | ||
| ret = script_runner.run(['scil_volume_validate_correct_strides', '--help']) | ||
| assert ret.success | ||
|
|
||
|
|
||
| def test_execution_processing_no_restride(script_runner, monkeypatch): | ||
| monkeypatch.chdir(os.path.expanduser(tmp_dir.name)) | ||
| in_dwi = os.path.join(SCILPY_HOME, 'processing', | ||
| 'dwi_crop_1000.nii.gz') | ||
|
|
||
| ret = script_runner.run(['scil_volume_validate_correct_strides', in_dwi, | ||
| 'dwi_restride.nii.gz', '-f']) | ||
| assert ret.success | ||
|
|
||
|
|
||
| def test_execution_processing_restride(script_runner, monkeypatch): | ||
| monkeypatch.chdir(os.path.expanduser(tmp_dir.name)) | ||
| in_dwi = os.path.join(SCILPY_HOME, 'processing', | ||
| 'dwi_crop_1000_bad_strides.nii.gz') | ||
|
|
||
| ret = script_runner.run(['scil_volume_validate_correct_strides', in_dwi, | ||
| 'dwi_restride.nii.gz', '-f']) | ||
| assert ret.success | ||
|
|
||
|
|
||
| def test_execution_processing_bvecs(script_runner, monkeypatch): | ||
| monkeypatch.chdir(os.path.expanduser(tmp_dir.name)) | ||
| in_dwi = os.path.join(SCILPY_HOME, 'processing', | ||
| 'dwi_crop_1000_bad_strides.nii.gz') | ||
| in_bvec = os.path.join(SCILPY_HOME, 'processing', | ||
| '1000.bvec') | ||
|
|
||
| ret = script_runner.run(['scil_volume_validate_correct_strides', in_dwi, | ||
| 'dwi_restride.nii.gz', '--in_bvec', in_bvec, | ||
| '--out_bvec', 'dwi_restride.bvec', '-f']) | ||
| assert ret.success | ||
|
|
||
|
|
||
| def test_execution_processing_validate_bvecs_v1(script_runner, monkeypatch): | ||
| # Validate with good data strides and bad bvecs | ||
| monkeypatch.chdir(os.path.expanduser(tmp_dir.name)) | ||
| in_dwi = os.path.join(SCILPY_HOME, 'processing', | ||
| 'dwi_crop_1000.nii.gz') | ||
| in_bvec = os.path.join(SCILPY_HOME, 'processing', | ||
| 'dwi_crop_1000_bad_strides.bvec') | ||
| in_bval = os.path.join(SCILPY_HOME, 'processing', | ||
| '1000.bval') | ||
|
|
||
| ret = script_runner.run(['scil_volume_validate_correct_strides', in_dwi, | ||
| 'dwi_restride.nii.gz', '--in_bvec', in_bvec, | ||
| '--out_bvec', 'dwi_restride.bvec', | ||
| '--validate_bvec', '--in_bval', in_bval, '-f']) | ||
| assert ret.success | ||
|
|
||
|
|
||
| def test_execution_processing_validate_bvecs_v2(script_runner, monkeypatch): | ||
| # Validate with bad data strides | ||
| monkeypatch.chdir(os.path.expanduser(tmp_dir.name)) | ||
| in_dwi = os.path.join(SCILPY_HOME, 'processing', | ||
| 'dwi_crop_1000_bad_strides.nii.gz') | ||
| in_bvec = os.path.join(SCILPY_HOME, 'processing', | ||
| '1000.bvec') | ||
| in_bval = os.path.join(SCILPY_HOME, 'processing', | ||
| '1000.bval') | ||
|
|
||
| ret = script_runner.run(['scil_volume_validate_correct_strides', in_dwi, | ||
| 'dwi_restride.nii.gz', '--in_bvec', in_bvec, | ||
| '--out_bvec', 'dwi_restride.bvec', | ||
| '--validate_bvec', '--in_bval', in_bval, '-f']) | ||
| assert ret.success | ||
|
|
||
|
|
||
| def test_execution_processing_validate_bvecs_v3(script_runner, monkeypatch): | ||
| # Validate with non-DWI data and bad bvecs | ||
| monkeypatch.chdir(os.path.expanduser(tmp_dir.name)) | ||
| in_dwi = os.path.join(SCILPY_HOME, 'processing', | ||
| 'nufo.nii.gz') | ||
| in_bvec = os.path.join(SCILPY_HOME, 'processing', | ||
| 'dwi_crop_1000_bad_strides.bvec') | ||
| in_bval = os.path.join(SCILPY_HOME, 'processing', | ||
| '1000.bval') | ||
|
|
||
| ret = script_runner.run(['scil_volume_validate_correct_strides', in_dwi, | ||
| 'dwi_restride.nii.gz', '--in_bvec', in_bvec, | ||
| '--out_bvec', 'dwi_restride.bvec', | ||
| '--validate_bvec', '--in_bval', in_bval, '-f']) | ||
| assert not ret.success | ||
|
|
||
|
|
||
| def test_execution_processing_no_out_bvec(script_runner, monkeypatch): | ||
| monkeypatch.chdir(os.path.expanduser(tmp_dir.name)) | ||
| in_dwi = os.path.join(SCILPY_HOME, 'processing', | ||
| 'dwi_crop_1000_bad_strides.nii.gz') | ||
| in_bvec = os.path.join(SCILPY_HOME, 'processing', | ||
| '1000.bvec') | ||
|
|
||
| ret = script_runner.run(['scil_volume_validate_correct_strides', in_dwi, | ||
| 'dwi_restride.nii.gz', '--in_bvec', in_bvec, | ||
| '-f']) | ||
| assert not ret.success | ||
|
|
||
|
|
||
| def test_execution_processing_no_in_bval(script_runner, monkeypatch): | ||
| monkeypatch.chdir(os.path.expanduser(tmp_dir.name)) | ||
| in_dwi = os.path.join(SCILPY_HOME, 'processing', | ||
| 'dwi_crop_1000_bad_strides.nii.gz') | ||
| in_bvec = os.path.join(SCILPY_HOME, 'processing', | ||
| '1000.bvec') | ||
|
|
||
| ret = script_runner.run(['scil_volume_validate_correct_strides', in_dwi, | ||
| 'dwi_restride.nii.gz', '--in_bvec', in_bvec, | ||
| '--out_bvec', 'dwi_restride.bvec', | ||
| '--validate_bvec', '-f']) | ||
| assert not ret.success |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.