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

Clean up the workflows #25

Merged
merged 2 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 34 additions & 0 deletions src/fmripost_aroma/utils/bids.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,44 @@
from pathlib import Path

from bids.layout import BIDSLayout
from bids.utils import listify

from fmripost_aroma.data import load as load_data


def extract_entities(file_list):
"""Return a dictionary of common entities given a list of files.

Examples
--------
>>> extract_entities("sub-01/anat/sub-01_T1w.nii.gz")
{'subject': '01', 'suffix': 'T1w', 'datatype': 'anat', 'extension': '.nii.gz'}
>>> extract_entities(["sub-01/anat/sub-01_T1w.nii.gz"] * 2)
{'subject': '01', 'suffix': 'T1w', 'datatype': 'anat', 'extension': '.nii.gz'}
>>> extract_entities(["sub-01/anat/sub-01_run-1_T1w.nii.gz",
... "sub-01/anat/sub-01_run-2_T1w.nii.gz"])
{'subject': '01', 'run': [1, 2], 'suffix': 'T1w', 'datatype': 'anat', 'extension': '.nii.gz'}

"""
from collections import defaultdict

from bids.layout import parse_file_entities

entities = defaultdict(list)
for e, v in [
ev_pair for f in listify(file_list) for ev_pair in parse_file_entities(f).items()
]:
entities[e].append(v)

def _unique(inlist):
inlist = sorted(set(inlist))
if len(inlist) == 1:
return inlist[0]
return inlist

return {k: _unique(v) for k, v in entities.items()}


def collect_derivatives(
raw_dir: Path | None,
derivatives_dir: Path,
Expand Down
19 changes: 19 additions & 0 deletions src/fmripost_aroma/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,3 +422,22 @@ def get_spectrum(data: np.array, tr: float):
freqs = np.fft.rfftfreq((power_spectrum.shape[0] * 2) - 1, tr)
idx = np.argsort(freqs)
return power_spectrum[idx, :], freqs[idx]


def _get_wf_name(bold_fname, prefix):
"""Derive the workflow name for supplied BOLD file.

>>> _get_wf_name("/completely/made/up/path/sub-01_task-nback_bold.nii.gz", "aroma")
'aroma_task_nback_wf'
>>> _get_wf_name(
... "/completely/made/up/path/sub-01_task-nback_run-01_echo-1_bold.nii.gz",
... "preproc",
... )
'preproc_task_nback_run_01_echo_1_wf'

"""
from nipype.utils.filemanip import split_filename

fname = split_filename(bold_fname)[1]
fname_nosub = '_'.join(fname.split('_')[1:-1])
return f"{prefix}_{fname_nosub.replace('-', '_')}_wf"
38 changes: 10 additions & 28 deletions src/fmripost_aroma/workflows/aroma.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from fmripost_aroma import config
from fmripost_aroma.interfaces.aroma import AROMAClassifier
from fmripost_aroma.interfaces.bids import DerivativesDataSink
from fmripost_aroma.utils.utils import _get_wf_name


def init_ica_aroma_wf(
Expand All @@ -47,11 +48,7 @@ def init_ica_aroma_wf(
#. Smooth data using FSL `susan`, with a kernel width FWHM=6.0mm.
#. Run FSL `melodic` outside of ICA-AROMA to generate the report
#. Run ICA-AROMA
#. Aggregate identified motion components (aggressive) to TSV
#. Return ``classified_motion_ICs`` and ``melodic_mix`` for user to complete
non-aggressive denoising in T1w space
#. Calculate ICA-AROMA-identified noise components
(columns named ``AROMAAggrCompXX``)
#. Aggregate components and classifications to TSVs

There is a current discussion on whether other confounds should be extracted
before or after denoising `here
Expand Down Expand Up @@ -376,9 +373,10 @@ def init_denoise_wf(bold_file):
niu.IdentityInterface(
fields=[
'bold_file',
'bold_mask_std',
'bold_mask',
'confounds',
'skip_vols',
'spatial_reference',
],
),
name='inputnode',
Expand All @@ -403,7 +401,7 @@ def init_denoise_wf(bold_file):
workflow.connect([
(inputnode, denoise, [
('confounds', 'confounds'),
('bold_mask_std', 'mask_file'),
('bold_mask', 'mask_file'),
]),
(rm_non_steady_state, denoise, [('bold_cut', 'bold_file')]),
]) # fmt:skip
Expand All @@ -429,7 +427,11 @@ def init_denoise_wf(bold_file):
run_without_submitting=True,
mem_gb=config.DEFAULT_MEMORY_MIN_GB,
)
workflow.connect([(add_non_steady_state, ds_denoised, [('bold_add', 'denoised_file')])])
workflow.connect([
# spatial_reference needs to be parsed into space, cohort, res, den, etc.
(inputnode, ds_denoised, [('spatial_reference', 'space')]),
(add_non_steady_state, ds_denoised, [('bold_add', 'denoised_file')]),
]) # fmt:skip

return workflow

Expand Down Expand Up @@ -479,26 +481,6 @@ def _add_volumes(bold_file, bold_cut_file, skip_vols):
return out


def _get_wf_name(bold_fname, prefix):
"""
Derive the workflow name for supplied BOLD file.

>>> _get_wf_name("/completely/made/up/path/sub-01_task-nback_bold.nii.gz", "aroma")
'aroma_task_nback_wf'
>>> _get_wf_name(
... "/completely/made/up/path/sub-01_task-nback_run-01_echo-1_bold.nii.gz",
... "preproc",
... )
'preproc_task_nback_run_01_echo_1_wf'

"""
from nipype.utils.filemanip import split_filename

fname = split_filename(bold_fname)[1]
fname_nosub = '_'.join(fname.split('_')[1:-1])
return f"{prefix}_{fname_nosub.replace('-', '_')}_wf"


def _select_melodic_files(melodic_dir):
"""Select the mixing and component maps from the Melodic output."""
import os
Expand Down
Loading
Loading