Delineating cerebellar peduncles#

The cerebellar peduncles are white matter tracts that connect the cerebellum to the brainstem and cortex. In this example, we show how to delineate the cerebellar peduncles using a subject from the Healthy Brain Network dataset.

This how-to will focus on the definition of the Cerebellar Peduncles (CP) based on [1]_, [2].

import AFQ.data.fetch as afd
import AFQ.api.bundle_dict as abd
from AFQ.api.group import GroupAFQ
from AFQ.definitions.image import RoiImage, ImageFile


"""
We will use a subject from the HBN dataset. When considering the data
for this operation, look to see whether the acquisition volume includes the
cerbellum. If it does not, it will be hard to delineate the CPs.
"""

bids_path = afd.fetch_hbn_afq(["NDARAA948VFH"])[1]

"""
The next line downloads the cerebellar peduncle templates from `Figshare <https://figshare.com/articles/dataset/Regions_of_interest_for_automated_fiber_quantification_of_cerebellar_bundles/23201630>`_.

"""

cp_rois = afd.read_cp_templates()


"""
The following line defines a bundle dictionary for the cerebellar
peduncles. There are three CPs: The ICP, the MCP, and the SCP. Each CP is
defined by two inclusion ROIs and one exclusion ROI. The Inferior CPs are
defined by inclusion ROIs. They do not decussate, so "cross_midline" is set to
False. The Superior CPs are defined by two inclusion ROIs and an exclusion ROI,
where each SCP's most superior inclusion ROI is the other SCP's exclusion ROI.
They decussate, so "cross_midline" is set to True. The Middle CPs are defined
by two inclusion ROIs and they use the SCP intermediate ROIs as exclusion ROIs.
"""

cp_bundles = abd.cerebellar_bd()

"""
The bundle dict has been defined, and now we are ready to run the AFQ pipeline.
Next, we define a GroupAFQ object. In this case, the tracking parameters
focus specifically on the CP, by using the ``RoiImage`` class to define the
seed region. We seed extensively in the ROIs that define the CPs.
"""

cp_afq = GroupAFQ(
    name="cp_afq",
    bids_path=bids_path,
    dwi_preproc_pipeline="qsiprep",
    tracking_params={
        "n_seeds": 4,
        "directions": "prob",
        "odf_model": "CSD",
        "seed_mask": RoiImage()},
    clip_edges=True,
    bundle_info=cp_bundles)


"""
The call to `export("bundles")` triggers the execution of the full pipeline.
"""
cp_afq.export("bundles")

"""

References
----------
.. [1] S. Jossinger, A. Sares, A. Zislis, D. Sury, V. Gracco, M. Ben-Shachar (2022)
       White matter correlates of sensorimotor synchronization in persistent
       developmental stuttering, Journal of Communication Disorders, 95.

.. [2] S. Jossinger, M. Yablonski, O. Amir, M. Ben-Shachar (2023). The
       contributions of the cerebellar peduncles and the frontal aslant tract
       in mediating speech fluency. Neurobiology of Language 2023;
       doi: https://doi.org/10.1162/nol_a_00098

"""
/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 22:54:31,864	INFO util.py:154 -- Missing packages: ['ipywidgets']. Run `pip install -U ipywidgets`, then restart the notebook server for rich notebook output.
  0%|          | 0/1 MB [00:00]
100%|██████████| 1/1 MB [00:00]

  0%|          | 0/1 MB [00:00]
100%|██████████| 1/1 MB [00:00]

  0%|          | 0/2 MB [00:00]
100%|██████████| 2/2 MB [00:00]

  0%|          | 0/1 MB [00:00]
100%|██████████| 1/1 MB [00:00]

  0%|          | 0/1 MB [00:00]
100%|██████████| 1/1 MB [00:00]

  0%|          | 0/2 MB [00:00]
100%|██████████| 2/2 MB [00:00]

  0%|          | 0/1 MB [00:00]
100%|██████████| 1/1 MB [00:00]

  0%|          | 0/2 MB [00:00]
100%|██████████| 2/2 MB [00:00]

  0%|          | 0/1 MB [00:00]
100%|██████████| 1/1 MB [00:00]

  0%|          | 0/1 MB [00:00]
100%|██████████| 1/1 MB [00:00]

  0%|          | 0/2 MB [00:00]
100%|██████████| 2/2 MB [00:00]

  0%|          | 0/1 MB [00:00]
100%|██████████| 1/1 MB [00:00]

  0%|          | 0/1 MB [00:00]
100%|██████████| 1/1 MB [00:00]

  0%|          | 0/2 MB [00:00]
100%|██████████| 2/2 MB [00:00]

---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Cell In[1], line 34
     30 They decussate, so "cross_midline" is set to True. The Middle CPs are defined
     31 by two inclusion ROIs and they use the SCP intermediate ROIs as exclusion ROIs.
     32 """
     33 
---> 34 cp_bundles = abd.cerebellar_bd()
     35 
     36 """
     37 The bundle dict has been defined, and now we are ready to run the AFQ pipeline.

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/AFQ/api/bundle_dict.py:1047, in cerebellar_bd()
   1045 def cerebellar_bd():
   1046     cp_rois = afd.read_cp_templates()
-> 1047     return BundleDict(
   1048         {
   1049             "Left Inferior Cerebellar Peduncle": {
   1050                 "include": [
   1051                     cp_rois["ICP_L_inferior_prob"],
   1052                     cp_rois["ICP_L_superior_prob"],
   1053                 ],
   1054                 "cross_midline": False,
   1055             },
   1056             "Right Inferior Cerebellar Peduncle": {
   1057                 "include": [
   1058                     cp_rois["ICP_R_inferior_prob"],
   1059                     cp_rois["ICP_R_superior_prob"],
   1060                 ],
   1061                 "cross_midline": False,
   1062             },
   1063             "Left Middle Cerebellar Peduncle": {
   1064                 "include": [
   1065                     cp_rois["MCP_L_inferior_prob"],
   1066                     cp_rois["MCP_R_superior_prob"],
   1067                 ],
   1068                 "exclude": [
   1069                     cp_rois["SCP_L_inter_prob"],
   1070                 ],
   1071                 "cross_midline": True,
   1072             },
   1073             "Right Middle Cerebellar Peduncle": {
   1074                 "include": [
   1075                     cp_rois["MCP_R_inferior_prob"],
   1076                     cp_rois["MCP_L_superior_prob"],
   1077                 ],
   1078                 "exclude": [
   1079                     cp_rois["SCP_R_inter_prob"],
   1080                 ],
   1081                 "cross_midline": True,
   1082             },
   1083             "Left Superior Cerebellar Peduncle": {
   1084                 "include": [
   1085                     cp_rois["SCP_L_inferior_prob"],
   1086                     cp_rois["SCP_L_inter_prob"],
   1087                     cp_rois["SCP_R_superior_prob"],
   1088                 ],
   1089                 "exclude": [
   1090                     cp_rois["SCP_L_superior_prob"],
   1091                 ],
   1092                 "cross_midline": True,
   1093             },
   1094             "Right Superior Cerebellar Peduncle": {
   1095                 "include": [
   1096                     cp_rois["SCP_R_inferior_prob"],
   1097                     cp_rois["SCP_R_inter_prob"],
   1098                     cp_rois["SCP_L_superior_prob"],
   1099                 ],
   1100                 "exclude": [
   1101                     cp_rois["SCP_R_superior_prob"],
   1102                 ],
   1103                 "cross_midline": True,
   1104             },
   1105         },
   1106         citations={"Jossinger2022"},
   1107     )

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/AFQ/api/bundle_dict.py:1240, in BundleDict.__init__(self, bundle_info, resample_to, resample_subject_to, keep_in_memory, citations, criteria_for_all)
   1236     raise TypeError(
   1237         (f"bundle_info must be a dict, currently a {type(bundle_info)}")
   1238     )
   1239 if resample_to is None:
-> 1240     resample_to = afd.read_mni_template()
   1241 self.resample_to = resample_to
   1242 self.resample_subject_to = resample_subject_to

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/AFQ/data/fetch.py:2119, in read_mni_template(resolution, mask, weight)
   2092 def read_mni_template(resolution=1, mask=True, weight="T2w"):
   2093     """
   2094 
   2095     Reads the MNI T1w or T2w template
   (...)   2115 
   2116     """
   2117     template_img = nib.load(
   2118         str(
-> 2119             tflow.get(
   2120                 "MNI152NLin2009cAsym",
   2121                 desc=None,
   2122                 resolution=resolution,
   2123                 suffix=weight,
   2124                 extension="nii.gz",
   2125             )
   2126         )
   2127     )
   2128     if not mask:
   2129         return template_img

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/templateflow/client.py:231, in TemplateFlowClient.get(self, template, raise_empty, **kwargs)
    174 """
    175 Pull files pertaining to one or more templates down.
    176 
   (...)    228 
    229 """
    230 # List files available
--> 231 out_file = self.ls(template, **kwargs)
    233 if raise_empty and not out_file:
    234     raise Exception('No results found')

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/templateflow/client.py:168, in TemplateFlowClient.ls(self, template, **kwargs)
    163 if 'extension' in kwargs:
    164     kwargs['extension'] = _normalize_ext(kwargs['extension'])
    166 return [
    167     Path(p)
--> 168     for p in self.cache.layout.get(
    169         template=Query.ANY if template is None else template, return_type='file', **kwargs
    170     )
    171 ]

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/functools.py:1025, in cached_property.__get__(self, instance, owner)
   1023 val = cache.get(self.attrname, _NOT_FOUND)
   1024 if val is _NOT_FOUND:
-> 1025     val = self.func(instance)
   1026     try:
   1027         cache[self.attrname] = val

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/templateflow/conf/cache.py:148, in TemplateFlowCache.layout(self)
    145 from .bids import Layout
    147 self.ensure()
--> 148 return Layout(
    149     self.config.root,
    150     validate=False,
    151     config='templateflow',
    152     indexer=BIDSLayoutIndexer(
    153         validate=False,
    154         ignore=(re.compile(r'scripts/'), re.compile(r'/\.'), re.compile(r'^\.')),
    155     ),
    156 )

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/bids/layout/layout.py:188, in BIDSLayout.__init__(self, root, validate, absolute_paths, derivatives, config, sources, regex_search, database_path, reset_database, indexer, is_derivative, **indexer_kwargs)
    183     # Do not overwrite indexer variable, so the same configuration is passed to
    184     # add_derivatives() below
    185     _indexer = indexer or BIDSLayoutIndexer(
    186         validate=validate and not is_derivative,
    187     )
--> 188     _indexer(self)
    190 # Add derivatives if any are found
    191 if derivatives:

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/bids/layout/index.py:159, in BIDSLayoutIndexer.__call__(self, layout)
    156 self.session.commit()
    158 if self.index_metadata:
--> 159     self._index_metadata()

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/bids/layout/index.py:318, in BIDSLayoutIndexer._index_metadata(self)
    313 # If key/value pairs in JSON files duplicate ones extracted from files,
    314 # we can end up with Tag collisions in the DB. To prevent this, we
    315 # store all filename/entity pairs and the value, and then check against
    316 # that before adding each new Tag.
    317 all_tags = {}
--> 318 for t in self.session.query(Tag).all():
    319     key = '{}_{}'.format(t.file_path, t.entity_name)
    320     all_tags[key] = str(t.value)

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/sqlalchemy/orm/query.py:2711, in Query.all(self)
   2689 def all(self) -> List[_T]:
   2690     """Return the results represented by this :class:`_query.Query`
   2691     as a list.
   2692 
   (...)   2709         :meth:`_engine.Result.scalars` - v2 comparable method.
   2710     """
-> 2711     return self._iter().all()

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/sqlalchemy/engine/result.py:1799, in ScalarResult.all(self)
   1791 def all(self) -> Sequence[_R]:
   1792     """Return all scalar values in a sequence.
   1793 
   1794     Equivalent to :meth:`_engine.Result.all` except that
   (...)   1797 
   1798     """
-> 1799     return self._allrows()

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/sqlalchemy/engine/result.py:560, in ResultInternal._allrows(self)
    556 post_creational_filter = self._post_creational_filter
    558 make_row = self._row_getter
--> 560 rows = self._fetchall_impl()
    561 made_rows: List[_InterimRowType[_R]]
    562 if make_row:

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/sqlalchemy/engine/result.py:1706, in FilterResult._fetchall_impl(self)
   1705 def _fetchall_impl(self) -> List[_InterimRowType[Row[Any]]]:
-> 1706     return self._real_result._fetchall_impl()

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/sqlalchemy/engine/result.py:2310, in IteratorResult._fetchall_impl(self)
   2308     self._raise_hard_closed()
   2309 try:
-> 2310     return list(self.iterator)
   2311 finally:
   2312     self._soft_close()

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/sqlalchemy/orm/loading.py:220, in instances.<locals>.chunks(size)
    218         break
    219 else:
--> 220     fetch = cursor._raw_all_rows()
    222 if single_entity:
    223     proc = process[0]

File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/sqlalchemy/engine/result.py:553, in ResultInternal._raw_all_rows(self)
    551 assert make_row is not None
    552 rows = self._fetchall_impl()
--> 553 return [make_row(row) for row in rows]

KeyboardInterrupt: