Running pyAFQ 2.x defaults in pyAFQ 3.x#

from AFQ.api.group import GroupAFQ
import AFQ.data.fetch as afd
import AFQ.definitions.image as afm
import AFQ.api.bundle_dict as abd

import os.path as op

afd.organize_stanford_data()
2026-05-26 22:57:31,228	INFO util.py:154 -- Missing packages: ['ipywidgets']. Run `pip install -U ipywidgets`, then restart the notebook server for rich notebook output.
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Cell In[1], line 8
      4 import AFQ.api.bundle_dict as abd
      5 
      6 import os.path as op
      7 
----> 8 afd.organize_stanford_data()

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/AFQ/data/fetch.py:1819, in organize_stanford_data(path, clear_previous_afq)
   1817 # fetches data for first subject and session
   1818 logger.info("fetching Stanford HARDI data")
-> 1819 dpd.fetch_stanford_hardi()
   1821 if path is None:
   1822     if not op.exists(afq_home):

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/dipy/data/fetcher.py:494, in _make_fetcher.<locals>.fetcher(include_optional)
    491         continue
    492     files[str(n)] = (baseurl + f, md5_list[i] if md5_list is not None else None)
--> 494 fetch_data(files, folder, data_size=data_size, use_headers=use_headers)
    496 if msg is not None:
    497     logger.info(msg)

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/data/fetcher.py:397, in fetch_data(files, folder, data_size, use_headers, raise_on_error)
    395 logger.info(f"From: {url}")
    396 try:
--> 397     _get_file_data(fullpath, url, use_headers=use_headers, stored_md5=md5)
    398     successful_downloads += 1
    399 except (FetcherError, Exception) as e:

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/dipy/data/fetcher.py:262, in _get_file_data(fname, url, use_headers, timeout, max_retries, stored_md5)
    260 with open(fname, "wb") as data:
    261     if response_size is None:
--> 262         copyfileobj(opener, data)
    263     else:
    264         copyfileobj_withprogress(opener, data, response_size)

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/shutil.py:203, in copyfileobj(fsrc, fdst, length)
    201 fsrc_read = fsrc.read
    202 fdst_write = fdst.write
--> 203 while buf := fsrc_read(length):
    204     fdst_write(buf)

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/http/client.py:478, in HTTPResponse.read(self, amt)
    475     return b""
    477 if self.chunked:
--> 478     return self._read_chunked(amt)
    480 if amt is not None and amt >= 0:
    481     if self.length is not None and amt > self.length:
    482         # clip the read to the "end of response"

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/http/client.py:608, in HTTPResponse._read_chunked(self, amt)
    605     self.chunk_left = chunk_left - amt
    606     break
--> 608 value.append(self._safe_read(chunk_left))
    609 if amt is not None:
    610     amt -= chunk_left

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/http/client.py:648, in HTTPResponse._safe_read(self, amt)
    641 """Read the number of bytes requested.
    642 
    643 This function should be used when <amt> bytes "should" be present for
    644 reading. If the bytes are truly not available (due to EOF), then the
    645 IncompleteRead exception can be used to detect the problem.
    646 """
    647 cursize = min(amt, _MIN_READ_BUF_SIZE)
--> 648 data = self.fp.read(cursize)
    649 if len(data) >= amt:
    650     return data

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/socket.py:719, in SocketIO.readinto(self, b)
    717     raise OSError("cannot read from timed out object")
    718 try:
--> 719     return self._sock.recv_into(b)
    720 except timeout:
    721     self._timeout_occurred = True

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/ssl.py:1304, in SSLSocket.recv_into(self, buffer, nbytes, flags)
   1300     if flags != 0:
   1301         raise ValueError(
   1302           "non-zero flags not allowed in calls to recv_into() on %s" %
   1303           self.__class__)
-> 1304     return self.read(nbytes, buffer)
   1305 else:
   1306     return super().recv_into(buffer, nbytes, flags)

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/ssl.py:1138, in SSLSocket.read(self, len, buffer)
   1136 try:
   1137     if buffer is not None:
-> 1138         return self._sslobj.read(len, buffer)
   1139     else:
   1140         return self._sslobj.read(len)

KeyboardInterrupt: 

Tractography parameters in the old way#

In pyAFQ 2.x, we used CSD with no asymmetric filtering, and seeded streamlines throughout the white matter instead of on the white matter / gray matter interface.

tracking_params = dict(
    odf_model="csd",
    n_seeds=1,
    random_seeds=False,
    minlen=50,
    directions="prob",
    seed_mask=afm.ScalarImage("dti_fa"),
    seed_threshold=0.2
)

Partial Volume Estimate in the old way#

In pyAFQ 2.x, we did not use PVE and instead thresholded on fractional anisotropy (FA) maps to create seed and stopping masks. Here, we recreate the PVE images using the FA maps. Note there the CSF map is not used in this case. Additionally, in pyAFQ 2.x, the brain mask was calculated using median OTSU. Here, we import it from the Freesurfer segmentation instead.

pve = afm.PVEImages(
    afm.ThresholdedScalarImage(
        "dti_fa",
        upper_bound=0.0),
    afm.ThresholdedScalarImage(
        "dti_fa",
        upper_bound=0.2),
    afm.ThresholdedScalarImage(
        "dti_fa",
        lower_bound=0.2))

bm_def = afm.LabelledImageFile(
    suffix="seg", filters={"scope": "freesurfer"},
    exclusive_labels=[0])

VOF / pAF / CST in the old way#

In pyAFQ 2.x, the vertical occipital fasciculus (VOF) and posterior arcuate fasciculus (pAF) were defined differently. The pAF in 3.0 has an increased restriction that it cannot overlap with the arcuate by more than 30%. The VOF has several changes:

  1. one endpoint ROI instead of both, but there is a minimum length requirement to of 25mm to compensate;

  2. The allowed overlap with the pAF has been reduced;

  3. it must be lateral to the inferior fronto-occipital fasciculus instead of the inferior longitudinal fasciculus;

  4. cleaning has been changed: there is now mahalanobis cleaning on orientation, and isolation forest cleaning instead of mahalanobis for distance. Additionally, in the new version, the inferior endpoints of the corticospinal tracts (CST) were removed, and the superior longitudinal fasciculus (SLF) was broken into three sub-bundles.

templates = afd.read_templates(as_img=False)
old_vof_paf_cst_slf_definitions = abd.BundleDict({
        'Left Corticospinal': {
            'cross_midline': False,
            'include': [templates['CST_roi2_L'],
                        templates['CST_roi1_L']],
            'exclude': [],
            'space': 'template',
            'prob_map': templates['CST_L_prob_map'],
            'end': templates['CST_L_start'],
            'start': templates['CST_L_end']},
        'Right Corticospinal': {
            'cross_midline': False,
            'include': [templates['CST_roi2_R'],
                        templates['CST_roi1_R']],
            'exclude': [],
            'space': 'template',
            'prob_map': templates['CST_R_prob_map'],
            'end': templates['CST_R_start'],
            'start': templates['CST_R_end']},
        "Left Superior Longitudinal": {
            "cross_midline": False,
            "include": [templates["SLF_roi1_L"], templates["SLF_roi2_L"]],
            "exclude": [templates["SLFt_roi2_L"]],
            "space": "template",
            "prob_map": templates["SLF_L_prob_map"],
            "start": templates["SLF_L_start"],
            "end": templates["SLF_L_end"]},
        "Right Superior Longitudinal": {
            "cross_midline": False,
            "include": [templates["SLF_roi1_R"], templates["SLF_roi2_R"]],
            "exclude": [templates["SLFt_roi2_R"]],
            "space": "template",
            "prob_map": templates["SLF_R_prob_map"],
            "start": templates["SLF_R_start"],
            "end": templates["SLF_R_end"]},
        'Left Posterior Arcuate': {'cross_midline': False,
                                   'include': [templates['SLFt_roi2_L']],
                                   'exclude': [templates['SLF_roi1_L']],
                                   'space': 'template',
                                   'start': templates['pARC_L_start'],
                                   'primary_axis': 'I/S',
                                   'primary_axis_percentage': 40},
        'Right Posterior Arcuate': {'cross_midline': False,
                                    'include': [templates['SLFt_roi2_R']],
                                    'exclude': [templates['SLF_roi1_R']],
                                    'space': 'template',
                                    'start': templates['pARC_R_start'],
                                    'primary_axis': 'I/S',
                                    'primary_axis_percentage': 40},
        'Left Vertical Occipital': {'cross_midline': False,
                                    'space': 'template',
                                    'start': templates['VOF_L_start'],
                                    'end': templates['VOF_L_end'],
                                    'inc_addtol': [4, 0],
                                    'Left Arcuate': {
                                        'node_thresh': 20},
                                    'Left Posterior Arcuate': {
                                        'node_thresh': 1,
                                        'core': 'Anterior'},
                                    'Left Inferior Longitudinal': {
                                        'core': 'Right'},
                                    'primary_axis': 'I/S',
                                    'primary_axis_percentage': 40},
        'Right Vertical Occipital': {'cross_midline': False,
                                     'space': 'template',
                                     'start': templates['VOF_R_start'],
                                     'end': templates['VOF_R_end'],
                                     'inc_addtol': [4, 0],
                                     'Right Arcuate': {
                                         'node_thresh': 20},
                                     'Right Posterior Arcuate': {
                                         'node_thresh': 1,
                                         'core': 'Anterior'},
                                     'Right Inferior Longitudinal': {
                                         'core': 'Left'},
                                     'primary_axis': 'I/S',
                                     'primary_axis_percentage': 40}})

Callosal bundles in the old way#

In pyAFQ 2.x, the callosal bundles were cleaned using mahalnobis instead of isolation forest.

callosal_templates =\
    afd.read_callosum_templates(as_img=False)
callosal_bd = abd.BundleDict({
    'Callosum Anterior Frontal': {
        'cross_midline': True,
        'include': [callosal_templates['R_AntFrontal'],
                    callosal_templates['Callosum_midsag'],
                    callosal_templates['L_AntFrontal']],
        'exclude': [],
        'space': 'template'},
    'Callosum Motor': {
        'cross_midline': True,
        'include': [callosal_templates['R_Motor'],
                    callosal_templates['Callosum_midsag'],
                    callosal_templates['L_Motor']],
        'exclude': [],
        'space': 'template'},
    'Callosum Occipital': {
        'cross_midline': True,
        'include': [callosal_templates['R_Occipital'],
                    callosal_templates['Callosum_midsag'],
                    callosal_templates['L_Occipital']],
        'exclude': [],
        'space': 'template'},
    'Callosum Orbital': {
        'cross_midline': True,
        'include': [callosal_templates['R_Orbital'],
                    callosal_templates['Callosum_midsag'],
                    callosal_templates['L_Orbital']],
        'exclude': [],
        'space': 'template'},
    'Callosum Posterior Parietal': {
        'cross_midline': True,
        'include': [callosal_templates['R_PostParietal'],
                    callosal_templates['Callosum_midsag'],
                    callosal_templates['L_PostParietal']],
        'exclude': [],
        'space': 'template'},
    'Callosum Superior Frontal': {
        'cross_midline': True,
        'include': [callosal_templates['R_SupFrontal'],
                    callosal_templates['Callosum_midsag'],
                    callosal_templates['L_SupFrontal']],
        'exclude': [],
        'space': 'template'},
    'Callosum Superior Parietal': {
        'cross_midline': True,
        'include': [callosal_templates['R_SupParietal'],
                    callosal_templates['Callosum_midsag'],
                    callosal_templates['L_SupParietal']],
        'exclude': [],
        'space': 'template'},
    'Callosum Temporal': {
        'cross_midline': True,
        'include': [callosal_templates['R_Temporal'],
                    callosal_templates['Callosum_midsag'],
                    callosal_templates['L_Temporal']],
        'exclude': [],
        'space': 'template'}})


bundle_info = abd.default_bd() + \
    old_vof_paf_cst_slf_definitions + \
    callosal_bd

Run GroupAFQ with these parameters#

Finally, we can run GroupAFQ with the 2.0 parameters. In sum, we changed: Tractography parameters to use CSD and seed throughout the white matter; PVE images to use FA thresholding; Bundle definitions for VOF, pAF, and CST to use the old definitions; Callosal bundles to use mahalanobis cleaning.

myafq = GroupAFQ(
    bids_path=op.join(afd.afq_home, 'stanford_hardi'),
    dwi_preproc_pipeline='vistasoft',
    t1_preproc_pipeline='freesurfer',
    tracking_params=tracking_params,
    brain_mask_definition=bm_def,
    pve=pve,
    bundle_info=bundle_info)

myafq.export_all()