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

Fix confound generation #67

Merged
merged 3 commits into from
Sep 20, 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
27 changes: 17 additions & 10 deletions src/fmripost_aroma/interfaces/confounds.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,41 +72,48 @@
aroma_features_df['classification'] != 'rejected'
].index.values
mixing_arr = np.loadtxt(mixing, ndmin=2)
n_vols = mixing_arr.shape[0]

Check warning on line 75 in src/fmripost_aroma/interfaces/confounds.py

View check run for this annotation

Codecov / codecov/patch

src/fmripost_aroma/interfaces/confounds.py#L75

Added line #L75 was not covered by tests
if n_vols != aroma_features_df.shape[0]:
raise ValueError('Mixing matrix and AROMA features do not match')

Check warning on line 77 in src/fmripost_aroma/interfaces/confounds.py

View check run for this annotation

Codecov / codecov/patch

src/fmripost_aroma/interfaces/confounds.py#L77

Added line #L77 was not covered by tests

# Prepare output paths
mixing_out = os.path.join(newpath, 'mixing.tsv')
aroma_confounds = os.path.join(newpath, 'AROMAAggrCompAROMAConfounds.tsv')

# pad mixing_arr with rows of zeros corresponding to number non steady-state volumes
# pad mixing_arr with rows of zeros corresponding to number of non steady-state volumes
if skip_vols > 0:
zeros = np.zeros([skip_vols, mixing_arr.shape[1]])
mixing_arr = np.vstack([zeros, mixing_arr])
padded_mixing_arr = np.vstack([zeros, mixing_arr])

Check warning on line 86 in src/fmripost_aroma/interfaces/confounds.py

View check run for this annotation

Codecov / codecov/patch

src/fmripost_aroma/interfaces/confounds.py#L86

Added line #L86 was not covered by tests

# save mixing_arr
np.savetxt(mixing_out, mixing_arr, delimiter='\t')
np.savetxt(mixing_out, padded_mixing_arr, delimiter='\t')

Check warning on line 89 in src/fmripost_aroma/interfaces/confounds.py

View check run for this annotation

Codecov / codecov/patch

src/fmripost_aroma/interfaces/confounds.py#L89

Added line #L89 was not covered by tests

# Return dummy list of ones if no noise components were found
if motion_ics.size == 0:
config.loggers.interfaces.warning('No noise components were classified')
return None, mixing_out

# return dummy lists of zeros if no signal components were found
good_ic_arr = np.delete(mixing_arr, motion_ics, 1).T
if good_ic_arr.size == 0:
config.loggers.interfaces.warning('No signal components were classified')
return None, mixing_out
if signal_ics.size == 0:
raise Exception('No signal components were classified')

Check warning on line 98 in src/fmripost_aroma/interfaces/confounds.py

View check run for this annotation

Codecov / codecov/patch

src/fmripost_aroma/interfaces/confounds.py#L98

Added line #L98 was not covered by tests

# Select the mixing matrix rows corresponding to the motion ICs
aggr_mixing_arr = mixing_arr[motion_ics, :].T
# Select the mixing matrix columns corresponding to the motion ICs
aggr_mixing_arr = mixing_arr[:, motion_ics]

Check warning on line 101 in src/fmripost_aroma/interfaces/confounds.py

View check run for this annotation

Codecov / codecov/patch

src/fmripost_aroma/interfaces/confounds.py#L101

Added line #L101 was not covered by tests

# Regress the good components out of the bad time series to get "pure evil" regressors
signal_mixing_arr = mixing_arr[signal_ics, :].T
signal_mixing_arr = mixing_arr[:, signal_ics]

Check warning on line 104 in src/fmripost_aroma/interfaces/confounds.py

View check run for this annotation

Codecov / codecov/patch

src/fmripost_aroma/interfaces/confounds.py#L104

Added line #L104 was not covered by tests
aggr_mixing_arr_z = stats.zscore(aggr_mixing_arr, axis=0)
signal_mixing_arr_z = stats.zscore(signal_mixing_arr, axis=0)
betas = np.linalg.lstsq(signal_mixing_arr_z, aggr_mixing_arr_z, rcond=None)[0]
pred_bad_timeseries = np.dot(signal_mixing_arr_z, betas)
orthaggr_mixing_arr = aggr_mixing_arr_z - pred_bad_timeseries

# pad confounds with rows of zeros corresponding to number of non steady-state volumes
if skip_vols > 0:
zeros = np.zeros([skip_vols, aggr_mixing_arr.shape[1]])
aggr_mixing_arr = np.vstack([zeros, aggr_mixing_arr])
orthaggr_mixing_arr = np.vstack([zeros, orthaggr_mixing_arr])

Check warning on line 115 in src/fmripost_aroma/interfaces/confounds.py

View check run for this annotation

Codecov / codecov/patch

src/fmripost_aroma/interfaces/confounds.py#L113-L115

Added lines #L113 - L115 were not covered by tests

# add one to motion_ic_indices to match melodic report.
aggr_confounds_df = pd.DataFrame(
aggr_mixing_arr,
Expand Down
6 changes: 2 additions & 4 deletions src/fmripost_aroma/workflows/aroma.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,9 +277,7 @@ def init_ica_aroma_wf(

# extract the confound ICs from the results
ica_aroma_confound_extraction = pe.Node(
ICAConfounds(
err_on_aroma_warn=config.workflow.err_on_warn,
),
ICAConfounds(err_on_aroma_warn=config.workflow.err_on_warn),
name='ica_aroma_confound_extraction',
)
workflow.connect([
Expand Down Expand Up @@ -311,7 +309,7 @@ def init_ica_aroma_wf(
niu.Function(function=_convert_to_tsv, output_names=['out_file']),
name='convert_to_tsv',
)
workflow.connect([(select_melodic_files, convert_to_tsv, [('mixing', 'in_file')])])
workflow.connect([(ica_aroma_confound_extraction, convert_to_tsv, [('mixing', 'in_file')])])

ds_mixing = pe.Node(
DerivativesDataSink(
Expand Down