Getting started with pyAFQ - ParticipantAFQ#

import os
import os.path as op

import matplotlib.pyplot as plt
import nibabel as nib
import plotly

from AFQ.api.participant import ParticipantAFQ
import AFQ.data.fetch as afd
import AFQ.definitions.image as afm
/opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm
2026-05-26 23:00:48,945	INFO util.py:154 -- Missing packages: ['ipywidgets']. Run `pip install -U ipywidgets`, then restart the notebook server for rich notebook output.

Example data#

The following call downloads a a single subject’s data from the Healthy Brain Network Processed Open Diffusion Derivatives dataset (HBN-POD2) [1], [2] and organizes it in BIDS in the user’s home directory under::

~/AFQ_data/HBN/

The data is also placed in a derivatives directory, signifying that it has already undergone the required preprocessing necessary for pyAFQ to run.

afd.fetch_hbn_preproc(["NDARAA948VFH"])
({'/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_desc-brain_mask.nii.gz': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_desc-brain_mask.nii.gz',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_desc-preproc_T1w.nii.gz': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_desc-preproc_T1w.nii.gz',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_dseg.nii.gz': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_dseg.nii.gz',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_from-MNI152NLin2009cAsym_to-T1w_mode-image_xfm.h5': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_from-MNI152NLin2009cAsym_to-T1w_mode-image_xfm.h5',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_from-T1w_to-MNI152NLin2009cAsym_mode-image_xfm.h5': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_from-T1w_to-MNI152NLin2009cAsym_mode-image_xfm.h5',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_label-CSF_probseg.nii.gz': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_label-CSF_probseg.nii.gz',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_label-GM_probseg.nii.gz': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_label-GM_probseg.nii.gz',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_label-WM_probseg.nii.gz': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_label-WM_probseg.nii.gz',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_space-MNI152NLin2009cAsym_desc-brain_mask.nii.gz': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_space-MNI152NLin2009cAsym_desc-brain_mask.nii.gz',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_space-MNI152NLin2009cAsym_desc-preproc_T1w.nii.gz': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_space-MNI152NLin2009cAsym_desc-preproc_T1w.nii.gz',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_space-MNI152NLin2009cAsym_dseg.nii.gz': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_space-MNI152NLin2009cAsym_dseg.nii.gz',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_space-MNI152NLin2009cAsym_label-CSF_probseg.nii.gz': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_space-MNI152NLin2009cAsym_label-CSF_probseg.nii.gz',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_space-MNI152NLin2009cAsym_label-GM_probseg.nii.gz': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_space-MNI152NLin2009cAsym_label-GM_probseg.nii.gz',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_space-MNI152NLin2009cAsym_label-WM_probseg.nii.gz': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_space-MNI152NLin2009cAsym_label-WM_probseg.nii.gz',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/figures/sub-NDARAA948VFH_seg_brainmask.svg': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/figures/sub-NDARAA948VFH_seg_brainmask.svg',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/figures/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_carpetplot.svg': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/figures/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_carpetplot.svg',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/figures/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_coreg.svg': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/figures/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_coreg.svg',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/figures/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_desc-resampled_b0ref.svg': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/figures/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_desc-resampled_b0ref.svg',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/figures/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_desc-sdc_b0.svg': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/figures/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_desc-sdc_b0.svg',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/figures/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_dwi_denoise_ses_HBNsiteRU_acq_64dir_dwi_wf_biascorr.svg': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/figures/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_dwi_denoise_ses_HBNsiteRU_acq_64dir_dwi_wf_biascorr.svg',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/figures/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_dwi_denoise_ses_HBNsiteRU_acq_64dir_dwi_wf_denoising.svg': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/figures/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_dwi_denoise_ses_HBNsiteRU_acq_64dir_dwi_wf_denoising.svg',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/figures/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_dwi_denoise_ses_HBNsiteRU_acq_64dir_dwi_wf_unringing.svg': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/figures/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_dwi_denoise_ses_HBNsiteRU_acq_64dir_dwi_wf_unringing.svg',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/figures/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_sampling_scheme.gif': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/figures/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_sampling_scheme.gif',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/figures/sub-NDARAA948VFH_t1_2_mni.svg': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/figures/sub-NDARAA948VFH_t1_2_mni.svg',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/anat/sub-NDARAA948VFH_ses-HBNsiteRU_acq-HCP_from-orig_to-T1w_mode-image_xfm.txt': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/anat/sub-NDARAA948VFH_ses-HBNsiteRU_acq-HCP_from-orig_to-T1w_mode-image_xfm.txt',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_confounds.tsv': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_confounds.tsv',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_desc-ImageQC_dwi.csv': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_desc-ImageQC_dwi.csv',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_desc-SliceQC_dwi.json': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_desc-SliceQC_dwi.json',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_dwiqc.json': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_dwiqc.json',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-brain_mask.nii.gz': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-brain_mask.nii.gz',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-eddy_cnr.nii.gz': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-eddy_cnr.nii.gz',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-preproc_dwi.b': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-preproc_dwi.b',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-preproc_dwi.bval': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-preproc_dwi.bval',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-preproc_dwi.bvec': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-preproc_dwi.bvec',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-preproc_dwi.nii.gz': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-preproc_dwi.nii.gz',
  '/home/runner/AFQ_data/HBN/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_dwiref.nii.gz': 'data/Projects/HBN/BIDS_curated/derivatives/qsiprep/sub-NDARAA948VFH/ses-HBNsiteRU/dwi/sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_dwiref.nii.gz'},
 '/home/runner/AFQ_data/HBN')

Defining data files#

If your data is not in BIDS format, you can still use pyAFQ. If you have BIDS compliant dataset, you can use GroupAFQ instead (:doc:plot_001_group_afq_api). Otherwise, You will need to define the data files that you want to use. In this case, we will define the data files for the subject we downloaded above. The data files are located in the ~/AFQ_data/HBN/derivatives/qsiprep directory, and are organized into a BIDS compliant directory structure. The data files are located in the dwi directories.

sub_dir = op.join(afd.afq_home, "HBN", "derivatives", "qsiprep",
                   "sub-NDARAA948VFH")
dwi_data_file = op.join(sub_dir, "ses-HBNsiteRU", "dwi", (
    "sub-NDARAA948VFH_"
    "ses-HBNsiteRU_"
    "acq-64dir_space-T1w_desc-preproc_dwi.nii.gz"))
bval_file = op.join(sub_dir, "ses-HBNsiteRU", "dwi", (
    "sub-NDARAA948VFH_"
    "ses-HBNsiteRU_"
    "acq-64dir_space-T1w_desc-preproc_dwi.bval"))
bvec_file = op.join(sub_dir, "ses-HBNsiteRU", "dwi", (
    "sub-NDARAA948VFH_"
    "ses-HBNsiteRU_"
    "acq-64dir_space-T1w_desc-preproc_dwi.bvec"))
t1_file = op.join(sub_dir, "anat",
                  "sub-NDARAA948VFH_desc-preproc_T1w.nii.gz")

# You will also need to define the output directory where you want to store the
# results. The output directory needs to exist before exporting ParticipantAFQ
# results.

output_dir = op.join(afd.afq_home, "HBN",
                     "derivatives", "afq", "sub-NDARAA948VFH",
                     "ses-HBNsiteRU", "dwi")
os.makedirs(output_dir, exist_ok=True)

Set tractography parameters (optional)#

We make create a tracking_params variable, which we will pass to the ParticipantAFQ object which specifies that we want 100,000 seeds randomly distributed in the white matter. We only do this to make this example faster and consume less space; normally, we use more seeds.

tracking_params = dict(n_seeds=1e5,
                       random_seeds=True,
                       rng_seed=2025,
                       trx=True)

Define PVE images (optional)#

To improve segmentation and tractography results, we can provide partial volume estimate (PVE) images for the cerebrospinal fluid (CSF), gray matter (GM), and white matter (WM). Here, we define these images using the AFQ.definitions.image.PVEImages class, which takes as input three AFQ.definitions.image.ImageFile objects, one for each tissue type. One can also provide a single PVE image with all three tissue types using the AFQ.definitions.image.PVEImage class. Finally, by default, if no PVE images are provided, pyAFQ will use SynthSeg2 to compute these images.

pve = afm.PVEImages(
    afm.ImageFile(
        path=op.join(sub_dir, "anat", 
                     "sub-NDARAA948VFH_label-CSF_probseg.nii.gz")),
    afm.ImageFile(
        path=op.join(sub_dir, "anat", 
                     "sub-NDARAA948VFH_label-GM_probseg.nii.gz")),
    afm.ImageFile(
        path=op.join(sub_dir, "anat", 
                     "sub-NDARAA948VFH_label-WM_probseg.nii.gz")))

Brain Mask Definition (optional)#

By default, pyAFQ will compute a brain mask from the T1. However, this requires onnxruntime to be installed. If you do not have onnxruntime installed, or if you want to use a different brain mask, you can specify it here.

brain_mask_definition = afm.ImageFile(
    path=op.join(sub_dir, "anat", "sub-NDARAA948VFH_desc-brain_mask.nii.gz"))

Initialize a ParticipantAFQ object:#

Creates a ParticipantAFQ object, that encapsulates tractometry. This object can be used to manage the entire :doc:/explanations/index, including:

  • Tractography

  • Registration

  • Segmentation

  • Cleaning

  • Profiling

  • Visualization

To initialize the object, we will pass in the diffusion data files and specify the output directory where we want to store the results. We will also pass in the tracking parameters we defined above.

myafq = ParticipantAFQ(
    dwi_data_file=dwi_data_file,
    bval_file=bval_file,
    bvec_file=bvec_file,
    t1_file=t1_file,
    output_dir=output_dir,
    tracking_params=tracking_params,
    pve=pve,
    brain_mask_definition=brain_mask_definition,
)

Calculating DTI FA (Diffusion Tensor Imaging Fractional Anisotropy)#

The ParticipantAFQ object has a method called export, which allows the user to calculate various derived quantities from the data.

For example, FA can be computed using the DTI model, by explicitly calling myafq.export("dti_fa"). This triggers the computation of DTI parameters, and stores the results in the AFQ derivatives directory. In addition, it calculates the FA from these parameters and stores it in a different file in the same directory.

Note

The AFQ API computes quantities lazily. This means that DTI parameters are not computed until they are required. This means that the first line below is the one that requires time.

The result of the call to export is the filename of the corresponding FA files.

FA_fname = myafq.export("dti_fa")

We will then use nibabel to load the deriviative file and retrieve the data array.

FA_img = nib.load(FA_fname)
FA = FA_img.get_fdata()

Visualize the result with Matplotlib#

At this point FA is an array, and we can use standard Python tools to visualize it or perform additional computations with it.

In this case we are going to take an axial slice halfway through the FA data array and plot using a sequential color map.

Note

The data array is structured as a xyz coordinate system.

fig, ax = plt.subplots(1)
ax.matshow(FA[:, :, FA.shape[-1] // 2], cmap="viridis")
ax.axis("off")
(np.float64(-0.5), np.float64(128.5), np.float64(107.5), np.float64(-0.5))
../../_images/59fe03523cc3ab7f377472f68db7834070d94b89aeb0b9f07d95ebe9c2c58736.png

Recognizing the bundles and calculating tract profiles:#

Typically, users of pyAFQ are interested in calculating not only an overall map of the FA, but also the major white matter pathways (or bundles) and tract profiles of tissue properties along their length. To trigger the pyAFQ pipeline that calculates the profiles, users can call the export("profiles") method:

Note

Running the code below triggers the full pipeline of operations leading to the computation of the tract profiles. Therefore, it takes a little while to run (about 40 minutes, typically).

myafq.export("profiles")
INFO:AFQ:Calculating _desc-bundles_tractography...
INFO:AFQ:Calculating _tractography...
INFO:AFQ:Calculating _model-csd_param-wm_dwimap.nii.gz...
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Cell In[11], line 1
----> 1 myafq.export("profiles")

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/AFQ/api/participant.py:218, in ParticipantAFQ.export(self, attr_name)
    215 else:
    216     plan = self.plans_dict
--> 218 return val_from_plan(plan, attr_name)

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/AFQ/api/utils.py:146, in val_from_plan(plan, attr_name)
    144 def val_from_plan(plan, attr_name):
    145     try:
--> 146         return plan[attr_name]
    147     except Exception as err:
    148         current_err = err

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/pcollections/_lazy.py:381, in ldict.__getitem__(self, key)
    379 v = pdict.__getitem__(self, key)
    380 if isinstance(v, lazy):
--> 381     v = v()
    382 return v

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/pcollections/_lazy.py:143, in lazy.__call__(self)
    141     val = self.value
    142 else:
--> 143     val = part()
    144     # We've successfully calculated the value; set the members
    145     # appropriately.
    146     object.__setattr__(self, 'value', val)

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/immlib/workflow/_core.py:855, in plan._source_lookup(inputtup, calctup, src)
    853     (cidx, oidx) = src
    854     lazycalc = calctup[cidx]
--> 855     val = lazycalc()[oidx]
    856 else:
    857     val = inputtup[src]()

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/pcollections/_lazy.py:143, in lazy.__call__(self)
    141     val = self.value
    142 else:
--> 143     val = part()
    144     # We've successfully calculated the value; set the members
    145     # appropriately.
    146     object.__setattr__(self, 'value', val)

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/immlib/workflow/_core.py:884, in plan._make_calctup.<locals>.<lambda>(c, args)
    881 # We take advantage of Python's weak closures here:
    882 calctup = ()
    883 calctup = tuple(
--> 884     lazy(lambda c,args: f(inputtup, calctup, c, args), c, args)
    885     for (c,args) in zip(calcdata.calcs, calcdata.args))
    886 return calctup

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/immlib/workflow/_core.py:868, in plan._call_calc(inputtup, calctup, c, args)
    866 kwargs = {}
    867 c = to_calc(c)
--> 868 for (p,arg) in zip(c.signature.parameters.values(), argvals):
    869     if p.kind == p.POSITIONAL_ONLY:
    870         args.append[arg]

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/immlib/workflow/_core.py:855, in plan._source_lookup(inputtup, calctup, src)
    853     (cidx, oidx) = src
    854     lazycalc = calctup[cidx]
--> 855     val = lazycalc()[oidx]
    856 else:
    857     val = inputtup[src]()

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/pcollections/_lazy.py:143, in lazy.__call__(self)
    141     val = self.value
    142 else:
--> 143     val = part()
    144     # We've successfully calculated the value; set the members
    145     # appropriately.
    146     object.__setattr__(self, 'value', val)

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/immlib/workflow/_core.py:884, in plan._make_calctup.<locals>.<lambda>(c, args)
    881 # We take advantage of Python's weak closures here:
    882 calctup = ()
    883 calctup = tuple(
--> 884     lazy(lambda c,args: f(inputtup, calctup, c, args), c, args)
    885     for (c,args) in zip(calcdata.calcs, calcdata.args))
    886 return calctup

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/immlib/workflow/_core.py:873, in plan._call_calc(inputtup, calctup, c, args)
    871     else:
    872         kwargs[p.name] = arg
--> 873 r = c.eager_call(*args, **kwargs)
    874 if is_amap(r):
    875     return tuple(map(r.__getitem__, c.outputs))

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/immlib/workflow/_core.py:448, in calc.eager_call(self, *args, **kwargs)
    432 """Eagerly calls the given calculation using the arguments.
    433 
    434 ``c.eager_call(...)`` returns the result of calling the calculation
   (...)    444 calc.eager_mapcall, calc.lazy_call, calc.lazy_mapcall
    445 """
    446 # Now we just pass these arguments along (the function itself has been
    447 # given the caching code via decorators already).
--> 448 res = self.function(*args, **kwargs)
    449 # Now interpret the result.
    450 outs = self.outputs

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/AFQ/tasks/decorators.py:104, in as_file.<locals>._as_file.<locals>.wrapper_as_file(*args, **kwargs)
    101 logger.info(f"Calculating {calculation_name}...")
    103 try:
--> 104     results = func(*args, **kwargs)
    106     if len(output_specs) == 1:
    107         results = [results]

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/AFQ/tasks/segmentation.py:56, in segment(structural_imap, data_imap, mapping_imap, tractography_imap, segmentation_params)
     54 bundle_dict = data_imap["bundle_dict"]
     55 reg_template = data_imap["reg_template"]
---> 56 streamlines = tractography_imap["streamlines"]
     57 if (
     58     streamlines.endswith(".trk")
     59     or streamlines.endswith(".tck")
     60     or streamlines.endswith(".vtk")
     61 ):
     62     tg = load_tractogram(streamlines, data_imap["dwi"], bbox_valid_check=False)

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/pcollections/_lazy.py:381, in ldict.__getitem__(self, key)
    379 v = pdict.__getitem__(self, key)
    380 if isinstance(v, lazy):
--> 381     v = v()
    382 return v

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/pcollections/_lazy.py:143, in lazy.__call__(self)
    141     val = self.value
    142 else:
--> 143     val = part()
    144     # We've successfully calculated the value; set the members
    145     # appropriately.
    146     object.__setattr__(self, 'value', val)

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/immlib/workflow/_core.py:855, in plan._source_lookup(inputtup, calctup, src)
    853     (cidx, oidx) = src
    854     lazycalc = calctup[cidx]
--> 855     val = lazycalc()[oidx]
    856 else:
    857     val = inputtup[src]()

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/pcollections/_lazy.py:143, in lazy.__call__(self)
    141     val = self.value
    142 else:
--> 143     val = part()
    144     # We've successfully calculated the value; set the members
    145     # appropriately.
    146     object.__setattr__(self, 'value', val)

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/immlib/workflow/_core.py:884, in plan._make_calctup.<locals>.<lambda>(c, args)
    881 # We take advantage of Python's weak closures here:
    882 calctup = ()
    883 calctup = tuple(
--> 884     lazy(lambda c,args: f(inputtup, calctup, c, args), c, args)
    885     for (c,args) in zip(calcdata.calcs, calcdata.args))
    886 return calctup

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/immlib/workflow/_core.py:873, in plan._call_calc(inputtup, calctup, c, args)
    871     else:
    872         kwargs[p.name] = arg
--> 873 r = c.eager_call(*args, **kwargs)
    874 if is_amap(r):
    875     return tuple(map(r.__getitem__, c.outputs))

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/immlib/workflow/_core.py:448, in calc.eager_call(self, *args, **kwargs)
    432 """Eagerly calls the given calculation using the arguments.
    433 
    434 ``c.eager_call(...)`` returns the result of calling the calculation
   (...)    444 calc.eager_mapcall, calc.lazy_call, calc.lazy_mapcall
    445 """
    446 # Now we just pass these arguments along (the function itself has been
    447 # given the caching code via decorators already).
--> 448 res = self.function(*args, **kwargs)
    449 # Now interpret the result.
    450 outs = self.outputs

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/AFQ/tasks/decorators.py:104, in as_file.<locals>._as_file.<locals>.wrapper_as_file(*args, **kwargs)
    101 logger.info(f"Calculating {calculation_name}...")
    103 try:
--> 104     results = func(*args, **kwargs)
    106     if len(output_specs) == 1:
    107         results = [results]

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/AFQ/tasks/tractography.py:82, in streamlines(structural_imap, data_imap, seed, tissue_imap, citations, tracking_params)
     79 citations.add("smith2012anatomically")
     81 this_tracking_params = tracking_params.copy()
---> 82 fodf = _fiber_odf(data_imap, tissue_imap, tracking_params)
     84 # get masks
     85 this_tracking_params["seed_mask"] = nib.load(seed).get_fdata()

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/AFQ/tasks/tractography.py:50, in _fiber_odf(data_imap, tissue_imap, tracking_params)
     48 calc_name = f"{odf_model.lower()}_params"
     49 if calc_name in data_imap:
---> 50     params_file = data_imap[calc_name]
     51 elif calc_name in tissue_imap:
     52     params_file = tissue_imap[calc_name]

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/pcollections/_lazy.py:381, in ldict.__getitem__(self, key)
    379 v = pdict.__getitem__(self, key)
    380 if isinstance(v, lazy):
--> 381     v = v()
    382 return v

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/pcollections/_lazy.py:143, in lazy.__call__(self)
    141     val = self.value
    142 else:
--> 143     val = part()
    144     # We've successfully calculated the value; set the members
    145     # appropriately.
    146     object.__setattr__(self, 'value', val)

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/immlib/workflow/_core.py:855, in plan._source_lookup(inputtup, calctup, src)
    853     (cidx, oidx) = src
    854     lazycalc = calctup[cidx]
--> 855     val = lazycalc()[oidx]
    856 else:
    857     val = inputtup[src]()

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/pcollections/_lazy.py:143, in lazy.__call__(self)
    141     val = self.value
    142 else:
--> 143     val = part()
    144     # We've successfully calculated the value; set the members
    145     # appropriately.
    146     object.__setattr__(self, 'value', val)

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/immlib/workflow/_core.py:884, in plan._make_calctup.<locals>.<lambda>(c, args)
    881 # We take advantage of Python's weak closures here:
    882 calctup = ()
    883 calctup = tuple(
--> 884     lazy(lambda c,args: f(inputtup, calctup, c, args), c, args)
    885     for (c,args) in zip(calcdata.calcs, calcdata.args))
    886 return calctup

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/immlib/workflow/_core.py:868, in plan._call_calc(inputtup, calctup, c, args)
    866 kwargs = {}
    867 c = to_calc(c)
--> 868 for (p,arg) in zip(c.signature.parameters.values(), argvals):
    869     if p.kind == p.POSITIONAL_ONLY:
    870         args.append[arg]

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/immlib/workflow/_core.py:855, in plan._source_lookup(inputtup, calctup, src)
    853     (cidx, oidx) = src
    854     lazycalc = calctup[cidx]
--> 855     val = lazycalc()[oidx]
    856 else:
    857     val = inputtup[src]()

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/pcollections/_lazy.py:143, in lazy.__call__(self)
    141     val = self.value
    142 else:
--> 143     val = part()
    144     # We've successfully calculated the value; set the members
    145     # appropriately.
    146     object.__setattr__(self, 'value', val)

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/immlib/workflow/_core.py:884, in plan._make_calctup.<locals>.<lambda>(c, args)
    881 # We take advantage of Python's weak closures here:
    882 calctup = ()
    883 calctup = tuple(
--> 884     lazy(lambda c,args: f(inputtup, calctup, c, args), c, args)
    885     for (c,args) in zip(calcdata.calcs, calcdata.args))
    886 return calctup

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/immlib/workflow/_core.py:873, in plan._call_calc(inputtup, calctup, c, args)
    871     else:
    872         kwargs[p.name] = arg
--> 873 r = c.eager_call(*args, **kwargs)
    874 if is_amap(r):
    875     return tuple(map(r.__getitem__, c.outputs))

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/immlib/workflow/_core.py:448, in calc.eager_call(self, *args, **kwargs)
    432 """Eagerly calls the given calculation using the arguments.
    433 
    434 ``c.eager_call(...)`` returns the result of calling the calculation
   (...)    444 calc.eager_mapcall, calc.lazy_call, calc.lazy_mapcall
    445 """
    446 # Now we just pass these arguments along (the function itself has been
    447 # given the caching code via decorators already).
--> 448 res = self.function(*args, **kwargs)
    449 # Now interpret the result.
    450 outs = self.outputs

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/AFQ/tasks/decorators.py:104, in as_file.<locals>._as_file.<locals>.wrapper_as_file(*args, **kwargs)
    101 logger.info(f"Calculating {calculation_name}...")
    103 try:
--> 104     results = func(*args, **kwargs)
    106     if len(output_specs) == 1:
    107         results = [results]

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/AFQ/tasks/decorators.py:228, in as_img.<locals>.wrapper_as_img(*args, **kwargs)
    225     dwi_affine = data_imap["dwi_affine"]
    227 start_time = time()
--> 228 results = func(*args, **kwargs)
    229 elapsed = time() - start_time
    231 is_single_output = isinstance(results[0], np.ndarray)

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/AFQ/tasks/data.py:492, in csd_params(dwi, brain_mask, gtab, data, citations, csd_response, csd_sh_order_max, csd_lambda_, csd_tau, csd_fa_thr)
    490 mask = nib.load(brain_mask).get_fdata()
    491 try:
--> 492     csdm, csdf, sh_order_max = csd_fit_model(
    493         gtab,
    494         data,
    495         mask=mask,
    496         response=csd_response,
    497         sh_order_max=csd_sh_order_max,
    498         lambda_=csd_lambda_,
    499         tau=csd_tau,
    500         csd_fa_thr=csd_fa_thr,
    501     )
    502 except CsdNanResponseError as e:
    503     raise CsdNanResponseError(
    504         f"Could not compute CSD response function for file: {dwi}."
    505     ) from e

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/AFQ/models/csd.py:75, in _fit(gtab, data, mask, response, sh_order_max, lambda_, tau, csd_fa_thr)
     71 """
     72 Helper function that does the core of fitting a model to data.
     73 """
     74 model, sh_order_max = _model(gtab, data, response, sh_order_max, csd_fa_thr)
---> 75 return model, model.fit(data, mask=mask), sh_order_max

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/dipy/reconst/multi_voxel.py:282, in multi_voxel_fit.<locals>.decorator.<locals>.new_fit(self, data, mask, **kwargs)
    279 if weights_is_array:
    280     kwargs["weights"] = weights[ijk]
--> 282 svf = single_voxel_fit(self, data[ijk], **kwargs)
    284 # Not all fit methods return extra, handle this here
    285 if isinstance(svf, tuple):

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/dipy/reconst/csdeconv.py:196, in ConstrainedSphericalDeconvModel.fit(self, data, **kwargs)
    193 @multi_voxel_fit
    194 def fit(self, data, **kwargs):
    195     dwi_data = data[self._where_dwi]
--> 196     shm_coeff, _ = csdeconv(
    197         dwi_data,
    198         self._X,
    199         self.B_reg,
    200         tau=self.tau,
    201         convergence=self.convergence,
    202         P=self._P,
    203     )
    204     return SphHarmFit(self, shm_coeff, None)

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/dipy/testing/decorators.py:201, in warning_for_keywords.<locals>.decorator.<locals>.wrapper(*args, **kwargs)
    194 # Check if the current version is within the warning range
    195 if (
    196     version.parse(from_version)
    197     <= version.parse(current_version)
    198     <= version.parse(until_version)
    199 ):
    200     # Convert positional to keyword arguments and issue a warning
--> 201     return convert_positional_to_keyword(func, args, kwargs)
    203 # If the version is greater than the until_version,
    204 # pass the arguments as they are
    205 elif version.parse(current_version) > version.parse(until_version):

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/dipy/testing/decorators.py:192, in warning_for_keywords.<locals>.decorator.<locals>.wrapper.<locals>.convert_positional_to_keyword(func, args, kwargs)
    182         warnings.warn(
    183             f"Pass {positionally_passed_kwonly_args} as keyword args. "
    184             f"From version {until_version} passing these as positional "
   (...)    187             stacklevel=3,
    188         )
    190     return func(*positional_args, **corrected_kwargs)
--> 192 return func(*args, **kwargs)

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/dipy/reconst/csdeconv.py:617, in csdeconv(dwsignal, X, B_reg, tau, convergence, P)
    614 H = B_reg.take(where_fodf_small, axis=0)
    616 # We use the Cholesky decomposition to solve for the SH coefficients.
--> 617 Q = P + np.dot(H.T, H)
    618 fodf_sh = _solve_cholesky(Q, z)
    620 # Sample the FOD using the regularization sphere and compute k.

KeyboardInterrupt: 

Visualizing the bundles and calculating tract profiles:#

The pyAFQ API provides several ways to visualize bundles and profiles.

First, we will run a function that exports an html file that contains an interactive visualization of the bundles that are segmented.

Note

By default we resample a 100 points within a bundle, however to reduce processing time we will only resample 50 points.

Once it is done running, it should pop a browser window open and let you interact with the bundles.

Note

You can hide or show a bundle by clicking the legend, or select a single bundle by double clicking the legend. The interactive visualization will also all you to pan, zoom, and rotate.

bundle_html = myafq.export("all_bundles_figure")
plotly.io.show(bundle_html[0])

We can also visualize the tract profiles in all of the bundles. These plots show both FA (left) and MD (right) laid out anatomically. To make this plot, it is required that you install with pip install pyAFQ[plot] so that you have the necessary dependencies.

fig_files = myafq.export("tract_profile_plots")

.. figure:: {{ fig_files[0] }}

Exporting citations#

Finally, we can export the citations for the some of methods used in this analysis. These are not guaranteed to be comprehensive, but they should be a good starting point.

myafq.export("citations")

References#

.. [1] Alexander LM, Escalera J, Ai L, et al. An open resource for transdiagnostic research in pediatric mental health and learning disorders. Sci Data. 2017;4:170181.

.. [2] Richie-Halford A, Cieslak M, Ai L, et al. An analysis-ready and quality controlled resource for pediatric brain white-matter research. Scientific Data. 2022;9(1):1-27.

.. [3] Cieslak M, Cook PA, He X, et al. QSIPrep: an integrative platform for preprocessing and reconstructing diffusion MRI data. Nat Methods. 2021;18(7):775-778.