I want to use XCP-D’s aroma or aroma_gsr pipeline to post-process minimally preprocessed ICA-AROMA outputs from fmriprep to generate functional connectivity matrix and fALFF from aparc+aseg regions of Desikan Killiany atlas in FreeSurfer. Since ALFF are generated from bandpass filtered time series, what would be the best way to get fALFF from there on? Should I re-run XCP-D with no filtering and get power spectrum of post-processed aroma/aroma_gsr’ed time series without any filtering and use that to get fALFF?
Also, is it possible to run multiple pipelines at once, as in can I run aroma and aroma_gsr at the same time with -p flag? I tried doing it with -p aroma -p aroma_gsr flags to my command but aroma outputs were overwritten by aroma_gsr so not sure if it is possible?
In the document they suggest to use custom parcellation for Nifti processing pipeline using NiftiLabelsMasker, however it does not cover any steps or guidelines. Is there any documentation to be able to incorporate this with XCP-D?
Running XCP-D with filtering, then without filtering, and using that to calculate fALFF sounds like a reasonable way to do it.
I don’t know much about fALFF, but FWIW, based on a discussion I had with Dr. Valerie Sydnor, it seems like fALFF is correlated with motion, per Satterthwaite et al. (2013).
No, XCP-D can’t use multiple denoising strategies in the same run. I would recommend running XCP-D twice; once with each nuisance group and a unique output folder.
Nilearn has examples that show how to use the NiftiLabelsMasker (e.g., this one). Unfortunately, XCP-D does a bit of extra processing to deal with missing voxels/vertices, so using NiftiLabelsMasker directly will produce slightly different results. I can’t think of a particularly easy way to do this, but I can draft something up and post it in GitHub - PennLINC/xcp_d-examples: Examples of advanced use-cases for xcp_d. later today.
Running XCP-D with filtering, then without filtering, and using that to calculate fALFF sounds like a reasonable way to do it.
I don’t know much about fALFF, but FWIW, based on a discussion I had with Dr. Valerie Sydnor, it seems like fALFF is correlated with motion, per Satterthwaite et al. (2013).
No, XCP-D can’t use multiple denoising strategies in the same run. I would recommend running XCP-D twice; once with each nuisance group and a unique output folder.
Thank you for your prompt response and these pointers. We will be using both ALFF and fALFF to measure cognitive outcomes post stroke. Hoping ica-aroma and additional regressors from aroma and aroma_gsr pipeline will be enough to deal with motion and capture neuronal signal than noise.
Nilearn has examples that show how to use the NiftiLabelsMasker (e.g., this one). Unfortunately, XCP-D does a bit of extra processing to deal with missing voxels/vertices, so using NiftiLabelsMasker directly will produce slightly different results. I can’t think of a particularly easy way to do this, but I can draft something up and post it in GitHub - PennLINC/xcp_d-examples: Examples of advanced use-cases for xcp_d. later today.
There’s one cell for dealing with NIfTIs and one for CIFTIs. The NIfTI code requires ANTS, while the CIFTI code requires Connectome Workbench, so it’s best to copy over the code to a script and run it through the XCP-D Docker image.
No worries, and thank you so much. Let me give this a try and will let you know if I come across any hiccups or success. Before these steps, are we required to have our custom atlas in a certain orientation or something?
traits.trait_errors.TraitError: Cannot set the undefined 'temporal_mask' attribute of a '_NiftiConnectInputSpec' object.
Following are my steps, am I doing anything wrong? I am skipping steps to warp my atlas to BOLD space as I ran freesurfer via fmriprep pipeline which already aligns freesurfer’s aparc+aseg segmentations to my supplied output space in the fmriprep command (MNI152NLin6Asym) and resamples it to the BOLD grid.
import os
from xcp_d.interfaces.ants import ApplyTransforms
from xcp_d.interfaces.connectivity import NiftiConnect
from xcp_d.interfaces.nilearn import IndexImage
from xcp_d.utils.utils import get_std2bold_xfms
xcpd_dir = "/xcp_d/sub-fcs65c/ses-20091124/func"
fmriprep_dir = "/fmriprep_output/sub-fcs65c/ses-20091124/func"
mask = os.path.join(fmriprep_dir, "sub-fcs65c_ses-20091124_task-rest_run-002_space-MNI152NLin6Asym_res-2_desc-brain_mask.nii.gz")
temporal_mask = os.path.join(xcpd_dir, "sub-fcs65c_ses-20091124_task-rest_run-002_outliers.tsv")
min_coverage = 0.5
correlate = True
denoise_bold_file = os.path.join(xcpd_dir, "sub-fcs65c_ses-20091124_task-rest_run-002_space-MNI152NLin6Asym_res-2_desc-denoised_bold.nii.gz")
warped_aparcaseg_atlas = os.path.join(fmriprep_dir, "sub-fcs65c_ses-20091124_task-rest_run-002_space-MNI152NLin6Asym_res-2_desc-aparcaseg_dseg.nii.gz")
aparcaseg_atlas_labels = "/fmriprep_output/desc-aparcaseg_dseg.tsv"
interface = NiftiConnect(
filtered_file=denoise_bold_file,
mask=mask,
temporal_mask=temporal_mask,
atlas=warped_aparcaseg_atlas ,
atlas_labels=aparcaseg_atlas_labels ,
min_coverage=min_coverage,
correlate=True,
)
traits.trait_errors.TraitError: Cannot set the undefined 'temporal_mask' attribute of a '_NiftiConnectInputSpec' object.
I was able to run xcp_d with aroma pipeline both with and without bandpass filtering (0.008 - 0.09 Hz). Would following AFNI commands be appropriate to use to compute fALFF from bandpassed ALFF and non-filtered aroma denoised time series?
# Get standard deviation of non-filtered denoised time series - this serves as the denominator for fALFF computation
3dTstat -stdev -mask /fmriprep_output/sub-fcs65c/ses-20091124/func/sub-fcs65c_ses-20091124_task-rest_run-002_space-MNI152NLin6Asym_res-2_desc-brain_mask.nii.gz \
-prefix /xcp_d_aroma/xcp_d/sub-fcs65c/ses-20091124/func/stdev_sub-fcs65c_ses-20091124_task-rest_run-002_space-MNI152NLin6Asym_res-2_desc-denoised_bold.nii.gz \
/xcp_d_aroma/xcp_d/sub-fcs65c/ses-20091124/func/sub-fcs65c_ses-20091124_task-rest_run-002_space-MNI152NLin6Asym_res-2_desc-denoised_bold.nii.gz
# Compute fALFF from ALFF
3dcalc -a /fmriprep_output/sub-fcs65c/ses-20091124/func/sub-fcs65c_ses-20091124_task-rest_run-002_space-MNI152NLin6Asym_res-2_desc-brain_mask.nii.gz \
-b /xcp_d_aroma_bpf/xcp_d/sub-fcs65c/ses-20091124/func/sub-fcs65c_ses-20091124_task-rest_run-002_space-MNI152NLin6Asym_res-2_alff.nii.gz \
-c /xcp_d_aroma/xcp_d/sub-fcs65c/ses-20091124/func/stdev_sub-fcs65c_ses-20091124_task-rest_run-002_space-MNI152NLin6Asym_res-2_desc-denoised_bold.nii.gz \
-expr '(1.0*bool(a))*((1.0*b)/(1.0*c))' \
-float -prefix /xcp_d_aroma_bpf/xcp_d/sub-fcs65c/ses-20091124/func/sub-fcs65c_ses-20091124_task-rest_run-002_space-MNI152NLin6Asym_res-2_falff.nii.gz
From init_alff_wf workflow of this source file, it seems that high-motion outlier volumes are already removed before filtering. My understanding is that this file is *space-MNI152NLin6Asym_res-2_desc-denoised_bold.nii.gz and I would not have to further postprocess this denoised file in terms of removing any outlier volumes or anything else to compute the denominator for fALFF computation?
Dear @tsalo I wanted to confirm if the above computation for fALFF made sense? Do I need to take any precautionary step to compute the denominator from denoised time series?
I’ve never calculated fALFF myself, but at a glance it looks like you’re essentially dividing the ALFF map by the standard deviation map from the unfiltered, denoised data. I don’t think the standard deviation is what you need. I think you need to essentially calculate ALFF on your unfiltered, denoised data. Perhaps the code XCP-D uses could be helpful for that: https://github.com/PennLINC/xcp_d/blob/e7d9bab99bcef4dc446cff348884137757ab6039/xcp_d/utils/restingstate.py#L94
Thank you for your response. Yes, it makes sense what you suggest and that’s the approach I am taking. I was initially trying to compute fALFF w/o having to recompute ALFF to eliminate unknowns and redundancy. I essentially added following two lines in compute_alff.py function of this script right after ALFF is computed to get fALFF from denoised func timeseries (*_desc-denoised_bold.nii.gz) generated from pipeline with bandpass filter enabled and censoring disabled --lower-bpf .008 --upper-bpf .09 --fd-thresh 0. I had to use *_desc-denoised_bold.nii.gz as an input file, which is the output of docker command with --lower-bpf .008 --upper-bpf .09 --fd-thresh 0 flags in it for the ALFF to match.
I am planning to either run this as a standalone script to just get fALFF in addition to ALFF or fork github repository and add these lines and make few downstream changes to reflect the change (if that is okay?)
# falff is sum(alff)/sum(full power spectrum range). Re-using the ff_alff lower bound so the denominator is power ([f_alff[0]-inf])
falff[ii] = np.sum(power_spec_density_sqrt[ff_alff[0]: ff_alff[1]]) / np.sum(power_spec_density_sqrt[ff_alff[0]:])
falff = np.reshape(falff, [len(falff), 1])
Thank you once again! If I may make a small request (knowing that fALFF is also used a lot in addition to other resting state derivatives) and if it is not too much of a hassle it might be best to add it in the next release.