-
Notifications
You must be signed in to change notification settings - Fork 133
ENH: Adds populate_intended_for for fmaps #482
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
Merged
Changes from 21 commits
Commits
Show all changes
94 commits
Select commit
Hold shift + click to select a range
6dbd28a
Adds `add_field_to_json` to utils.py
pvelasco d5f01b9
ENH: Adds `populate_intended_for`
pvelasco 7117659
Adds `populate_intended_for` to `process_extra_commands`
pvelasco 5c4ff83
BF: acq_str/run_str will not break if no acq/run label present
pvelasco 77d63f1
RF: `populate-intended-for` command uses dashes (`-`)
pvelasco 42d3c20
RF: Prepares way for further `populate_intended_for` unit tests
pvelasco 0d821d5
Corrects usage of `populate-intended-for` in allowed commands
pvelasco 7edcf36
Merge pull request #4 from nipy/master
pvelasco 34b2acc
BF: Fixes broken test for `populate_intended_for` in Python3.5
pvelasco 7a01b5f
RF: make `get_shim_setting` a separate function
pvelasco 36643ae
RF: Simplify a little bit `create_dummy_pepolar_bids_session`
pvelasco 1be873c
RF: `test_bids.py: create_dummy_pepolar_bids_session` now also return…
pvelasco 7815c92
Adds tests for the case in which there are no ShimSettings
pvelasco 4b21d45
BF: Fixes bug in `populate_intended_for`
pvelasco 03d4005
Adds a new test case for `populate_intended_for`
pvelasco d3c9962
RF: `test_convert.py` imports full module `convert.py`
pvelasco 1bd783a
ENH: Adds a call to `populate_intended_for` in `convert.py:convert`
pvelasco d9dabe7
BF: Fixes unit tests for test_convert.py
pvelasco fb7dd70
BF: Fixes unit tests for test_convert.py
pvelasco 015f79c
BF: skips populate_intended_for for non-BIDS-compliant datasets
pvelasco 90ccae5
Merge 'nipy_heudiconv/master' into adds_populate_intended_for
pvelasco 0690c1a
Update heudiconv/bids.py
pvelasco 9b397de
Improve lgr in `bids.py`
pvelasco fefa521
Merge remote-tracking branch 'nipy_heudiconv/master' into adds_popula…
pvelasco d801fee
Simplify sorting of fmaps in heudiconv/bids.py
pvelasco 8f25fb6
Simplify expression to find session field maps
pvelasco b9a1d7a
Minor: Use preferred str formatting in lgr call
pvelasco 37c9b7a
RF: Add find_fmap_groups for populating IntendedFor
pvelasco cfcb118
Add new test case for find_fmap_groups
pvelasco 6027ab2
ENH: New function to extract info relevant for IntendedFor
pvelasco 4672145
RF: populate_intended_for uses now get_key_info_for_fmap_assignment
pvelasco 1aa5f2d
Add functions to find fmaps compatible with runs/sessions
pvelasco ec38175
RF: rename variables
pvelasco c05b8cf
RF: `find_compatible_fmaps_*` now return compatible_fmap_groups
pvelasco af80739
Add `select_fmap_from_compatible_groups`, allowing the user to select…
pvelasco 2f9055b
Fix test_get_key_info_for_fmap_assignment
pvelasco 3d85e2b
RF: improve `populate_intended_for`
pvelasco 394d8f9
RF: Add `remove_prefix`/`remove_suffix` for path strings
pvelasco af65665
Merge nipy/heudiconv:master into this branch
pvelasco bf8f0a9
$BF: Fix test_convert.test_convert()
pvelasco b5b5051
Merge branch 'adds_populate_intended_for' into adds_populate_intended…
pvelasco f947173
RF, ENH: Expand choices for `populate_intended_for`
pvelasco 5615f99
Merge branch 'master' into adds_populate_intended_for
pvelasco 8811491
BF: Fix op.join(tmpdir,...) in test_bids for Python3.5 compatibility
pvelasco e6ba946
Merge remote-tracking branch 'origin/adds_populate_intended_for' into…
pvelasco 1e1ba4d
BF: Make sure the order of the test files is the intended one
pvelasco a0ee48e
BF: Make sure we remove the trailing op.sep in path
pvelasco 09e0d1d
BF: Fixes test_convert.test_convert
pvelasco c38253b
ENH: `convert` now takes `populate_intended_for` params, from heuristic
pvelasco fa7b809
RF: test_convert -> test_populate_intended_for
pvelasco 47bab3c
RF: Get rid of sys_modules call
pvelasco 5b0595a
Change format of debug logger.
pvelasco 0650b68
RF: utils.add_field_to_json -> utils.update_json
pvelasco ce1f0d2
Don't specify intent in `save_json`
pvelasco c4a2224
RF: Allow user to specify `pretty` argument for update_json
pvelasco 267bf62
BF: Delete duplicated functions in bids.py
pvelasco c56cba4
Add documentation for `populate_intended_for`
pvelasco c4c7587
ENH: do use update_json for IntendedFor addition with pretty=True
yarikoptic 467a261
Merge remote-tracking branch 'origin/master' into adds_populate_inten…
yarikoptic ae61382
Revert to importing specific functions in test_convert.
pvelasco f3dd790
Merge remote into local for branch 'adds_populate_intended_for'
pvelasco 723fa67
BF: call `populate_intended_for` using the full session path
pvelasco 5a06034
RF: only call `populate_intended_for` if heuristic specifies the options
pvelasco 1bd151b
Add test for populate_intended_for using heuristic without POPULATE_I…
pvelasco ccb96df
RF: allow multiple parameters that need to be matched for populate_in…
pvelasco b72ecd8
Make `'ImagingVolume'` the default matching_parameters
pvelasco 34c1b41
Add `'Force'` as `matching_parameter` option for fmap matching
pvelasco bdbc91e
RF: Add `'AcquisitionLabel'` as `matching_parameter` option for fmap …
pvelasco 156ed9b
ENH: pass POPULATE_INTENDED_FOR_OPTS from heuristic upon command styl…
bf9a3e1
BF: Various minor fixes while trying to use with nibabel 3.2.1
bda7cc5
Cover the case key_info is a string; modify unit tests
pvelasco 606a727
Remove import namedtuple (no longer needed)
pvelasco 2886525
ENH: Add BIDSFile class
pvelasco d3b8210
Merge pull request #11 from cbinyu/BIDSFile_helper
pvelasco 141f6e9
Merge pull request #34 from cbinyu/patch_dbic
yarikoptic 0ecd867
Merge pull request #10 from dbic/adds_populate_intended_for
pvelasco 1867898
Remove default arguments for populate-intended-for functions
pvelasco f8c5528
Update docs/heuristics.rst for RF matching_parameters key
pvelasco 312e139
Update heudiconv/bids.py
pvelasco 961fd83
Replace type check with isinstance
pvelasco 51d1a96
ENH: Autopopulate subjects and sessions for populate-intended-for\n\n…
pvelasco d53e50b
Check arguments for populate_intended_for functions only at top level
pvelasco 0f38e5e
Merge commit 'v0.10.0-10-g80a6538' (origin/master) into adds_populate…
yarikoptic 88e93eb
When json field is list of numbers, change evaluation to proper block
717f500
Merge pull request #12 from neurorepro/adds_populate_intended_for
pvelasco 784e946
Add matching by custom label
45af83a
Remove unused function parameters
4848977
Update new function docstring to include new parameter description
2f6a4e9
Correct docstrings for new parameter descriptions
457bf40
Correct random seeding by using random library instead of numpy
547dafd
Set label seed for the whole test suite and print it to stdout
4b23544
Merge pull request #13 from neurorepro/adds_populate_intended_for
pvelasco 97b76f3
Merge remote-tracking branch 'origin/master' into adds_populate_inten…
yarikoptic 1c5ca68
RF+typo fix: no need for explicit list in sorted()
yarikoptic 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ | |
save_json, | ||
create_file_if_missing, | ||
json_dumps_pretty, | ||
add_field_to_json, | ||
set_readonly, | ||
is_readonly, | ||
get_datetime, | ||
|
@@ -46,6 +47,9 @@ class BIDSError(Exception): | |
|
||
BIDS_VERSION = "1.4.1" | ||
|
||
SHIM_KEY = 'ShimSetting' | ||
lgr.debug('shim_key: {}'.format(SHIM_KEY)) | ||
|
||
|
||
def populate_bids_templates(path, defaults={}): | ||
"""Premake BIDS text files with templates""" | ||
|
@@ -457,3 +461,145 @@ def convert_sid_bids(subject_id): | |
lgr.warning('{0} contained nonalphanumeric character(s), subject ' | ||
'ID was cleaned to be {1}'.format(subject_id, sid)) | ||
return sid, subject_id | ||
|
||
|
||
def get_shim_setting(json_file): | ||
""" | ||
Gets the "ShimSetting" field from a json_file. | ||
If not present: | ||
- for fmap files: use the <acq> entity from the file name | ||
- for other files: use the folder name ('anat', 'func', 'dwi', ...) | ||
|
||
Parameters: | ||
---------- | ||
json_file : str | ||
|
||
Returns: | ||
------- | ||
str with "ShimSetting" value or <acq> entity | ||
""" | ||
data = load_json(json_file) | ||
if SHIM_KEY in data.keys(): | ||
shims = data[SHIM_KEY] | ||
else: | ||
modality = op.basename(op.dirname(json_file)) | ||
if modality == 'fmap': | ||
# extract the <acq> entity: | ||
shims = re.search('(?<=[/_]acq-)\w+',json_file).group(0).split('_')[0] | ||
if shims.lower() in ['fmri', 'bold']: | ||
shims = 'func' | ||
else: | ||
shims = modality | ||
return shims | ||
|
||
|
||
def populate_intended_for(path_to_bids_session): | ||
""" | ||
Adds the 'IntendedFor' field to the fmap .json files in a session folder. | ||
It goes through the session folders and checks what runs have the same | ||
'ShimSetting' as the fmaps. If there is no 'ShimSetting' field in the json | ||
file, we'll use the folder name ('func', 'dwi', 'anat') and see which fmap | ||
with a matching '_acq' entity. | ||
|
||
If several fmap runs have the same 'ShimSetting' (or '_acq'), it will use | ||
the first one. Because fmaps come in groups (with reversed PE polarity, | ||
or magnitude/phase), it adds the same runs to the 'IntendedFor' of the | ||
corresponding fmaps by checking the '_acq' and '_run' entities. | ||
|
||
Note: the logic behind the way we decide how to populate the "IntendedFor" | ||
is: we want all images in the session (except for the fmap images | ||
themselves) to have AT MOST one fmap. (That is, a pair of SE EPI with | ||
reversed polarities, or a magnitude a phase field map). If there are more | ||
than one fmap (more than a fmap pair) with the same acquisition parameters | ||
as, say, a functional run, we will just assign that run to the FIRST pair, | ||
while leaving the other fmap pairs without any assigned images. If the | ||
user's intentions were different, he/she will have to manually edit the | ||
fmap json files. | ||
|
||
Parameters: | ||
---------- | ||
path_to_bids_session : str or os.path | ||
path to the session folder (or to the subject folder, if there are no | ||
sessions). | ||
""" | ||
lgr.info('') | ||
lgr.info('Adding "IntendedFor" to the fieldmaps in {}.'.format(path_to_bids_session)) | ||
|
||
# Resolve path (eliminate '..') | ||
path_to_bids_session = op.abspath(path_to_bids_session) | ||
|
||
# get the BIDS folder (if "data_folder" includes the session, remove it): | ||
if op.basename(path_to_bids_session).startswith('ses-'): | ||
bids_folder = op.dirname(path_to_bids_session) | ||
else: | ||
bids_folder = path_to_bids_session | ||
|
||
fmap_dir = op.join(path_to_bids_session, 'fmap') | ||
if not op.exists(fmap_dir): | ||
lgr.warning('Fmap folder not found in {}.'.format(path_to_bids_session)) | ||
lgr.warning('We cannot add the IntendedFor field') | ||
pvelasco marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return | ||
|
||
# Get a list of all fmap json files in the session: | ||
# (we will remove elements later on, so don't just iterate) | ||
fmap_jsons = sorted([j for j in glob(op.join(path_to_bids_session, 'fmap/*.json'))]) | ||
pvelasco marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# Get a set with all non-fmap json files in the session (set is easier): | ||
# We also exclude the SBRef files. | ||
session_jsons = set( | ||
j for j in glob(op.join(path_to_bids_session, '*/*.json')) if not ( | ||
j in fmap_jsons | ||
# j[:-5] removes the '.json' from the end | ||
or j[:-5].endswith('_sbref') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just thinking out loud: we so need "BIDS filename" parser -- our code is full of similar constructs. #452 |
||
) | ||
) | ||
|
||
# Loop through all the fmap json files and, for each one, find which other | ||
# non-fmap images in the session have the same shim settings. Those that | ||
# match are added to the intended_for list and removed from the list of | ||
# non-fmap json files in the session (since they have already assigned to | ||
# a fmap). | ||
# After finishing with all the non-fmap images in the session, we go back | ||
# to the fmap json file list, and find any other fmap json files of the | ||
# same acquisition type and run number (because fmaps have several files: | ||
# normal- and reversed-polarity, or magnitude and phase, etc.) We add the | ||
# same IntendedFor list to those other corresponding fmap json files, and | ||
# remove them from the list of available fmap json files. | ||
# Once we have gone through all the fmap json files, we are done. | ||
runs_accounted_for = set() | ||
fmaps_accounted_for = set() | ||
for fm_json in fmap_jsons: | ||
if fm_json not in fmaps_accounted_for: | ||
lgr.debug('Looking for runs for {}'.format(fm_json)) | ||
pvelasco marked this conversation as resolved.
Show resolved
Hide resolved
|
||
fm_shims = get_shim_setting(fm_json) | ||
|
||
intended_for = [] | ||
for image_json in session_jsons: | ||
image_shims = get_shim_setting(image_json) | ||
if image_shims == fm_shims: | ||
# BIDS specifies that the intended for are: | ||
# - **image** files | ||
# - path relative to the **subject level** | ||
image_json_relative_path = op.relpath(image_json, start=bids_folder) | ||
# image_json_relative_path[:-5] removes the '.json' extension: | ||
intended_for.append( | ||
image_json_relative_path[:-5] + '.nii.gz' | ||
) | ||
runs_accounted_for.add(image_json) | ||
if len(intended_for) > 0: | ||
intended_for = sorted([str(f) for f in intended_for]) | ||
# find all fmap json files with the same <acq> and <run> entities: | ||
fm_json_name = op.basename(fm_json) | ||
acq_match = re.findall('([/_]acq-([a-zA-Z0-9]*))', fm_json_name) | ||
acq_str = acq_match[0][0] if acq_match else '' | ||
run_match = re.findall('([/_]run-([a-zA-Z0-9]*))', fm_json_name) | ||
run_str = run_match[0][0] if run_match else '' | ||
# Loop through all the files that have the same "acq-" and "run-" | ||
# Note: the following loop will also include 'fm_json' | ||
for linked_fm_json in glob(op.join(path_to_bids_session, 'fmap/*' + acq_str + '*' + run_str + '*.json')): | ||
# add the IntendedFor field to the json file: | ||
add_field_to_json(linked_fm_json, {"IntendedFor": intended_for}) | ||
fmaps_accounted_for.update({linked_fm_json}) | ||
# Remove the runs accounted for from the session_jsons list, so that | ||
# we don't assign another fmap to this image: | ||
session_jsons -= runs_accounted_for |
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
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.